【问题标题】:How to determine the width of the lines?如何确定线条的宽度?
【发布时间】:2014-02-27 22:25:08
【问题描述】:

我需要检测这些线的宽度:

这些线是平行的,上面有一些噪音。

目前,我所做的是:

1.使用细化找中心(张素恩)

ZhanSuenThinning(binImage, thin);

2.计算距离变换

cv::distanceTransform(binImage, distImg, CV_DIST_L2, CV_DIST_MASK_5);

3.围绕中心累加一半距离

double halfWidth = 0.0;
int count = 0;
for(int a = 0; a < thinImg.cols; a++)
    for(int b = 0; b < thinImg.rows; b++)
        if(thinImg.ptr<uchar>(b, a)[0] > 0)
        {
            halfWidth += distImg.ptr<float>(b, a)[0];
            count ++;
        }

4.最后得到实际宽度

width = halfWidth / count * 2;

结果不是很好,大约有 1-2 个像素的误差。在更大的图像上,结果更糟,有什么建议吗?

【问题讨论】:

  • 您需要每条线的宽度,还是所有线的宽度始终相同?
  • 它们是平行的,宽度相同,只是长度不同

标签: opencv image-processing computer-vision


【解决方案1】:

一个快速简单的建议:

  • 统计黑色像素的总数。

  • 检测每行的长度。 (也许用 CVHoughLinesP,或者只是每条细线周围边界框的对角线)

  • 将黑色像素的数量除以所有线长的总和,这应该可以得出平均线宽。

我不确定这是否比您现有的方法更准确。每行不规则的末端部分可能会扔掉它。

您可以尝试一件事来提高该案例的准确性:

  • 测量线条的平均角度
  • 旋转图像,使线条水平对齐
  • 裁剪形状的矩形子部分,使所有线条的长度相同
    (你可以通过形态闭合得到你的形状的轮廓,然后找到一个完全包含在形状内的矩形。确保矩形的水平边缘在线条之间)
  • 然后再次计算黑色像素的数量(将旋转图像导致的灰色像素计算为整个像素的 x%)
  • 除以 (rectangle_width * number_of_lines_in_rectangle)

【讨论】:

  • 对不起,尝试了你的想法,但结果仍然相差几个像素。也许“光栅方法”不是很好。
【解决方案2】:

您可以调整条形码阅读器算法,这是更快的方法。

扫描水平线和垂直线。 让 X 与黑线的水平交点的长度和 Y 的垂直交点的长度(如果有一些噪音,你可以让它计算几个 X 和 Y 的中值)。

X * Y / 2 = area
X²+Y² = hypotenuse²
hypotenuse * width / 2 = area

所以:宽度 = 2 * 面积 / 斜边

编辑:您还可以使用 PCA 轻松找到角度。

【讨论】:

  • 这可能非常棘手,因为线条只能是 1 像素宽度,但感谢您的回答 :)
  • 实际上,如果宽度为 1(与其他算法具有相同的精度),则它可以工作。此外,如果宽度为 1,您可以通过检查 X 和 Y 值轻松找到它。
  • 哦,对不起,我没有仔细阅读您的答案,我对您的想法非常感兴趣,将了解有关此条形码阅读器算法的更多信息:)
【解决方案3】:

您需要为图像中的每个轮廓找到RotatedRect,这是OpenCV tutorial 的操作方法。然后只需从旋转矩形中获取“大小”的值,您将获得轮廓的高度和宽度,高度和宽度可能会因轮廓的不同对齐方式而互换。在上图中,高度变为宽度,宽度变为高度。

Contour-->RotatedRect 
              | 
              '-->  Size2f size
                            |
                            |-->width
                            '-->height

找到轮廓后就做

RotatedRect minRect = minAreaRect( Mat(contours[i]) );
Size2f contourSize=minRect.size  //  width and height of the rectangle

每个轮廓的旋转矩形

这里是 C++ 代码

Mat src=imread("line.png",1);
Mat thr,gray;
blur(src,src,Size(3,3));
cvtColor(src,gray,CV_BGR2GRAY);
Canny(gray,thr,50, 190, 3, false );
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( thr.clone(),contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE,Point(0,0));
vector<RotatedRect> minRect( contours.size() );

for( int i = 0; i < contours.size(); i++ )
  minRect[i] = minAreaRect( Mat(contours[i]) );

for( int i = 0; i< contours.size(); i++ )
 {
    cout<<"  Size ="<<minRect[i].size<<endl; //The width may interchange according to contour alignment
    Size2f s=minRect[i].size;
    // rotated rectangle
    Point2f rect_points[4]; minRect[i].points( rect_points );
    for( int j = 0; j < 4; j++ )
      line( src, rect_points[j], rect_points[(j+1)%4], Scalar(0,0,255), 1, 8 );
}

imshow("src",src);
imshow("Canny",thr);

【讨论】:

  • 旋转矩形符合最小二乘法,可能不代表条纹的真实大小或尺寸。一点点离群值(并且有一些噪音)可以把它扔掉很多。
【解决方案4】:
  1. 霍夫线适合查找每条线
  2. 从每条线拟合的每个像素,沿垂直方向扫描以获得到边缘的距离。使用样条拟合或类似的亚像素方法查找边缘。
  3. 根据您的需要/愿望,取中值或平均距离。要消除异常值的问题,请在计算平均值或中位数之前剔除低于第 10 个百分位和高于第 90 个百分位的距离。您还可以使用统计数据报告尺寸:线宽 W、标准差 S。

虽然可以使用连通分量算法来查找直线,但它无法像样条拟合那样准确地找到“真实”边。

【讨论】:

    【解决方案5】:

    像您显示的图像嘈杂/模糊,因此黑色像素的数量可能无法反映线条属性;例如,黑色像素可以部分归因于椒盐噪声。您可以通过形态侵蚀来消除它,但这也会影响您的线条。

    更好的方法是提取连通分量,删除可能来自噪声或小斑点的小分量,然后计算像素数并将其除以行数。这种方法还可以帮助您分析图像中对象的形状,并消除除噪点或线条以外的任何伪影。

    另一种实际情况是当您在线条边界附近有一些灰色像素时。您可以使用阈值来丢弃它们,也可以使用权重

    【讨论】:

    • 是的,噪声会影响计算,但在我试用我的程序一段时间后,真正重要的是线条的方向(例如,光栅图像上的线条旋转 45 度)。这可能不是微不足道的,因为边缘是别名的。
    • 混叠包含在模糊点下 - 请参阅我答案的后半部分。尝试连接组件——算法真的不关心方向,只关心连通性。有大量可用的实现,而且速度非常快 - 它只是两遍算法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-16
    • 2013-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多