OpenCV + CPP 系列(三十)基于距离变换与分水岭的图像分割
Posted SongpingWang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV + CPP 系列(三十)基于距离变换与分水岭的图像分割相关的知识,希望对你有一定的参考价值。
文章目录
一、图像分割
图像分割概述 【详情请点击】
图像分割(Image Segmentation)是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。
根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法 - KMeans
二、距离变换与分水岭
距离变换常见算法有两种
- 不断膨胀/ 腐蚀得到
- 基于倒角距离
分水岭变换常见的算法
- 基于浸泡理论实现
步骤
- 将白色背景变成黑色-目的是为后面的变换做准备
- 使用filter2D与拉普拉斯算子实现图像对比度提高,sharp(锐化)
- 转为二值图像通过threshold
- 距离变换
- 对距离变换结果进行归一化到[0~1]之间
- 使用阈值,再次二值化,得到标记
- 腐蚀得到每个Peak - erode
- 发现轮廓 – findContours
- 绘制轮廓- drawContours
- 分水岭变换 watershed
- 对每个分割区域着色输出结果
头文件 quick_opencv.h
:声明类与公共函数
#pragma once
#include <opencv2\\opencv.hpp>
using namespace cv;
class QuickDemo
public:
...
void Moments_Demo(Mat& image1);
;
主函数调用该类的公共成员函数
#include <opencv2\\opencv.hpp>
#include <quick_opencv.h>
#include <iostream>
using namespace cv;
int main(int argc, char** argv)
Mat src = imread("D:\\\\Desktop\\\\maomao.png");
if (src.empty())
printf("Could not load images...\\n");
return -1;
namedWindow("input", WINDOW_NORMAL);
imshow("input", src);
QuickDemo qk;
qk.Moments_Demo(src);
waitKey(0);
destroyAllWindows();
return 0;
三、演示
源文件 quick_demo.cpp
:实现类与公共函数
void QuickDemo::Image_segmentation_Demo(Mat& image)
for (int row = 0; row < image.rows; row++)
for (int col = 0; col < image.cols; col++)
if (image.at<Vec3b>(row, col) == Vec3b(255, 255, 255))
image.at<Vec3b>(row, col)[0] = 0;
image.at<Vec3b>(row, col)[1] = 0;
image.at<Vec3b>(row, col)[2] = 0;
imshow("image", image);
// 使用掩码的方式替换颜色:python:a = np.where(a=255,0)
//Mat mask_;
//inRange(image, Scalar(255, 255, 255), Scalar(255, 255, 255), mask);
//image.setTo(Scalar(0, 0, 0), mask_);
Mat kernel_ = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
Mat ImgLaplance;
Mat sharp = image;
filter2D(sharp, ImgLaplance, CV_32F, kernel_, Point(-1, -1), 0);
cout << "&sharp" << &sharp << endl;
cout << "&image" << &image << endl;
image.convertTo(sharp, CV_32F);
Mat ImgResult = sharp - ImgLaplance;
ImgResult.convertTo(ImgResult, CV_8UC3);
ImgLaplance.convertTo(ImgLaplance, CV_8UC3);
imshow("ImgResult", ImgResult);
imshow("ImgLaplance", ImgLaplance);
Mat binImg;
image = ImgResult; // 指向锐化后的图像
cvtColor(image, binImg, COLOR_BGR2GRAY);
threshold(binImg, binImg, 40, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binImg", binImg);
Mat distImg;
distanceTransform(binImg, distImg, DIST_L2, 5);
normalize(distImg, distImg, 0, 1, NORM_MINMAX);
imshow("normalize", distImg);
threshold(distImg, distImg, 0.4, 1, THRESH_BINARY);
imshow("distance_threshold", distImg);
// 查看二值化后的效果,觉得没必要做这一步了
Mat kernel2_ = Mat::ones(5, 5, CV_8UC1);
erode(distImg, distImg, kernel2_);
imshow("peaks", distImg);
// 轮廓查找,并绘制轮廓
Mat dist_u8;
distImg.convertTo(dist_u8, CV_8U);
vector<vector<Point>> contours;
findContours(dist_u8, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
if (contours.size() == 0)
cout << "未找到轮廓" << endl;
return;
Mat marks = Mat::zeros(distImg.size(), CV_32SC1);
for (size_t i = 0; i < contours.size(); i++)
drawContours(marks, contours, static_cast<int>(i), Scalar::all(static_cast<uchar>(i + 1)), -1);
circle(marks, Point(5, 5), 3, Scalar(255), -1);
//imshow("mark_dist", marks * 1000); //报错
watershed(image, marks);
Mat mark_dist = Mat::zeros(marks.size(), CV_8UC1);
marks.convertTo(mark_dist, CV_8UC1);
bitwise_not(mark_dist, mark_dist);
imshow("watershed", mark_dist);
// generator random color
RNG rng(12335);
vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++)
uchar b = rng.uniform(0, 255);
uchar g = rng.uniform(0, 255);
uchar r = rng.uniform(0, 255);
colors.push_back(Vec3b(b, g, r));
//着色
Mat drawImg = Mat::zeros(marks.size(), CV_8UC3);
for (int row = 0; row < marks.rows; row++)
for (int col = 0; col < marks.cols; col++)
uchar index = marks.at<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size()))
drawImg.at<Vec3b>(row, col) = colors[index - 1];
else
drawImg.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
imshow("dst", drawImg);
效果图(展示关键步骤效果):
以上是关于OpenCV + CPP 系列(三十)基于距离变换与分水岭的图像分割的主要内容,如果未能解决你的问题,请参考以下文章