rust_droid/
vision.rs

1use crate::common::rect::Rect as DroidRect;
2use crate::error::{DroidError, Result};
3use image::{DynamicImage, GenericImageView};
4use imageproc::template_matching::{self, MatchTemplateMethod};
5use std::path::Path;
6
7#[derive(Debug, Clone, Copy)]
8pub struct MatchResult {
9    pub rect: DroidRect,
10    pub confidence: f32,
11}
12
13pub fn find_template(
14    haystack: &DynamicImage,
15    needle: &DynamicImage,
16    threshold: f32,
17    needle_path: &Path,
18    search_rect: Option<DroidRect>,
19) -> Result<MatchResult> {
20    log::debug!(
21        "Searching for template {:?} with threshold {:.2} inside region {:?}",
22        needle_path,
23        threshold,
24        search_rect
25    );
26
27    let needle_gray = needle.to_luma8();
28    let haystack_gray_full = haystack.to_luma8();
29
30    let (haystack_to_search, offset_x, offset_y) = if let Some(rect) = search_rect {
31        let cropped_view = haystack_gray_full.view(rect.x, rect.y, rect.width, rect.height);
32        (cropped_view.to_image(), rect.x, rect.y)
33    } else {
34        (haystack_gray_full, 0, 0)
35    };
36
37    let result = template_matching::match_template_parallel(
38        &haystack_to_search,
39        &needle_gray,
40        MatchTemplateMethod::CrossCorrelationNormalized,
41    );
42
43    let extremes = imageproc::template_matching::find_extremes(&result);
44    let best_match_value = extremes.max_value;
45    let mut best_match_location = extremes.max_value_location;
46
47    best_match_location.0 += offset_x;
48    best_match_location.1 += offset_y;
49
50    log::trace!(
51        "Best match found with confidence {:.4} at absolute point ({}, {})",
52        best_match_value,
53        best_match_location.0,
54        best_match_location.1
55    );
56
57    if best_match_value >= threshold {
58        let (needle_width, needle_height) = needle.dimensions();
59        let result_rect = DroidRect::new(
60            best_match_location.0,
61            best_match_location.1,
62            needle_width,
63            needle_height,
64        );
65
66        let match_result = MatchResult {
67            rect: result_rect,
68            confidence: best_match_value,
69        };
70
71        log::debug!("Match found: {:?}", match_result);
72        Ok(match_result)
73    } else {
74        log::warn!(
75            "No match found for {:?}. Best confidence was {:.4}, which is below threshold {:.4}",
76            needle_path,
77            best_match_value,
78            threshold
79        );
80        Err(DroidError::ImageNotFound(needle_path.to_path_buf()))
81    }
82}