chess_corners_core/
descriptor.rs1use crate::ring::ring_offsets;
13#[cfg(feature = "tracing")]
14use tracing::instrument;
15
16#[derive(Clone, Debug)]
18pub struct Corner {
19 pub x: f32,
21 pub y: f32,
23 pub strength: f32,
25}
26
27#[derive(Clone, Copy, Debug)]
29pub struct CornerDescriptor {
30 pub x: f32,
32 pub y: f32,
33
34 pub response: f32,
36
37 pub orientation: f32,
43}
44
45#[cfg_attr(
49 feature = "tracing",
50 instrument(
51 level = "info",
52 skip(img, corners),
53 fields(corners = corners.len())
54 )
55)]
56pub fn corners_to_descriptors(
57 img: &[u8],
58 w: usize,
59 h: usize,
60 radius: u32,
61 corners: Vec<Corner>,
62) -> Vec<CornerDescriptor> {
63 let ring = ring_offsets(radius);
64 let mut out = Vec::with_capacity(corners.len());
65 for c in corners {
66 let samples = sample_ring(img, w, h, c.x, c.y, ring);
67 let orientation = estimate_orientation_from_ring(&samples, ring);
68
69 out.push(CornerDescriptor {
70 x: c.x,
71 y: c.y,
72 response: c.strength,
73 orientation,
74 });
75 }
76 out
77}
78
79fn sample_ring(
81 img: &[u8],
82 w: usize,
83 h: usize,
84 x: f32,
85 y: f32,
86 ring: &[(i32, i32); 16],
87) -> [f32; 16] {
88 let mut samples = [0.0f32; 16];
89 for (i, &(dx, dy)) in ring.iter().enumerate() {
90 let sx = x + dx as f32;
91 let sy = y + dy as f32;
92 samples[i] = sample_bilinear(img, w, h, sx, sy);
93 }
94 samples
95}
96
97#[inline]
98fn estimate_orientation_from_ring(samples: &[f32; 16], ring: &[(i32, i32); 16]) -> f32 {
99 let mean = samples.iter().copied().sum::<f32>() / 16.0;
101
102 let mut c2 = 0.0f32;
103 let mut s2 = 0.0f32;
104
105 for (&v_raw, &(dx_i, dy_i)) in samples.iter().zip(ring.iter()) {
106 let v = v_raw - mean;
107 let angle = (dy_i as f32).atan2(dx_i as f32);
108 let a2 = 2.0 * angle;
109 c2 += v * a2.cos();
110 s2 += v * a2.sin();
111 }
112
113 let mut theta = 0.5 * s2.atan2(c2);
114 if theta < 0.0 {
115 theta += core::f32::consts::PI;
116 }
117 if !theta.is_finite() {
118 theta = 0.0;
119 }
120
121 theta
122}
123
124fn sample_bilinear(img: &[u8], w: usize, h: usize, x: f32, y: f32) -> f32 {
125 if w == 0 || h == 0 {
126 return 0.0;
127 }
128
129 let max_x = (w - 1) as f32;
130 let max_y = (h - 1) as f32;
131 let xf = x.clamp(0.0, max_x);
132 let yf = y.clamp(0.0, max_y);
133
134 let x0 = xf.floor() as usize;
135 let y0 = yf.floor() as usize;
136 let x1 = (x0 + 1).min(w - 1);
137 let y1 = (y0 + 1).min(h - 1);
138
139 let wx = xf - x0 as f32;
140 let wy = yf - y0 as f32;
141
142 let i00 = img[y0 * w + x0] as f32;
143 let i10 = img[y0 * w + x1] as f32;
144 let i01 = img[y1 * w + x0] as f32;
145 let i11 = img[y1 * w + x1] as f32;
146
147 let i0 = i00 * (1.0 - wx) + i10 * wx;
148 let i1 = i01 * (1.0 - wx) + i11 * wx;
149 i0 * (1.0 - wy) + i1 * wy
150}