数学建模社区-数学中国
标题:
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 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。
分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。
#include <opencv2/imgcodecs.hpp>
( I. b7 i6 |9 Z0 j( f
#include <opencv2/highgui.hpp>
# @% C* h2 e* [% w
#include <opencv2/imgproc.hpp>
) u! o6 { X$ S! |: f2 o* f" ]
) ` A1 ]4 ]; F b% F5 E+ H1 K" e
void showImg(const std::string& windowName, const cv::Mat& img){
( j/ S7 l5 I1 Y% k5 z
cv::imshow(windowName, img);
- ~8 J) I+ ]& _0 G5 a
}
7 W/ G& B1 F# m( q f4 l: n( }
U' K; k. c7 s3 k/ ]; n8 u7 Z
void getBackground(const cv::Mat& source, cv::Mat& dst) {
- N; k, y$ H% _
cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
5 w7 H+ r: Y$ Q. w' [; Q
}
7 q3 i' {* }. r+ Q2 w' a
0 A2 g3 B+ @; s3 ~
void getForeground(const cv::Mat& source, cv::Mat& dst) {
2 G1 u5 d5 d# [; i O% r6 o' k
cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
6 ~' v# l9 ]) h% D# s2 @
cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
5 d% `6 A# t1 J# L5 v
}
. J/ a9 M/ T, N6 N
6 R: j1 y- L. t: c( @0 H# Y
void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {
8 M7 J$ z) e, j% ~! V" b
cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
$ M) n1 H9 X5 e( J2 N
// 绘制前景标记
: r5 l9 V$ N" K/ |
for (size_t i = 0, size = contours.size(); i < size; i++)
( T8 D- t( Y* D2 d
drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
2 M4 x; g# y' C% N
}
5 |$ I) K# R- a" N- X5 p! H
. J: Y6 e, M4 o! V1 q. o
void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {
+ e2 _/ {; b0 u2 D' F
for (int i = 0; i < size ; ++i) {
( q& l3 b) a6 ]7 ~6 ?& D" q3 }; z; N6 S
int b = cv::theRNG().uniform(0, 256);
7 F. f- Z( a0 T6 ?1 r8 h/ x. e
int g = cv::theRNG().uniform(0, 256);
O& \$ o" C+ y7 E
int r = cv::theRNG().uniform(0, 256);
6 j% r: w) o5 ^$ B
colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
+ K: y+ g; A( H4 v d
}
: A/ [; s5 {. B% o+ u' l
}
0 G3 E+ F0 m3 D
u6 O9 p* @/ x3 y p7 q
int main(int argc, char** argv) {
2 I, V3 k! H. a/ R$ P$ }
if(argc < 2){
# t+ G4 O; X8 y. v- G; A5 u2 z
std::cerr << "Errorn";
# Y) Y7 E, u, a* ]
std::cerr << "Provide Input Image:n n";
' R9 x- l) H7 t0 z
return -1;
! \: i( ~$ U8 D$ Y
}
4 r' G: u( j5 y* o: E
cv::Mat original_img = cv::imread(argv[1]);
( l2 Y& K5 c1 _8 C9 |
if(original_img.empty()){
2 D' ^% G1 U8 D% x
std::cerr << "Errorn";
2 t, E& S5 g1 D5 X, Q. e
std::cerr << "Cannot Read Imagen";
1 R2 v: N* \' @; \. _1 r* D
return -1;
. U- Q4 P- ]0 y+ o
}
3 v4 n0 M5 u2 q" q8 t: G# \
cv::Mat shifted;
6 e4 }- r& ^6 @4 S( G' t9 a* F
cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);
8 n( g. @/ g& c2 d+ q5 L7 m0 v! X
showImg("Mean Shifted", shifted);
- G. k8 |' M( x6 {# G
cv::Mat gray_img;
! z& J- `* L. g: Y
cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
5 \2 |, r$ Q8 K. `# F- e7 d' \
showImg("GrayIMg", gray_img);
" u( M3 C3 h: L5 `9 [
cv::Mat bin_img;
% f0 ]5 \, ^7 u; b' }7 T5 Y
cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
' V! v9 K6 k9 F
showImg("thres img", bin_img);
% [; Y8 C; ~4 f
cv::Mat sure_bg;
) U$ |6 t. l: k) G4 i) f
getBackground(bin_img, sure_bg);
' Z! ]& y2 G8 }- Y' K8 |
showImg("Sure Background", sure_bg);
( B( y6 o; P0 s2 i5 ]. o
cv::Mat sure_fg;
& B7 D9 M; r, @ E' j, }/ r
getForeground(bin_img, sure_fg);
1 s* ^# ]4 p" a1 X
showImg("Sure ForeGround", sure_fg);
0 l# _, V& K3 Y4 Z! E" Q
cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);
8 d. b! L+ p, b' Z
std::vector<std::vector<cv::Point>> contours;
( y# t5 \( w2 ?( T; d
findMarker(sure_bg, markers, contours);
2 D1 Q/ k5 q) f
cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈
1 [9 _( _+ u _
4 c% _% K1 }+ q2 d# ?
cv::watershed(original_img, markers);
4 f+ T; [; d0 S7 s: u1 t5 N$ @
cv::Mat mark;
: E7 `' I% b3 B' T& m( J
markers.convertTo(mark, CV_8U);
) J* D# ?7 x/ X G- [
cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色
7 V$ v/ C! E' |5 h+ E+ P
showImg("MARKER", mark);
0 _. r2 I* U( F$ h+ M: V/ ?9 a
// 在图像中突出显示标记 /
# |' T( B* {& {" ^' c
std::vector<cv::Vec3b> colors;
% Z, C5 |; |; u. A
getRandomColor(colors, contours.size()); // 创建结果图像
! C- r9 s2 ]2 O5 E8 v _0 i, x
cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
) {5 ^1 h1 T: [/ ^6 H0 w- n
// 用随机颜色填充标记的对象
% y, T3 M3 u1 V! O, R
for (int i = 0; i < markers.rows; i++)
- c9 h5 |; w1 ~4 i
{
# R) c& P4 b8 p3 q+ j
for (int j = 0; j < markers.cols; j++)
1 p. Z T! D m
{
. [- H& I& Q3 f! D! P4 E
int index = markers.at(i,j);
) C; E- V8 |, B
if (index > 0 && index <= static_cast<int>(contours.size()))
0 K- k h$ A) @
dst.at<cv::Vec3b>(i,j) = colors[index-1];
# z) N! ?8 O7 G* K/ L1 ?9 w
}
5 p6 X2 Q" W) a+ h8 S6 c) I8 b
}
2 K5 K; `9 F3 @# Y$ m
showImg("Final Result", dst);
; ~1 x7 R7 A" K2 f4 x
cv::waitKey(0);
2 R, ]9 d: k7 J2 f7 h
return 0;
! g3 y) }0 j. P# c
}
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/138211359
3 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