数学建模社区-数学中国

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

作者: 2744557306    时间: 2024-4-27 10:36
标题: OpenCV 使用分水岭算法进行图像分割
分水岭算法:模拟地理形态的图像分割
9 a( X2 M( J) A
分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中,每个像素的灰度值被视作其高度,灰度值较高的像素形成山脊,即分水岭,而二值化阈值则相当于水平面,低于这个水平面的区域会被“淹没”。
, D# |4 b, @/ ^6 X; ?8 m; I
测地线距离:地形分析的核心  r" G! d% m. x. _$ E2 W
测地线距离是分水岭算法中的一个关键概念,它代表地球表面两点间的最短路径。这一概念在图论中同样适用,指的是图中两节点间的最短路径,与欧氏距离相比,测地线距离考虑的是实际路径。% r& n; Y9 m: U" z5 P8 N
分水岭算法的执行步骤, u4 P) |1 W1 @2 @( R2 s: r
梯度图像分类:根据灰度值对梯度图像中的像素进行分类,并设定测地距离阈值。起始点标记:选择灰度值最小的像素点作为起始点,这些点通常是局部最小值。水平面上升:随着阈值的增长,测量周围邻域像素到起始点的测地距离。若小于阈值,则淹没这些像素;若大于阈值,则在这些像素上建立“大坝”。大坝设置与区域分区:随着水平面的上升,建立更多的大坝,直到所有区域在分水岭线上相遇,完成图像的分区。避免过度分割的策略: c2 \; j2 Q8 d% e
分水岭算法可能会因噪声或干扰导致图像过度分割,形成过多的小区域。解决这一问题的方法包括:# b9 I! w- A% }9 ]6 X7 M
高斯平滑:通过高斯平滑减少噪声,合并小分区。基于标记的分水岭算法:选择相对较高的灰度值像素作为起始点,手动标记或使用自动方法如距离变换来确定,从而合并小区域。OpenCV 实现 Watershed 算法函数原型:void watershed( InputArray image, InputOutputArray markers );1参数说明:image:输入的图像,必须是8位的单通道灰度图像。这个图像的梯度信息将被用来模拟水流向低洼地区流动的过程。
7 }' G+ t8 s9 q! u3 G7 h8 U/ f
markers:输入输出参数,是一个与原图像大小相同的图像,用于存放分割标记。在函数调用前,这个图像应该被初始化,其中包含了用户定义的分割区域的标记。标记是通过正整数索引来表示的,表示用户已知的前景或背景区域。所有未知区域(即算法需要确定的区域)应该被标记为0。函数执行完成后,每个像素点的标记将被更新为“种子”组件的值,或者在区域边界处被设置为-1。
  x. ~. `' I1 d9 ?1 j
功能说明:watershed 函数会分析 image 的梯度信息,并使用 markers 中定义的已知区域作为分割的起点(种子点)。算法将从这些种子点开始,逐步对图像中的其他像素点进行区域归属的判定,直到所有像素点都被标记。在分割过程中,如果两个相邻的已知区域(种子点)相遇,算法会在它们之间创建一个边界,以避免这些区域合并在一起,从而实现分割。注意事项:markers 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。
  1. #include <opencv2/imgcodecs.hpp>( I. b7 i6 |9 Z0 j( f
  2. #include <opencv2/highgui.hpp># @% C* h2 e* [% w
  3. #include <opencv2/imgproc.hpp>) u! o6 {  X$ S! |: f2 o* f" ]
  4. ) `  A1 ]4 ]; F  b% F5 E+ H1 K" e
  5. void showImg(const std::string& windowName, const cv::Mat& img){
    ( j/ S7 l5 I1 Y% k5 z
  6.     cv::imshow(windowName, img);- ~8 J) I+ ]& _0 G5 a
  7. }7 W/ G& B1 F# m( q  f4 l: n( }
  8.   U' K; k. c7 s3 k/ ]; n8 u7 Z
  9. void getBackground(const cv::Mat& source, cv::Mat& dst) {- N; k, y$ H% _
  10.     cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核5 w7 H+ r: Y$ Q. w' [; Q
  11. }7 q3 i' {* }. r+ Q2 w' a

  12. 0 A2 g3 B+ @; s3 ~
  13. void getForeground(const cv::Mat& source, cv::Mat& dst) {2 G1 u5 d5 d# [; i  O% r6 o' k
  14.     cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
    6 ~' v# l9 ]) h% D# s2 @
  15.     cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
    5 d% `6 A# t1 J# L5 v
  16. }
    . J/ a9 M/ T, N6 N
  17. 6 R: j1 y- L. t: c( @0 H# Y
  18. void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {
    8 M7 J$ z) e, j% ~! V" b
  19.     cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);$ M) n1 H9 X5 e( J2 N
  20.     // 绘制前景标记
    : r5 l9 V$ N" K/ |
  21.     for (size_t i = 0, size = contours.size(); i < size; i++)( T8 D- t( Y* D2 d
  22.         drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);2 M4 x; g# y' C% N
  23. }5 |$ I) K# R- a" N- X5 p! H

  24. . J: Y6 e, M4 o! V1 q. o
  25. void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {+ e2 _/ {; b0 u2 D' F
  26.     for (int i = 0; i < size ; ++i) {( q& l3 b) a6 ]7 ~6 ?& D" q3 }; z; N6 S
  27.         int b = cv::theRNG().uniform(0, 256);
    7 F. f- Z( a0 T6 ?1 r8 h/ x. e
  28.         int g = cv::theRNG().uniform(0, 256);
      O& \$ o" C+ y7 E
  29.         int r = cv::theRNG().uniform(0, 256);6 j% r: w) o5 ^$ B
  30.         colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));+ K: y+ g; A( H4 v  d
  31.     }: A/ [; s5 {. B% o+ u' l
  32. }
    0 G3 E+ F0 m3 D

  33.   u6 O9 p* @/ x3 y  p7 q
  34. int main(int argc, char** argv) {
    2 I, V3 k! H. a/ R$ P$ }
  35.     if(argc < 2){# t+ G4 O; X8 y. v- G; A5 u2 z
  36.         std::cerr << "Errorn";# Y) Y7 E, u, a* ]
  37.         std::cerr << "Provide Input Image:n n";
    ' R9 x- l) H7 t0 z
  38.         return -1;
    ! \: i( ~$ U8 D$ Y
  39.     }4 r' G: u( j5 y* o: E
  40.     cv::Mat original_img = cv::imread(argv[1]);
    ( l2 Y& K5 c1 _8 C9 |
  41.     if(original_img.empty()){2 D' ^% G1 U8 D% x
  42.         std::cerr << "Errorn";
    2 t, E& S5 g1 D5 X, Q. e
  43.         std::cerr << "Cannot Read Imagen";
    1 R2 v: N* \' @; \. _1 r* D
  44.         return -1;. U- Q4 P- ]0 y+ o
  45.     }3 v4 n0 M5 u2 q" q8 t: G# \
  46.     cv::Mat shifted;6 e4 }- r& ^6 @4 S( G' t9 a* F
  47.     cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);8 n( g. @/ g& c2 d+ q5 L7 m0 v! X
  48.     showImg("Mean Shifted", shifted);
    - G. k8 |' M( x6 {# G
  49.     cv::Mat gray_img;
    ! z& J- `* L. g: Y
  50.     cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);5 \2 |, r$ Q8 K. `# F- e7 d' \
  51.     showImg("GrayIMg", gray_img);" u( M3 C3 h: L5 `9 [
  52.     cv::Mat bin_img;% f0 ]5 \, ^7 u; b' }7 T5 Y
  53.     cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    ' V! v9 K6 k9 F
  54.     showImg("thres img", bin_img);% [; Y8 C; ~4 f
  55.     cv::Mat sure_bg;
    ) U$ |6 t. l: k) G4 i) f
  56.     getBackground(bin_img, sure_bg);
    ' Z! ]& y2 G8 }- Y' K8 |
  57.     showImg("Sure Background", sure_bg);
    ( B( y6 o; P0 s2 i5 ]. o
  58.     cv::Mat sure_fg;
    & B7 D9 M; r, @  E' j, }/ r
  59.     getForeground(bin_img, sure_fg);
    1 s* ^# ]4 p" a1 X
  60.     showImg("Sure ForeGround", sure_fg);
    0 l# _, V& K3 Y4 Z! E" Q
  61.     cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);8 d. b! L+ p, b' Z
  62.     std::vector<std::vector<cv::Point>> contours;( y# t5 \( w2 ?( T; d
  63.     findMarker(sure_bg, markers, contours);
    2 D1 Q/ k5 q) f
  64.     cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈
    1 [9 _( _+ u  _
  65.    
    4 c% _% K1 }+ q2 d# ?
  66.     cv::watershed(original_img, markers);4 f+ T; [; d0 S7 s: u1 t5 N$ @
  67.     cv::Mat mark;
    : E7 `' I% b3 B' T& m( J
  68.     markers.convertTo(mark, CV_8U);
    ) J* D# ?7 x/ X  G- [
  69.     cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色7 V$ v/ C! E' |5 h+ E+ P
  70.     showImg("MARKER", mark);0 _. r2 I* U( F$ h+ M: V/ ?9 a
  71.     // 在图像中突出显示标记 /# |' T( B* {& {" ^' c
  72.     std::vector<cv::Vec3b> colors;% Z, C5 |; |; u. A
  73.     getRandomColor(colors, contours.size()); // 创建结果图像! C- r9 s2 ]2 O5 E8 v  _0 i, x
  74.     cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
    ) {5 ^1 h1 T: [/ ^6 H0 w- n
  75.     // 用随机颜色填充标记的对象% y, T3 M3 u1 V! O, R
  76.     for (int i = 0; i < markers.rows; i++)
    - c9 h5 |; w1 ~4 i
  77.     {# R) c& P4 b8 p3 q+ j
  78.         for (int j = 0; j < markers.cols; j++)
    1 p. Z  T! D  m
  79.         {. [- H& I& Q3 f! D! P4 E
  80.             int index = markers.at(i,j);
    ) C; E- V8 |, B
  81.             if (index > 0 && index <= static_cast<int>(contours.size()))0 K- k  h$ A) @
  82.                 dst.at<cv::Vec3b>(i,j) = colors[index-1];
    # z) N! ?8 O7 G* K/ L1 ?9 w
  83.         }5 p6 X2 Q" W) a+ h8 S6 c) I8 b
  84.     }
    2 K5 K; `9 F3 @# Y$ m
  85.     showImg("Final Result", dst);
    ; ~1 x7 R7 A" K2 f4 x
  86.     cv::waitKey(0);
    2 R, ]9 d: k7 J2 f7 h
  87.     return 0;
    ! g3 y) }0 j. P# c
  88. }4 _, z9 K" l3 z; y. P3 Q
复制代码
————————————————, A4 M; s1 A* u* p- ]2 s. X
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/matt45m/article/details/1382113593 F0 h; f, P4 M4 Z) J9 k: a8 v' ^
3 @6 Z5 j$ C) `& h  ^: q5 n* M





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