2015-01-11 23:07:44 jia20003 阅读数 11856
• ###### MATLAB图像处理

全面系统的学习MATLAB在图像处理中的应用

20789 人正在学习 去看看 魏伟

1.      对像素数组进行初始化，所有背景颜色像素点初始距离为无穷大，前景像素点距

离为0

2.      开始倒角距离变换中的第一步，并保存结果

3.      基于第一步结果完成倒角距离变换中的第二步

4.      根据距离变换结果显示所有不同灰度值，形成图像

```package com.gloomyfish.image.transform;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Arrays;

import com.gloomyfish.filter.study.AbstractBufferedImageOp;

public class CDTFilter extends AbstractBufferedImageOp {
private float[] dis; // nn-distances
private int[] pos; // nn-positions, 32 bit index
private Color bakcgroundColor;

public CDTFilter(Color bgColor)
{
this.bakcgroundColor = bgColor;
}

@Override
public BufferedImage filter(BufferedImage src, BufferedImage dest) {
int width = src.getWidth();
int height = src.getHeight();

if (dest == null)
dest = createCompatibleDestImage(src, null);

int[] inPixels = new int[width * height];
pos = new int[width * height];
dis = new float[width * height];
src.getRGB(0, 0, width, height, inPixels, 0, width);
// 随机生成距离变换点
int index = 0;
Arrays.fill(dis, Float.MAX_VALUE);
int numOfFC = 0;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
index = row * width + col;
if (inPixels[index] != bakcgroundColor.getRGB()) {
dis[index] = 0;
pos[index] = index;
numOfFC++;
}
}
}
final float d1 = 1;
final float d2 = (float) Math.sqrt(d1 * d1 + d1 * d1);
System.out.println(numOfFC);
float nd, nd_tmp;
int i, in, cols, rows, nearestPixel;

// 1 2 3
// 0 i 4
// 7 6 5
// first pass: forward -> L->R, T-B
for (rows = 1; rows < height - 1; rows++) {
for (cols = 1; cols < width - 1; cols++) {
i = rows * width + cols;

nd = dis[i];
nearestPixel = pos[i];
if (nd != 0) { // skip background pixels
in = i;

in += -1; // 0
if ((nd_tmp = d1 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

in += -width; // 1
if ((nd_tmp = d2 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

in += +1; // 2
if ((nd_tmp = d1 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

in += +1; // 3
if ((nd_tmp = d2 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

dis[i] = nd;
pos[i] = nearestPixel;
}
}
}

// second pass: backwards -> R->L, B-T
// exactly same as first pass, just in the reverse direction
for (rows = height - 2; rows >= 1; rows--) {
for (cols = width - 2; cols >= 1; cols--) {
i = rows * width + cols;

nd = dis[i];
nearestPixel = pos[i];
if (nd != 0) {
in = i;

in += +1; // 4
if ((nd_tmp = d1 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

in += +width; // 5
if ((nd_tmp = d2 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

in += -1; // 6
if ((nd_tmp = d1 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

in += -1; // 7
if ((nd_tmp = d2 + dis[in]) < nd) {
nd = nd_tmp;
nearestPixel = pos[in];
}

dis[i] = nd;
pos[i] = nearestPixel;

}
}
}

for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
index = row * width + col;
if (Float.MAX_VALUE != dis[index]) {
int gray = clamp((int) (dis[index]));
inPixels[index] = (255 << 24) | (gray << 16) | (gray << 8)
| gray;
}
}
}
setRGB(dest, 0, 0, width, height, inPixels);
return dest;
}

private int clamp(int i) {
return i > 255 ? 255 : (i < 0 ? 0 : i);
}

}```

2019-07-26 10:22:40 weixin_43925403 阅读数 74
• ###### MATLAB图像处理

全面系统的学习MATLAB在图像处理中的应用

20789 人正在学习 去看看 魏伟

## 倒角距离变换(Chamfer Distance Transform)

(1)首先对原始二值矩阵进行放大得到c2，整体乘以一个膨胀因子p。p的大小会决定能算出的最大距离，即求出的最终距离矩阵中的数值上界，p越大距离越大。
(2)使用距离变换模板图a对二值化图像c2从左到右、从上到下的进行移动遍历。如果该点为0则跳过，为1则使用模板a[5]对应该点，计算该点的a[1-5]所对应的每一个点的像素值与a中每一个点的模板值之和。

(3)使用模板b对前向遍历的结果矩阵f1从右到左、从下到上的进行移动遍历。操作与上一步相同，得到矩阵f2。同上，不计算矩阵的最后1行，第1列和最后1列。
(4)f2即为最终矩阵，除以3之后形成图d，效果图如下：

``````%对二值地图进行倒角距离变换，输出一张距离地图。
inf_factor=1200;%膨胀因子，代表像素值的上线，极大值
raw_a=zeros(1000,1000);
for i=300:700
for j=200:300
raw_a(i,j)=1;
end
end
for i=300:700
for j=700:800
raw_a(i,j)=1;
end
end
raw_a=double(~raw_a)*inf_factor;

figure('NumberTitle', 'off', 'Name', '原始二值地图');
imagesc(raw_a);
colormap(flipud(gray));

h=size(raw_a,1);
w=size(raw_a,2);
a_transpro=raw_a;

tic
for i=2:h%前向遍历过程，从上到下、从左到右
for j=2:w-1
tmp=a_transpro(i,j);%初值取目标像素点初始值
if a_transpro(i,j)%只操作前景点，即为1的点
tmp=min(a_transpro(i-1,j-1)+4,tmp);
tmp=min(a_transpro(i-1,j)+3,tmp);
tmp=min(a_transpro(i-1,j+1)+4,tmp);
tmp=min(a_transpro(i,j-1)+3,tmp);
a_transpro(i,j)=tmp;%最终保留几个和值的最小值作为该点距离值
end
end
end

for i=h-1:-1:1%从下到上、从右到左
for j=w-1:-1:2
tmp=a_transpro(i,j);
if a_transpro(i,j)
tmp=min(a_transpro(i+1,j+1)+4,tmp);
tmp=min(a_transpro(i+1,j)+3,tmp);
tmp=min(a_transpro(i+1,j-1)+4,tmp);
tmp=min(a_transpro(i,j+1)+3,tmp);
a_transpro(i,j)=tmp;
end
end
end

a_final=a_transpro/3;%遍历结束后除以3作为最终结果
t=toc

figure('NumberTitle', 'off', 'Name', '距离地图');
imagesc(a_final);
colormap(flipud(gray));

``````

2020-02-17 23:56:07 weixin_45553439 阅读数 32
• ###### MATLAB图像处理

全面系统的学习MATLAB在图像处理中的应用

20789 人正在学习 去看看 魏伟
1. 图像分割(Image Segmentation)是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合，每个集合包含一类像素。
根据算法分为监督学习方法和无监督学习方法，图像分割的算法多数都是无监督学习方法 - KMeans
2. 距离变换
①不断膨胀/腐蚀得到
②基于倒角距离
``````distanceTransform(InputArray  src, OutputArray dst,  OutputArray  labels,  int  distanceType,  int maskSize,  int labelType=DIST_LABEL_CCOMP)
distanceType = DIST_L1/DIST_L2
labels离散维诺图输出
dst输出8位或者32位的浮点数，单一通道，大小与输入图像一致
``````
1. 分水岭变换(基于浸泡理论实现)
``````watershed(InputArray image, InputOutputArray  markers)
``````
1. 步骤:
将背景变成黑色-目的是为后面的变换做准备
使用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;
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的透明底图像,来源网络)

2018-08-16 14:27:26 qq_26907755 阅读数 1110
• ###### MATLAB图像处理

全面系统的学习MATLAB在图像处理中的应用

20789 人正在学习 去看看 魏伟

- 不断膨胀/ 腐蚀得到
- 基于倒角距离

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 labelType=DIST_LABEL_CCOMP // Type of the label array to build, see cv::DistanceTransformLabelTypes
)``````

• 将白色背景变成黑色-目的是为后面的变换做准备
• 使用filter2D与拉普拉斯算子实现图像对比度提高，sharp锐化
• 转灰度转为二值图像通过threshold
• 距离变换
• 对距离变换结果进行归一化到0-1之间
• 使用阈值再次二值化得到标记
• 腐蚀得到每个peak-erode
• 发现轮廓-findContours
• 绘制轮廓-drawcontours
• 分水岭变换watershed
• 对每个分割区域着色输出结果

``````#include <opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv;
using namespace std;

int main() {
Mat src;
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<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;
}
}
}
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<Point>>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<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));
}

// 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<Vec3b>(row, col) = colors[index - 1]; // 因为上面传的是 Scalar::all(i + 1), -1) 所以要减1
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0); // 轮廓之外全部黑色
}
}
}
imshow("Final Result", dst);

waitKey(0);
}
``````

2019-11-19 09:10:29 z961968549 阅读数 61
• ###### MATLAB图像处理

全面系统的学习MATLAB在图像处理中的应用

20789 人正在学习 去看看 魏伟

## 概述

### 什么是图像分割

• 图像分割(`image segmentation`)是图像处理最重要的处理手段之一
• 图像分割的目标是将图像中像素根据一定的规则分为若干(`N`)个`cluser`集合，每个集合包含一类像素
• 根据算法分为监督学习方法和无监督学习方法，图像分割的算法多数都是无监督的学习方法-`KMeans`
• 距离变换常见的额算法有两种
• 不断膨胀/腐蚀得到
• 基于倒角距离
• 分水岭变换常见方法
• 基于浸泡理论实现

## 相关函数API

distanceTransform函数API

``````void distanceTransform(
InputArray src,
OutputArray dst,
OutputArray labels,
int distanceType,
int labelType=DIST_LABEL_CCOMP
);
``````

• src是单通道的`8bit`的二值图像（只有`0``1`
• dst表示的是计算距离的输出图像，可以使单通道`32bit`浮点数据
• distanceType表示的是选取距离的类型，可以设置为`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|<c ? x^2/2 :c(|x|-c/2), c=1.345
• maskSize表示的是距离变换的掩膜模板，可以设置为`3``5``CV_DIST_MASK_PRECISE``CV_DIST_L1``CV_DIST_C` 的情况，参数值被强制设定为 3, 因为`3×3 mask` 给出`5×5 mask` 一样的结果，而且速度还更快。
• labels表示可选输出`2`维数组；
• labelType表示的是输出二维数组的类型,`8`位或者`32`位浮点数，单一通道，大小与输入图像一致

## watershed 分水岭函数API`void watershed( InputArray image, InputOutputArray markers );`

• 第一个参数 image，必须是一个`8bit3`通道彩色图像矩阵序列。
• 第二个参数 markers`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.

## 代码演示处理流程

• 将白色背景变为黑色背景-目的是为后面的变换做准备
• 使用filter2D与拉普拉斯算子实现图像对比度提高，**sharp
• 转换为二值图像**threshold****
• 距离变换
• 对距离变换结果进行归一化处理`[0~1]`之间
• 使用阈值，再次二值化，得到标记
• 腐蚀得到每个Peak-erode
• 发现轮廓-findContours
• 会之轮廓-drawContours
• 分水岭变换watershed
• 对每个分割区着色输出结果

## 代码演示

``````#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

#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;

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();
}
``````