You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于相似角度矩形聚类分离条码: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:19:50