八邻域断点检测
本文的理论思想主要来源大家可以参照,迈克老狼2012:OpenCV学习(13) 细化算法(1) 。本文是我自己尝试着将八邻域的细化思想
运用到断点检测上,个人觉得其实仅仅是八邻域应用的一小方面大家可以尝试着往其他方面应用。其实相对于八邻域的细化思想,断
点检测其实更加简单。这里简单给大家介绍一下:
很简单大家看到以P1为中心的它的八个领域,如果 p2+p3+p4+p5+p6+p7+p8+p9小于等于6保证p1点是一个边界点,大于6则是一个内部点;等于0时候,周围没有等于1的像素,所以p1为孤立点;等于1的时候,周围只有1个灰度等于1的像素,所以是端点。
而我们的断点检测就是需要检测图像的端点,当然大家也可以根据这个检测图像中的孤立点或者其他。我这里仅以端点检测为例。
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
void breakImage(IplImage* src, IplImage* dst, int maxIterations = -1);
CvPoint2D32f center;
float x, y;
int main(int argc, char* argv[])
{
//传入一个灰度图像
IplImage* pSrc = cvLoadImage("C:\\Users\\w\\Desktop\\学习资料\\outP2.jpg", 0);
int n = 0, m = 0;
IplImage* pTmp = cvCloneImage(pSrc);
IplImage*pDst = cvCreateImage(cvGetSize(pSrc), 8, 1);
cvZero(pDst);
cvThreshold(pSrc, pTmp, 125, 1,1/*CV_THRESH_BINARY_INV*/);//做二值处理,将图像转换成0,1格式
cvShowImage("2", pDst);
breakImage(pTmp, pDst, 8);//调用断点检测函数
cvThreshold(pDst, pDst, 0.5, 255, CV_THRESH_BINARY);//做二值处理,将图像转换成0,1格式
cvNamedWindow("src", 1);
cvNamedWindow("dst", 1);
cvShowImage("src", pSrc);
cvShowImage("dst", pDst);
cvSaveImage("outP.jpg", pDst);
cvWaitKey(0);
cvReleaseImage(&pSrc);
cvReleaseImage(&pDst);
cvReleaseImage(&pTmp);
cvDestroyWindow("src");
cvDestroyWindow("dst");
return 0;
}
void breakImage(IplImage* src, IplImage* dst, int maxIterations)
{
int xypiont [10000][2];
int num=0;
CvSize size = cvGetSize(src);
cvCopy(src, dst);//将src中的内容拷贝到dst中
int count = 0; //记录迭代次数
while (true)
{
count++;
if (maxIterations != -1 && count > maxIterations) //限制次数并且迭代次数到达
break;
for (int i = 0; i < size.height; ++i)
{
for (int j = 0; j < size.width;++ j)
{
// p9 p2 p3
// p8 p1 p4
// p7 p6 p5
int p1 = CV_IMAGE_ELEM(dst, uchar, i, j);
if (p1 != 1) continue;
int p2 = (i == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j);
int p3 = (i == 0 || j == size.width - 1) ?0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j + 1);
int p4 = (j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i, j + 1);
int p5 = (i == size.height - 1 || j == size.width - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j + 1)
int p6 = (i == size.height - 1) ? 0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j);
int p7 = (i == size.height - 1 || j == 1) ?0 : CV_IMAGE_ELEM(dst, uchar, i + 1, j - 1);
int p8 = (j == 0) ? 0 : CV_IMAGE_ELEM(dst, uchar, i, j - 1);
int p9 = (i == 0 || j == 0) ?0 : CV_IMAGE_ELEM(dst, uchar, i - 1, j - 1);
if (p1 ==1)
{
if (( p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9)==1)
{
cout << "(y,x):" << "(" << i << "," << j << ")"<<"\n";
xypiont[num][0] = j;
xypiont[num][1] = i;
num++;
}
}
}
}
}
for (int k=0; k < num; k++)
{
center.y=xypiont[k][1];
center.x = xypiont[k][0];
cvCircle(dst, cvPointFrom32f(center), 15, cvScalar(155, 0, 0));
}
cvShowImage("dst", dst);
cvSaveImage("dsc_c.jpg", dst);
cout << "次数:" << count << "\n";
}
这个例子我对断点进行了标记,画出了以断点为圆心的小圆,圆的半径自己去决定就好。在这里需要强调的是,输入的图像必须要进行二值化,因为我们的理论思想就是像素点的值是0或1的;而且前提是将图像进行细化,这样结果会更加准确。本文输入的图像是我进行了轮廓的提取以及细化操作之后的;至于轮廓提取和细化的程序大家在网上搜索有很多的。由于我输入图像的问题所以我进行了两次二值化处理,具体的大家根据自己的输入情况定吧。
由于我的操作的图像较大,所以截取了部分区域,给他家看一下结果。
最初的原始图像:
下图是我进行轮廓提取以及细化操作后输出的图像也是我进行断点检测输入:
最后是我运行结果图:
代码的书写格式可能会有存在写问题,请大家见谅。自己也是初学者在代码编写中也遇到了许多问题,比如本文中的二值化的问题,我想仅进行一次二值化,但是总是出现问题,现在也没搞明白哪里的原因。希望和大家相互交流相互进步。