ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
face_follow.c
Go to the documentation of this file.
2#include <math.h>
3#include <string.h>
4
5static float clampf(float value, float low, float high) {
6 return fminf(high, fmaxf(low, value));
7}
8
9static face_follow_rect_t full_frame(void) {
10 return (face_follow_rect_t){0, 0, 1, 1};
11}
12
14 memset(state, 0, sizeof(*state));
15 state->crop = state->target = full_frame();
16}
17
18static float face_follow_overlap(face_follow_rect_t a, face_follow_rect_t b) {
19 float w = fmaxf(0, fminf(a.x + a.w, b.x + b.w) - fmaxf(a.x, b.x));
20 float h = fmaxf(0, fminf(a.y + a.h, b.y + b.h) - fmaxf(a.y, b.y));
21 float intersection = w * h;
22 return intersection / fmaxf(0.000001f, a.w * a.h + b.w * b.h - intersection);
23}
24
25void face_follow_observe(face_follow_state_t *state, const face_follow_detection_t *faces, size_t count, double now) {
26 int selected = -1;
27 float best = -1;
28 bool retain = state->tracking && now - state->last_seen < 1.5;
29 for (size_t i = 0; i < count; i++) {
30 face_follow_rect_t box = faces[i].box;
31 if (!isfinite(box.x) || !isfinite(box.y) || !isfinite(box.w) || !isfinite(box.h) || faces[i].confidence < 0.65f ||
32 box.w <= 0 || box.h <= 0)
33 continue;
34 float cx = box.x + box.w * 0.5f, cy = box.y + box.h * 0.5f;
35 float score;
36 if (retain) {
37 float dx = cx - (state->face.x + state->face.w * 0.5f);
38 float dy = cy - (state->face.y + state->face.h * 0.5f);
39 float distance = sqrtf(dx * dx + dy * dy);
40 float match = face_follow_overlap(box, state->face);
41 // A bystander must not steal the crop while the selected subject is absent.
42 if (match < 0.08f && distance > fmaxf(0.12f, state->face.w * 0.8f))
43 continue;
44 score = 3 * match - distance;
45 } else {
46 float dx = cx - 0.5f, dy = cy - 0.45f;
47 score = sqrtf(box.w * box.h) - 0.35f * sqrtf(dx * dx + dy * dy);
48 }
49 if (score > best) {
50 best = score;
51 selected = (int)i;
52 }
53 }
54 state->detected = selected >= 0;
55 if (selected < 0) {
56 state->pan_pending = state->zoom_pending = false;
57 state->misses++;
58 return;
59 }
60 bool acquired = !retain;
61 state->face = faces[selected].box;
62 state->last_seen = now;
63 state->tracking = true;
64 state->misses = 0;
65 face_follow_rect_t box = state->face;
66 // Retain the source aspect ratio and let the face fill about two thirds of the view.
67 float size = clampf(fmaxf(box.w * 1.8f, box.h * 1.6f), 0.35f, 1.0f);
68 float cx = box.x + box.w * 0.5f;
69 float cy = box.y + box.h * 0.55f;
70 // Measure against the last accepted face position, not the moving/clamped crop.
71 // Keep the entire composition fixed through breathing and detector box noise.
72 float dx = fabsf(cx - state->anchor_x) / state->target.w;
73 float dy = fabsf(cy - state->anchor_y) / state->target.h;
74 if (state->pan_pending &&
75 (cx - state->anchor_x) * state->pan_direction_x + (cy - state->anchor_y) * state->pan_direction_y <= 0)
76 state->pan_pending = false;
77 if (dx < 0.12f && dy < 0.09f)
78 state->pan_pending = false;
79 else if (!state->pan_pending && (dx > 0.18f || dy > 0.14f)) {
80 state->pan_pending = true;
81 state->pan_outside_since = now;
82 state->pan_direction_x = cx - state->anchor_x;
83 state->pan_direction_y = cy - state->anchor_y;
84 }
85 // Large moves near the edge still need two or more consistent detections.
86 double pan_grace = dx > 0.35f || dy > 0.25f ? 0.2 : 0.85;
87 bool pan = acquired || (state->pan_pending && now - state->pan_outside_since >= pan_grace);
88 float zoom_delta = logf(size / state->target.w);
89 float zoom_change = fabsf(zoom_delta);
90 if (state->zoom_pending && zoom_delta * state->zoom_direction <= 0)
91 state->zoom_pending = false;
92 if (zoom_change < 0.12f)
93 state->zoom_pending = false;
94 else if (!state->zoom_pending && zoom_change > 0.18f) {
95 state->zoom_pending = true;
96 state->zoom_outside_since = now;
97 state->zoom_direction = zoom_delta;
98 }
99 bool zoom = acquired || (state->zoom_pending && now - state->zoom_outside_since >= 1.2);
100 if (pan) {
101 state->anchor_x = cx;
102 state->anchor_y = cy;
103 state->pan_pending = false;
104 }
105 if (zoom) {
106 state->target.w = state->target.h = size;
107 state->zoom_pending = false;
108 }
109 if (pan || zoom) {
110 state->target.x = clampf(state->anchor_x - state->target.w * 0.5f, 0, 1 - state->target.w);
111 state->target.y = clampf(state->anchor_y - state->target.h * 0.5f, 0, 1 - state->target.h);
112 }
113}
114
115void face_follow_tick(face_follow_state_t *state, double now, bool enabled) {
116 if (!state->initialized) {
117 if (enabled && state->tracking && now - state->last_seen <= 0.7)
118 state->crop = state->target;
119 state->last_tick = now;
120 state->initialized = true;
121 }
122 double dt = fmax(0, fmin(0.1, now - state->last_tick));
123 state->last_tick = now;
124 bool lost = !state->tracking || now - state->last_seen > 0.7;
125 face_follow_rect_t target = enabled && !lost ? state->target : full_frame();
126 if (state->tracking && now - state->last_seen > 1.5)
127 state->tracking = false;
128 float pan = (float)(1 - exp(-dt / (lost || !enabled ? 0.45 : 0.28)));
129 float zoom = (float)(1 - exp(-dt / (lost || !enabled ? 0.45 : 0.35)));
130 float cx = state->crop.x + state->crop.w * 0.5f;
131 float cy = state->crop.y + state->crop.h * 0.5f;
132 cx += (target.x + target.w * 0.5f - cx) * pan;
133 cy += (target.y + target.h * 0.5f - cy) * pan;
134 if (fabsf(cx - (target.x + target.w * 0.5f)) < 0.0001f)
135 cx = target.x + target.w * 0.5f;
136 if (fabsf(cy - (target.y + target.h * 0.5f)) < 0.0001f)
137 cy = target.y + target.h * 0.5f;
138 float size = expf(logf(state->crop.w) + (logf(target.w) - logf(state->crop.w)) * zoom);
139 if (fabsf(size - target.w) < 0.0001f)
140 size = target.w;
141 state->crop =
142 (face_follow_rect_t){clampf(cx - size * 0.5f, 0, 1 - size), clampf(cy - size * 0.5f, 0, 1 - size), size, size};
143}
void face_follow_tick(face_follow_state_t *state, double now, bool enabled)
void face_follow_init(face_follow_state_t *state)
Definition face_follow.c:13
void face_follow_observe(face_follow_state_t *state, const face_follow_detection_t *faces, size_t count, double now)
Definition face_follow.c:25
bool enabled
Is filtering active?
Definition grep.c:62
🔢 Mathematical Utility Functions
🔤 String Manipulation and Shell Escaping Utilities
face_follow_rect_t box
Definition face_follow.h:28
When code needs to work with face follow detection, it keeps box, confidence here instead of scatteri...
Definition face_follow.h:23
face follow state keeps the values that move through the subsystem: crop, target, face,...
Definition face_follow.h:36