数学建模社区-数学中国

标题: OpenCV 使用分水岭算法进行图像分割 [打印本页]

作者: 2744557306    时间: 2024-4-27 10:36
标题: OpenCV 使用分水岭算法进行图像分割
分水岭算法:模拟地理形态的图像分割' T% y9 h7 S1 J, s! |3 X/ X3 ~; w
分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中,每个像素的灰度值被视作其高度,灰度值较高的像素形成山脊,即分水岭,而二值化阈值则相当于水平面,低于这个水平面的区域会被“淹没”。4 i: ]* R( H0 a
测地线距离:地形分析的核心5 z7 z$ S3 O, a1 I; {1 A
测地线距离是分水岭算法中的一个关键概念,它代表地球表面两点间的最短路径。这一概念在图论中同样适用,指的是图中两节点间的最短路径,与欧氏距离相比,测地线距离考虑的是实际路径。
7 N, f! Z" _9 X
分水岭算法的执行步骤
) \* R! E4 {2 U1 }( L
梯度图像分类:根据灰度值对梯度图像中的像素进行分类,并设定测地距离阈值。起始点标记:选择灰度值最小的像素点作为起始点,这些点通常是局部最小值。水平面上升:随着阈值的增长,测量周围邻域像素到起始点的测地距离。若小于阈值,则淹没这些像素;若大于阈值,则在这些像素上建立“大坝”。大坝设置与区域分区:随着水平面的上升,建立更多的大坝,直到所有区域在分水岭线上相遇,完成图像的分区。避免过度分割的策略
2 u& ~2 o: u0 D& _0 A1 _+ ^, T
分水岭算法可能会因噪声或干扰导致图像过度分割,形成过多的小区域。解决这一问题的方法包括:( b3 v; H7 G, \1 N5 u& {
高斯平滑:通过高斯平滑减少噪声,合并小分区。基于标记的分水岭算法:选择相对较高的灰度值像素作为起始点,手动标记或使用自动方法如距离变换来确定,从而合并小区域。OpenCV 实现 Watershed 算法函数原型:void watershed( InputArray image, InputOutputArray markers );1参数说明:image:输入的图像,必须是8位的单通道灰度图像。这个图像的梯度信息将被用来模拟水流向低洼地区流动的过程。
3 \$ n7 |; q% h3 B1 V
markers:输入输出参数,是一个与原图像大小相同的图像,用于存放分割标记。在函数调用前,这个图像应该被初始化,其中包含了用户定义的分割区域的标记。标记是通过正整数索引来表示的,表示用户已知的前景或背景区域。所有未知区域(即算法需要确定的区域)应该被标记为0。函数执行完成后,每个像素点的标记将被更新为“种子”组件的值,或者在区域边界处被设置为-1。
; b9 }2 o0 c8 P
功能说明:watershed 函数会分析 image 的梯度信息,并使用 markers 中定义的已知区域作为分割的起点(种子点)。算法将从这些种子点开始,逐步对图像中的其他像素点进行区域归属的判定,直到所有像素点都被标记。在分割过程中,如果两个相邻的已知区域(种子点)相遇,算法会在它们之间创建一个边界,以避免这些区域合并在一起,从而实现分割。注意事项:markers 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。
  1. #include <opencv2/imgcodecs.hpp>
    2 B9 ^8 [" Q) {% c
  2. #include <opencv2/highgui.hpp>3 W# U, O; ]* Z) A
  3. #include <opencv2/imgproc.hpp>+ }% E: [- p/ _% ?+ s9 o

  4. 1 G7 Z1 E" i/ {. v6 R& b( i
  5. void showImg(const std::string& windowName, const cv::Mat& img){
    ( f; @) H7 F. L4 p3 f. q7 d
  6.     cv::imshow(windowName, img);) v, L: g5 d& `. v$ G$ |
  7. }
    ! J2 K: [# V" I* b8 [3 ^

  8. # X6 Q1 Y! _: {3 O- q
  9. void getBackground(const cv::Mat& source, cv::Mat& dst) {
    * T" p' ^+ e4 H/ A! h
  10.     cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核8 c! B7 [$ Q6 I4 g4 D2 o+ I  ~
  11. }7 k' X# f/ h  y/ g/ A
  12. 4 P& h+ Y8 M* M& O; l
  13. void getForeground(const cv::Mat& source, cv::Mat& dst) {7 A% C+ B/ ^0 z' s
  14.     cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
    ; v+ d. J1 j, C) P
  15.     cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
    + W. L' K/ ?% b
  16. }
    - @, `" H+ d5 f4 J1 \3 {, d# j
  17. 3 J& [- r7 X" b& r* ?" v9 n
  18. void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {  k+ `* h! l/ b4 `4 @; v  ^  Z
  19.     cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    8 _/ p( D5 G( \, _$ d% K# [
  20.     // 绘制前景标记. M" h5 O6 F; M# f3 ^! m
  21.     for (size_t i = 0, size = contours.size(); i < size; i++)
    " x9 U  `' v( Q7 i" E  Y% K# t, L0 |
  22.         drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
    1 i3 d' I! z. s6 V. ?
  23. }& c8 `% S) T3 y6 B$ I# W( i
  24. 9 z5 c' l) C  A# R; T
  25. void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {
    ' J2 b1 F4 O& J: D6 R
  26.     for (int i = 0; i < size ; ++i) {5 h" r0 v3 O) F$ D4 w
  27.         int b = cv::theRNG().uniform(0, 256);
    0 j% M. ?: a* q2 Q5 @
  28.         int g = cv::theRNG().uniform(0, 256);
    * b4 B6 X3 H) K8 {. r9 N0 o( D
  29.         int r = cv::theRNG().uniform(0, 256);
    , V- f1 C5 q3 [( o: G
  30.         colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
    ! T2 Q, t* _3 U
  31.     }
      p3 Z* V, a, h) D% m- Y' W6 M
  32. }
    ) O# Q' u  |& w6 b

  33. $ v2 ?6 \1 G% R3 z6 l" _
  34. int main(int argc, char** argv) {
    : a: N/ W' E0 a; x1 J1 }$ `. ]
  35.     if(argc < 2){$ q& T# W& b' M1 n. ~
  36.         std::cerr << "Errorn";1 @2 v+ A; Y; n( E2 E, i3 k. P
  37.         std::cerr << "Provide Input Image:n n";% |2 ], \: n0 P. I( y
  38.         return -1;$ V! a! }% W8 {/ \' M: w
  39.     }
    4 a  [4 M8 m5 z0 _6 n
  40.     cv::Mat original_img = cv::imread(argv[1]);  [( r$ x. K# j! R
  41.     if(original_img.empty()){. F5 H) n* a6 N. O  @/ o) M  E
  42.         std::cerr << "Errorn";# f& k5 q- f% c; L# o, p' T
  43.         std::cerr << "Cannot Read Imagen";
    9 F3 o* W# K+ s3 X. C9 M3 O/ t
  44.         return -1;3 N$ y. j  v9 M) O6 ?. j
  45.     }! b  M5 Z2 C4 @# H/ C4 U
  46.     cv::Mat shifted;/ W! L' ~: B+ \) B% R7 a) P( I
  47.     cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);/ x+ V+ z8 g/ ^0 L, I
  48.     showImg("Mean Shifted", shifted);
    ; N; N% g- k4 |/ ~. N9 v7 ^
  49.     cv::Mat gray_img;7 d/ v$ n% ^. F/ A- R2 i. g) X, Y
  50.     cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
    ' I7 `* K7 t+ T' r
  51.     showImg("GrayIMg", gray_img);
    2 [. |4 ]2 n4 A, X" [
  52.     cv::Mat bin_img;
    # o+ u  `$ D% g0 d7 N' A
  53.     cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    % v5 U2 j% Z% {# F! j$ J
  54.     showImg("thres img", bin_img);
    6 c* t2 X. v& U: V8 P# o
  55.     cv::Mat sure_bg;
    4 Z- u2 o# y2 Z, x' p, [
  56.     getBackground(bin_img, sure_bg);% N% ~, r! J$ N2 P$ I: @7 S2 [- Y, D
  57.     showImg("Sure Background", sure_bg);
    * _" D; C0 g, M% f6 F- d/ C
  58.     cv::Mat sure_fg;7 [9 J& X. a- {, {7 m6 a$ T) a5 D% ]
  59.     getForeground(bin_img, sure_fg);8 t1 `8 s$ O# ]: L3 ?# s4 c
  60.     showImg("Sure ForeGround", sure_fg);6 S! e. R5 O2 r9 ^
  61.     cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);
    + z9 [7 }- c5 {, N8 p3 J
  62.     std::vector<std::vector<cv::Point>> contours;
    3 O0 b3 \5 T9 i- z9 v" H
  63.     findMarker(sure_bg, markers, contours);
    * v3 |- y, r) b& m3 q
  64.     cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈0 R7 B5 c4 h  u7 g, t2 f
  65.    
    3 i) [: k# J1 P' U1 f$ }/ _
  66.     cv::watershed(original_img, markers);2 y6 p( O2 D' \$ f, c
  67.     cv::Mat mark;
    , U% z# v" Y! @* e
  68.     markers.convertTo(mark, CV_8U);
    & U# `3 \. t$ b& p/ {
  69.     cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色
    ' G8 F% o) d3 X9 ~
  70.     showImg("MARKER", mark);
    6 S: B/ _+ ^( ~% D
  71.     // 在图像中突出显示标记 /
    / E9 S6 E* q4 C! m2 o! H$ \3 T
  72.     std::vector<cv::Vec3b> colors;& I, U, c1 f( p0 Z
  73.     getRandomColor(colors, contours.size()); // 创建结果图像& @: z% N+ f. X) q( F) `
  74.     cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
    % q$ E+ A( N( \1 E" s. `! i
  75.     // 用随机颜色填充标记的对象
    * q. m0 g" P+ \( `4 M
  76.     for (int i = 0; i < markers.rows; i++)
    5 @% j- g# w. _" k: A
  77.     {
    & ~8 L1 o* ?, w3 F: D
  78.         for (int j = 0; j < markers.cols; j++)
    ) F9 `" |7 T0 N
  79.         {- K& Y4 N2 f+ P8 {! o
  80.             int index = markers.at(i,j);
    : C) O* `; k, Z  T8 f
  81.             if (index > 0 && index <= static_cast<int>(contours.size()))
    1 }' X% Y  [3 {& W7 @! s
  82.                 dst.at<cv::Vec3b>(i,j) = colors[index-1];
    0 M5 M7 c4 s! }2 N/ e
  83.         }
    # ?1 V7 s1 f( J8 M) |4 t
  84.     }2 v0 Y- ^8 f/ C+ z& _5 }
  85.     showImg("Final Result", dst);
    ; x. y, X7 M+ p( B7 s: p
  86.     cv::waitKey(0);
    ! A9 y2 Y( v! v: k% }) X
  87.     return 0;
      x6 Z6 o; j; n6 J/ K
  88. }& B# v; ^) r  T, t0 M
复制代码
————————————————- K+ l4 k# A* b+ K
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/matt45m/article/details/138211359. h: C: y( \- ?2 z. W& j8 A2 o# D

. X" |& H8 [6 T; _& J% m




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5