• OpenCV 图像处理学习二十二,基于距离变换和分水岭的图像分割


     一.图像分割

    图像分割是根据灰度、颜色、纹理和形状等特征,把图像分成若干个特定的、具有独特性质的区域,这些特征在同一区域内呈现出相似性,而在不同区域间呈现出明显的差异性,并提出感兴趣目标的技术和过程。 它是由图像处理到图像分析的关键步骤。从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。图像分割的过程也是一个标记过程,即把属于同一区域的像索赋予相同的编号。
     

    其目的是将图像中像素根据一定的规则分为若干(N)个聚(cluster)集合,每个集合包含一类像素。将对象在背景提取出来。


    二.基于距离变换和分水岭的图像分割

    分水岭法(Meyer)是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。该算法的实现可以模拟成洪水淹没的过程,图像的最低点首先被淹没,然后水逐渐淹没整个山谷。当水位到达一定高度的时候将会溢出,这时在水溢出的地方修建堤坝,重复这个过程直到整个图像上的点全部被淹没,这时所建立的一系列堤坝就成为分开各个盆地的分水岭。分水岭算法对微弱的边缘有着良好的响应,但图像中的噪声会使分水岭算法产生过分割的现象。
    ---------------------------------------------------------------------------------------------------------------------------------

    距离变换API函数接口

    距离变换用于计算图像中每一个非零点像素与其周围最近的零点像素之间的距离,返回的值保存了每一个非零点与最近零点的距离信息;在图像上的体现为图像上越亮的点,代表了离零点的距离越远。

    1. void distanceTransform(
    2. InputArray src,
    3. OutputArray dst,
    4. OutputArray labels,
    5. int distanceType,
    6. int maskSize,
    7. int labelType=DIST_LABEL_CCOMP
    8. );

    参数说明

    (1)src是单通道的8bit的二值图像(只有0或1)
    (2)dst表示的是计算距离的输出图像,可以使单通道32bit浮点数据
    (3)distanceType表示的是选取距离的类型,可以设置为                                          CV_DIST_L1,CV_DIST_L2,CV_DIST_C等,具体如下:

    DIST_USERUser defined distance
    DIST_L1=1distance = |x1-x2| + |y1-y2
    DIST_L2the simple euclidean distance
    DIST_Cdistance = max(|x1-x2|,|y1-y2|)
    DIST_L12L1-L2 metric: distance =2(sqrt(1+x*x/2) - 1))
    DIST_FAIRdistance = c^2(|x|/c-log(1+|x|/c)),c = 1.3998
    DIST_WELSCHdistance = c2/2(1-exp(-(x/c)2)), c= 2.9846
    DIST_HUBERdistance = |x|

    (4)maskSize表示的是距离变换的掩膜模板,可以设置为35CV_DIST_MASK_PRECISE,对 CV_DIST_L1 或CV_DIST_C 的情况,参数值被强制设定为 3, 因为3×3 mask 给出5×5 mask 一样的结果,而且速度还更快。

    DIST_MASK_3mask=3
    DIST_MASK_5mask=5
    DIST_MASK-PRECISE

    (5)labels表示可选输出2维数组;
    (6)labelType表示的是输出二维数组的类型,8位或者32位浮点数,图像是单一通道,并且大小与输入图像一致

    ---------------------------------------------------------------------------------------------------------------------------------

    watershed 分水岭函数API接口

    void watershed( InputArray image, InputOutputArray markers );

    参数说明

    (1)参数 image,必须是一个8bit3通道彩色图像矩阵序列。
    (2) 输入或输出32位单通道的标记,和图像一样大小。(输入高峰轮廓标记);在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过OpencvfindContours方法实现,这个是执行分水岭之前的要求。

    算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。

    ---------------------------------------------------------------------------------------------------------------------------------

    代码实现

    1、将白色背景改为黑色,为后面的变换做准备;
    2、使用filter2D与Laplace算子实现图像对比度的提高;sharpen锐化
    3、通过threshold()转换为二值图像;
    4、距离变换;
    5、对距离变换的结果归一化到0-1之间;
    6、使用阈值,再次二值化,得到标记;
    7、腐蚀(erode)得到每个Peak;(peak : 山峰,山顶 )
    8、发现轮廓findContours
    9、绘制轮廓;
    10、分水岭变换;
    11、对每个分割区域着色输出结果;

    ---------------------------------------------------------------------------------------------------------------------------------

    1. #include <opencv2/opencv.hpp>
    2. #include <iostream>
    3. #include <math.h>
    4. using namespace std;
    5. using namespace cv;
    6. int main(int argc, char** argv) {
    7. char input_win[] = "input image";
    8. char watershed_win[] = "watershed segmentation demo";
    9. Mat src = imread("D:/vcprojects/images/cards.png");
    10. // Mat src = imread("D:/kuaidi.jpg");
    11. if (src.empty()) {
    12. printf("could not load image...\n");
    13. return -1;
    14. }
    15. namedWindow(input_win, CV_WINDOW_AUTOSIZE);
    16. imshow(input_win, src);
    17. // 1. change background
    18. for (int row = 0; row < src.rows; row++) {
    19. for (int col = 0; col < src.cols; col++) {
    20. if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) {
    21. src.at<Vec3b>(row, col)[0] = 0;
    22. src.at<Vec3b>(row, col)[1] = 0;
    23. src.at<Vec3b>(row, col)[2] = 0;
    24. }
    25. }
    26. }
    27. namedWindow("black background", CV_WINDOW_AUTOSIZE);
    28. imshow("black background", src);
    29. // sharpen
    30. Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
    31. Mat imgLaplance;
    32. Mat sharpenImg = src;
    33. filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
    34. src.convertTo(sharpenImg, CV_32F);
    35. Mat resultImg = sharpenImg - imgLaplance;
    36. resultImg.convertTo(resultImg, CV_8UC3);
    37. imgLaplance.convertTo(imgLaplance, CV_8UC3);
    38. imshow("sharpen image", resultImg);
    39. // src = resultImg; // copy back
    40. // convert to binary
    41. Mat binaryImg;
    42. cvtColor(src, resultImg, CV_BGR2GRAY);
    43. threshold(resultImg, binaryImg, 40, 255, THRESH_BINARY | THRESH_OTSU);
    44. imshow("binary image", binaryImg);
    45. Mat distImg;
    46. distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);
    47. normalize(distImg, distImg, 0, 1, NORM_MINMAX);
    48. imshow("distance result", distImg);
    49. // binary again
    50. threshold(distImg, distImg, .4, 1, THRESH_BINARY);
    51. Mat k1 = Mat::ones(13, 13, CV_8UC1);
    52. erode(distImg, distImg, k1, Point(-1, -1));
    53. imshow("distance binary image", distImg);
    54. // markers
    55. Mat dist_8u;
    56. distImg.convertTo(dist_8u, CV_8U);
    57. vector<vector<Point>> contours;
    58. findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));
    59. // create makers
    60. Mat markers = Mat::zeros(src.size(), CV_32SC1);
    61. for (size_t i = 0; i < contours.size(); i++) {
    62. drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1), -1);
    63. }
    64. circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
    65. imshow("my markers", markers*1000);
    66. // perform watershed
    67. watershed(src, markers);
    68. Mat mark = Mat::zeros(markers.size(), CV_8UC1);
    69. markers.convertTo(mark, CV_8UC1);
    70. bitwise_not(mark, mark, Mat());
    71. imshow("watershed image", mark);
    72. // generate random color
    73. vector<Vec3b> colors;
    74. for (size_t i = 0; i < contours.size(); i++) {
    75. int r = theRNG().uniform(0, 255);
    76. int g = theRNG().uniform(0, 255);
    77. int b = theRNG().uniform(0, 255);
    78. colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    79. }
    80. // fill with color and display final result
    81. Mat dst = Mat::zeros(markers.size(), CV_8UC3);
    82. for (int row = 0; row < markers.rows; row++) {
    83. for (int col = 0; col < markers.cols; col++) {
    84. int index = markers.at<int>(row, col);
    85. if (index > 0 && index <= static_cast<int>(contours.size())) {
    86. dst.at<Vec3b>(row, col) = colors[index - 1];
    87. }
    88. else {
    89. dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
    90. }
    91. }
    92. }
    93. imshow("Final Result", dst);
    94. waitKey(0);
    95. return 0;
    96. }

    ---------------------------------------------------------------------------------------------------------------------------------

    图像处理效果

    输入原图像和锐化图像 

    原图和黑背景图(背景应为黑色) 

    threshold转化的二值化图片和距离变换结果图

     距离变换结果图和二值化图像

     

  • 相关阅读:
    Flash的学习
    html之表格标签和列表标签
    栈的和队列的实现
    U9二次开发之补丁制作
    【网页设计】基于HTML+CSS+JavaScript制作美食网站舌尖上的美食
    nodejs+vue宠物店管理系统
    Vmware虚拟机创建快照、克隆和备份 创建文件夹共享方法
    一个算子在深度学习框架中的旅程
    【无标题】
    输入输出系统:M/M/1排队系统例题
  • 原文地址:https://blog.csdn.net/weixin_44651073/article/details/126692642