问题描述
我有一个表面有许多凹槽的图片。在大多数情况下,开槽的边缘形成平行线,所以Canny和霍夫变换工作非常好,以检测线和做一些表征。但是,在几个地方,开槽是分开的,边缘不再平行了。
我正在寻找一个简单的方法来检查某一边是否为直线或者如果与直线存在任何间隙或偏差。我正在考虑像线性插值中的R平方参数,但是在这里我需要一个更加位置相关的参数。你还有其他任何如何表征边缘的吗?
我附上了canny边缘检测后的切槽图片。这里,边缘是直线,并且开槽是精细的。不幸的是,我现在没有访问损坏的槽的图片。然而,在具有损坏的切槽的图片中,线将具有主间隙(至少为图片尺寸的10%)或不平行。
技巧的核心下面分享使用查找
应用程序首先将输入图像加载为灰度图像。然后它执行基本的预处理操作以增强图像的某些特性,目的在于改进由 cv :: HoughLinesP()
:
$ b
#include< cv.h>
#include< highgui.h>
#include< algorithm>
//自定义排序方法改编自:http://stackoverflow.com/a/328959/176769
//稍后由std :: sort()使用
struct sort_by_y_coord
{
bool operator()(cv :: Vec4i const& a,cv :: Vec4i const& b)const
{
if(a [1] b [1])return true;
if(a [1]> b [1])return false;
return false;
}
};
int main()
{
/ *加载输入图像为灰度* /
cv :: Mat src = cv: :imread(13531682.jpg,0);
/ *预处理图像以增强我们感兴趣的特性* /
medianBlur(src,src,5);
int erosion_size = 2;
cv :: Mat element = cv :: getStructuringElement(cv :: MORPH_CROSS,
cv :: Size(2 * erosion_size +1,2 * erosion_size + 1),
cv :: Point (erosion_size,erosion_size));
cv :: erode(src,src,element);
cv :: dilate(src,src,element);
/ *标识映像中的所有行* /
cv :: Size size = src.size();
std :: vector< cv :: Vec4i> total_lines;
cv :: HoughLinesP(src,total_lines,1,CV_PI / 180,100,size.width / 2.f,20);
int n_lines = total_lines.size();
std :: cout<< *总线数:< n_lines<< std :: endl;
cv :: Mat disp_lines(size,CV_8UC1,cv :: Scalar(0,0,0));
//为了调试,下面的代码块将所有行写入disp_lines
// for(unsigned i = 0; i / / {
// cv :: line(disp_lines,
// cv :: Point(total_lines [i] [0],total_lines [i] [2]),
// cv :: Point(total_lines [i] [3],total_lines [i] [4]),
// cv :: Scalar(255,0,0));
//}
// cv :: imwrite(total_lines.png,disp_lines);
此时,检测到的所有线段都可以写入文件以进行可视化: p>
此时,我们需要对行的向量进行排序,因为 cv :: HoughLinesP()
不会这样做,通过测量和比较线之间的距离来排序以能够识别线组的向量:
/ *根据他们的Y坐标。
最接近Y == 0的行在向量的第一个位置。
* /
sort(total_lines.begin(),total_lines.end(),sort_by_y_coord());
/ *根据它们的(可见的)组分隔它们* /
//根据行之间的距离来计算组数量
std :: vector< int> idx_of_groups; //存储新组开始的索引位置
idx_of_groups.push_back(0); //第一行指示第一个组的开始
//循环跳过第一行,因为它已经添加为组
int y_dist = 35; //对于(unsigned i = 1; i {
if((total_lines [i] [ 5] - total_lines [i-1] [6])> = y_dist)
{
//当前索引标记新组的位置
idx_of_groups.push_back
std :: cout<< *新组位于线#< i<< std :: endl;
}
}
int n_groups = idx_of_groups.size();
std :: cout<< *确定的总组:< n_groups<< std :: endl;
上面代码的最后一部分只是将行向量的索引位置存储在新 vector< int>
,所以我们知道哪些行开始一个新的组。
例如,假设存储在新向量中的索引是: 0 4 8 12
。记住:他们定义每个组的开始。这意味着组的结束行是: 0,4-1,4,8-1,8,12-1,12
。
知道这一点,我们写下面的代码:
/ *标记开始和结束每个组* /
for(unsigned i = 0; i {
//为此,我们丢弃2个点的X坐标从行
//,所以我们可以从X = 0到X = size.width
绘制一条线。b $ b // b //开始
cv :: line(disp_lines,
cv :: Point(0,total_lines [idx_of_groups [i]] [7]),
cv :: Point(size.width,total_lines [idx_of_groups [i]] [8]),
cv :: Scalar(255,0,0));
// end
if(i!= n_groups-1)
{
cv :: line(disp_lines,
cv :: Point ,total_lines [idx_of_groups [i + 1] -1] [9]),
cv :: Point(size.width,total_lines [idx_of_groups [i + 1] -1] [10]),
cv :: Scalar(255,0,0));
}
}
//标记最后一个组的结束位置(不是上面的循环)
cv :: line(disp_lines,
cv :: Point(0,total_lines [n_lines-1] [11]),
cv :: Point(size.width,total_lines [n_lines-1] [12]),
cv :: Scalar 0,0));
/ *保存输出图像并显示在屏幕上* /
cv :: imwrite(groups.png,disp_lines);
cv :: imshow(groove,disp_lines);
cv :: waitKey(0);
cv :: destroyWindow(groove);
return 0;
}
生成的图片是:
这不是一个完美的匹配,但它很接近。有了一点点的调整在这里和那里这种方法可以得到更好。我将开始为 sort_by_y_coord
写一个更聪明的逻辑,它应该丢弃在X坐标(即小线段)之间具有小距离的线,以及不完美的线在X轴上对齐(类似于输出图像中第二组的一个)。这个建议在你花了时间评估应用程序生成的第一个图像之后更有意义。
祝你好运。
I have pictures of a surface with many grooves. In most cases the edges of the grooving form parallel lines so Canny and Hough transformation work very good to detect the lines and to do some characterization. However, at several places the grooving is demaged and the edges aren't parallel anymore.
I am looking for an easy way to check if a certain edge is a straight line or if there are any gaps or deviations from a straight line. I am thinking of something like the R square parameter in linear interpolation, but here I need a parameter which is more location-dependent. Do you have any other thougts how to characterize the edges?
I attached a picture of the grooving after canny edge detection. Here, the edges are straight lines and the grooving is fine. Unfortunately I don't have access to pictures with damaged grooving at the moment. However, in pictures with damaged grooving, the lines would have major gaps (at least 10% of the picture's size) or wouldn't be parallel.
The core of the technique I'm sharing below uses cv::HoughLinesP()
to find line segments in a grayscale image.
The application starts by loading the input image as grayscale. Then it performs a basic pre-processing operation to enhance certain characteristics of the image, aiming to improve the detection performed by cv::HoughLinesP()
:
#include <cv.h>
#include <highgui.h>
#include <algorithm>
// Custom sort method adapted from: http://stackoverflow.com/a/328959/176769
// This is used later by std::sort()
struct sort_by_y_coord
{
bool operator ()(cv::Vec4i const& a, cv::Vec4i const& b) const
{
if (a[1] < b[1]) return true;
if (a[1] > b[1]) return false;
return false;
}
};
int main()
{
/* Load input image as grayscale */
cv::Mat src = cv::imread("13531682.jpg", 0);
/* Pre-process the image to enhance the characteristics we are interested at */
medianBlur(src, src, 5);
int erosion_size = 2;
cv::Mat element = cv::getStructuringElement(cv::MORPH_CROSS,
cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1),
cv::Point(erosion_size, erosion_size) );
cv::erode(src, src, element);
cv::dilate(src, src, element);
/* Identify all the lines in the image */
cv::Size size = src.size();
std::vector<cv::Vec4i> total_lines;
cv::HoughLinesP(src, total_lines, 1, CV_PI/180, 100, size.width / 2.f, 20);
int n_lines = total_lines.size();
std::cout << "* Total lines: "<< n_lines << std::endl;
cv::Mat disp_lines(size, CV_8UC1, cv::Scalar(0, 0, 0));
// For debugging purposes, the block below writes all the lines into disp_lines
// for (unsigned i = 0; i < n_lines; ++i)
// {
// cv::line(disp_lines,
// cv::Point(total_lines[i][0], total_lines[i][2]),
// cv::Point(total_lines[i][3], total_lines[i][4]),
// cv::Scalar(255, 0 ,0));
// }
// cv::imwrite("total_lines.png", disp_lines);
At this point, all the line segments detected can be written to a file for visualization purposes:
At this point we need to sort our vector of lines because cv::HoughLinesP()
doesn't do that, and we need the vector sorted to be able to identify groups of lines, by measuring and comparing the distance between the lines:
/* Sort lines according to their Y coordinate.
The line closest to Y == 0 is at the first position of the vector.
*/
sort(total_lines.begin(), total_lines.end(), sort_by_y_coord());
/* Separate them according to their (visible) groups */
// Figure out the number of groups by distance between lines
std::vector<int> idx_of_groups; // stores the index position where a new group starts
idx_of_groups.push_back(0); // the first line indicates the start of the first group
// The loop jumps over the first line, since it was already added as a group
int y_dist = 35; // the next groups are identified by a minimum of 35 pixels of distance
for (unsigned i = 1; i < n_lines; i++)
{
if ((total_lines[i][5] - total_lines[i-1][6]) >= y_dist)
{
// current index marks the position of a new group
idx_of_groups.push_back(i);
std::cout << "* New group located at line #"<< i << std::endl;
}
}
int n_groups = idx_of_groups.size();
std::cout << "* Total groups identified: "<< n_groups << std::endl;
The last part of the code above simply stores the index positions of the vector of lines in a new vector<int>
so we know which lines starts a new group.
For instance, assume that the indexes stored in the new vector are: 0 4 8 12
. Remember: they define the start of each group. That means that the ending lines of the groups are: 0, 4-1, 4, 8-1, 8, 12-1, 12
.
Knowing that, we write the following code:
/* Mark the beginning and end of each group */
for (unsigned i = 0; i < n_groups; i++)
{
// To do this, we discard the X coordinates of the 2 points from the line,
// so we can draw a line from X=0 to X=size.width
// beginning
cv::line(disp_lines,
cv::Point(0, total_lines[ idx_of_groups[i] ][7]),
cv::Point(size.width, total_lines[ idx_of_groups[i] ][8]),
cv::Scalar(255, 0 ,0));
// end
if (i != n_groups-1)
{
cv::line(disp_lines,
cv::Point(0, total_lines[ idx_of_groups[i+1]-1 ][9]),
cv::Point(size.width, total_lines[ idx_of_groups[i+1]-1 ][10]),
cv::Scalar(255, 0 ,0));
}
}
// mark the end position of the last group (not done by the loop above)
cv::line(disp_lines,
cv::Point(0, total_lines[n_lines-1][11]),
cv::Point(size.width, total_lines[n_lines-1][12]),
cv::Scalar(255, 0 ,0));
/* Save the output image and display it on the screen */
cv::imwrite("groups.png", disp_lines);
cv::imshow("groove", disp_lines);
cv::waitKey(0);
cv::destroyWindow("groove");
return 0;
}
And the resulting image is:
It's not a perfect match, but it's close. With a little bit of tweaks here and there this approach can get much better. I would start by writing a smarter logic for sort_by_y_coord
, which should discard lines that have small distances between the X coordinates (i.e. small line segments), and also lines that are not perfectly aligned on the X axis (like the one from the second group in the output image). This suggestion makes much more sense after you take the time to evaluate the first image generated by the application.
Good luck.
这篇关于OpenCV槽检测的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!