Opencv - 如何获取图像中存在的垂直线数(行数)

Posted

技术标签:

【中文标题】Opencv - 如何获取图像中存在的垂直线数(行数)【英文标题】:Opencv - How to get number of vertical lines present in image (count of lines) 【发布时间】:2021-06-16 17:13:29 【问题描述】:

首先,我将 OpenCV 框架集成到 XCode,所有 OpenCV 代码都在 ObjectiveC 上,我在 Swift 中使用桥接头。我是 OpenCV 框架的新手,并试图从图像中获得垂直线数。

这是我的代码: 首先,我将图像转换为灰度

 + (UIImage *)convertToGrayscale:(UIImage *)image 
    cv::Mat mat;
    UIImageToMat(image, mat);
    cv::Mat gray;
    cv::cvtColor(mat, gray, CV_RGB2GRAY);
    UIImage *grayscale = MatToUIImage(gray);
    return grayscale;

然后,我正在检测边缘,以便找到灰色线

+ (UIImage *)detectEdgesInRGBImage:(UIImage *)image 
    cv::Mat mat;
    UIImageToMat(image, mat);
    
    //Prepare the image for findContours
    cv::threshold(mat, mat, 128, 255, CV_THRESH_BINARY);

    //Find the contours. Use the contourOutput Mat so the original image doesn't get overwritten
    std::vector<std::vector<cv::Point> > contours;
    cv::Mat contourOutput = mat.clone();
    cv::findContours( contourOutput, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE );

    NSLog(@"Count =>%lu", contours.size());
    
    //For Blue
    /*cv::GaussianBlur(mat, gray, cv::Size(11, 11), 0); */
    
    UIImage *grayscale = MatToUIImage(mat);
    return grayscale;

这两个函数都是用Objective C写的

这里,我同时调用两个函数 Swift

override func viewDidLoad() 
        super.viewDidLoad()
        let img = UIImage(named: "imagenamed")
        let img1 = Wrapper.convert(toGrayscale: img)
        self.capturedImageView.image = Wrapper.detectEdges(inRGBImage: img1)
    

我做了几天,找到了一些有用的文件(参考链接)

OpenCV - how to count objects in photo?

How to count number of lines (Hough Trasnform) in OpenCV

OPENCV 文档

https://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?#findcontours

基本上,我明白了首先我们需要将这张图片转换成黑白,然后使用cvtColor, threshold and findContours我们可以找到颜色或线条。

我正在附上我想要获得的垂直线条的图像。

原图

我得到的输出图像

我得到了行数 =>10 我无法在这里获得准确的计数。

请指导我。谢谢!

【问题讨论】:

我猜你的行计数器将分支计数为两行或更多行? 【参考方案1】:

既然你想检测垂直线的数量,我可以为你推荐一个非常简单的方法。你已经得到了一个清晰的输出,我在我的代码中使用了这个输出。以下是代码前的步骤:

    预处理输入图像以获得清晰的线条 检查每一行,检查直到得到一个像素值大于100(阈值我选择) 然后增加该行的行计数器 在该行上继续,直到得到一个值小于 100 的像素 从第 3 步重新开始,完成每一行的图像 最后,检查您为每一行分配行号的数组中重复次数最多的元素。这个数字将是垂直线的数量。

注意:如果步骤难以理解,可以这样想:

" 我正在检查第一行,我发现一个像素高于 100,现在这是一条线边开始,为此增加计数器 排。在这一行上搜索,直到得到一个小于 100 的像素,然后 研究一个大于 100 的像素。当行完成时,分配 该行的行号到一个大数组。对所有图像执行此操作。在 结束,因为有些行看起来像顶部的两条线,还有一些 可能会出现噪音,你应该取大中重复次数最多的元素 数组作为行数。”

这是C++中的代码部分:

#include <vector>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>


int main()

    cv::Mat img = cv::imread("/ur/img/dir/img.jpg",cv::IMREAD_GRAYSCALE);

    std::vector<int> numberOfVerticalLinesForEachRow;


    cv::Rect r(0,0,img.cols-10,200);

    img = img(r);

    bool blackCheck = 1;

    for(int i=0; i<img.rows; i++)
    
        int numberOfLines = 0;

        for(int j=0; j<img.cols; j++)
        
            if((int)img.at<uchar>(cv::Point(j,i))>100 && blackCheck)
            
                numberOfLines++;
                blackCheck = 0;
            
            if((int)img.at<uchar>(cv::Point(j,i))<100)
                blackCheck = 1;
        

        numberOfVerticalLinesForEachRow.push_back(numberOfLines);
    

    // In this part you need a simple algorithm to check the most repeated element
    for(int k:numberOfVerticalLinesForEachRow)
        std::cout<<k<<std::endl;





    cv::namedWindow("WinWin",0);

    cv::imshow("WinWin",img);

    cv::waitKey(0);



【讨论】:

我用你的代码找到了一个大数组,但我正在寻找的数字不包含在数组中,而且 numberOfLines 总是显示为零。你能帮我解决这个问题吗?谢谢! 你有没有使用你得到的图像作为输出。您分享的第二张图片是哪张?我在我的代码中使用了您共享的第二张图片作为输入,它工作正常【参考方案2】:

这是另一种可能的方法。它主要依靠扩展图像处理模块中的cv::thinning函数来减少1像素宽度的线条。我们可以从这张图片中裁剪出ROI,并计算从255(白色)到0(黑色)的转换次数。这些是步骤:

    阈值使用 Otsu 方法的图像 应用一些形态学来清理二值图像 获取图像的骨架 从图片中心裁剪ROI 计数2550跳跃次数

这是代码,一定要包含扩展的图像处理模块(ximgproc)并在编译之前链接它:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/ximgproc.hpp> // The extended image processing module

// Read Image:
std::string imagePath = "D://opencvImages//";
cv::Mat inputImage = cv::imread( imagePath+"IN2Xh.png" );

// Convert BGR to Grayscale:
cv::cvtColor( inputImage, inputImage, cv::COLOR_BGR2GRAY );

// Get binary image via Otsu:
cv::threshold( inputImage, inputImage, 0, 255, cv::THRESH_OTSU );

上面的 sn-p 生成如下图像:

请注意,由于阈值处理会产生一点噪音,让我们尝试通过应用一些形态来去除那些孤立的白色像素块。可能是opening,即erosion,后跟dilation。然而,结构元素和迭代并不相同,这些都是通过实验发现的。我想在不过多修改原始图像的情况下删除大部分孤立的 blob:

// Apply Morphology. Erosion + Dilation:
// Set rectangular structuring element of size 3 x 3:
cv::Mat SE = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(3, 3) );
// Set the iterations:
int morphoIterations = 1;
cv::morphologyEx( inputImage, inputImage, cv::MORPH_ERODE, SE, cv::Point(-1,-1), morphoIterations);

// Set rectangular structuring element of size 5 x 5:
SE = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(5, 5) );
// Set the iterations:
morphoIterations = 2;
cv::morphologyEx( inputImage, inputImage, cv::MORPH_DILATE, SE, cv::Point(-1,-1), morphoIterations);

结构元素和迭代的这种组合产生了以下过滤后的图像:

看起来不错。现在是算法的主要思想。如果我们计算这张图片的骨架,我们会将所有线条“标准化”为 1 像素的宽度,这非常方便,因为我们可以将图片缩减为 1 x 1(行)矩阵并计算跳跃次数.由于线条是“标准化”的,我们可以消除线条之间可能的重叠。现在,骨架化图像有时会在图像边界附近产生伪影。这些伪影类似于图像第一行和最后一行的加厚锚。为了防止这些伪影,我们可以在计算骨架之前扩展边界:

// Extend borders to avoid skeleton artifacts, extend 5 pixels in all directions:
cv::copyMakeBorder( inputImage, inputImage, 5, 5, 5, 5, cv::BORDER_CONSTANT, 0 );

// Get the skeleton:
cv::Mat imageSkelton;
cv::ximgproc::thinning( inputImage, imageSkelton );

这是得到的骨架:

很好。不过,在我们计算跳跃之前,我们必须观察到线条是倾斜。如果我们直接将此图像缩减为一行,那么在过于倾斜的线条之间确实可能会发生一些重叠。为了防止这种情况,我裁剪了骨架图像的中间部分并计算那里的过渡。让我们裁剪图像:

// Crop middle ROI:
cv::Rect linesRoi;
linesRoi.x = 0;
linesRoi.y = 0.5 * imageSkelton.rows;
linesRoi.width = imageSkelton.cols;
linesRoi.height = 1;

cv::Mat imageROI = imageSkelton( linesRoi );

这将是新的ROI,它只是骨架图像的中间行:

让我准备一份BGR 的副本,只是为了得出一些结果:

// BGR version of the Grayscale ROI:
cv::Mat colorROI;
cv::cvtColor( imageROI, colorROI, cv::COLOR_GRAY2BGR );

好的,让我们循环遍历图像并计算2550 之间的转换。当我们查看当前像素的值并将其与之前迭代获得的值进行比较时,就会发生这种情况。当前像素必须是0,过去的像素必须是255。在C++ 中循环通过cv::Mat 的方法不止一种。我更喜欢使用cv::MatIterator_s 和指针算法:

// Set the loop variables:
cv::MatIterator_<cv::Vec3b> it, end;
uchar pastPixel = 0;
int jumpsCounter = 0;
int i = 0; 

// Loop thru image ROI and count 255-0 jumps:
for (it = imageROI.begin<cv::Vec3b>(), end = imageROI.end<cv::Vec3b>(); it != end; ++it) 

    // Get current pixel
    uchar &currentPixel = (*it)[0];
    // Compare it with past pixel:
    if ( (currentPixel == 0) && (pastPixel == 255) )
        // We have a jump:
        jumpsCounter++;
        // Draw the point on the BGR version of the image:
        cv::line( colorROI, cv::Point(i, 0), cv::Point(i, 0), cv::Scalar(0, 0, 255), 1 );
    

    // current pixel is now past pixel:
    pastPixel = currentPixel;
    i++;



// Show image and print number of jumps found:
cv::namedWindow( "Jumps Found", CV_WINDOW_NORMAL );
cv::imshow( "Jumps Found", colorROI );
cv::waitKey( 0 );

std::cout<<"Jumps Found: "<<jumpsCounter<<std::endl;

发现跳跃的点用红色绘制,打印的总跳跃数为:

Jumps Found: 9

【讨论】:

以上是关于Opencv - 如何获取图像中存在的垂直线数(行数)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 OpenCV 2 中从图像中获取通道数?

如何使用倾斜的线在openCV中获取矩阵(ROI)

OpenCV-获取图像中直线上的数据

OpenCV-获取图像中直线上的数据

如何获取矩阵二范数 opencv

使用PythonOpenCV翻转图像(水平垂直水平垂直翻转)