文章目录

一、图像分割

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

图像分割(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 + CPP 系列(三十)基于距离变换与分水岭的图像分割_opencv


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


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


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


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

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