C++图形轮廓提取
在C++怎样提取用TIFF库加载的黑白二值图像上面所有图形的外部轮廓,不要提取内部孔洞轮廓,另外轮廓需要提取2像素厚度,如果需要对轮廓进行膨胀需要向内膨胀,不要向外,就像Opencv库的RETR_EXTERNAL,但这里不要使用Opencv库处理,因为我的TIFF库数据是存放在 std::vector<unsigned char> intermediateBuffer(nPixels);数组内,所以需要直接对数组进行处理,转成其它格式的数据比较费时间在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 小助理,如未能正确解答您的问题,请继续追问。 FishC 发表于 2025-11-12 15:08
在C++中提取黑白二值图像的外部轮廓(不包含内部孔洞),可以通过以下步骤实现:
这些代码把物体内部的内容轮廓也提取出来了,我只需要图像最外层的轮廓,比如一个黑色的圆,里面有白色的各种图案文字,但我只需要提取圆的外轮廓,至于圆内部的任何轮廓都不要提取 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 小助理,如未能正确解答您的问题,请继续追问。 FishC 发表于 2025-11-12 15:20
要实现只提取最外层轮廓(不包含内部孔洞),可以使用连通组件标记算法。以下是完整的解决方案:
方 ...
几种方法函数里面所有的的auto 出错,提示未定义标识符,这应该是什么类型 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 小助理,如未能正确解答您的问题,请继续追问。 FishC 发表于 2025-11-12 15:26
在C++中,auto 是C++11引入的类型推断关键字。如果编译器报错,可能是由于以下原因:
1. 编译器不支 ...
为何这次保存出来的图片是全黑底,没有轮廓了 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 小助理,如未能正确解答您的问题,请继续追问。 FishC 发表于 2025-11-12 15:08
在C++中提取黑白二值图像的外部轮廓(不包含内部孔洞),可以通过以下步骤实现:
如果我已经将轮廓提取出来了并存入vector<unsigned char> externalContours数组里,我怎样直接用这个轮廓数组将它向内膨胀1像素或者2像素,轮廓需要填充为黑色 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 小助理,如未能正确解答您的问题,请继续追问。 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”类型的右操作数的运算符(或没有可接受的转换)
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 小助理,如未能正确解答您的问题,请继续追问。 {:10_257:}
页:
[1]