opencv|基于opencv的重叠图像的凹点分割(C++)

重叠图像的凹点分割(C++实现) 针对图像处理中多个颗粒重叠的问题,提出了一种利用重叠区域边界寻找凹点来分割重叠图像的算法。其实凹点分割并不是什么新鲜的方法了,因为其分割方法并不能保证百分百找到准确的凹点,很多硕士论文都一直在以这个方法为切入点讨论。但是我翻阅了很多的博客也没有找到一个真正的能够跑起来的代码。索性自己写了一个,写的有些粗糙,希望大家能多指正交流。
下面是我要处理的图像:
可以看出有的部分粘连的十分厉害,开运算腐蚀等方法把它腐蚀没了都不能很好的分割开来。看下面这张图是我单纯靠腐蚀做的:
opencv|基于opencv的重叠图像的凹点分割(C++)
文章图片

凹点分割的方法理论上是有三种,分别是方向链码法、矢量夹角法和切线法
1、链码法
链码可以表示图像的边界,表示方法为具体有大小和方向的直线段。其起始点为绝对坐标,其余点根据边界的走向确定一个值。常用的链码有4方向和8方向的链码。链码的方法和对应关系如下图表示:
opencv|基于opencv的重叠图像的凹点分割(C++)
文章图片

链码取值与边界的方向有关,N点链码和的平均为平均链码,代表这N点连线的方向。曲率描述边界的弯曲程度,与弧长和边界的偏转角有关。由于链码的单位长度相同,同样弧度的边界,其曲率仅与偏转角度有关,而平均链码代表方向,因此可以由平均链码差代表偏转角度,即曲率。曲率的极值点可能为凹点。
2、矢量夹角法
矢量夹角法的思想在于对于边界上的每一个边缘点,选择等距离的前继点和后继点,边缘点和前继点,边缘点和后继点之间的连线构成一个夹角。由夹角的正负和大小判断此边缘是否为凹点。此方法思想简单,但是实现效果难以把握。(本文就是采用此方法)首先凹点的判断需要根据角度进行,所以夹角必定要设置一个阈值,其次步长的选择也对夹角的大小影响较大。如果步长过小或者阈值过大,则噪声点可能会被当成凹点,如果步长过大或者阈值过小,可能把一些凹点滤除了。
3、切线法
切线法思想在于通过判断边界上某点的切线是否通过连通区域内部来判断此点是否是凹点。如果凹点的两侧的点都在区域外部,则此点为局部凸点。如果在区域内部,此点为局部凹点。如果一条直线与区域边界多点相切,那么这些点为该物体最大凸点,且最大切点间必有凹点。此方法可以比较精确的找到凹点,但是计算量比较大。
【opencv|基于opencv的重叠图像的凹点分割(C++)】本文主要介绍第二种方法----矢量夹角法
首先对图像进行二值化,如下图:
opencv|基于opencv的重叠图像的凹点分割(C++)
文章图片
对二值化图像进行边缘检测,把边缘检测到的边缘点保存起来,对每一个连通区域进行凹点检测,可以得到下面的凹点检测效果图:
opencv|基于opencv的重叠图像的凹点分割(C++)
文章图片
然后对凹点进行匹配切割可以得到切割后的图。切割后的效果图:
opencv|基于opencv的重叠图像的凹点分割(C++)
文章图片
可以看出基本上可以分割开,但是矢量夹角方法太过简单,计算量也很少,所以做不到很精确,有一些凹点就找不到,所以分割效果不是太好。大家可以试试其他两种凹点分割方法。然后我又对其进行了距离变换,效果如下:
opencv|基于opencv的重叠图像的凹点分割(C++)
文章图片

最后附上所有代码:`

/ opencv4.1.0#include #include using namespace std; using namespace cv; void distance_star(Mat& imge, Mat& outimge) //距离变换函数 void connected_components_stat(Mat& image); //带统计信息 RNG rng(123); void distance_star(Mat& imge, Mat& outimge) //距离变换函数 { Mat gray, binary; //滤波后的二值化 //imshow("binary", binary); // distance transform 距离变换 Mat dist; distanceTransform(imge, dist, DistanceTypes::DIST_L2, 3, CV_32F); normalize(dist, dist, 0, 1, NORM_MINMAX); // 归一化函数 imshow("距离变换图", dist); // binary二值化函数 threshold(dist, outimge, 0.17, 255, THRESH_BINARY); imshow("距离变换结果图", outimge); waitKey(1000); return; }void connected_components_stat(Mat& image) { int sum = 0; //记录总面积 int average = 0; //记录平均面积的1/2 int A = 0; //豌豆数量 int B = 0; //绿豆数量 // 二值化 Mat gray, binary; cvtColor(image, gray, COLOR_BGR2GRAY); threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU); distance_star(binary, binary); Mat dist_m; binary.convertTo(binary, CV_8UC1); //通道转换 //开运算、闭运算 Mat k = getStructuringElement(MORPH_RECT, Size(13, 13), Point(-1, -1)); morphologyEx(binary, binary, MORPH_OPEN, k); //morphologyEx(binary, binary, MORPH_CLOSE, k); // 形态学操作 - 彩色图像,目的是去掉干扰,让结果更好 Mat o = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(binary, binary, MORPH_ERODE, o); // 腐蚀,去粘连部位的干扰 //计算连通域 Mat labels = Mat::zeros(image.size(), CV_32S); Mat stats, centroids; int num_labels = connectedComponentsWithStats(binary, labels, stats, centroids, 8, 4); //对比新旧函数,用于过滤原始图像中轮廓分析后较小的区域,留下较大区域。 //使用不同的颜色标记连通域 vector colors(num_labels); vector colors1(num_labels); // background color colors[0] = Vec3b(0, 0, 0); colors1[0] = Vec3b(0, 0, 0); // object color for (int i = 1; i < num_labels; i++) { colors1[i] = Vec3b(rng.uniform(125, 125), rng.uniform(125, 125), rng.uniform(125, 125)); colors[i] = Vec3b(rng.uniform(255, 255), rng.uniform(255, 255), rng.uniform(255, 255)); } //Mat dst = Mat::zeros(image.size(), image.type()); // render result Mat dst = Mat::zeros(image.size(), image.type()); int w = image.cols; int h = image.rows; for (int row = 0; row < h; row++) { for (int col = 0; col < w; col++) { int label = labels.at(row, col); if (label == 0) continue; dst.at(row, col) = colors[label]; } } //利用统计信息标记连通域 for (int i = 1; i < num_labels; i++) { Vec2d pt = centroids.at(i, 0); int x = stats.at(i, CC_STAT_LEFT); int y = stats.at(i, CC_STAT_TOP); int width = stats.at(i, CC_STAT_WIDTH); int height = stats.at(i, CC_STAT_HEIGHT); int area = stats.at(i, CC_STAT_AREA); sum += area; //printf("area : %d, center point(%.2f, %.2f)\n", area, pt[0], pt[1]); //circle(dst, Point(pt[0], pt[1]), 2, Scalar(0, 0, 255), -1, 8, 0); //rectangle(dst, Rect(x, y, width, height), Scalar(255, 0, 255), 1, 8, 0); } average = sum / num_labels; for (int i = 1; i < num_labels; i++) { Vec2d pt = centroids.at(i, 0); int x = stats.at(i, CC_STAT_LEFT); int y = stats.at(i, CC_STAT_TOP); int width = stats.at(i, CC_STAT_WIDTH); int height = stats.at(i, CC_STAT_HEIGHT); int area = stats.at(i, CC_STAT_AREA); if (area > average) { A = A + 1; circle(dst, CvPoint2D32f(pt[0], pt[1]), 2, Scalar(0, 0, 255), -1, 8, 0); //rectangle(dst, Rect(x, y, width, height), Scalar(255, 0, 255), 1, 8, 0); } else { B = B + 1; circle(dst, CvPoint2D32f(pt[0], pt[1]), 2, Scalar(255, 0, 0), -1, 8, 0); //rectangle(dst, Rect(x, y, width, height), Scalar(255, 255, 0), 1, 8, 0); } } //CString str; printf("豌豆 : %d, \n绿豆:%d", A, B); //imwrite("D://3.bmp", dst); imshow("二分类图像", dst); waitKey(0); //nihe(dst); 椭圆拟合函数 }void pro(Mat& img1) //处理图像,所有处理过程均通过这个函数调用处理 { IplImage* src= https://www.it610.com/article/cvLoadImage("D://2.bmp", 0); //原彩色图像的二值化图像,用于转化通道处理 Mat img = imread("D://2.bmp"); IplImage* dst = cvCreateImage(cvSize(src->width, src->height), 8, 3); CvMemStorage* stor = cvCreateMemStorage(0); CvSeq* cont = NULL; int num = cvFindContours(src, stor, &cont, sizeof(CvContour), CV_RETR_LIST); //记录总轮廓数 int i = 0; int j = 0; int k = 0; float g = 0.0; int z = 0; float w = 0.0; float d = 0.0; float flag = 0.0; //没有用到,原本想给个标签的 float array1[700][3] = { 0.0 }; //实际上没有用到这个 这个可以扩展使用 for (i = 0; cont; cont = cont->h_next) { int* length = newint[num]; //作用是记录每个轮廓上的点数 length[i++] = cont->total; //记录每个轮廓上的元素个数//printf("cont->total:%d\n", cont->total); CvPoint* point = new CvPoint[cont->total]; CvSeqReader reader; CvPoint pt = cvPoint(0, 0); CvPoint pt1 = cvPoint(0, 0); CvPoint pt2 = cvPoint(0, 0); CvPoint pt3 = cvPoint(0, 0); CvPoint pt4 = cvPoint(0, 0); cvStartReadSeq(cont, &reader); for (int j = 0; j < cont->total; j++) { CV_READ_SEQ_ELEM(pt, reader); point[j] = pt; //cout << pt.x << "" << pt.y << endl; } for (j = 0; j < cont->total; j++) { int k = (j + 1) % cont->total; cvLine(dst, point[j], point[k], cvScalar(0, 0, 255), 1, 4, 0); }for (int j = 0; j < cont->total - 14; j++) { CV_READ_SEQ_ELEM(pt1, reader); CV_READ_SEQ_ELEM(pt2, reader); CV_READ_SEQ_ELEM(pt3, reader); pt1 = point[j]; pt2 = point[j + 7]; //取步长为7 pt3 = point[j + 14]; if ((pt1.x != pt2.x) & (pt3.x != pt2.x)) { //w = j + 8; //int g = int(((pt1.y-pt2.y)/(pt1.x-pt2.x))+ ((pt2.y - pt3.y) / (pt2.x - pt3.x))); float g = floor((pt2.x - pt1.x) * (pt3.x - pt2.x) + (pt2.y - pt1.y) * (pt3.y - pt2.y)); if (g < 1.0) { //w = j + 8; flag = 1; //cvLine(dst, point[j], point[j+2], cvScalar(0, 255, 0), 1, 4, 0); cvCircle(dst, pt2, 1, Scalar(0, 255, 255), -1); array1[z][0] = floor(pt2.x); array1[z][1] = floor(pt2.y); array1[z][2] = flag; z++; } } }delete point; } for (i = 0; i < 700; i++) { if (array1[i][2] == 1.0) { for (j = 1; j < 700; j++) { w = 35.0; float d = sqrt(pow((array1[i][0] - array1[j][0]), 2) + pow((array1[i][1] - array1[j][1]), 2)); if ((d < w) & (d > 5.0)) { //w = d; CvPoint2D32f pt5 = cvPoint2D32f(0.0, 0.0); CvPoint2D32f pt6 = cvPoint2D32f(0.0, 0.0); //CvPoint pt6 = cvPoint(0, 0); pt5.x = array1[i][0]; pt5.y = array1[i][1]; pt6.x = array1[j][0]; pt6.y = array1[j][1]; //凹点分割画线 line(img, pt5, pt6, Scalar(0, 0, 0), 2, 4, 0); } } } else {} } cvShowImage("寻找凹点图", dst); imshow("凹点分割后", img); waitKey(1000); connected_components_stat(img); return; }int main() { Mat img = imread("D://1.bmp", 0); 进行二值化处理,选择30,200.0为阈值 threshold(img, img, 40, 255, CV_THRESH_BINARY); bitwise_not(img, img, Mat()); //对二进制数据进行“非”操作 imshow("初始灰度图", img); //显示二值化图像 imwrite("D://2.bmp", img); //保存二值化图像 waitKey(500); pro(img); //处理图像 return 0; }

    推荐阅读