1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// src/edge/morphology.rs
use std::time::Instant;
/// 形态学操作工具集
pub struct Morphology;
impl Morphology {
/// 对二值边缘图执行膨胀操作(Dilation),用于填补 Canny 输出中的边缘断点。
///
/// # 算法说明
/// 使用 (2*radius+1)×(2*radius+1) 的矩形结构元素。
/// 对每个像素,若其邻域内存在任意非零值,则输出置为 255。
/// 膨胀后相邻的短边缘段会连通,使轮廓追踪能提取出完整的长轮廓。
///
/// # 参数
/// - `src`:输入二值图(0 或 255),长度为 width * height
/// - `width`、`height`:图像尺寸
/// - `radius`:膨胀半径(像素),推荐值 1~2
///
/// # 返回
/// 膨胀后的二值图,与输入等长
pub fn dilate(src: &[u8], width: usize, height: usize, radius: usize) -> Vec<u8> {
let start = Instant::now();
let mut dst = vec![0u8; width * height];
// 统计输入边缘像素数,用于日志对比
let src_edge_count = src.iter().filter(|&&v| v > 0).count();
for y in 0..height {
for x in 0..width {
// 检查以 (x,y) 为中心的邻域内是否存在边缘像素
'outer: for dy in 0..=(2 * radius) {
for dx in 0..=(2 * radius) {
// 将无符号偏移转换为有符号坐标,避免下溢
let ny = y as isize + dy as isize - radius as isize;
let nx = x as isize + dx as isize - radius as isize;
// 边界裁剪
if nx < 0 || ny < 0 || nx >= width as isize || ny >= height as isize {
continue;
}
let nidx = ny as usize * width + nx as usize;
if src[nidx] > 0 {
dst[y * width + x] = 255;
break 'outer;
}
}
}
}
}
let dst_edge_count = dst.iter().filter(|&&v| v > 0).count();
log::debug!(
"[Edge::Morphology] - Dilate done: radius={}, src_edges={}, dst_edges={}, \
growth_ratio={:.2}x. Elapsed: {}ms",
radius,
src_edge_count,
dst_edge_count,
if src_edge_count > 0 {
dst_edge_count as f32 / src_edge_count as f32
} else {
0.0
},
start.elapsed().as_millis()
);
dst
}
/// 对二值边缘图执行腐蚀操作(Erosion),与膨胀配合实现闭运算(Close)。
///
/// # 算法说明
/// 使用 (2*radius+1)×(2*radius+1) 的矩形结构元素。
/// 对每个像素,若其邻域内全部为非零值,则输出置为 255,否则为 0。
/// 先膨胀后腐蚀(闭运算)可填补断点同时抑制膨胀带来的边缘增厚。
pub fn erode(src: &[u8], width: usize, height: usize, radius: usize) -> Vec<u8> {
let start = Instant::now();
let mut dst = vec![0u8; width * height];
let src_edge_count = src.iter().filter(|&&v| v > 0).count();
for y in 0..height {
for x in 0..width {
let mut all_edge = true;
'outer: for dy in 0..=(2 * radius) {
for dx in 0..=(2 * radius) {
let ny = y as isize + dy as isize - radius as isize;
let nx = x as isize + dx as isize - radius as isize;
// 边界像素视为非边缘(保守策略,避免边界膨胀)
if nx < 0 || ny < 0 || nx >= width as isize || ny >= height as isize {
all_edge = false;
break 'outer;
}
let nidx = ny as usize * width + nx as usize;
if src[nidx] == 0 {
all_edge = false;
break 'outer;
}
}
}
if all_edge {
dst[y * width + x] = 255;
}
}
}
let dst_edge_count = dst.iter().filter(|&&v| v > 0).count();
log::debug!(
"[Edge::Morphology] - Erode done: radius={}, src_edges={}, dst_edges={}. Elapsed: {}ms",
radius,
src_edge_count,
dst_edge_count,
start.elapsed().as_millis()
);
dst
}
/// 形态学闭运算(Close = Dilate → Erode),填补边缘断点并恢复边缘宽度。
///
/// # 参数
/// - `src`:输入二值边缘图
/// - `width`、`height`:图像尺寸
/// - `radius`:结构元素半径,推荐值 1(轻度填补)或 2(强力填补)
pub fn close(src: &[u8], width: usize, height: usize, radius: usize) -> Vec<u8> {
let start = Instant::now();
log::debug!(
"[Edge::Morphology] - Close operation start: {}x{}, radius={}",
width,
height,
radius
);
let dilated = Self::dilate(src, width, height, radius);
let closed = Self::erode(&dilated, width, height, radius);
let src_edges = src.iter().filter(|&&v| v > 0).count();
let closed_edges = closed.iter().filter(|&&v| v > 0).count();
log::info!(
"[Edge::Morphology] - Close done: src_edges={}, closed_edges={}, \
net_change={:+}. Elapsed: {}ms",
src_edges,
closed_edges,
closed_edges as i64 - src_edges as i64,
start.elapsed().as_millis()
);
closed
}
}