重叠图像的凹点分割(C++实现)
针对图像处理中多个颗粒重叠的问题,提出了一种利用重叠区域边界寻找凹点来分割重叠图像的算法。其实凹点分割并不是什么新鲜的方法了,因为其分割方法并不能保证百分百找到准确的凹点,很多硕士论文都一直在以这个方法为切入点讨论。但是我翻阅了很多的博客也没有找到一个真正的能够跑起来的代码。索性自己写了一个,写的有些粗糙,希望大家能多指正交流。
下面是我要处理的图像:
可以看出有的部分粘连的十分厉害,开运算腐蚀等方法把它腐蚀没了都不能很好的分割开来。看下面这张图是我单纯靠腐蚀做的:
凹点分割的方法理论上是有三种,分别是方向链码法、矢量夹角法和切线法
1、链码法
链码可以表示图像的边界,表示方法为具体有大小和方向的直线段。其起始点为绝对坐标,其余点根据边界的走向确定一个值。常用的链码有4方向和8方向的链码。链码的方法和对应关系如下图表示:
链码取值与边界的方向有关,N点链码和的平均为平均链码,代表这N点连线的方向。曲率描述边界的弯曲程度,与弧长和边界的偏转角有关。由于链码的单位长度相同,同样弧度的边界,其曲率仅与偏转角度有关,而平均链码代表方向,因此可以由平均链码差代表偏转角度,即曲率。曲率的极值点可能为凹点。
2、矢量夹角法
矢量夹角法的思想在于对于边界上的每一个边缘点,选择等距离的前继点和后继点,边缘点和前继点,边缘点和后继点之间的连线构成一个夹角。由夹角的正负和大小判断此边缘是否为凹点。此方法思想简单,但是实现效果难以把握。(本文就是采用此方法)首先凹点的判断需要根据角度进行,所以夹角必定要设置一个阈值,其次步长的选择也对夹角的大小影响较大。如果步长过小或者阈值过大,则噪声点可能会被当成凹点,如果步长过大或者阈值过小,可能把一些凹点滤除了。
3、切线法
切线法思想在于通过判断边界上某点的切线是否通过连通区域内部来判断此点是否是凹点。如果凹点的两侧的点都在区域外部,则此点为局部凸点。如果在区域内部,此点为局部凹点。如果一条直线与区域边界多点相切,那么这些点为该物体最大凸点,且最大切点间必有凹点。此方法可以比较精确的找到凹点,但是计算量比较大。
本文主要介绍第二种方法----矢量夹角法
首先对图像进行二值化,如下图:
对二值化图像进行边缘检测,把边缘检测到的边缘点保存起来,对每一个连通区域进行凹点检测,可以得到下面的凹点检测效果图:
然后对凹点进行匹配切割可以得到切割后的图。切割后的效果图:
可以看出基本上可以分割开,但是矢量夹角方法太过简单,计算量也很少,所以做不到很精确,有一些凹点就找不到,所以分割效果不是太好。大家可以试试其他两种凹点分割方法。然后我又对其进行了距离变换,效果如下:
最后附上所有代码:`
/
opencv4.1.0
#include<opencv2/opencv.hpp>
#include<iostream>
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::