QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1820|回复: 0
打印 上一主题 下一主题

OpenCV 使用分水岭算法进行图像分割

[复制链接]
字体大小: 正常 放大

1186

主题

4

听众

2923

积分

该用户从未签到

跳转到指定楼层
1#
发表于 2024-4-27 10:36 |只看该作者 |倒序浏览
|招呼Ta 关注Ta
分水岭算法:模拟地理形态的图像分割3 i) t5 ~" }- x: F0 h# ?
分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中,每个像素的灰度值被视作其高度,灰度值较高的像素形成山脊,即分水岭,而二值化阈值则相当于水平面,低于这个水平面的区域会被“淹没”。
9 e9 T4 G+ h4 c
测地线距离:地形分析的核心! `) [0 s( N8 c! Q: C% Q
测地线距离是分水岭算法中的一个关键概念,它代表地球表面两点间的最短路径。这一概念在图论中同样适用,指的是图中两节点间的最短路径,与欧氏距离相比,测地线距离考虑的是实际路径。
# }: U+ o" |4 F" [
分水岭算法的执行步骤
6 }, B/ P- i5 S8 ?
梯度图像分类:根据灰度值对梯度图像中的像素进行分类,并设定测地距离阈值。起始点标记:选择灰度值最小的像素点作为起始点,这些点通常是局部最小值。水平面上升:随着阈值的增长,测量周围邻域像素到起始点的测地距离。若小于阈值,则淹没这些像素;若大于阈值,则在这些像素上建立“大坝”。大坝设置与区域分区:随着水平面的上升,建立更多的大坝,直到所有区域在分水岭线上相遇,完成图像的分区。避免过度分割的策略
, G5 H6 s9 f$ p. s( @
分水岭算法可能会因噪声或干扰导致图像过度分割,形成过多的小区域。解决这一问题的方法包括:& ?7 r7 b% @7 z/ a5 P1 l+ ~' d0 v
高斯平滑:通过高斯平滑减少噪声,合并小分区。基于标记的分水岭算法:选择相对较高的灰度值像素作为起始点,手动标记或使用自动方法如距离变换来确定,从而合并小区域。OpenCV 实现 Watershed 算法函数原型:void watershed( InputArray image, InputOutputArray markers );1参数说明:image:输入的图像,必须是8位的单通道灰度图像。这个图像的梯度信息将被用来模拟水流向低洼地区流动的过程。
3 Y8 c: I+ s& ]3 P: ?/ G" W
markers:输入输出参数,是一个与原图像大小相同的图像,用于存放分割标记。在函数调用前,这个图像应该被初始化,其中包含了用户定义的分割区域的标记。标记是通过正整数索引来表示的,表示用户已知的前景或背景区域。所有未知区域(即算法需要确定的区域)应该被标记为0。函数执行完成后,每个像素点的标记将被更新为“种子”组件的值,或者在区域边界处被设置为-1。% `* _8 |' Y5 J5 f
功能说明:watershed 函数会分析 image 的梯度信息,并使用 markers 中定义的已知区域作为分割的起点(种子点)。算法将从这些种子点开始,逐步对图像中的其他像素点进行区域归属的判定,直到所有像素点都被标记。在分割过程中,如果两个相邻的已知区域(种子点)相遇,算法会在它们之间创建一个边界,以避免这些区域合并在一起,从而实现分割。注意事项:markers 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。
  1. #include <opencv2/imgcodecs.hpp>
    4 k5 `% y* j5 d. O3 H
  2. #include <opencv2/highgui.hpp>! E' Q3 x: q( }; `9 \( P4 Y
  3. #include <opencv2/imgproc.hpp>& ~7 E\" l# `2 Z

  4. , f1 m3 K+ K- N) M
  5. void showImg(const std::string& windowName, const cv::Mat& img){5 q4 K. B0 ?# [* J, F
  6.     cv::imshow(windowName, img);' ^* J$ P) ?1 S# N
  7. }4 f7 I% g. `( l1 i3 E

  8. 3 t; ^, K, ]\" Y# {% I' o( P
  9. void getBackground(const cv::Mat& source, cv::Mat& dst) {
    ; {  q4 \- M# k
  10.     cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
    4 w+ a( |( r6 O- _+ f
  11. }7 O6 V& d6 o, t- Z( M- Z

  12. ( B! U7 O; U/ ~8 V/ S: r
  13. void getForeground(const cv::Mat& source, cv::Mat& dst) {; X\" v0 }( W7 T+ ^9 c0 w: y\" }
  14.     cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
      P( q; h1 S) C
  15.     cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);$ U7 L& `  u/ {9 b- k8 {' \
  16. }
    % F% W+ t5 F1 R0 b( {( n& |

  17. : |# s( w- s4 G: J, g
  18. void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {3 j6 Y# I- L6 d# f
  19.     cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    9 @* i  V& r' T# M% p
  20.     // 绘制前景标记
    0 N5 E) E# u. H$ K* F3 r& G( y7 y\" r
  21.     for (size_t i = 0, size = contours.size(); i < size; i++)
    8 P1 }3 O3 {+ a- S0 e* Q! E- ^
  22.         drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
    / D* w8 L5 f3 J& ^* }( G/ z
  23. }
    + E* p& r3 A; O- {+ l
  24. 7 t1 |  p7 z1 ^! E: _: Q
  25. void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {
    4 f4 ~5 p7 p8 b% K\" D  _! j4 E
  26.     for (int i = 0; i < size ; ++i) {  U8 D% a/ r# c
  27.         int b = cv::theRNG().uniform(0, 256);0 ~2 A/ f. I& w/ B7 ]7 w3 n8 j% \
  28.         int g = cv::theRNG().uniform(0, 256);9 P/ M* r# j2 b
  29.         int r = cv::theRNG().uniform(0, 256);
    # Z7 C) V  b3 n! [: V  q' P
  30.         colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));: [  S* ]0 A- ]. {+ H
  31.     }
    3 H0 v% r8 Y% E( E/ H7 u; y
  32. }
    $ H6 p0 ^+ \# g4 O$ B

  33. , o$ b$ W$ G; p) c4 O/ w$ l
  34. int main(int argc, char** argv) {# m+ {; P0 U( Q* s  q
  35.     if(argc < 2){
    0 O\" ]* f+ x% b( }
  36.         std::cerr << "Errorn";
    3 ?- h  Z: f* r5 l, v* ]* B\" D
  37.         std::cerr << "Provide Input Image:n n";
    ! m% a2 q4 A+ @' g* ]  ?) D
  38.         return -1;+ F8 M- u' A6 C6 ^3 W2 ^
  39.     }
    1 t: v. L$ V% U* S- ]* J\" W+ A
  40.     cv::Mat original_img = cv::imread(argv[1]);
    : m- {' d1 C  i) i+ [$ ^% _
  41.     if(original_img.empty()){
      P7 G' Z+ {8 l  h
  42.         std::cerr << "Errorn";
    - R4 ?/ \+ m6 U\" _
  43.         std::cerr << "Cannot Read Imagen";, ~( n5 s' d0 u  Z! _  w; k! `$ t/ d1 ^
  44.         return -1;9 k$ K5 g9 W; F( P/ ]
  45.     }
    ( R% }# U5 i7 |+ z
  46.     cv::Mat shifted;2 z* r$ l( S: l2 F6 H# P3 n
  47.     cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);
    & N' e5 T# ?+ l# h
  48.     showImg("Mean Shifted", shifted);0 R! y0 U( ^( X\" j  w- q# {! Z
  49.     cv::Mat gray_img;
    8 D\" E* z# O9 C\" X  g
  50.     cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
    % Z# h0 ?. w2 D) r1 w9 @\" H
  51.     showImg("GrayIMg", gray_img);
    & \, Z7 Z* J) F; d& f8 ^. n* I
  52.     cv::Mat bin_img;
    ) M) }% J& B, h
  53.     cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    \" R* T8 r# `& Y
  54.     showImg("thres img", bin_img);5 {$ e$ T8 m, d& e
  55.     cv::Mat sure_bg;
    . d9 r/ w- D: S; r\" i2 X. O4 C1 g
  56.     getBackground(bin_img, sure_bg);
    0 Z3 h\" y5 _# O+ d6 r
  57.     showImg("Sure Background", sure_bg);
    + Q9 Q! m3 k$ L6 K3 m+ ~
  58.     cv::Mat sure_fg;9 \5 _6 k: h4 l8 b
  59.     getForeground(bin_img, sure_fg);
    ; S1 N  h9 K& A3 I/ w7 b
  60.     showImg("Sure ForeGround", sure_fg);( Q/ }. P0 v/ c8 N
  61.     cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);, w0 e) \' r& F\" U( a  ^$ R, C
  62.     std::vector<std::vector<cv::Point>> contours;
    5 v/ u) p& k# |4 N\" N5 B& ^( l+ g
  63.     findMarker(sure_bg, markers, contours);+ S$ n  w$ R% F, s. u
  64.     cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈
    7 i& ?: J3 L9 B6 U
  65.    
    / Q5 W+ N. W0 q# ], H, M* S4 y
  66.     cv::watershed(original_img, markers);
    & z! \7 C# P; G3 f; n
  67.     cv::Mat mark;
    \" S4 q1 A# Y2 a! ]* F) N3 q7 I
  68.     markers.convertTo(mark, CV_8U);: _5 c4 Y% U; H( K\" e0 x
  69.     cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色
    , G& n/ V# s  h4 w( j
  70.     showImg("MARKER", mark);
      v# H& V  s7 H4 I, w) |
  71.     // 在图像中突出显示标记 /
    ; {1 `4 \/ n8 e5 b( \\" g. s
  72.     std::vector<cv::Vec3b> colors;( e; v9 Q+ K\" K: {( M
  73.     getRandomColor(colors, contours.size()); // 创建结果图像
    * p; [\" W& C\" _% S
  74.     cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);* o7 h& \9 O! ?7 H- y
  75.     // 用随机颜色填充标记的对象
    % ]: g! s5 e6 V
  76.     for (int i = 0; i < markers.rows; i++)- g- P# n7 J$ {' Y
  77.     {
    , T. e& E- M2 R
  78.         for (int j = 0; j < markers.cols; j++)9 J) t' r\" y( c7 K' s- ~
  79.         {( b  k+ b3 Y4 {  e9 c
  80.             int index = markers.at(i,j);0 f/ d4 X# O) b( _
  81.             if (index > 0 && index <= static_cast<int>(contours.size()))* L( O7 t9 \4 E! v2 c# l# f8 @
  82.                 dst.at<cv::Vec3b>(i,j) = colors[index-1];
    * e9 e4 H: [0 ?: j
  83.         }
    # ?4 v( \. K! N
  84.     }3 @3 C; O. J- B9 R; t- n4 `1 t+ [4 {0 A
  85.     showImg("Final Result", dst);
    4 D, a0 c# h( Q
  86.     cv::waitKey(0);8 ~+ [* {; D# i5 a2 m( O: {% a9 D
  87.     return 0;
    + y% e, o1 U) W) |8 V9 z) y
  88. }& g\" [: M% `' i( X
复制代码
————————————————6 R5 _3 u: l: I& s+ b1 O5 R+ g/ i4 k
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/matt45m/article/details/138211359; R0 k( w  G" |: d7 _# ^/ w3 l; m

# _7 A2 ?% J- G7 l  [$ l, v# S) \
zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
您需要登录后才可以回帖 登录 | 注册地址

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

手机版|Archiver| |繁體中文 手机客户端  

蒙公网安备 15010502000194号

Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

GMT+8, 2026-4-22 09:30 , Processed in 0.602273 second(s), 51 queries .

回顶部