- 图像分割(Image Segmentation)是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。
根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法 - KMeans - 距离变换
①不断膨胀/腐蚀得到
②基于倒角距离
distanceTransform(InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP)
distanceType = DIST_L1/DIST_L2
maskSize = 3x3,最新的支持5x5,推荐3x3
labels离散维诺图输出
dst输出8位或者32位的浮点数,单一通道,大小与输入图像一致
- 分水岭变换(基于浸泡理论实现)
watershed(InputArray image, InputOutputArray markers)
- 步骤:
将背景变成黑色-目的是为后面的变换做准备
使用filter2D与拉普拉斯算子实现图像对比度提高sharp
转为二值图像通过threshold
距离变换
对距离变换结果进行归一化到[0~1]之间
使用阈值,再次二值化,得到标记
腐蚀得到每个Peak-erode
发现轮廓findContours
绘制轮廓drawContours
分水岭变换 watershed
对每个分割区域着色输出结果
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat src;
src = imread("../path.png");
if (src.empty())
{
cout << "could not load image1..." << endl;
return -1;
}
namedWindow("1src", WINDOW_AUTOSIZE);
imshow("1src", src);
//将背景变成黑色 - 目的是为后面的变换做准备
for (int row = 0; row < src.rows; row++)
{
for (int col = 0; col < src.cols; col++)
{
if (src.at<Vec3b>(row, col) == Vec3b(0, 0, 0))//把透明底(纯黑色)覆盖为黑色
{
src.at<Vec3b>(row, col)[0] = 0;
src.at<Vec3b>(row, col)[1] = 0;
src.at<Vec3b>(row, col)[2] = 0;
}
}
}
namedWindow("2background_black", WINDOW_AUTOSIZE);
imshow("2background_black", src);
//使用filter2D与拉普拉斯算子实现图像对比度提高sharp
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1,
1, -8, 1,
1, 1, 1);//定义卷积核
Mat Laplacian_img;
Mat temp = src;//把src赋给中间变量temp,temp进行卷积变换生成Laplacian_img,再把src数据类型转换给中间变量temp(src全过程不变,temp始终是中间变量)
filter2D(temp, Laplacian_img, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);//自定义卷积//增强对比度
src.convertTo(temp, CV_32F);//矩阵数据类型转换
Mat Sharp_img = temp - Laplacian_img;//图像锐化
Sharp_img.convertTo(Sharp_img, CV_8UC3);//矩阵数据类型转换
Laplacian_img.convertTo(Laplacian_img, CV_8UC3);//矩阵数据类型转换
namedWindow("3Sharp", WINDOW_AUTOSIZE);
imshow("3Sharp", Sharp_img);
//转为二值图像通过threshold
Mat thresh_img;
cvtColor(Sharp_img, thresh_img, COLOR_BGR2GRAY);//转换为灰度图像
threshold(thresh_img, thresh_img, 40, 255, THRESH_BINARY | THRESH_OTSU);//边缘变黑,即像素值为0
namedWindow("4threshold", WINDOW_AUTOSIZE);
imshow("4threshold", thresh_img);
//距离变换
Mat distance_img;
distanceTransform(thresh_img, distance_img, DIST_L2, 3);//L2为欧式距离度量//无标记距离变换(使得边缘为0,非边缘为非0)
normalize(distance_img, distance_img, 0, 1, NORM_MINMAX);//对距离变换结果进行归一化到[0~1]之间
namedWindow("5distance_img", WINDOW_AUTOSIZE);
imshow("5distance_img", distance_img);
threshold(distance_img, distance_img, 0.2, 1, THRESH_BINARY);//使用阈值,再次二值化,得到标记
//执行一些形态学操作,以便从图像中提取峰值
//腐蚀得到每个Peak-erode
Mat kernel_2 = Mat::ones(5, 5, CV_8UC1);//自定义卷积核
erode(distance_img, distance_img, kernel_2);//腐蚀(缩减明亮区域)
namedWindow("6erode", WINDOW_AUTOSIZE);
imshow("6erode", distance_img);
//发现轮廓findContours//为分水岭算法创建种子/标记
Mat contours_img;
distance_img.convertTo(contours_img, CV_8U);
vector<vector<Point>> contours;//定义轮廓
findContours(contours_img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());//查找轮廓
//绘制轮廓drawContours
Mat markers = Mat::zeros(src.size(), CV_32SC1);//定义标志Mat
for (size_t i = 0; i < contours.size(); i++)
{
//drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1),1,8,Mat());//绘制轮廓
drawContours(markers,//待绘制轮廓的图像
contours, //要绘制的轮廓
static_cast<int>(i), //轮廓索引值//需要绘制的是contours参数中的某一条轮廓还是全部轮廓
Scalar::all(static_cast<int>(i) + 1), -1);//颜色等
}
// 创建marker,标记的位置 如果在要分割的图像块上 会影响分割的结果,如果不创建,分水岭变换会无效//所以标记在纯黑背景上
circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1); //在markers这个Mat图像上画一个点,被点上(标记)的区域会被认为是一个连通区域
markers.convertTo(markers, CV_8U);
namedWindow("7markers", WINDOW_AUTOSIZE);
imshow("7markers", markers*1000);//imshow只能显示8U类型,而markers是32SC1类型//markers灰度级很低,所以要乘1000,不然显示出来会感觉什么都没有
markers.convertTo(markers, CV_32SC1);
//分水岭变换
watershed(src, markers);
Mat mark = Mat::zeros(markers.size(), CV_8UC1);
markers.convertTo(mark, CV_8UC1);//数据类型转换
bitwise_not(mark, mark, Mat());//取反//让每个区域显灰白
namedWindow("8watershed", WINDOW_AUTOSIZE);
imshow("8watershed", mark);
//对每个分割区域着色输出结果
vector<Vec3b> 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));//STL
}
Mat dst = Mat::zeros(markers.size(), CV_8UC3);
for (int row = 0; row < markers.rows; row++) {
for (int col = 0; col < markers.cols; col++) {
int index = markers.at<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size())) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
namedWindow("9Final Result", WINDOW_AUTOSIZE);
imshow("9Final Result", dst);
waitKey(0);
return 0;
}
src为:(格式为png的透明底图像,来源网络)
输出结果:
分割结果: