cxnet 发表于 7 天前

C++图形轮廓提取

在C++怎样提取用TIFF库加载的黑白二值图像上面所有图形的外部轮廓,不要提取内部孔洞轮廓,另外轮廓需要提取2像素厚度,如果需要对轮廓进行膨胀需要向内膨胀,不要向外,就像Opencv库的RETR_EXTERNAL,但这里不要使用Opencv库处理,因为我的TIFF库数据是存放在    std::vector<unsigned char> intermediateBuffer(nPixels);数组内,所以需要直接对数组进行处理,转成其它格式的数据比较费时间

FishC 发表于 7 天前

在C++中提取黑白二值图像的外部轮廓(不包含内部孔洞),可以通过以下步骤实现:


[*]首先定义图像尺寸和数据结构

int width = ...;// 图像宽度
int height = ...; // 图像高度
std::vector<unsigned char> contourBuffer(nPixels, 0); // 轮廓存储


[*]使用边界跟踪算法提取外部轮廓

for (int y = 1; y < height-1; y++) {
    for (int x = 1; x < width-1; x++) {
      int idx = y * width + x;
      // 当前像素为前景(255),且四邻域有背景(0)则为边界
      if (intermediateBuffer == 255) {
            if (intermediateBuffer == 0 ||   // 左
                intermediateBuffer == 0 ||   // 右
                intermediateBuffer == 0 || // 上
                intermediateBuffer == 0)   // 下
            {
                contourBuffer = 255; // 标记为轮廓
            }
      }
    }
}


[*]实现向内膨胀2像素厚度

// 第一遍膨胀
std::vector<unsigned char> tempBuffer = contourBuffer;
for (int y = 1; y < height-1; y++) {
    for (int x = 1; x < width-1; x++) {
      int idx = y * width + x;
      if (contourBuffer == 255) {
            // 向内膨胀:只对原始图像前景区域扩展
            if (intermediateBuffer == 255) tempBuffer = 255;
            if (intermediateBuffer == 255) tempBuffer = 255;
            if (intermediateBuffer == 255) tempBuffer = 255;
            if (intermediateBuffer == 255) tempBuffer = 255;
      }
    }
}

// 第二遍膨胀得到2像素厚度
contourBuffer = tempBuffer;
for (int y = 1; y < height-1; y++) {
    for (int x = 1; x < width-1; x++) {
      int idx = y * width + x;
      if (tempBuffer == 255) {
            // 继续向内膨胀
            if (intermediateBuffer == 255) contourBuffer = 255;
            if (intermediateBuffer == 255) contourBuffer = 255;
            if (intermediateBuffer == 255) contourBuffer = 255;
            if (intermediateBuffer == 255) contourBuffer = 255;
      }
    }
}


[*]可选:清理孤立点

for (int y = 1; y < height-1; y++) {
    for (int x = 1; x < width-1; x++) {
      int idx = y * width + x;
      if (contourBuffer == 255) {
            // 检查是否为孤立点
            int count = 0;
            count += (contourBuffer == 255);
            count += (contourBuffer == 255);
            count += (contourBuffer == 255);
            count += (contourBuffer == 255);
            if (count == 0) contourBuffer = 0;
      }
    }
}



关键点说明:

[*]使用四邻域检测确保只提取外部轮廓
[*]通过两次膨胀操作实现2像素厚度
[*]膨胀时检查 intermediateBuffer 确保只向内膨胀
[*]避免处理图像边界防止越界


最终轮廓数据存储在 contourBuffer 中,可以直接用于后续处理或保存。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 7 天前

FishC 发表于 2025-11-12 15:08
在C++中提取黑白二值图像的外部轮廓(不包含内部孔洞),可以通过以下步骤实现:




这些代码把物体内部的内容轮廓也提取出来了,我只需要图像最外层的轮廓,比如一个黑色的圆,里面有白色的各种图案文字,但我只需要提取圆的外轮廓,至于圆内部的任何轮廓都不要提取

FishC 发表于 7 天前

cxnet 发表于 2025-11-12 15:18
FishC 发表于 2025-11-12 15:08
在C++中提取黑白二值 ...

要实现只提取最外层轮廓(不包含内部孔洞),可以使用连通组件标记算法。以下是完整的解决方案:

方法一:使用边界标记算法


#include <vector>
#include <queue>
using namespace std;

void extractExternalContours(vector<unsigned char>& image, int width, int height) {
    vector<unsigned char> result(width * height, 0);
    vector<bool> visited(width * height, false);
   
    // 从图像边界开始扫描,标记所有与边界相连的背景
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
            // 只处理边界像素
            if (x == 0 || x == width-1 || y == 0 || y == height-1) {
                int idx = y * width + x;
                if (image == 0 && !visited) { // 背景像素
                  // BFS标记所有相连的背景
                  queue<pair<int, int>> q;
                  q.push({x, y});
                  visited = true;
                  
                  while (!q.empty()) {
                        auto = q.front();
                        q.pop();
                        
                        int directions = {{1,0}, {-1,0}, {0,1}, {0,-1}};
                        for (auto : directions) {
                            int nx = cx + dx, ny = cy + dy;
                            if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                              int nidx = ny * width + nx;
                              if (!visited && image == 0) {
                                    visited = true;
                                    q.push({nx, ny});
                              }
                            }
                        }
                  }
                }
            }
      }
    }
   
    // 提取外部轮廓:前景像素且至少有一个邻居是外部背景
    for (int y = 1; y < height-1; y++) {
      for (int x = 1; x < width-1; x++) {
            int idx = y * width + x;
            if (image == 255) { // 前景像素
                // 检查4邻域是否有外部背景
                bool isContour = false;
                int neighbors = {{1,0}, {-1,0}, {0,1}, {0,-1}};
                for (auto : neighbors) {
                  int nx = x + dx, ny = y + dy;
                  int nidx = ny * width + nx;
                  if (visited && image == 0) {
                        isContour = true;
                        break;
                  }
                }
               
                if (isContour) {
                  result = 255;
                }
            }
      }
    }
   
    // 将结果复制回原数组
    image = result;
}


方法二:简化版本(适合大多数情况)


void extractOuterContourSimple(vector<unsigned char>& img, int w, int h) {
    vector<unsigned char> contour(w * h, 0);
   
    for (int y = 1; y < h-1; y++) {
      for (int x = 1; x < w-1; x++) {
            int idx = y * w + x;
            if (img == 255) { // 前景
                // 检查是否与图像边界背景相连
                bool touchesBorderBg = false;
               
                // 检查4方向连接性
                if (x == 1 && img == 0) touchesBorderBg = true;
                if (x == w-2 && img == 0) touchesBorderBg = true;
                if (y == 1 && img == 0) touchesBorderBg = true;
                if (y == h-2 && img == 0) touchesBorderBg = true;
               
                // 检查4邻域背景连通性
                if (!touchesBorderBg) {
                  int dirs = {{1,0},{-1,0},{0,1},{0,-1}};
                  for (auto : dirs) {
                        int nx = x + dx, ny = y + dy;
                        int nidx = ny * w + nx;
                        if (img == 0) {
                            // 检查这个背景是否能连接到边界
                            vector<bool> checked(w * h, false);
                            queue<pair<int, int>> q;
                            q.push({nx, ny});
                            checked = true;
                           
                            while (!q.empty()) {
                              auto = q.front();
                              q.pop();
                              
                              // 如果到达边界,则说明是外部轮廓
                              if (cx == 0 || cx == w-1 || cy == 0 || cy == h-1) {
                                    touchesBorderBg = true;
                                    break;
                              }
                              
                              int ndirs = {{1,0},{-1,0},{0,1},{0,-1}};
                              for (auto : ndirs) {
                                    int nnx = cx + ddx, nny = cy + ddy;
                                    int nnidx = nny * w + nnx;
                                    if (nnx >= 0 && nnx < w && nny >= 0 && nny < h &&
                                        !checked && img == 0) {
                                        checked = true;
                                        q.push({nnx, nny});
                                    }
                              }
                            }
                            if (touchesBorderBg) break;
                        }
                  }
                }
               
                if (touchesBorderBg) {
                  contour = 255;
                }
            }
      }
    }
   
    img = contour;
}


使用方法:


int main() {
    int width = 800;// 你的图像宽度
    int height = 600; // 你的图像高度
    int nPixels = width * height;
   
    std::vector<unsigned char> intermediateBuffer(nPixels);
    // ... 从TIFF加载数据到intermediateBuffer ...
   
    // 提取外部轮廓
    extractExternalContours(intermediateBuffer, width, height);
   
    // 现在intermediateBuffer中只包含最外层轮廓
    return 0;
}


这两个算法都能确保只提取最外层轮廓:
- 方法一通过标记所有与图像边界相连的背景区域
- 方法二通过检查每个前景像素的背景邻居是否能连接到图像边界

这样就能完美实现类似OpenCV的RETR_EXTERNAL功能,且不包含任何内部孔洞轮廓。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 7 天前

FishC 发表于 2025-11-12 15:20
要实现只提取最外层轮廓(不包含内部孔洞),可以使用连通组件标记算法。以下是完整的解决方案:

方 ...

几种方法函数里面所有的的auto 出错,提示未定义标识符,这应该是什么类型

FishC 发表于 7 天前

cxnet 发表于 2025-11-12 15:26
FishC 发表于 2025-11-12 15:20
要实现只提取最外层 ...

在C++中,auto 是C++11引入的类型推断关键字。如果编译器报错,可能是由于以下原因:

1. 编译器不支持C++11或更高版本
2. 需要包含必要的头文件

请将代码中的 auto 替换为具体类型:

方法一中的替换:

将:
for (auto neighbor : neighbors)
替换为:
for (int i = 0; i < 4; i++) {
    int dx = neighbors;
    int dy = neighbors;
}

将:
queue<pair<int, int>> q;
确保包含头文件:
#include <utility>

将:
auto = q.front();
替换为:
int cx = q.front().first;
int cy = q.front().second;

完整修改后的代码:

#include <vector>
#include <queue>
#include <utility>
using namespace std;

void extractExternalContours(vector<unsigned char>& image, int width, int height) {
    vector<unsigned char> result(width * height, 0);
    vector<bool> visited(width * height, false);
   
    // 从图像边界开始扫描,标记所有与边界相连的背景
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
            // 只处理边界像素
            if (x == 0 || x == width-1 || y == 0 || y == height-1) {
                int idx = y * width + x;
                if (image == 0 && !visited) {
                  // BFS标记所有相连的背景
                  queue<pair<int, int>> q;
                  q.push(make_pair(x, y));
                  visited = true;
                  
                  while (!q.empty()) {
                        int cx = q.front().first;
                        int cy = q.front().second;
                        q.pop();
                        
                        // 检查4邻域
                        int directions = {{1,0}, {-1,0}, {0,1}, {0,-1}};
                        for (int i = 0; i < 4; i++) {
                            int nx = cx + directions;
                            int ny = cy + directions;
                            if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                              int nidx = ny * width + nx;
                              if (!visited && image == 0) {
                                    visited = true;
                                    q.push(make_pair(nx, ny));
                              }
                            }
                        }
                  }
                }
            }
      }
    }
   
    // 提取外部轮廓
    int neighbors = {{1,0}, {-1,0}, {0,1}, {0,-1}};
    for (int y = 1; y < height-1; y++) {
      for (int x = 1; x < width-1; x++) {
            int idx = y * width + x;
            if (image == 255) {
                bool isContour = false;
                for (int i = 0; i < 4; i++) {
                  int nx = x + neighbors;
                  int ny = y + neighbors;
                  int nidx = ny * width + nx;
                  if (visited && image == 0) {
                        isContour = true;
                        break;
                  }
                }
               
                if (isContour) {
                  result = 255;
                }
            }
      }
    }
   
    image = result;
}

如果仍有编译问题,请检查编译器是否支持C++11标准,可以在编译时添加 -std=c++11 标志。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 7 天前

FishC 发表于 2025-11-12 15:26
在C++中,auto 是C++11引入的类型推断关键字。如果编译器报错,可能是由于以下原因:

1. 编译器不支 ...

为何这次保存出来的图片是全黑底,没有轮廓了

FishC 发表于 7 天前

cxnet 发表于 2025-11-12 15:42
FishC 发表于 2025-11-12 15:26
在C++中,auto 是C++ ...

出现全黑图片的问题通常是因为轮廓提取逻辑有误。以下是修正后的代码:


void extractExternalContours(std::vector<unsigned char>& image, int width, int height) {
    std::vector<unsigned char> result(width * height, 0);
    std::vector<bool> externalBackground(width * height, false);
   
    // 标记外部背景(与图像边界相连的背景区域)
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
            if (x == 0 || x == width-1 || y == 0 || y == height-1) {
                int idx = y * width + x;
                if (image == 0 && !externalBackground) {
                  std::queue<std::pair<int, int>> q;
                  q.push(std::make_pair(x, y));
                  externalBackground = true;
                  
                  while (!q.empty()) {
                        auto current = q.front();
                        q.pop();
                        int cx = current.first;
                        int cy = current.second;
                        
                        // 检查4邻域
                        int dx[] = {1, -1, 0, 0};
                        int dy[] = {0, 0, 1, -1};
                        
                        for (int i = 0; i < 4; i++) {
                            int nx = cx + dx;
                            int ny = cy + dy;
                            if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                              int nidx = ny * width + nx;
                              if (!externalBackground && image == 0) {
                                    externalBackground = true;
                                    q.push(std::make_pair(nx, ny));
                              }
                            }
                        }
                  }
                }
            }
      }
    }
   
    // 提取外部轮廓:前景像素且至少有一个邻居是外部背景
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
            int idx = y * width + x;
            if (image == 255) { // 前景像素
                bool isContour = false;
               
                // 检查8邻域是否有外部背景
                for (int dy = -1; dy <= 1; dy++) {
                  for (int dx = -1; dx <= 1; dx++) {
                        if (dx == 0 && dy == 0) continue;
                        
                        int nx = x + dx;
                        int ny = y + dy;
                        if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                            int nidx = ny * width + nx;
                            if (externalBackground) {
                              isContour = true;
                              break;
                            }
                        }
                  }
                  if (isContour) break;
                }
               
                if (isContour) {
                  result = 255;
                }
            }
      }
    }
   
    // 将结果复制回原数组
    image = result;
}


主要修正点:
1. 确保包含了必要的头文件:

#include <queue>
#include <utility>// for std::pair


2. 使用8邻域检测而不是4邻域,这样能更好地检测轮廓

3. 确保BFS算法正确标记了所有外部背景像素

4. 确保轮廓检测逻辑正确

如果仍然有问题,请检查:
- 图像数据是否正确加载(确认 intermediateBuffer 中的数据是有效的二值图像)
- 宽度和高度参数是否正确
- 图像数据是否按预期存储(行优先还是列优先)

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 7 天前

FishC 发表于 2025-11-12 15:08
在C++中提取黑白二值图像的外部轮廓(不包含内部孔洞),可以通过以下步骤实现:




如果我已经将轮廓提取出来了并存入vector<unsigned char> externalContours数组里,我怎样直接用这个轮廓数组将它向内膨胀1像素或者2像素,轮廓需要填充为黑色

FishC 发表于 7 天前

cxnet 发表于 2025-11-12 17:44
FishC 发表于 2025-11-12 15:08
在C++中提取黑白二值 ...

出现全黑图片的问题可能是由于轮廓提取逻辑有误。以下是修正后的向内膨胀轮廓的代码:


void dilateContourInward(std::vector<unsigned char>& externalContours,
                         const std::vector<unsigned char>& originalImage,
                         int width, int height, int thickness) {
    std::vector<unsigned char> result = externalContours;
   
    for (int t = 0; t < thickness; t++) {
      std::vector<unsigned char> temp = result;
      
      for (int y = 1; y < height-1; y++) {
            for (int x = 1; x < width-1; x++) {
                int idx = y * width + x;
               
                // 如果当前点是轮廓点
                if (result == 255) {
                  // 检查8邻域中的内部点
                  for (int dy = -1; dy <= 1; dy++) {
                        for (int dx = -1; dx <= 1; dx++) {
                            if (dx == 0 && dy == 0) continue;
                           
                            int nx = x + dx;
                            int ny = y + dy;
                            int nidx = ny * width + nx;
                           
                            // 确保在图像范围内
                            if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                              // 如果邻域点在原始图像中是前景(物体内部)
                              // 并且在当前轮廓中不是轮廓点
                              if (originalImage == 255 && result == 0) {
                                    temp = 255; // 向内膨胀轮廓
                              }
                            }
                        }
                  }
                }
            }
      }
      result = temp;
    }
   
    externalContours = result;
}

使用方法:


// 假设你已经有了:
// std::vector<unsigned char> externalContours - 提取的轮廓
// std::vector<unsigned char> intermediateBuffer - 原始图像
// int width, height - 图像尺寸

// 向内膨胀1像素
dilateContourInward(externalContours, intermediateBuffer, width, height, 1);

// 或者向内膨胀2像素
dilateContourInward(externalContours, intermediateBuffer, width, height, 2);

关键点说明:
1. 使用原始图像 intermediateBuffer 来区分物体内部和外部
2. 只向物体内部方向膨胀(originalImage == 255 的区域)
3. 膨胀厚度由 thickness 参数控制
4. 轮廓点保持为255(白色),背景为0(黑色)

如果仍然有问题,请检查:
1. 确保原始图像 intermediateBuffer 是有效的二值图像
2. 确保轮廓提取函数正确工作
3. 验证图像尺寸参数是否正确

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 7 天前

FishC 发表于 2025-11-12 17:46
出现全黑图片的问题可能是由于轮廓提取逻辑有误。以下是修正后的向内膨胀轮廓的代码:




                if (result == 255) {
二进制“==”: 没有找到接受“std::vector<uchar,std::allocator<uchar>>”类型的左操作数的运算符(或没有可接受的转换)
                              if (originalImage == 255 && result == 0) {

二进制“==”: 没有找到接受“const std::vector<uchar,std::allocator<uchar>>”类型的左操作数的运算符(或没有可接受的转换)
                                    temp = 255; // 向内膨胀轮廓
二元“=”: 没有找到接受“int”类型的右操作数的运算符(或没有可接受的转换)

FishC 发表于 7 天前

cxnet 发表于 2025-11-12 17:54
FishC 发表于 2025-11-12 17:46
出现全黑图片的问题 ...

抱歉出现了编译错误。以下是修正后的向内膨胀轮廓代码:


void dilateContourInward(std::vector<unsigned char>& externalContours,
                         const std::vector<unsigned char>& originalImage,
                         int width, int height, int thickness) {
    std::vector<unsigned char> result = externalContours;
   
    for (int t = 0; t < thickness; t++) {
      std::vector<unsigned char> temp = result;
      
      for (int y = 1; y < height-1; y++) {
            for (int x = 1; x < width-1; x++) {
                int idx = y * width + x;
               
                // 如果当前点是轮廓点
                if (result == 255) {
                  // 检查4邻域中属于物体内部但不是轮廓的点
                  int dx[] = {1, -1, 0, 0};
                  int dy[] = {0, 0, 1, -1};
                  
                  for (int i = 0; i < 4; i++) {
                        int nx = x + dx;
                        int ny = y + dy;
                        int nidx = ny * width + nx;
                        
                        // 如果邻域点属于物体内部但不是轮廓,则将其设为轮廓
                        if (originalImage == 255 && result == 0) {
                            temp = 255; // 向内膨胀轮廓
                        }
                  }
                }
            }
      }
      
      result = temp;
    }
   
    // 将结果复制回原数组
    externalContours = result;
}


这个修正版本:
1. 使用数组索引 result 而不是直接比较整个vector
2. 使用明确的循环代替auto
3. 确保语法正确

使用方法:

// 假设你已经有了轮廓数组和原图数组
dilateContourInward(externalContours, intermediateBuffer, width, height, 2);


这样就会将轮廓向内膨胀2像素厚度。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

馍蘸酱 发表于 6 天前

{:10_257:}
页: [1]
查看完整版本: C++图形轮廓提取