use std::cell::RefCell;
use crate::frame::Frame;
use crate::matcher::{Match, Matcher};
use crate::template::Template;
use corrmatch::{
CompileConfig, ImageView, MatchConfig, Matcher as CorrInner, RotationMode,
Template as CorrTemplate,
};
struct Cached {
key: u64,
inner: CorrInner,
}
pub struct CorrMatcher {
cache: RefCell<Option<Cached>>,
gray: RefCell<Vec<u8>>,
}
impl CorrMatcher {
pub fn new() -> Self {
CorrMatcher {
cache: RefCell::new(None),
gray: RefCell::new(Vec::new()),
}
}
fn ensure_compiled(&self, tpl: &Template) -> Option<()> {
let key = tpl.content_key();
let mut cache = self.cache.borrow_mut();
if cache.as_ref().map(|c| c.key) == Some(key) {
return Some(());
}
let corr_tpl = CorrTemplate::new(tpl.to_gray(), tpl.width, tpl.height).ok()?;
let compiled = corr_tpl.compile(CompileConfig::default()).ok()?;
let inner = CorrInner::new(compiled).with_config(MatchConfig {
rotation: RotationMode::Disabled,
..MatchConfig::default()
});
*cache = Some(Cached { key, inner });
Some(())
}
}
impl Default for CorrMatcher {
fn default() -> Self {
Self::new()
}
}
impl Matcher for CorrMatcher {
fn find(&self, frame: &Frame, tpl: &Template) -> Option<Match> {
self.ensure_compiled(tpl)?;
frame.to_gray_into(&mut self.gray.borrow_mut());
let cache = self.cache.borrow();
let gray = self.gray.borrow();
let inner = &cache.as_ref()?.inner;
let view = ImageView::from_slice(&gray, frame.width, frame.height).ok()?;
let res = inner.match_image(view).ok()?;
Some(Match {
x: res.x as i32,
y: res.y as i32,
score: res.score,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::Rect;
fn noisy_bg(w: usize, h: usize) -> Vec<u8> {
let mut px = vec![0u8; w * h * 4];
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 4;
px[i] = ((x * 7 + y * 13) % 256) as u8;
px[i + 1] = ((x * 3 + y * 11) % 256) as u8;
px[i + 2] = ((x * 5 + y * 17) % 256) as u8;
px[i + 3] = 255;
}
}
px
}
fn paste_textured(px: &mut [u8], w: usize, tx: usize, ty: usize, s: usize) -> Vec<u8> {
let mut rgb = Vec::with_capacity(s * s * 3);
for yy in 0..s {
for xx in 0..s {
let i = ((ty + yy) * w + (tx + xx)) * 4;
let checker = if (xx / 4 + yy / 4) % 2 == 0 { 240 } else { 15 };
let r = (checker - xx as i32 * 2).clamp(0, 255) as u8;
let g = (checker - yy as i32 * 2).clamp(0, 255) as u8;
let b = ((xx * 5 + yy * 3) % 256) as u8;
px[i] = r;
px[i + 1] = g;
px[i + 2] = b;
rgb.extend_from_slice(&[r, g, b]);
}
}
rgb
}
#[test]
fn cached_compile_is_consistent() {
let (w, h) = (256usize, 256usize);
let mut px = noisy_bg(w, h);
let rgb = paste_textured(&mut px, w, 150, 120, 32);
let frame = Frame::rgba8(w, h, px);
let t = Template::from_rgb(rgb, 32, 32);
let m = CorrMatcher::new();
let a = m.find(&frame, &t).expect("first find");
let b = m.find(&frame, &t).expect("cached find");
assert_eq!((a.x, a.y), (150, 120));
assert_eq!(a.x, b.x);
assert_eq!(a.y, b.y);
}
#[test]
fn region_default_works() {
let (w, h) = (256usize, 256usize);
let mut px = noisy_bg(w, h);
let rgb = paste_textured(&mut px, w, 60, 70, 32);
let frame = Frame::rgba8(w, h, px);
let t = Template::from_rgb(rgb, 32, 32);
let m = CorrMatcher::new();
let hit = m
.find_in(&frame, &t, Rect::new(50, 60, 64, 64))
.expect("区域内应命中");
assert_eq!((hit.x, hit.y), (60, 70));
}
}