QQ登录

只需要一步,快速开始

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

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

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

1198

主题

4

听众

2977

积分

该用户从未签到

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

  4. % j% J; _: Q# \, m
  5. void showImg(const std::string& windowName, const cv::Mat& img){
    ' c3 I( _) N7 {3 q, t7 O& D  f
  6.     cv::imshow(windowName, img);$ Z% _8 K1 W2 {% x4 B
  7. }
    / u$ V\" e% F( n' `/ f
  8. * @3 |) e. f/ [1 t% M1 K
  9. void getBackground(const cv::Mat& source, cv::Mat& dst) {2 x! z\" m\" ^3 |( l5 M) P+ ~% O- Q0 g
  10.     cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
    ; g9 j! U# G$ }0 I
  11. }
    % L8 c3 ^1 m' V/ e5 e+ P
  12. , ?9 X1 @6 a  ~+ U) P) N* l
  13. void getForeground(const cv::Mat& source, cv::Mat& dst) {
    * K; Y$ a2 h( ~4 P5 H/ ?5 H
  14.     cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
    ) Z, d7 u. @1 D$ M\" L# H# [
  15.     cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);9 K& N$ a7 Z7 B0 [
  16. }$ ?: n: J3 n& A
  17. * h: H1 }9 W. f+ w1 G
  18. void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {+ _6 n5 f0 n& F. m; q/ H( N
  19.     cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    + T) R& h8 j; t3 K
  20.     // 绘制前景标记! j$ R5 h; V& C+ I# i1 F7 }
  21.     for (size_t i = 0, size = contours.size(); i < size; i++)+ L\" X' X/ P% |) C9 v+ F
  22.         drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);- U! w6 ^1 V$ Y4 v, x2 x1 x; P- e
  23. }
    4 a) \) c- j6 `

  24. 3 e! e: @# B4 d: U
  25. void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {
    5 ~  f  I3 m0 k6 W# ?) ?
  26.     for (int i = 0; i < size ; ++i) {; J' i* a  b8 k
  27.         int b = cv::theRNG().uniform(0, 256);
    - }, w3 `, _$ n( M7 r
  28.         int g = cv::theRNG().uniform(0, 256);' C$ X! m4 g- @  ~
  29.         int r = cv::theRNG().uniform(0, 256);4 Q* A: }0 c/ K! ?# B: n
  30.         colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));( u8 j3 _: D8 m8 {/ I
  31.     }& ?4 i5 B4 T/ x* e\" t- b7 M
  32. }, r/ i/ C, P/ |\" V6 }: }
  33. % d- o2 n\" p5 D; X+ W3 w5 U
  34. int main(int argc, char** argv) {
    5 q/ l/ m$ n0 Q  f! G# A3 n3 Y
  35.     if(argc < 2){6 P- @/ R  A- ^; f
  36.         std::cerr << "Errorn";6 e; w, a5 m1 x$ q/ b9 |
  37.         std::cerr << "Provide Input Image:n n";/ H, x) n2 j  H1 S
  38.         return -1;( J5 h% `0 n2 |# M# U( t& A2 T
  39.     }, E2 w; F/ J( l: |8 z% ?
  40.     cv::Mat original_img = cv::imread(argv[1]);' L) s  @\" A. P% Y$ f+ I( M- A1 a
  41.     if(original_img.empty()){& j- u3 c; E. ~4 E$ {7 J# q2 o\" U
  42.         std::cerr << "Errorn";
    4 }# i5 j$ r+ |8 K/ A- j) z* l6 j
  43.         std::cerr << "Cannot Read Imagen";! M/ A$ J1 L1 s0 ]6 [* n
  44.         return -1;
    ! }4 Z* G4 m9 G* N3 l) Y
  45.     }$ v- k% B8 ^* p2 s, V0 z0 w! X6 ?
  46.     cv::Mat shifted;! K) d2 Y' |9 B' M8 B
  47.     cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);
    7 d9 |; K! s% ^- \* o
  48.     showImg("Mean Shifted", shifted);
    9 ^* X8 q+ ~: d5 u4 H- A  g
  49.     cv::Mat gray_img;
    % B. I  X- j6 e. u0 L: D% y. a: J\" T
  50.     cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
    ; W+ O, P: `6 L! U
  51.     showImg("GrayIMg", gray_img);
    ( u, @0 ^) v& e! Q; q- ?) }\" L! |
  52.     cv::Mat bin_img;1 W5 P& j2 B7 E( d
  53.     cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    4 i0 o3 e7 m% ^2 ~0 ~7 }  ~
  54.     showImg("thres img", bin_img);
    ; u1 T; O' n/ G  C. K  \* t
  55.     cv::Mat sure_bg;3 H$ ]' P& \7 D- E7 Y: g
  56.     getBackground(bin_img, sure_bg);+ A: f# r- f2 p2 `
  57.     showImg("Sure Background", sure_bg);+ N# c2 Z: G+ G0 r
  58.     cv::Mat sure_fg;7 ~, ]1 L3 t& X; F6 X! \0 [1 ^
  59.     getForeground(bin_img, sure_fg);- t: U& a4 j, p* O. r# s
  60.     showImg("Sure ForeGround", sure_fg);3 l2 D% v6 G# l' I& V0 ]7 X% W2 Q
  61.     cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);
    : t/ p$ F' n7 _\" Y& B) k
  62.     std::vector<std::vector<cv::Point>> contours;
    4 d! P2 w+ b0 f! q* \- }9 G\" i/ [
  63.     findMarker(sure_bg, markers, contours);/ F4 Q- L. Q1 V5 e7 Y\" i
  64.     cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈2 ~& _# Q; L, D$ s- g
  65.       q! E% w# |. [
  66.     cv::watershed(original_img, markers);3 o- t6 V3 E: i/ t/ j& a8 B
  67.     cv::Mat mark;' c* q% e8 b& j6 |/ _6 R+ ~
  68.     markers.convertTo(mark, CV_8U);3 ^\" n7 m3 Q4 z' c3 z$ h$ x6 N
  69.     cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色
    : C! T7 h+ e3 i  ^8 }
  70.     showImg("MARKER", mark);
    2 U+ H3 v- H& f& l
  71.     // 在图像中突出显示标记 /
    & I! w4 @5 [. [$ j
  72.     std::vector<cv::Vec3b> colors;
    2 \6 T& `! s. q! j6 ^* x# ~( Q( f
  73.     getRandomColor(colors, contours.size()); // 创建结果图像
    % N+ S) d2 a2 G# G\" z
  74.     cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
    1 a! Q: V- y6 m% ?, i  A
  75.     // 用随机颜色填充标记的对象+ t8 S( ?3 `% D) Z
  76.     for (int i = 0; i < markers.rows; i++)$ j/ w, d- I\" y' i& A! |3 v
  77.     {
    2 a# x8 v$ c\" j* y. V+ c
  78.         for (int j = 0; j < markers.cols; j++)
    4 U% f! N& ^7 k5 a( T1 h; Z
  79.         {  o3 ?5 o  q0 t3 u7 v5 a+ \( J6 m  j: q+ Y
  80.             int index = markers.at(i,j);
    6 ?0 f& u  G2 n
  81.             if (index > 0 && index <= static_cast<int>(contours.size()))
    , ^4 i4 Z. z  h
  82.                 dst.at<cv::Vec3b>(i,j) = colors[index-1];0 ]! ?9 P: g% c\" o& a
  83.         }' _) a% R; t  _: b3 Z# T
  84.     }7 @; g+ Z: `\" o2 E; S
  85.     showImg("Final Result", dst);
    - i5 t* V6 `  r- q) @+ I
  86.     cv::waitKey(0);
    4 V1 p$ e\" f0 H' {7 _* f8 |) p
  87.     return 0;
    . ?3 h' z6 k7 j
  88. }8 z& q+ f  |( C\" r7 Q- {) `# X
复制代码
————————————————
- F7 \7 H! X5 Q! M, @! B" r! Q
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/matt45m/article/details/1382113598 o- w" R4 `1 F; u/ v
8 d1 w. p* A/ F8 K
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-9-22 23:44 , Processed in 0.428617 second(s), 51 queries .

回顶部