image segmentation
)是图像处理最重要的处理手段之一N
)个cluser
集合,每个集合包含一类像素KMeans
distanceTransform函数API
void distanceTransform(
InputArray src,
OutputArray dst,
OutputArray labels,
int distanceType,
int maskSize,
int labelType=DIST_LABEL_CCOMP
);
函数功能
用于计算图像中每一个非零点像素与其最近的零点像素之间的距离,输出的是保存每一个非零点与最近零点的距离信息;图像上越亮的点,代表了离零点的距离越远。
参数介绍
8bit
的二值图像(只有0
或1
)32bit
浮点数据CV_DIST_L1
,CV_DIST_L2
,CV_DIST_C
等,具体如下:DIST_USER | User defined distance |
---|---|
DIST_L1=1 | distance = |x1-x2| + |y1-y2 |
DIST_L2 | the simple euclidean distance |
DIST_C | distance = max(|x1-x2|,|y1-y2|) |
DIST_L12 | L1-L2 metric: distance =2(sqrt(1+x*x/2) - 1)) |
DIST_FAIR | distance = c^2(|x|/c-log(1+|x|/c)),c = 1.3998 |
DIST_WELSCH | distance = c2/2(1-exp(-(x/c)2)), c= 2.9846 |
DIST_HUBER | distance = |x| |
3
,5
或CV_DIST_MASK_PRECISE
,对 CV_DIST_L1
或CV_DIST_C
的情况,参数值被强制设定为 3, 因为3×3 mask
给出5×5 mask
一样的结果,而且速度还更快。DIST_MASK_3 | mask=3 |
---|---|
DIST_MASK_5 | mask=5 |
DIST_MASK-PRECISE |
2
维数组;8
位或者32
位浮点数,单一通道,大小与输入图像一致void watershed( InputArray image, InputOutputArray markers );
参数介绍
8bit3
通道彩色图像矩阵序列。Opencv
官方文档的说明如下:Before passing the image to the function, you have to roughly outline the desired regions in the image markers with positive (>0) indices. So, every region is represented as one or more connected components with the pixel values 1, 2, 3, and so on. Such markers can be retrieved from a binary mask using findContours() and drawContours(). The markers are “seeds” of the future image regions. All the other pixels in markers , whose relation to the outlined regions is not known and should be defined by the algorithm, should be set to 0’s. In the function output, each pixel in markers is set to a value of the “seed” components or to -1 at boundaries between the regions.
就不一句一句翻译了,大意说的是在执行分水岭函数watershed
之前,必须对第二个参数markers
进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv
中findContours
方法实现,这个是执行分水岭之前的要求。
接下来执行分水岭会发生什么呢?算法会根据markers
传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。
简单概括一下就是说第二个入参markers
必须包含了种子点信息。Opencv
官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours
可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed
其实并不简单。
[0~1]
之间#include
#include
#include
#define PIC_PATH "C:\\pic\\"
#define PIC_NAME "4.jpg"
using namespace cv;
using namespace std;
int main(void)
{
Mat src;
string pic = string(PIC_PATH) + string(PIC_NAME);
cout << "原始图片为:" << pic << endl;
src = imread(pic);
if (src.empty()) {
cout << "图片不存在" << endl;
return -1;
}
namedWindow("原始图片", WINDOW_AUTOSIZE);
imshow("原始图片", src);
//将图片背景转换为黑色
for(size_t row=0;row<src.rows;row++)
for (size_t col = 0; col < src.cols; col++)
{
if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) //如果检测到点为白色 替换为黑色
{
src.at<Vec3b>(row, col)[0] = 0;
src.at<Vec3b>(row, col)[1] = 0;
src.at<Vec3b>(row, col)[2] = 0;
}
}
namedWindow("背景转换图", WINDOW_AUTOSIZE);
imshow("背景转换图", src);
//拉普拉斯变换 图片进行锐化
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1); //定义拉普拉斯算子
Mat imgLapLance;
Mat sharp ;
//由于拉普拉斯变化可能会产生负数 图片类型为32f
filter2D(src, imgLapLance, CV_32F, kernel, Point(-1, -1), 0,BORDER_DEFAULT);
src.convertTo(sharp, CV_32F);
Mat imgResult = sharp - imgLapLance; //增强锐化效果
imgResult.convertTo(imgResult, CV_8UC3); //图片转化为3通道rgb格式
namedWindow("锐化图", WINDOW_AUTOSIZE);
imshow("锐化图", imgResult);
//二值距离变换
Mat binImage;
cvtColor(imgResult, binImage, COLOR_BGR2GRAY); //图片转化为灰度图
threshold(binImage, binImage, 40, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); //图片进行阈值处理变为二值图
imshow("二值图像", binImage);
//距离检测
Mat distImage;
distanceTransform(binImage, distImage, DIST_L1, 3, 5);
normalize(distImage, distImage, 0, 1, NORM_MINMAX); //距离结果进行归一化处理
threshold(distImage, distImage, 0.3, 1, THRESH_BINARY); //对结果再次归一化处理
Mat k1 = Mat::zeros(3, 3, CV_8UC1);
erode(binImage, binImage, k1); //二值腐蚀 将粘连的部分分开
imshow("距离变换", distImage);
Mat dist_8u;
distImage.convertTo(dist_8u, CV_8U); //图片转化为单通道图
vector<vector<Point>> contours;
findContours(dist_8u, contours, RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); //寻找轮廓
Mat marks = Mat::zeros(src.size(), CV_32SC1); //定义mark图
for (size_t i = 0; i < contours.size(); i++)
{
drawContours(marks, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i)+1),-1); //绘制轮廓
}
circle(marks, Point(5, 5), 3, Scalar(255, 255, 255), -1); //画个白圈标记一下
imshow("marks", marks*1000); //灰度值较小 看不出来 *1000更明显
//分水岭变换
watershed(src, marks); //分水岭处理
Mat mark = Mat::zeros(marks.size(), CV_8UC1);
marks.convertTo(mark, CV_8UC1);
bitwise_not(mark, mark, Mat()); //像素取反
imshow("mark", 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));
}
Mat dst = Mat::zeros(marks.size(), CV_8UC3);
for (size_t row = 0; row < dst.rows; row++)
for (size_t col = 0; col < dst.cols; col++)
{
int index = marks.at<int>(col, row);
if (index > 0 && index<=static_cast<int>(contours.size()))
{
dst.at<Vec3b>(col, row) = colors[index-1];
}
else
{
dst.at<Vec3b>(col, row) = Vec3b(0, 0, 0);
}
}
imshow("dst", dst);
waitKey(0);
destroyAllWindows();
}