QQ登录

只需要一步,快速开始

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

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

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

1186

主题

4

听众

2922

积分

该用户从未签到

跳转到指定楼层
1#
发表于 2024-4-27 10:36 |只看该作者 |倒序浏览
|招呼Ta 关注Ta
分水岭算法:模拟地理形态的图像分割
- N2 T7 N8 {' F  H* K
分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中,每个像素的灰度值被视作其高度,灰度值较高的像素形成山脊,即分水岭,而二值化阈值则相当于水平面,低于这个水平面的区域会被“淹没”。3 U3 r  H: x+ R( h
测地线距离:地形分析的核心
0 T7 \8 y2 v7 j/ _  Y
测地线距离是分水岭算法中的一个关键概念,它代表地球表面两点间的最短路径。这一概念在图论中同样适用,指的是图中两节点间的最短路径,与欧氏距离相比,测地线距离考虑的是实际路径。
- i' w1 V5 X: X4 K( P) w
分水岭算法的执行步骤9 l* e) f! @6 `4 e; z
梯度图像分类:根据灰度值对梯度图像中的像素进行分类,并设定测地距离阈值。起始点标记:选择灰度值最小的像素点作为起始点,这些点通常是局部最小值。水平面上升:随着阈值的增长,测量周围邻域像素到起始点的测地距离。若小于阈值,则淹没这些像素;若大于阈值,则在这些像素上建立“大坝”。大坝设置与区域分区:随着水平面的上升,建立更多的大坝,直到所有区域在分水岭线上相遇,完成图像的分区。避免过度分割的策略
' V; O: S  u! l
分水岭算法可能会因噪声或干扰导致图像过度分割,形成过多的小区域。解决这一问题的方法包括:8 [  K3 n8 {6 c; t
高斯平滑:通过高斯平滑减少噪声,合并小分区。基于标记的分水岭算法:选择相对较高的灰度值像素作为起始点,手动标记或使用自动方法如距离变换来确定,从而合并小区域。OpenCV 实现 Watershed 算法函数原型:void watershed( InputArray image, InputOutputArray markers );1参数说明:image:输入的图像,必须是8位的单通道灰度图像。这个图像的梯度信息将被用来模拟水流向低洼地区流动的过程。
- I! q) c& r6 g. r& D" [( ^
markers:输入输出参数,是一个与原图像大小相同的图像,用于存放分割标记。在函数调用前,这个图像应该被初始化,其中包含了用户定义的分割区域的标记。标记是通过正整数索引来表示的,表示用户已知的前景或背景区域。所有未知区域(即算法需要确定的区域)应该被标记为0。函数执行完成后,每个像素点的标记将被更新为“种子”组件的值,或者在区域边界处被设置为-1。
' ?: I  i, }5 @5 h
功能说明:watershed 函数会分析 image 的梯度信息,并使用 markers 中定义的已知区域作为分割的起点(种子点)。算法将从这些种子点开始,逐步对图像中的其他像素点进行区域归属的判定,直到所有像素点都被标记。在分割过程中,如果两个相邻的已知区域(种子点)相遇,算法会在它们之间创建一个边界,以避免这些区域合并在一起,从而实现分割。注意事项:markers 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。
  1. #include <opencv2/imgcodecs.hpp>% y) H. n( T% u3 {+ R5 V' @5 ?
  2. #include <opencv2/highgui.hpp>3 T' G+ d4 q% T5 {$ x7 h
  3. #include <opencv2/imgproc.hpp>) I( N/ q. v4 K9 c7 A/ D
  4. # K7 {3 e1 `) D& i% M6 {. U/ a2 W
  5. void showImg(const std::string& windowName, const cv::Mat& img){7 M% o; @3 E4 d/ O; Z3 ]
  6.     cv::imshow(windowName, img);3 G4 T1 ^4 `1 b! u$ N$ t8 F
  7. }
    , b7 k8 O* k# ]' M, n
  8. 8 b/ B2 h( B! y7 w
  9. void getBackground(const cv::Mat& source, cv::Mat& dst) {+ N7 ?0 j+ A$ ^9 a6 `
  10.     cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
    5 E5 W2 F+ z6 u. U
  11. }
    & d\" h! E! Y8 h( ]
  12. + t# ^. }- W: [& e  Q& C
  13. void getForeground(const cv::Mat& source, cv::Mat& dst) {
    1 L0 I( J7 \9 n: {  J3 E, ?! f; X
  14.     cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
    * Z/ f% J, m) q
  15.     cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
    + \5 d- o/ X* Z; ]8 K6 e, P# p
  16. }: l% d6 I1 V0 b; C

  17. ) {/ V8 V2 k  c8 r! ^
  18. void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {  R1 w0 t, \$ b: W- {% P: z/ F- ~
  19.     cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);$ w+ C- _* ?! ]1 a
  20.     // 绘制前景标记/ n% W5 U) P5 r+ h7 Z1 r
  21.     for (size_t i = 0, size = contours.size(); i < size; i++)
    ) |$ Q( U5 p/ O# ?3 x* n% U
  22.         drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
    7 Y( q$ M* x- \2 b2 ^2 k
  23. }
      e. [\" v: g  N4 b- @: @4 r7 `, P
  24. 0 s# H\" a% o' B
  25. void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {
    * {: j, o7 Q% P  n
  26.     for (int i = 0; i < size ; ++i) {) W: j/ f- q0 `' n% t
  27.         int b = cv::theRNG().uniform(0, 256);
    $ l- S) o8 F+ m. D: D) z\" @  e, Z1 b
  28.         int g = cv::theRNG().uniform(0, 256);
    $ J( P# e2 ~3 h\" S3 K2 v
  29.         int r = cv::theRNG().uniform(0, 256);
    0 L* Q6 a# ]4 E! c2 Z$ V3 v3 N
  30.         colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
    \" W/ w; Q7 ?( @/ q6 A
  31.     }
    ! a& |) D) F7 ?9 m. U! B# k
  32. }; c7 K* n, i% ~$ C

  33. 9 t0 D/ h+ h2 R9 b
  34. int main(int argc, char** argv) {+ [8 _* [+ j: w( V1 ]8 Z
  35.     if(argc < 2){' `$ C: |4 P; e* j1 s1 `7 C& j
  36.         std::cerr << "Errorn";
    \" |, N( {6 K1 ^3 E8 e! Y
  37.         std::cerr << "Provide Input Image:n n";
    / O4 I7 {: ?9 A# a% n! G* [
  38.         return -1;1 e9 B; k- I6 t8 ]: q- M
  39.     }& M; e+ ^) _' w, d: T
  40.     cv::Mat original_img = cv::imread(argv[1]);
    - |$ M, X0 M- P  F
  41.     if(original_img.empty()){
    2 ~( p2 `8 g3 ?
  42.         std::cerr << "Errorn";' R# a& d( K5 ]) X+ J7 z/ v
  43.         std::cerr << "Cannot Read Imagen";' ^. r1 K3 W9 w4 _\" b
  44.         return -1;
    : d( r# w( }/ C* v8 Q) V* _
  45.     }5 P3 t) ^# w8 d
  46.     cv::Mat shifted;
    7 M$ W' F, u( _3 v: l1 i
  47.     cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);& h  I\" L$ f( b4 ~3 F5 s* Y/ p* v5 _2 k) B( ^
  48.     showImg("Mean Shifted", shifted);$ k1 l\" Q1 N\" I  g8 f$ P
  49.     cv::Mat gray_img;1 [  n\" d$ [6 u, g
  50.     cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
    7 a6 F1 V' M5 H9 v% {- A8 n5 p
  51.     showImg("GrayIMg", gray_img);
    ' k: g: o/ w' r8 R# J. Z; @0 q
  52.     cv::Mat bin_img;
    $ w; L\" v' ~; f& c- E1 l5 @
  53.     cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    \" M; N# N- N1 i0 P$ q
  54.     showImg("thres img", bin_img);
    9 K- A( s( A3 H. K; M5 m: o  d
  55.     cv::Mat sure_bg;1 G+ W0 S7 i$ T5 n0 z! T, t) I
  56.     getBackground(bin_img, sure_bg);
    9 _% o+ `( C# z
  57.     showImg("Sure Background", sure_bg);
    9 o- q  a2 G\" W* H) w3 z
  58.     cv::Mat sure_fg;
    1 |1 i/ F1 W' Q5 l4 A
  59.     getForeground(bin_img, sure_fg);  H6 S, m2 s0 T) }  ?3 c
  60.     showImg("Sure ForeGround", sure_fg);( ]% ^. g  E' D: Z( p/ ~1 n! k
  61.     cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);3 ~\" g( H. T0 A/ H% r7 ~
  62.     std::vector<std::vector<cv::Point>> contours;
    ' ?% h* i1 |8 z; |/ S) E, N& Y( ~
  63.     findMarker(sure_bg, markers, contours);4 m# Q, v3 ^7 B# R4 m! v
  64.     cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈
    , }6 x# N, p# ]) O3 e8 g9 n
  65.    
    . z# p5 |9 G; K+ [' C
  66.     cv::watershed(original_img, markers);
    ( y$ ^$ o\" x7 a7 A
  67.     cv::Mat mark;$ y: H3 L9 l+ a' f
  68.     markers.convertTo(mark, CV_8U);9 a# I3 {5 ], n. g$ g
  69.     cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色6 C- q( _- W9 y9 T
  70.     showImg("MARKER", mark);
    \" ?, n8 Q$ L# ^, x( u\" F' s) Y' f
  71.     // 在图像中突出显示标记 /- P\" J' h0 {  E& a, Z
  72.     std::vector<cv::Vec3b> colors;7 N5 O. p  \$ o0 b+ ?
  73.     getRandomColor(colors, contours.size()); // 创建结果图像4 F0 T4 p* U( y- G) f
  74.     cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);, ?) W( q- T0 x' t
  75.     // 用随机颜色填充标记的对象+ ~! r( _) `4 f  w* P\" E
  76.     for (int i = 0; i < markers.rows; i++)
    ( I4 C9 ~/ ?\" d8 j, E
  77.     {' o; N6 ^7 v7 w6 M% v$ P5 N( {
  78.         for (int j = 0; j < markers.cols; j++)) `5 b; N: w8 {
  79.         {
    & h( ]* s& M% |$ u5 b$ b! y
  80.             int index = markers.at(i,j);  n# P( s; X% d! s  d\" H
  81.             if (index > 0 && index <= static_cast<int>(contours.size()))9 J5 i. c8 M! ?/ R
  82.                 dst.at<cv::Vec3b>(i,j) = colors[index-1];
    7 F7 ^4 F3 u: U4 h4 R, l
  83.         }5 ^# h+ @# u- Z2 \2 `
  84.     }
    / i% e5 C2 q* M% V
  85.     showImg("Final Result", dst);
    ; m2 H& r0 e6 c% g8 F
  86.     cv::waitKey(0);, O/ b: n$ S: F, ]4 H
  87.     return 0;
    1 q* n2 I8 i0 X0 _, u* R
  88. }
    6 D$ z0 K* p, D' @# ~
复制代码
————————————————
; t" x: X* D9 Q
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/matt45m/article/details/138211359
4 R2 ]& [5 X( D% J9 D: E, w* {3 R9 f
9 [8 a* `( x4 y6 B4 m4 X
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-13 09:44 , Processed in 0.423441 second(s), 51 queries .

回顶部