当前位置: 首页 > news >正文

OpenCV C++ 二值图像分析:从连通组件到轮廓匹配

二值图像分析是计算机视觉的重要基础,通过对二值化后的图像进行连通组件标记、轮廓提取与特征分析,可实现目标计数、形状识别、尺寸测量等核心任务。本章将系统讲解二值图像分析的关键技术,从基础算法到实战应用,构建完整的技术体系。

一、连通组件标记算法原理

连通组件标记(Connected Component Labeling)是二值图像分析的基础,它能识别图像中相互连通的像素区域并赋予唯一标识,实现目标分离。

1.1 连通性定义

在二值图像中,连通性描述像素间的连接关系:

  • 4 连通:一个像素只与其上、下、左、右四个方向的像素连接
  • 8 连通:一个像素与其上、下、左、右及四个对角线方向的像素连接
4连通邻居       8连通邻居N              N  NE  E
W C E          NW  C  SES              W  SW  S

1.2 两遍扫描算法

两遍扫描算法是最常用的连通组件标记方法,通过两次图像扫描完成标记:

// 两遍扫描连通组件标记算法(4连通)
Mat twoPassLabeling(const Mat& binary) {CV_Assert(binary.type() == CV_8UC1);int rows = binary.rows;int cols = binary.cols;Mat labels = Mat::zeros(rows, cols, CV_32SC1);  // 标记结果int currentLabel = 0;vector<int> parent;  // 用于处理等价标签// 第一遍扫描for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {if (binary.at<uchar>(i, j) == 255 && labels.at<int>(i, j) == 0) {currentLabel++;parent.push_back(currentLabel);  // 初始父标签为自身// 检查左邻和上邻(4连通)vector<int> neighborLabels;// 左邻if (j > 0 && labels.at<int>(i, j-1) > 0) {neighborLabels.push_back(labels.at<int>(i, j-1));}// 上邻if (i > 0 && labels.at<int>(i-1, j) > 0) {neighborLabels.push_back(labels.at<int>(i-1, j));}if (neighborLabels.empty()) {// 无邻居,分配新标签labels.at<int>(i, j) = currentLabel;} else {// 有邻居,取最小标签int minLabel = *min_element(neighborLabels.begin(), neighborLabels.end());labels.at<int>(i, j) = minLabel;// 记录等价标签for (int label : neighborLabels) {// 路径压缩的并查集操作while (parent[label-1] != label) {parent[label-1] = parent[parent[label-1]-1];label = parent[label-1];}if (label != minLabel) {parent[label-1] = minLabel;}}}}}}// 第二遍扫描:处理等价标签for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {if (labels.at<int>(i, j) > 0) {int label = labels.at<int>(i, j);// 找到根标签while (parent[label-1] != label) {label = parent[label-1];}labels.at<int>(i, j) = label;}}}return labels;
}

算法步骤解析:

  1. 第一遍扫描:逐像素遍历图像,对每个前景像素(255)检查其左邻和上邻
  2. 标签分配:根据邻居标签情况分配新标签或复用已有标签,记录等价标签对
  3. 等价处理:使用并查集(Union-Find)数据结构管理等价标签
  4. 第二遍扫描:将所有等价标签替换为其根标签,确保同一组件具有唯一标识

1.3 种子填充算法

种子填充算法通过从种子像素开始,递归或迭代地标记所有连通像素:

// 种子填充算法(8连通)
void floodFillLabeling(const Mat& binary, Mat& labels, int i, int j, int label) {int rows = binary.rows;int cols = binary.cols;// 边界检查if (i < 0 || i >= rows || j < 0 || j >= cols) return;// 若为前景且未标记,则标记并处理邻居if (binary.at<uchar>(i, j) == 255 && labels.at<int>(i, j) == 0) {labels.at<int>(i, j) = label;// 8连通邻居floodFillLabeling(binary, labels, i-1, j-1, label);  // 左上floodFillLabeling(binary, labels, i-1, j, label);   // 上floodFillLabeling(binary, labels, i-1, j+1, label);  // 右上floodFillLabeling(binary, labels, i, j-1, label);   // 左floodFillLabeling(binary, labels, i, j+1, label);   // 右floodFillLabeling(binary, labels, i+1, j-1, label);  // 左下floodFillLabeling(binary, labels, i+1, j, label);   // 下floodFillLabeling(binary, labels, i+1, j+1, label);  // 右下}
}// 种子填充算法包装函数
Mat floodFillLabeling(const Mat& binary) {CV_Assert(binary.type() == CV_8UC1);int rows = binary.rows;int cols = binary.cols;Mat labels = Mat::zeros(rows, cols, CV_32SC1);int currentLabel = 0;for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {if (binary.at<uchar>(i, j) == 255 && labels.at<int>(i, j) == 0) {currentLabel++;floodFillLabeling(binary, labels, i, j, currentLabel);}}}return labels;
}

两种算法对比:

  • 两遍扫描:内存效率高,适合处理大图像,但实现较复杂
  • 种子填充:实现简单直观,但递归版本可能栈溢出,迭代版本需队列 / 栈存储像素

二、连通组件标记算法应用

连通组件标记为后续分析提供基础,可实现目标计数、筛选与特性分析。

2.1 组件计数与可视化

// 连通组件可视化(为每个组件分配不同颜色)
Mat visualizeComponents(const Mat& labels) {// 生成随机颜色表RNG rng(12345);int maxLabel = 0;minMaxLoc(labels, 0, &maxLabel);vector<Vec3b> colors(maxLabel + 1);colors[0] = Vec3b(0, 0, 0);  // 背景为黑色for (int i = 1; i <= maxLabel; ++i) {colors[i] = Vec3b(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));}// 为每个标签分配颜色Mat result(labels.size(), CV_8UC3);for (int i = 0; i < labels.rows; ++i) {for (int j = 0; j < labels.cols; ++j) {int label = labels.at<int>(i, j);result.at<Vec3b>(i, j) = colors[label];}}return result;
}// 连通组件分析示例
int main() {Mat img = imread("objects.jpg", IMREAD_GRAYSCALE);if (img.empty()) {cout << "图像加载失败!" << endl;return -1;}// 二值化Mat binary;threshold(img, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);// 连通组件标记Mat labels = twoPassLabeling(binary);  // 或使用floodFillLabeling(binary)// 计算组件数量int maxLabel;minMaxLoc(labels, 0, &maxLabel);cout << "检测到 " << maxLabel << " 个连通组件" << endl;// 可视化Mat visualization = visualizeComponents(labels);imshow("原图", i

文章转载自:

http://na6i4glP.rnmmh.cn
http://beuqdBIw.rnmmh.cn
http://6qbxtReb.rnmmh.cn
http://p4KAalCJ.rnmmh.cn
http://n09syUfu.rnmmh.cn
http://pq4NIPkV.rnmmh.cn
http://47Ui9EY5.rnmmh.cn
http://OTb8t8ik.rnmmh.cn
http://lnTIgYBu.rnmmh.cn
http://At6wQof0.rnmmh.cn
http://edrzfvhv.rnmmh.cn
http://yxjIaefO.rnmmh.cn
http://jeEwWKhv.rnmmh.cn
http://1BbKFKFo.rnmmh.cn
http://FeOp4V3k.rnmmh.cn
http://hfmKTDdf.rnmmh.cn
http://pWEjdZiL.rnmmh.cn
http://Fp6IwRBI.rnmmh.cn
http://wfNOlJOs.rnmmh.cn
http://TZceCKe5.rnmmh.cn
http://QX9NNqg0.rnmmh.cn
http://U5db1bwn.rnmmh.cn
http://TzytgQtk.rnmmh.cn
http://zLwh08iD.rnmmh.cn
http://JawKPm60.rnmmh.cn
http://2ZHnYeZM.rnmmh.cn
http://hap9W3EE.rnmmh.cn
http://OwuVHKVK.rnmmh.cn
http://6UGUEznW.rnmmh.cn
http://8t5hdtFj.rnmmh.cn
http://www.dtcms.com/a/373720.html

相关文章:

  • Java分页 Element—UI
  • Flow-GRPO: Training Flow Matching Models via Online RL
  • C#中解析XML时遇到注释节点报错
  • 联邦学习辅导流程
  • MySQL MVCC原理
  • QSS加载失败的奇葩问题--已解决
  • 一体化伺服电机在管道焊缝检测爬行机器人中的应用案例
  • flowable发起申请后无法查看申请记录
  • 鸿蒙实现APP和网页跳转方案总结
  • 【数据结构与算符Trip第2站】稀疏数组
  • 国产EtherCAT从站芯片FCE1353与N32G435 MCU功能板测试流程
  • 0908 C++标准模板库和异常处理
  • 【PostgreSQL内核学习:基于 ExprState 的哈希计算优化—— GROUP BY 与 SubPlan 的性能提升】
  • Hive基础简介
  • Hive实战(一)
  • SQL 函数从入门到精通:原理、类型、窗口函数与实战指南
  • 嵌入式 - ARM2
  • 【后端】阿里巴巴 Java 开发规范 —— 换行速查表
  • 基于 OpenCV 的信用卡数字识别:从原理到实现
  • 为什么要做智慧养老?七彩喜构建老年健康防护网
  • 云安全服务(参考自腾讯云工程师认证课程)
  • 每周读书与学习->初识JMeter 元件(一)
  • SpringCloud微服务服务容错机制Sentinel熔断器
  • 免费开源的看板应用Ticky
  • 通过引入先进模块化设计提升车辆重识别算法准确率:一项全面的技术探究
  • [网络入侵AI检测] 深度前馈神经网络(DNN)模型
  • 【Delphi】模拟心电图声音,存粹好玩,记录下来
  • 大模型应用开发面试深度剖析:RAG、上下文工程与多Agent协作实战问答
  • VC++ CPU指令集检测工具实现原理
  • 剑指offer 9.8