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
计数从255
到0
的跳跃次数
这是代码,一定要包含扩展的图像处理模块(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 );
好的,让我们循环遍历图像并计算255
和0
之间的转换。当我们查看当前像素的值并将其与之前迭代获得的值进行比较时,就会发生这种情况。当前像素必须是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 ¤tPixel = (*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 - 如何获取图像中存在的垂直线数(行数)的主要内容,如果未能解决你的问题,请参考以下文章