图像分割是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干个cluster集合每个集合包括一类像素
根据算法分为监督学习和无监督学习,图像分割的算法多数都是无监督学习-KMenas
距离变换常见算法有两种
- 不断膨胀/ 腐蚀得到
- 基于倒角距离
分水岭变换常见的算法
基于浸泡理论实现,假设颜色数据为一个个山头,在山底不停加水,直到各大山头之间形成了明显的分水线
API:
watershed ( // 分水岭变换
InputArray image,
InputOutputArray markers
)
distanceTransform ( // 距离变换
InputArray src, // 输入的图像,一般为二值图像
OutputArray dst, // 输出8位或者32位的浮点数,单一通道,大小与输入图像一致
OutputArray labels, // 输出 2D 的标签(离散Voronoi(维诺)图),类型为 CV_32SC1
,相同距离的算做同一个 label ,算出总共由多少个 labels
int distanceType, // 所用的求解距离的类型
CV_DIST_L1 distance = |x1-x2| + |y1-y2|
CV_DIST_L2 distance = sqrt((x1-x2)^2 + (y1-y2)^2) 欧几里得距离
CV_DIST_C distance = max(|x1-x2|, |y1-y2|)
int maskSize, // 最新的支持5x5,推荐3x3
int labelType=DIST_LABEL_CCOMP // Type of the label array to build, see cv::DistanceTransformLabelTypes
)
步骤:
代码:
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
int main() {
Mat src;
src = imread("C:\\Users\\Administrator\\Desktop\\pic\\10.jpg");
imshow("input", src);
//printf("1 depth=%d, type=%d, channels=%d\n", src.depth(), src.type(), src.channels());
//将白色背景变成黑色
for (int row = 0; row < src.rows; row++) {
for (int col = 0; col < src.cols; col++) {
if (src.at(row, col) == Vec3b(255, 255, 255)) {
src.at(row, col)[0] = 0;
src.at(row, col)[1] = 0;
src.at(row, col)[2] = 0;
}
}
}
imshow("blacksrc", src);
//锐化
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);//拉普拉斯算子
Mat lapulasimg;
Mat sharpimg = src;
//printf("2 depth=%d, type=%d, channels=%d\n", sharpimg.depth(), sharpimg.type(), sharpimg.channels());
filter2D(src, lapulasimg, CV_32F, kernel);// 这里计算的颜色数据有可能是负值,所以深度传 CV_32F, 不要传 -1,原图的深度是 CV_8U,不能保存负值
src.convertTo(sharpimg, CV_32F); // mat.type 由 CV_8UC3 转换为 CV_32FC3 ,为了下面的减法计算
Mat resultimg = sharpimg - lapulasimg;
lapulasimg.convertTo(lapulasimg, CV_8UC3);
resultimg.convertTo(resultimg, CV_8UC3);
imshow("ruihua", resultimg);
//转为二值
Mat binimg;
cvtColor(resultimg, resultimg, CV_RGB2GRAY);
threshold(resultimg, binimg, 40, 255, THRESH_BINARY);
imshow("binimg", binimg);
//距离变化
Mat disimg;
distanceTransform(binimg, disimg, DIST_L1, 3,5);// CV_32F表示输出图像的深度,通道数与输入图形一致
imshow("disimg", disimg);
//对距离变换结果进行归一化到0-1之间
normalize(disimg, disimg, 0, 1, NORM_MINMAX);
imshow("normal", disimg);
//使用阈值再次二值化得到标记(即颜色值达到0.4的地方,表示轮廓的边界,为发现轮廓做准备)
threshold(disimg, disimg, 0.4, 1, THRESH_BINARY);
imshow("erzhiimg", disimg);
//腐蚀得到每个peak - erode
Mat k1 = Mat::zeros(13, 13, CV_8UC1);
erode(disimg, disimg, k1);
imshow("erodeimg", disimg);
//发现轮廓
Mat dist_8u;
disimg.convertTo(dist_8u, CV_8UC1);
imshow("dist_8u*100", dist_8u*100 );//元素放大100倍
vector<vector >contours;
findContours(dist_8u, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
//绘制轮廓
RNG rng(12345);
Mat show_contours;
src.copyTo(show_contours);
Mat makers = Mat::zeros(src.size(), CV_32SC1);
for (size_t i = 0; i < contours.size(); i++) {
if (contours[i].size() <= 2)
continue;//过滤排除点数不够的轮廓,最终图像分割效果更好
drawContours(makers, contours, i, Scalar::all(i + 1), -1);//thickness=-1表示填充轮廓
if (i == 1) {//腐蚀的mat尺寸为3*3时下标1的轮廓两个点,在上面已经排除
printf("contours[1][0].x=%d, contours[1][0].y=%d, contours[1][1].x=%d,contours[1][1].y=%d\n",
contours[1][0].x, contours[1][0].y, contours[1][1].x, contours[1][1].y);
circle(show_contours, contours[1][0], 5, Scalar(0, 0, 255), -1);
circle(show_contours, contours[1][1], 5, Scalar(0, 0, 0), -1);
}
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(show_contours, contours, i, color, -1);
}
//创建标记,标记的位置如果要分割的图像块上会影响分割的效果,若果不创建,分水岭变换会无效
circle(makers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
imshow("maker*1000", makers * 1000);
imshow("show_contours", show_contours);
//分水岭变换,将绘制的轮廓区域的颜色数据蔓延到各轮廓所在的分水岭,这样图像分割已完成,后续不同着色显示
watershed(src, makers);
imshow("waterimg", makers * 1000);
Mat mark = Mat::zeros(makers.size(), CV_8UC1);
makers.convertTo(mark, CV_8UC1);
//bitwise_not(mark, mark, Mat()); // 颜色反差
imshow("markimg", mark);
// 为每个轮廓生成随机颜色
vector colors;
for (size_t i = 0; i < contours.size(); i++) {
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
// fill with color and display final result
Mat dst = Mat::zeros(makers.size(), CV_8UC3);
for (int row = 0; row < makers.rows; row++) {
for (int col = 0; col < makers.cols; col++) {
int index = makers.at<int>(row, col); // 对应上面传的 Scalar::all(i + 1), -1)
if (index > 0 && index <= static_cast<int>(contours.size())) { // 给各轮廓上不同色
dst.at(row, col) = colors[index - 1]; // 因为上面传的是 Scalar::all(i + 1), -1) 所以要减1
}
else {
dst.at(row, col) = Vec3b(0, 0, 0); // 轮廓之外全部黑色
}
}
}
imshow("Final Result", dst);
waitKey(0);
}