基于相似角度矩形聚类分离条码:C++ OpenCV新手方案咨询
嘿,作为刚接触C++ OpenCV的新手,能走到提取轮廓和生成最小外接矩形这一步已经超棒啦!针对你想通过角度相似的矩形聚类分离条码区域的需求,我给你梳理一套实操性拉满的方案,一步步来:
一、先统一矩形的角度表示,避免歧义
首先得搞懂OpenCV里minAreaRect()返回的RotatedRect的角度规则:它是矩形相对水平x轴的旋转角度,范围是[-90, 0)。但这里有个坑——当矩形的宽比高小时,OpenCV会自动旋转角度,导致同方向的矩形角度看起来相差很大。所以第一步要做角度归一化,把所有角度统一到[0, 90)的范围:
float normalizeAngle(float angle) { // 把角度转换到0-90区间,方便后续比较 if (angle < -45) { angle += 90; } return fabs(angle); }
二、选择适合新手的聚类方案
不用一开始就搞复杂的机器学习算法,先从简单的方法入手,效果够好还容易调试:
方案1:阈值聚类(最易上手)
- 步骤1:把所有
RotatedRect的角度都用上面的函数归一化 - 步骤2:统计所有角度的分布(可以用OpenCV的
calcHist()画直方图),找到出现次数最多的角度区间 - 步骤3:设定一个角度阈值(比如±2度,根据你的图像调整),把角度落在这个区间内的矩形筛选出来——这些就是条码区域的候选矩形
方案2:K-means聚类(更灵活)
如果阈值聚类不够准确,试试OpenCV现成的K-means API,它能自动把相似角度的矩形归为一类:
- 步骤1:把所有归一化后的角度整理成N行1列的浮点型
Mat - 步骤2:调用
kmeans()函数,先把聚类数设为2(毕竟主要是条码和干扰物两类) - 步骤3:统计每个簇的矩形数量,数量最多的那个簇就是条码对应的矩形集合
三、加个筛选buff,提升准确率
只靠角度可能会误判一些干扰物,再加上这几个辅助条件:
- 宽高比筛选:条码的条都是细长的,宽高比很小(比如宽度远小于高度,或反过来,取决于条码方向),可以设个阈值,比如宽高比<0.2或者>5
- 面积筛选:排除面积特别小的噪声轮廓,或者特别大的干扰物
- 位置连续性:条码的条是紧密排列的,筛选后的矩形中心应该在x或y方向上连续分布,可以检查中心坐标的集中程度
四、核心代码示例
给你写个可直接参考的核心片段,结合了K-means和宽高比筛选:
// 假设你已经有了轮廓集合vector<vector<Point>> contours; vector<RotatedRect> allRects; vector<float> angles; // 1. 提取所有最小外接矩形并归一化角度 for (auto &contour : contours) { RotatedRect rect = minAreaRect(contour); allRects.push_back(rect); float normAngle = normalizeAngle(rect.angle); angles.push_back(normAngle); } // 2. 转换为OpenCV格式用于K-means Mat anglesMat(angles.size(), 1, CV_32F); for (int i = 0; i < angles.size(); i++) { anglesMat.at<float>(i, 0) = angles[i]; } // 3. 执行K-means聚类 int clusterCount = 2; Mat labels, centers; kmeans(anglesMat, clusterCount, labels, TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 10, 1.0), 3, KMEANS_RANDOM_CENTERS, centers); // 4. 找到包含矩形最多的簇(大概率是条码) vector<int> clusterCounts(clusterCount, 0); for (int i = 0; i < labels.rows; i++) { clusterCounts[labels.at<int>(i, 0)]++; } int maxClusterIdx = max_element(clusterCounts.begin(), clusterCounts.end()) - clusterCounts.begin(); // 5. 筛选目标簇+宽高比过滤 vector<RotatedRect> barcodeRects; float aspectRatioThreshold = 0.2; // 可根据图像调整 for (int i = 0; i < allRects.size(); i++) { if (labels.at<int>(i, 0) == maxClusterIdx) { RotatedRect rect = allRects[i]; float width = rect.size.width; float height = rect.size.height; float aspectRatio = min(width, height) / max(width, height); if (aspectRatio < aspectRatioThreshold) { barcodeRects.push_back(rect); } } } // 6. 绘制结果看看 Mat resultImg = img.clone(); for (auto &rect : barcodeRects) { Point2f pts[4]; rect.points(pts); for (int j = 0; j < 4; j++) { line(resultImg, pts[j], pts[(j+1)%4], Scalar(0,255,0), 2); } } imshow("Barcode Rects", resultImg); waitKey(0);
五、调试小技巧
- 先把所有矩形都绘制出来,观察角度分布,再调整归一化逻辑和阈值
- 如果K-means设2类效果不好,可以试试3类,然后选择矩形数量符合预期的簇
- 宽高比阈值要根据条码方向调整:垂直条码用小阈值,水平条码用大阈值
内容的提问来源于stack exchange,提问作者mcally
相关产品推荐
相关产品推荐

