数学建模社区-数学中国

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

作者: 2744557306    时间: 2024-4-27 10:36
标题: OpenCV 使用分水岭算法进行图像分割
分水岭算法:模拟地理形态的图像分割  N  G4 M4 U' ?; v& G) f: A
分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中,每个像素的灰度值被视作其高度,灰度值较高的像素形成山脊,即分水岭,而二值化阈值则相当于水平面,低于这个水平面的区域会被“淹没”。
1 o4 C# ?; @4 R1 w+ @8 q
测地线距离:地形分析的核心
0 B" x, K9 f5 ^& r( _/ Y- n, j
测地线距离是分水岭算法中的一个关键概念,它代表地球表面两点间的最短路径。这一概念在图论中同样适用,指的是图中两节点间的最短路径,与欧氏距离相比,测地线距离考虑的是实际路径。9 {: o1 L- |& g, Q. `
分水岭算法的执行步骤
$ Q0 b  B; @1 |! ~) t* F
梯度图像分类:根据灰度值对梯度图像中的像素进行分类,并设定测地距离阈值。起始点标记:选择灰度值最小的像素点作为起始点,这些点通常是局部最小值。水平面上升:随着阈值的增长,测量周围邻域像素到起始点的测地距离。若小于阈值,则淹没这些像素;若大于阈值,则在这些像素上建立“大坝”。大坝设置与区域分区:随着水平面的上升,建立更多的大坝,直到所有区域在分水岭线上相遇,完成图像的分区。避免过度分割的策略3 ~; S, e: d2 _( D' N4 y' {+ J
分水岭算法可能会因噪声或干扰导致图像过度分割,形成过多的小区域。解决这一问题的方法包括:
! u1 b" S2 @1 T% M7 c7 s
高斯平滑:通过高斯平滑减少噪声,合并小分区。基于标记的分水岭算法:选择相对较高的灰度值像素作为起始点,手动标记或使用自动方法如距离变换来确定,从而合并小区域。OpenCV 实现 Watershed 算法函数原型:void watershed( InputArray image, InputOutputArray markers );1参数说明:image:输入的图像,必须是8位的单通道灰度图像。这个图像的梯度信息将被用来模拟水流向低洼地区流动的过程。4 J' o* l; W' C: s. _
markers:输入输出参数,是一个与原图像大小相同的图像,用于存放分割标记。在函数调用前,这个图像应该被初始化,其中包含了用户定义的分割区域的标记。标记是通过正整数索引来表示的,表示用户已知的前景或背景区域。所有未知区域(即算法需要确定的区域)应该被标记为0。函数执行完成后,每个像素点的标记将被更新为“种子”组件的值,或者在区域边界处被设置为-1。
7 Y7 B8 |7 x0 F! B( V0 S: ?# m
功能说明:watershed 函数会分析 image 的梯度信息,并使用 markers 中定义的已知区域作为分割的起点(种子点)。算法将从这些种子点开始,逐步对图像中的其他像素点进行区域归属的判定,直到所有像素点都被标记。在分割过程中,如果两个相邻的已知区域(种子点)相遇,算法会在它们之间创建一个边界,以避免这些区域合并在一起,从而实现分割。注意事项:markers 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。
  1. #include <opencv2/imgcodecs.hpp>/ C# s6 D6 p* U3 C0 @2 p3 R7 b4 U4 E- d
  2. #include <opencv2/highgui.hpp>8 |" H! P# t7 O0 k8 x9 [. f
  3. #include <opencv2/imgproc.hpp>5 m( _4 G5 T# [& H% O, }$ f

  4. 0 d9 K4 |8 F' {% E( u
  5. void showImg(const std::string& windowName, const cv::Mat& img){
    7 q: ?- G1 R1 e: T8 p/ S
  6.     cv::imshow(windowName, img);: ]3 ?* T0 P! [
  7. }
    0 z2 t1 _% }9 ]1 r

  8. / j6 j- k1 I6 f- G. d
  9. void getBackground(const cv::Mat& source, cv::Mat& dst) {# Z4 X4 |3 C) d2 s  s  ?/ ]
  10.     cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
    # [; j+ D6 P" [/ W9 C
  11. }, |/ c# X1 e. W; l' k

  12. $ ^' I. R1 c& w  K# j' p4 n
  13. void getForeground(const cv::Mat& source, cv::Mat& dst) {( R1 C' [% o! w+ A, ~, q" o
  14.     cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
    - E; S* _5 u: C
  15.     cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
    7 \, e# L# K* G6 X' ?
  16. }  b0 u) z) t4 u4 w6 }% _: P; S

  17. / o& o5 _  P" i
  18. void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {
    ! I9 z8 q* P2 Z, W% t
  19.     cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    * U7 [% ^3 z# r2 p3 W
  20.     // 绘制前景标记. e% C) I3 X9 ~4 J
  21.     for (size_t i = 0, size = contours.size(); i < size; i++)6 L9 K# q1 }& J8 }1 v
  22.         drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);+ T/ H# L5 b3 C. P& c
  23. }
    % }6 x- Z* b' |% P$ B, I$ j

  24. : d4 l. Y: m* W$ |% c
  25. void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {2 J* N, }1 R' _( V) A" o" D3 D1 g
  26.     for (int i = 0; i < size ; ++i) {
    ) U7 h4 {6 g! X7 T8 _
  27.         int b = cv::theRNG().uniform(0, 256);
    + t; c# {" ^* o4 i0 z) U# U# v
  28.         int g = cv::theRNG().uniform(0, 256);7 `6 j, T! q1 O1 P- p8 q
  29.         int r = cv::theRNG().uniform(0, 256);
    ( n7 |1 L7 C: u, |8 X( z' n- t* L
  30.         colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
    ' I# w5 k) N: h& y+ x
  31.     }
    $ i5 K- L4 E6 o2 U( s0 @
  32. }
    3 H) b5 C9 g. x' A7 F' S
  33. 9 o; G. y% k/ q- L) i) `4 W6 C
  34. int main(int argc, char** argv) {! d7 W; {( w/ V) i
  35.     if(argc < 2){
    6 L( W  F% N* a  @5 r/ a1 ^: h
  36.         std::cerr << "Errorn";
    * l: H: x8 f( s
  37.         std::cerr << "Provide Input Image:n n";, Y% N4 M  a  b+ S1 M
  38.         return -1;+ Q1 i! Y0 g5 E( Y+ \
  39.     }0 ]0 D0 f  n* L5 g$ w  |0 A  j
  40.     cv::Mat original_img = cv::imread(argv[1]);, N3 U3 Z7 E  r. D- R
  41.     if(original_img.empty()){0 T% z* x) d: s& H9 u+ w, F
  42.         std::cerr << "Errorn";
    , G1 I5 O2 f5 _6 z  C  w: P3 b
  43.         std::cerr << "Cannot Read Imagen";
    * u$ c4 }8 r2 V
  44.         return -1;
    + D! h9 D5 w3 ~4 o! r* k8 o
  45.     }8 H/ h8 Y5 L7 D0 h* Z, l
  46.     cv::Mat shifted;: S& g0 o( v3 N3 M/ V* ]$ u0 Q
  47.     cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);
    8 `+ _2 ]) M( ?
  48.     showImg("Mean Shifted", shifted);- v2 [/ G7 X7 Z; Z
  49.     cv::Mat gray_img;
    2 H. j" n* E* s
  50.     cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);6 D) j  U- G, o  l/ y  ^9 w$ L; d
  51.     showImg("GrayIMg", gray_img);9 r! r8 e- s3 A, P" X6 v7 L
  52.     cv::Mat bin_img;, i$ t  I. b7 u3 p( D8 q  i
  53.     cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);6 ?4 I, J9 E8 I0 c+ v" G
  54.     showImg("thres img", bin_img);
    - a) u1 c* S( \& J9 @# @0 g) Z
  55.     cv::Mat sure_bg;
    8 ^6 m& W8 b% X
  56.     getBackground(bin_img, sure_bg);/ ]8 y- p5 h9 q0 _9 n# c
  57.     showImg("Sure Background", sure_bg);" H+ y' k& w9 ]& Z" V6 Q1 c
  58.     cv::Mat sure_fg;
      H! x! n6 W4 j, C$ u
  59.     getForeground(bin_img, sure_fg);
    ( S8 L  c1 c3 I8 C% m- v  U
  60.     showImg("Sure ForeGround", sure_fg);8 W7 r+ K5 _" w8 |9 Z& U8 i; ?/ w$ e3 Q
  61.     cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);- F/ C+ m, J% W
  62.     std::vector<std::vector<cv::Point>> contours;7 e9 D% V. _; ?  ?$ Q% q
  63.     findMarker(sure_bg, markers, contours);
    1 s  T2 F. v, B% f/ n/ {
  64.     cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈" N1 N8 O# E- w# ^! y& f
  65.    
    , B) M) s& v8 n2 _8 o7 H/ ]% w
  66.     cv::watershed(original_img, markers);
    2 N' E1 R/ r' {; V* D' c0 C$ h
  67.     cv::Mat mark;4 r, _/ N9 j3 M
  68.     markers.convertTo(mark, CV_8U);. K" k4 i# [/ o# \
  69.     cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色1 p/ |: W# m0 @: i( s
  70.     showImg("MARKER", mark);
    : M& i0 z# ]" F8 N
  71.     // 在图像中突出显示标记 /$ i$ L% _  K% R4 b0 `
  72.     std::vector<cv::Vec3b> colors;
    % v) g$ x6 o4 D6 p" H/ L' f
  73.     getRandomColor(colors, contours.size()); // 创建结果图像
    3 m3 D8 v8 O' F9 B9 L7 G4 d: t
  74.     cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);& w& H+ n) L/ P$ c9 j, X1 K
  75.     // 用随机颜色填充标记的对象4 v+ q( `4 S9 [# v4 Y6 }& O
  76.     for (int i = 0; i < markers.rows; i++)
    - o- A! b# X8 V5 o& v9 l6 x
  77.     {
    5 a# Q5 a$ y4 \+ p* `
  78.         for (int j = 0; j < markers.cols; j++)
    $ `: U* O6 M$ m4 I0 [/ i
  79.         {
    8 n' q" k3 G5 Y. L- ~
  80.             int index = markers.at(i,j);* H" T5 q8 f) V; ~0 r# W5 v" V
  81.             if (index > 0 && index <= static_cast<int>(contours.size()))
    $ s' m# M+ @  o, s2 i
  82.                 dst.at<cv::Vec3b>(i,j) = colors[index-1];
    % p4 {' [+ G, ?% \
  83.         }
    # n! N7 T% H: }- {) C- R" i, _
  84.     }
    ( k1 n8 X6 ]/ ?: u- q+ x
  85.     showImg("Final Result", dst);
    3 R8 b- g  E/ L- v1 ]& ?, D
  86.     cv::waitKey(0);9 [+ d& s2 ]; Q# n/ }' c
  87.     return 0;8 z+ F6 R4 u7 g5 ^4 H2 `: {
  88. }( W2 C* r0 z0 d. m5 d
复制代码
————————————————
8 p! D) C- m& `6 |7 J$ x
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/matt45m/article/details/138211359
% S, i' f- B5 _2 e0 q  x9 p
  I8 l0 A/ s: O) K5 {, u% \$ C




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