feat: 优化 slide.rs
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
use crate::utils::cv_ops::{min_max_loc, rgb_to_gray, ndarray_to_luma8, abs_diff};
|
||||
use crate::utils::cv_ops;
|
||||
use crate::utils::cv_ops::{abs_diff, min_max_loc, ndarray_to_luma8, rgb_to_gray};
|
||||
use crate::utils::image_io::image_to_ndarray;
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
use image::{ImageBuffer, Luma};
|
||||
use imageproc::contrast::{ThresholdType, threshold};
|
||||
use imageproc::distance_transform::Norm;
|
||||
use imageproc::edges::canny;
|
||||
use imageproc::morphology::{close, open};
|
||||
use imageproc::region_labelling::{Connectivity, connected_components};
|
||||
use imageproc::template_matching::{MatchTemplateMethod, match_template};
|
||||
use std::cmp::{max, min};
|
||||
use imageproc::contrast::{threshold, ThresholdType};
|
||||
use tract_onnx::prelude::tract_ndarray::{Array2, Array3, ArrayView2, ArrayView3, Axis, s};
|
||||
|
||||
pub struct SlideResult {
|
||||
@@ -78,17 +79,12 @@ impl Slide {
|
||||
// 1. 计算差异数组 (复用 cv2::absdiff)
|
||||
let diff_array = abs_diff(&target, &background);
|
||||
|
||||
// 2. 转换为灰度数组 (复用你的 cv2::rgb_to_gray)
|
||||
// 2. 转换为灰度数组 (复用你的 cv2.cvtColor)
|
||||
let gray_array = rgb_to_gray(diff_array.view());
|
||||
// 3. 转为 ImageBuffer 以使用 imageproc 的高级功能
|
||||
let gray_buffer = ndarray_to_luma8(gray_array.view());
|
||||
|
||||
// 2. 二值化 (对应 cv2.threshold(..., 30, 255, cv2.THRESH_BINARY))
|
||||
// let mut binary = ImageBuffer::new(w as u32, h as u32);
|
||||
// for (x, y, pixel) in diff_buffer.enumerate_pixels() {
|
||||
// let val = if pixel.0[0] > 30 { 255u8 } else { 0u8 };
|
||||
// binary.put_pixel(x, y, Luma([val]));
|
||||
// }
|
||||
let binary = threshold(&gray_buffer, 30, ThresholdType::Binary);
|
||||
// 3. 形态学操作去噪 (对应 cv2.morphologyEx)
|
||||
// 闭运算 (Close): 先膨胀后腐蚀,用于填补缺口内的细小黑色空洞
|
||||
@@ -98,65 +94,32 @@ impl Slide {
|
||||
let closed = close(&binary, norm, radius);
|
||||
let cleaned = open(&closed, norm, radius);
|
||||
|
||||
// 4. 寻找最大连通区域 (对应 findContours + max area)
|
||||
// connected_components 会给每个独立的白色区域打上不同的标签 (ID)
|
||||
let background_label = Luma([0u8]);
|
||||
let labelled = connected_components(&cleaned, Connectivity::Eight, background_label);
|
||||
|
||||
// 统计每个标签出现的频率(即面积)
|
||||
let mut max_label = 0;
|
||||
let mut max_area = 0;
|
||||
let mut areas = std::collections::HashMap::new();
|
||||
// // 统计每个标签出现的频率(即面积)
|
||||
// 4. 寻找最大连通区域 (对应 findContours + max area)
|
||||
if let Some(max_label) = cv_ops::find_contours_and_max(&labelled) {
|
||||
// 5. 计算最大区域的边界框 (对应 cv2.boundingRect)
|
||||
let (x, y, w, h) = cv_ops::bounding_rect(&labelled, max_label);
|
||||
// 6. 计算中心点 (调用之前封装的 calculate_center)
|
||||
let (center_x, center_y) = cv_ops::calculate_center((x, y), w as usize, h as usize);
|
||||
|
||||
for pixel in labelled.pixels() {
|
||||
let label = pixel.0[0];
|
||||
if label == 0 {
|
||||
continue;
|
||||
} // 跳过背景
|
||||
let count = areas.entry(label).or_insert(0);
|
||||
*count += 1;
|
||||
if *count > max_area {
|
||||
max_area = *count;
|
||||
max_label = label;
|
||||
}
|
||||
}
|
||||
|
||||
if max_label == 0 {
|
||||
return Ok(SlideResult {
|
||||
Ok(SlideResult {
|
||||
target: [center_x, center_y],
|
||||
target_x: center_x,
|
||||
target_y: center_y,
|
||||
confidence: 1.0, // Comparison 模式下通常认为找到即为 1.0
|
||||
})
|
||||
} else {
|
||||
Ok(SlideResult {
|
||||
target: [0, 0],
|
||||
target_x: 0,
|
||||
target_y: 0,
|
||||
confidence: 0.0,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 5. 计算最大区域的边界框 (对应 cv2.boundingRect)
|
||||
let mut min_x = w as u32;
|
||||
let mut max_x = 0;
|
||||
let mut min_y = h as u32;
|
||||
let mut max_y = 0;
|
||||
|
||||
for (x, y, pixel) in labelled.enumerate_pixels() {
|
||||
if pixel.0[0] == max_label {
|
||||
min_x = min(min_x, x);
|
||||
max_x = max(max_x, x);
|
||||
min_y = min(min_y, y);
|
||||
max_y = max(max_y, y);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 计算中心点
|
||||
let rect_w = max_x - min_x;
|
||||
let rect_h = max_y - min_y;
|
||||
let center_x = (min_x + rect_w / 2) as i32;
|
||||
let center_y = (min_y + rect_h / 2) as i32;
|
||||
|
||||
Ok(SlideResult {
|
||||
target: [center_x, center_y],
|
||||
target_x: center_x,
|
||||
target_y: center_y,
|
||||
confidence: 1.0, // Comparison 模式下通常认为找到即为 1.0
|
||||
})
|
||||
}
|
||||
|
||||
/// 对应 Python: _perform_slide_match
|
||||
@@ -210,8 +173,8 @@ impl Slide {
|
||||
|
||||
// 4. 计算中心点 (与 Python 逻辑完全一致)
|
||||
let (th, tw) = target.dim();
|
||||
let center_x = max_loc.0 as i32 + (tw as i32 / 2);
|
||||
let center_y = max_loc.1 as i32 + (th as i32 / 2);
|
||||
|
||||
let (center_x, center_y) = cv_ops::calculate_center(max_loc, tw as usize, th as usize);
|
||||
// println!("Rust Target Width (tw): {}", tw);
|
||||
// println!("Rust Best Max Loc X: {}", max_loc.0);
|
||||
// println!("Rust Final Center X: {}", center_x);
|
||||
@@ -256,8 +219,7 @@ impl Slide {
|
||||
// 5. 计算中心位置 (对齐 Python 逻辑)
|
||||
// target_w, target_h 来自输入数组的维度
|
||||
let (th, tw) = target.dim();
|
||||
let center_x = max_loc.0 as i32 + (tw as i32 / 2);
|
||||
let center_y = max_loc.1 as i32 + (th as i32 / 2);
|
||||
let (center_x, center_y) = cv_ops::calculate_center(max_loc, tw as usize, th as usize);
|
||||
|
||||
// 打印调试信息,方便与 Python 对比
|
||||
// println!("Edge Match: max_val: {}, max_loc: {:?}", max_val, max_loc);
|
||||
@@ -271,6 +233,4 @@ impl Slide {
|
||||
confidence: max_val as f64,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::cmp::{max, min};
|
||||
use image::{ImageBuffer, Luma};
|
||||
use tract_onnx::prelude::tract_ndarray::{azip, Array2, Array3, ArrayView2, ArrayView3};
|
||||
|
||||
@@ -45,6 +46,55 @@ pub fn min_max_loc(result_map: &ImageBuffer<Luma<f32>, Vec<f32>>) -> (f32, (u32,
|
||||
}
|
||||
(max_val, max_loc)
|
||||
}
|
||||
|
||||
/// 1. 模拟 findContours 并获取最大面积区域的 Label
|
||||
/// 返回 Option<u32>,如果找不到任何区域则返回 None
|
||||
pub fn find_contours_and_max(labelled: &ImageBuffer<Luma<u32>, Vec<u32>>) -> Option<u32> {
|
||||
// 统计每个标签出现的频率(即面积)
|
||||
let mut max_label = 0;
|
||||
let mut max_area = 0;
|
||||
let mut areas = std::collections::HashMap::new();
|
||||
|
||||
for pixel in labelled.pixels() {
|
||||
let label = pixel.0[0];
|
||||
if label == 0 {
|
||||
continue;
|
||||
} // 跳过背景
|
||||
let count = areas.entry(label).or_insert(0);
|
||||
*count += 1;
|
||||
if *count > max_area {
|
||||
max_area = *count;
|
||||
max_label = label;
|
||||
}
|
||||
}
|
||||
if max_label == 0 { None } else { Some(max_label) }
|
||||
}
|
||||
pub fn bounding_rect(labelled: &ImageBuffer<Luma<u32>, Vec<u32>>,max_label: u32) -> (u32, u32, u32, u32) {
|
||||
// 5. 计算最大区域的边界框 (对应 cv2.boundingRect)
|
||||
let mut min_x = labelled.width();
|
||||
let mut max_x = 0;
|
||||
let mut min_y = labelled.height();
|
||||
let mut max_y = 0;
|
||||
|
||||
for (x, y, pixel) in labelled.enumerate_pixels() {
|
||||
if pixel.0[0] == max_label {
|
||||
min_x = min(min_x, x);
|
||||
max_x = max(max_x, x);
|
||||
min_y = min(min_y, y);
|
||||
max_y = max(max_y, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let w = max_x - min_x;
|
||||
let h = max_y - min_y;
|
||||
(min_x, min_y, w, h)
|
||||
}
|
||||
pub fn calculate_center(max_loc: (u32, u32), tw: usize, th: usize) -> (i32, i32) {
|
||||
let center_x = max_loc.0 as i32 + (tw as i32 / 2);
|
||||
let center_y = max_loc.1 as i32 + (th as i32 / 2);
|
||||
(center_x, center_y)
|
||||
}
|
||||
pub fn ndarray_to_luma8(array: ArrayView2<u8>) -> ImageBuffer<Luma<u8>, Vec<u8>> {
|
||||
let (height, width) = array.dim();
|
||||
let mut buffer = ImageBuffer::new(width as u32, height as u32);
|
||||
|
||||
Reference in New Issue
Block a user