在上述博客中,我分别对平滑滤波、边缘检测、直线检测做了一定程度的了解,那么最终的目的我们是需要解决实际问题,那么今天就试着完成一个简单的综合训练,来在巩固前面所学知识的同时,学到新的东西!
1.分别检测两线段的长度;
2.算出这两平行线之间的距离。
题目参考:http://www.opencvchina.com/thread-854-1-2.html
先看第一问,要求直线的距离。很容易联想到上一回所说的霍夫变换中的PHTT方法,返回的是直线的方向以及范围,说的具体一点,实际上返回的就是检测到的直线的两端点。我们不妨用霍夫变换直接试试。具体步骤:
将图像转换成灰度图像->霍夫变换->将检测直线的端点(绿色圆圈)标记出来
得到下面的结果:
可以看到,我们检测到了很多条直线,如图有很多直线的端点,尤其是直线端点处,被检测到了多个端点(可以看得出有重叠效果)。其实也不难理解,这条直线在像素上来说是有厚度的,所以难免被检测到多条直线,这样的话就带来了问题,我们无法确定到底哪个是真正直线的两端点,于是就不能够得到线段的长度!那么我们在使用霍夫变换前需要做一些处理。-----------图像细化。
图像细化实际上就是图像骨架化,把图像核心的骨架留下,其他的赘余点删除,对于这个图来说就是把这条直线的厚度消除,得到一条单像素厚度的直线段。实际上,图像细化在图像处理、模式识别中使用的非常广发,也是非常关键的过程,这里我们介绍一种比较经典实用的图像细化方法----Zhang并行快速算法:
具体实现方法、步骤如下:
其中P1是我们讨论的像素点,首先前提条件是P1是前景点!若要删除该点,需要满足一下条件:
过程1:
(1)2 <= N(P1) <= 6,N(x)为x的8领域中黑点(背景点)的个数;
(2)A(P1) = 1,A(x)为P2-P8之间按序前后分别为0、1的对数;
(3)P2*P4*P6 = 0;
(4)P4*P6*P8 = 0.
满足上述四个条件,可将P1点去除;
过程2:
(1)2 <= N(P1) <= 6,N(x)为x的8领域中黑点(背景点)的个数;
(2)A(P1) = 1,A(x)为P2-P8之间按序前后分别为0、1的对数;
(3)P2*P4*P8 = 0;
(4)P2*P6*P8 = 0.
满足上述四个条件,可将P1点去除;
以上即为Zhang并行算法的主要步骤,需要反复运行直到无法得到可删除点为止。对于图像细化,这里只做一个简单的了解,很多理论知识以及高级的算法我后续会再进行分析。
给出关键代码函数如下:
//寻找按序的前后分别是0、1的对数函数
int Findn(IplImage* img, int i, int j)
{
CvScalar s1 = cvGet2D(img, i, j);
CvScalar s2 = cvGet2D(img, i - 1, j);
CvScalar s3 = cvGet2D(img, i - 1, j + 1);
CvScalar s4 = cvGet2D(img, i, j + 1);
CvScalar s5 = cvGet2D(img, i + 1, j + 1);
CvScalar s6 = cvGet2D(img, i + 1, j);
CvScalar s7 = cvGet2D(img, i + 1, j - 1);
CvScalar s8 = cvGet2D(img, i, j - 1);
CvScalar s9 = cvGet2D(img, i - 1, j - 1);
int a = s1.val[0];
int b = s2.val[0];
int c = s3.val[0];
int d = s4.val[0];
int e = s5.val[0];
int f = s6.val[0];
int g = s7.val[0];
int h = s8.val[0];
int l = s9.val[0];
int find[] = { a, b, c, d, e, f, g, h, l };//按8领域顺序定义数组,方便操作
int n = 0;
for (int x = 2; x < 9; ++x)
{
if (find[x] == 0 && find[x + 1] == 255)
{
n = n + 1;
}
}
return n;
}
//这是细化直线的功能函数
IplImage* ThinImage(IplImage* img, int k) //确保输入的是二值图像
{
int condition = 0;//满足的条件个数
int mark = 0;//成功的标志位
int firstN = 0;//第一个条件黑点的个数
CvScalar s;
for (int n = 0; n < k; ++n)
{
for (int i = 1; i < img->height - 1; ++i)
{
for (int j = 1; j < img->width - 1;++j)
{//开始过程1的寻找
condition = 0;//初始化条件满足数
//cout << "1" << endl;
s = cvGet2D(img, i, j);
if (s.val[0] == 255)//如果这是前景点,即边缘
{
//cout << "2" << endl;
//*************************第一过程****************************
//*************************step1****************************
firstN = 0;
for (int ii = -1; ii < 1; ++ii)
{
for (int jj = -1; jj < 1; ++jj)
{
s = cvGet2D(img, i + ii, j + jj);
if (s.val[0] == 255)
{
firstN = firstN + 1;
}
}
}
if (firstN < 3 || firstN > 7)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step2*********************************
if (Findn(img, i, j) != 1)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step3*********************************
CvScalar s1 = cvGet2D(img, i - 1, j);
CvScalar s2 = cvGet2D(img, i, j + 1);
CvScalar s3 = cvGet2D(img, i + 1, j);
CvScalar s4 = cvGet2D(img, i, j - 1);
int a = s1.val[0];//2
int b = s2.val[0];//4
int c = s3.val[0];//6
int d = s4.val[0];//8
if (a * b * c != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//******************************step4**********************************
if (b * c * d != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//第一过程的结果操作
if (condition == 4)
{
mark = 1;
//((char *)(img->imageData + img->widthStep * (i)))[j] = 0;
CvScalar p;
p.val[0] = 0;
cvSet2D(img, i, j, p);
//cout << "11111111111111111111111111111111111" << endl;
}
}
}
}
//****************************过程2**************************
for (int i = 1; i < img->height - 1; ++i)
{
for (int j = 1; j < img->width - 1;++j)
{//开始过程1的寻找
condition = 0;//初始化条件满足数
//cout << "1" << endl;
s = cvGet2D(img, i, j);
if (s.val[0] == 255)//如果这是前景点,即边缘
{
//cout << "2" << endl;
//*************************第一过程****************************
//*************************step1****************************
firstN = 0;
for (int ii = -1; ii < 1; ++ii)
{
for (int jj = -1; jj < 1; ++jj)
{
s = cvGet2D(img, i + ii, j + jj);
if (s.val[0] == 255)
{
firstN = firstN + 1;
}
}
}
if (firstN < 3 || firstN > 7)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step2*********************************
if (Findn(img, i, j) != 1)
{
continue;
}
else
{
condition = condition + 1;
}
//****************************************************************
//*************************step3*********************************
CvScalar s1 = cvGet2D(img, i - 1, j);
CvScalar s2 = cvGet2D(img, i, j + 1);
CvScalar s3 = cvGet2D(img, i + 1, j);
CvScalar s4 = cvGet2D(img, i, j - 1);
int a = s1.val[0];//2
int b = s2.val[0];//4
int c = s3.val[0];//6
int d = s4.val[0];//8
if (a * b * d != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//******************************step4**********************************
if (a * c * d != 0)
{
continue;
}
else
{
condition = condition + 1;
}
//*********************************************************************
//第一过程的结果操作
if (condition == 4)
{
mark = 1;
//((char *)(img->imageData + img->widthStep * (i)))[j] = 0;
CvScalar p;
p.val[0] = 0;
cvSet2D(img, i, j, p);
//cout << "222222222222222222222222" << endl;
}
}
}
}
//cout << " end " << endl;
}
return img;
}
以上即为图像骨架化的编程实现,让我们看一看效果吧:
可以看到,直线已经骨架化成功了,那么接下来,我们再使用霍夫变换检测线段。使用PPHT方法,得到线段的方向以及范围信息,并将检测的直线端点标记出来,代码如下:
CvMemStorage* storageline = cvCreateMemStorage(0);
CvSeq* lines;
lines = cvHoughLines2(binaryline, storageline, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 300, 100, 10, 1000);
int n = 0;
CvPoint* point;
double temp[8];
for (int i = 0; i < lines->total; ++i)
{
point = (CvPoint*)cvGetSeqElem(lines, i);
temp[i * 4] = point[0].x;
temp[i * 4 + 1] = point[0].y;
temp[i * 4 + 2] = point[1].x;
temp[i * 4 + 3] = point[1].y;
cvLine(resultline, point[0], point[1], CV_RGB(255, 0, 0));
cvCircle(resultline, point[0], 3, CV_RGB(0, 255, 0), 1, 8, 3);
cvCircle(resultline, point[1], 3, CV_RGB(0, 255, 0), 1, 8, 3);
double distance = sqrt((point[1].x - point[0].x) * (point[1].x - point[0].x) + (point[1].y - point[0].y) * (point[1].y - point[0].y));
cout << "the " << i << " line's distance :" << distance << endl;
++n;
}
cout << n << endl;
n = 0;
结果如下:
成功得到了直线端点坐标的信息,并且输出n = 2,即刚刚好检测到两条直线!nice,这样的话直线长度就很好求了,使用数学方法,计算两坐标之间的距离即可。(结果将在后面显示)
第二问实际上在第一问的基础上,就是一个数学问题。
如图,正是求两平行线距离x。
x = |AC|/sin(a)
于是通过向量求得
cos = (AB·AC)/|AB||AC|
sin = sqrt(i - cos^2)
于是便可求得x。