OpenCV + CPP 系列(三十)基于距离变换与分水岭的图像分割

Posted SongpingWang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV + CPP 系列(三十)基于距离变换与分水岭的图像分割相关的知识,希望对你有一定的参考价值。


文章目录

一、图像分割

图像分割概述​【详情请点击】​

图像分割(Image Segmentation)是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干(N)个cluster集合,每个集合包含一类像素。
根据算法分为监督学习方法和无监督学习方法,图像分割的算法多数都是无监督学习方法 - KMeans

二、距离变换与分水岭

OpenCV—Python 分水岭算法图像分割

距离变换常见算法有两种

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

分水岭变换常见的算法

  • 基于浸泡理论实现

步骤

  1. 将白色背景变成黑色-目的是为后面的变换做准备
  2. 使用filter2D与拉普拉斯算子实现图像对比度提高,sharp(锐化)
  3. 转为二值图像通过threshold
  4. 距离变换
  5. 对距离变换结果进行归一化到[0~1]之间
  6. 使用阈值,再次二值化,得到标记
  7. 腐蚀得到每个Peak - erode
  8. 发现轮廓 – findContours
  9. 绘制轮廓- drawContours
  10. 分水岭变换 watershed
  11. 对每个分割区域着色输出结果

头文件 ​​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


OpenCV


OpenCV


OpenCV


OpenCV

OpenCV


以上是关于OpenCV + CPP 系列(三十)基于距离变换与分水岭的图像分割的主要内容,如果未能解决你的问题,请参考以下文章

opencv::基于距离变换与分水岭的图像分割

C++ OpenCV基于距离变换与分水岭的图像分割

opencv 单目相机pnp测距(Cpp)

opencv 图像平移缩放旋转翻转 图像仿射变换

OpenCV 距离变换的错误输出

奇怪的 OpenCV 距离变换结果