数学建模社区-数学中国
标题:
OpenCV 使用分水岭算法进行图像分割
[打印本页]
作者:
2744557306
时间:
2024-4-27 10:36
标题:
OpenCV 使用分水岭算法进行图像分割
分水岭算法:模拟地理形态的图像分割
' T% y9 h7 S1 J, s! |3 X/ X3 ~; w
分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中,每个像素的灰度值被视作其高度,灰度值较高的像素形成山脊,即分水岭,而二值化阈值则相当于水平面,低于这个水平面的区域会被“淹没”。
4 i: ]* R( H0 a
测地线距离:地形分析的核心
5 z7 z$ S3 O, a1 I; {1 A
测地线距离是分水岭算法中的一个关键概念,它代表地球表面两点间的最短路径。这一概念在图论中同样适用,指的是图中两节点间的最短路径,与欧氏距离相比,测地线距离考虑的是实际路径。
7 N, f! Z" _9 X
分水岭算法的执行步骤
) \* R! E4 {2 U1 }( L
梯度图像分类:根据灰度值对梯度图像中的像素进行分类,并设定测地距离阈值。
起始点标记:选择灰度值最小的像素点作为起始点,这些点通常是局部最小值。
水平面上升:随着阈值的增长,测量周围邻域像素到起始点的测地距离。若小于阈值,则淹没这些像素;若大于阈值,则在这些像素上建立“大坝”。
大坝设置与区域分区:随着水平面的上升,建立更多的大坝,直到所有区域在分水岭线上相遇,完成图像的分区。
避免过度分割的策略
2 u& ~2 o: u0 D& _0 A1 _+ ^, T
分水岭算法可能会因噪声或干扰导致图像过度分割,形成过多的小区域。解决这一问题的方法包括:
( b3 v; H7 G, \1 N5 u& {
高斯平滑:通过高斯平滑减少噪声,合并小分区。
基于标记的分水岭算法:选择相对较高的灰度值像素作为起始点,手动标记或使用自动方法如距离变换来确定,从而合并小区域。
OpenCV 实现 Watershed 算法
函数原型:
void watershed( InputArray image, InputOutputArray markers );
1
参数说明:
image:输入的图像,必须是8位的单通道灰度图像。这个图像的梯度信息将被用来模拟水流向低洼地区流动的过程。
3 \$ n7 |; q% h3 B1 V
markers:输入输出参数,是一个与原图像大小相同的图像,用于存放分割标记。在函数调用前,这个图像应该被初始化,其中包含了用户定义的分割区域的标记。标记是通过正整数索引来表示的,表示用户已知的前景或背景区域。所有未知区域(即算法需要确定的区域)应该被标记为0。函数执行完成后,每个像素点的标记将被更新为“种子”组件的值,或者在区域边界处被设置为-1。
; b9 }2 o0 c8 P
功能说明:
watershed 函数会分析 image 的梯度信息,并使用 markers 中定义的已知区域作为分割的起点(种子点)。
算法将从这些种子点开始,逐步对图像中的其他像素点进行区域归属的判定,直到所有像素点都被标记。
在分割过程中,如果两个相邻的已知区域(种子点)相遇,算法会在它们之间创建一个边界,以避免这些区域合并在一起,从而实现分割。
注意事项:
markers 中的标记非常重要,它们直接影响分割的结果。因此,用户需要仔细考虑如何标记已知的前景和背景区域。
分水岭算法可能会导致过度分割,特别是当图像中存在大量噪声时。在实际应用中,可能需要对图像进行预处理,如使用高斯模糊去除小的局部最小值,以减少过度分割的问题。
#include <opencv2/imgcodecs.hpp>
2 B9 ^8 [" Q) {% c
#include <opencv2/highgui.hpp>
3 W# U, O; ]* Z) A
#include <opencv2/imgproc.hpp>
+ }% E: [- p/ _% ?+ s9 o
1 G7 Z1 E" i/ {. v6 R& b( i
void showImg(const std::string& windowName, const cv::Mat& img){
( f; @) H7 F. L4 p3 f. q7 d
cv::imshow(windowName, img);
) v, L: g5 d& `. v$ G$ |
}
! J2 K: [# V" I* b8 [3 ^
# X6 Q1 Y! _: {3 O- q
void getBackground(const cv::Mat& source, cv::Mat& dst) {
* T" p' ^+ e4 H/ A! h
cv::dilate(source, dst, cv::Mat::ones(3, 3, CV_8U)); // 3x3 核
8 c! B7 [$ Q6 I4 g4 D2 o+ I ~
}
7 k' X# f/ h y/ g/ A
4 P& h+ Y8 M* M& O; l
void getForeground(const cv::Mat& source, cv::Mat& dst) {
7 A% C+ B/ ^0 z' s
cv::distanceTransform(source, dst, cv::DIST_L2, 3, CV_32F);
; v+ d. J1 j, C) P
cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
+ W. L' K/ ?% b
}
- @, `" H+ d5 f4 J1 \3 {, d# j
3 J& [- r7 X" b& r* ?" v9 n
void findMarker(const cv::Mat& sureBg, cv::Mat& markers, std::vector<std::vector<cv::Point>>& contours) {
k+ `* h! l/ b4 `4 @; v ^ Z
cv::findContours(sureBg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
8 _/ p( D5 G( \, _$ d% K# [
// 绘制前景标记
. M" h5 O6 F; M# f3 ^! m
for (size_t i = 0, size = contours.size(); i < size; i++)
" x9 U `' v( Q7 i" E Y% K# t, L0 |
drawContours(markers, contours, static_cast<int>(i), cv::Scalar(static_cast<int>(i)+1), -1);
1 i3 d' I! z. s6 V. ?
}
& c8 `% S) T3 y6 B$ I# W( i
9 z5 c' l) C A# R; T
void getRandomColor(std::vector<cv::Vec3b>& colors, size_t size) {
' J2 b1 F4 O& J: D6 R
for (int i = 0; i < size ; ++i) {
5 h" r0 v3 O) F$ D4 w
int b = cv::theRNG().uniform(0, 256);
0 j% M. ?: a* q2 Q5 @
int g = cv::theRNG().uniform(0, 256);
* b4 B6 X3 H) K8 {. r9 N0 o( D
int r = cv::theRNG().uniform(0, 256);
, V- f1 C5 q3 [( o: G
colors.emplace_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
! T2 Q, t* _3 U
}
p3 Z* V, a, h) D% m- Y' W6 M
}
) O# Q' u |& w6 b
$ v2 ?6 \1 G% R3 z6 l" _
int main(int argc, char** argv) {
: a: N/ W' E0 a; x1 J1 }$ `. ]
if(argc < 2){
$ q& T# W& b' M1 n. ~
std::cerr << "Errorn";
1 @2 v+ A; Y; n( E2 E, i3 k. P
std::cerr << "Provide Input Image:n n";
% |2 ], \: n0 P. I( y
return -1;
$ V! a! }% W8 {/ \' M: w
}
4 a [4 M8 m5 z0 _6 n
cv::Mat original_img = cv::imread(argv[1]);
[( r$ x. K# j! R
if(original_img.empty()){
. F5 H) n* a6 N. O @/ o) M E
std::cerr << "Errorn";
# f& k5 q- f% c; L# o, p' T
std::cerr << "Cannot Read Imagen";
9 F3 o* W# K+ s3 X. C9 M3 O/ t
return -1;
3 N$ y. j v9 M) O6 ?. j
}
! b M5 Z2 C4 @# H/ C4 U
cv::Mat shifted;
/ W! L' ~: B+ \) B% R7 a) P( I
cv::pyrMeanShiftFiltering(original_img, shifted, 21, 51);
/ x+ V+ z8 g/ ^0 L, I
showImg("Mean Shifted", shifted);
; N; N% g- k4 |/ ~. N9 v7 ^
cv::Mat gray_img;
7 d/ v$ n% ^. F/ A- R2 i. g) X, Y
cv::cvtColor(original_img, gray_img, cv::COLOR_BGR2GRAY);
' I7 `* K7 t+ T' r
showImg("GrayIMg", gray_img);
2 [. |4 ]2 n4 A, X" [
cv::Mat bin_img;
# o+ u `$ D% g0 d7 N' A
cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
% v5 U2 j% Z% {# F! j$ J
showImg("thres img", bin_img);
6 c* t2 X. v& U: V8 P# o
cv::Mat sure_bg;
4 Z- u2 o# y2 Z, x' p, [
getBackground(bin_img, sure_bg);
% N% ~, r! J$ N2 P$ I: @7 S2 [- Y, D
showImg("Sure Background", sure_bg);
* _" D; C0 g, M% f6 F- d/ C
cv::Mat sure_fg;
7 [9 J& X. a- {, {7 m6 a$ T) a5 D% ]
getForeground(bin_img, sure_fg);
8 t1 `8 s$ O# ]: L3 ?# s4 c
showImg("Sure ForeGround", sure_fg);
6 S! e. R5 O2 r9 ^
cv::Mat markers = cv::Mat::zeros(sure_bg.size(), CV_32S);
+ z9 [7 }- c5 {, N8 p3 J
std::vector<std::vector<cv::Point>> contours;
3 O0 b3 \5 T9 i- z9 v" H
findMarker(sure_bg, markers, contours);
* v3 |- y, r) b& m3 q
cv::circle(markers, cv::Point(5, 5), 3, cv::Scalar(255), -1); // 在标记周围绘制圆圈
0 R7 B5 c4 h u7 g, t2 f
3 i) [: k# J1 P' U1 f$ }/ _
cv::watershed(original_img, markers);
2 y6 p( O2 D' \$ f, c
cv::Mat mark;
, U% z# v" Y! @* e
markers.convertTo(mark, CV_8U);
& U# `3 \. t$ b& p/ {
cv::bitwise_not(mark, mark); // 将白色转换为黑色,黑色转换为白色
' G8 F% o) d3 X9 ~
showImg("MARKER", mark);
6 S: B/ _+ ^( ~% D
// 在图像中突出显示标记 /
/ E9 S6 E* q4 C! m2 o! H$ \3 T
std::vector<cv::Vec3b> colors;
& I, U, c1 f( p0 Z
getRandomColor(colors, contours.size()); // 创建结果图像
& @: z% N+ f. X) q( F) `
cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
% q$ E+ A( N( \1 E" s. `! i
// 用随机颜色填充标记的对象
* q. m0 g" P+ \( `4 M
for (int i = 0; i < markers.rows; i++)
5 @% j- g# w. _" k: A
{
& ~8 L1 o* ?, w3 F: D
for (int j = 0; j < markers.cols; j++)
) F9 `" |7 T0 N
{
- K& Y4 N2 f+ P8 {! o
int index = markers.at(i,j);
: C) O* `; k, Z T8 f
if (index > 0 && index <= static_cast<int>(contours.size()))
1 }' X% Y [3 {& W7 @! s
dst.at<cv::Vec3b>(i,j) = colors[index-1];
0 M5 M7 c4 s! }2 N/ e
}
# ?1 V7 s1 f( J8 M) |4 t
}
2 v0 Y- ^8 f/ C+ z& _5 }
showImg("Final Result", dst);
; x. y, X7 M+ p( B7 s: p
cv::waitKey(0);
! A9 y2 Y( v! v: k% }) X
return 0;
x6 Z6 o; j; n6 J/ K
}
& B# v; ^) r T, t0 M
复制代码
————————————————
- K+ l4 k# A* b+ K
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/matt45m/article/details/138211359
. h: C: y( \- ?2 z. W& j8 A2 o# D
. X" |& H8 [6 T; _& J% m
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5