2015-01-11 23:07:44 jia20003 阅读数 11856

图像处理之倒角距离变换

图像处理中的倒角距离变换(Chamfer Distance Transform)在对象匹配识别中经常用到,

算法基本上是基于3x3的窗口来生成每个像素的距离值,分为两步完成距离变换,第一

步从左上角开始,从左向右、从上到下移动窗口扫描每个像素,检测在中心像素x的周

围0、1、2、3四个像素,保存最小距离与位置作为结果,图示如下:


第二步从底向上、从右向左,对每个像素,检测相邻像素4、5、6、7保存最小距离与

位置作为结果,如图示所:


完成这两步以后,得到的结果输出即为倒角距离变换的结果。完整的图像倒角距离变

换代码实现可以分为如下几步:

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

  离为0

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

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

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

最终结果显示如下(左边表示原图、右边表示CDT之后结果)


完整的二值图像倒角距离变换的源代码如下:

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

倒角距离变换(Chamfer Distance Transform)

在这里插入图片描述
图a,b为距离变换模板,设其值分别为a[1-5],b[5-9]。
图c为待处理的二维二值化图像,其中1代表目标区域,0代表背景区域。设其值为c[m][n]。
目标是求出目标区域中的每一个点距离背景区域的最小距离,即图d。
倒角距离变化算法思路如下:
(1)首先对原始二值矩阵进行放大得到c2,整体乘以一个膨胀因子p。p的大小会决定能算出的最大距离,即求出的最终距离矩阵中的数值上界,p越大距离越大。
(2)使用距离变换模板图a对二值化图像c2从左到右、从上到下的进行移动遍历。如果该点为0则跳过,为1则使用模板a[5]对应该点,计算该点的a[1-5]所对应的每一个点的像素值与a中每一个点的模板值之和。
如果该点为矩阵的四周上的点,即模板上部分点超出了原始矩阵的边界,则设置这些点的像素值为极大值1*p。不过为了优化算法效率,实际计算时可以仅对矩阵的非边界区域进行操作,即前向时不计算第1行,第1列和最后1列。这样做的最终效果是:只有地图的左右两列未被计算,并不影响我们在局部路径规划中的使用。
最终该点保留的值为这5个和值的最小值,得到矩阵f1具体过程以下图为例:
在这里插入图片描述
在这里插入图片描述
(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
  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
maskSize = 3x3,最新的支持5x5,推荐3x3
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;
	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的透明底图像,来源网络)
在这里插入图片描述
输出结果:
在这里插入图片描述
分割结果:
在这里插入图片描述

2018-08-16 14:27:26 qq_26907755 阅读数 1110

图像分割是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干个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
)

步骤:

  • 将白色背景变成黑色-目的是为后面的变换做准备
  • 使用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;
    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<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

概述

什么是图像分割

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

相关函数API

distanceTransform函数API

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

函数功能
用于计算图像中每一个非零点像素与其最近的零点像素之间的距离,输出的是保存每一个非零点与最近零点的距离信息;图像上越亮的点,代表了离零点的距离越远。
参数介绍

  • src是单通道的8bit的二值图像(只有01
  • 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表示的是距离变换的掩膜模板,可以设置为35CV_DIST_MASK_PRECISECV_DIST_L1CV_DIST_C 的情况,参数值被强制设定为 3, 因为3×3 mask 给出5×5 mask 一样的结果,而且速度还更快。
DIST_MASK_3 mask=3
DIST_MASK_5 mask=5
DIST_MASK-PRECISE
  • labels表示可选输出2维数组;
  • labelType表示的是输出二维数组的类型,8位或者32位浮点数,单一通道,大小与输入图像一致

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

参数介绍

  • 第一个参数 image,必须是一个8bit3通道彩色图像矩阵序列。
  • 第二个参数 markersOpencv官方文档的说明如下:

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进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过OpencvfindContours方法实现,这个是执行分水岭之前的要求。
接下来执行分水岭会发生什么呢?算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。
简单概括一下就是说第二个入参markers必须包含了种子点信息。Opencv官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed其实并不简单。

代码演示处理流程

  • 将白色背景变为黑色背景-目的是为后面的变换做准备
  • 使用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;

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

程序运行结果

在这里插入图片描述

距离变换

阅读数 364

没有更多推荐了,返回首页