mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 06:00:09 +02:00
Hands: fixes from the second headset test (pinches)
- The palm-down filter is off by default: the user's deliberate pinches, hand raised, read 0.90-0.99, like typing. - Typing is told apart by the keyboard instead: the input relay sends the pointer helper "typing" on key presses, and it takes no pinch within POINTER_PINCH_TYPING (1 s) of one. - Grips are still held to hands raised (POINTER_GRIP_BELOW, 0.35 m below the eyes); pinches aren't, since the user's own sat 0.35-0.45 m below, elbow resting. - A grip doesn't begin with the thumb on the index tip: that's a pinch with the other fingers curled, which was taken for a grip. - A pinch's point is the index and middle knuckles: the tips' midpoint moved 1-2 cm as the pinch opened, dragging every release off its press. - ft-hands --gesture-log prints what the detectors measure, 10 times a second. Pinches still aren't reliable enough to use; hand tracking is parked for now in favour of the controllers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fbd188f5f9
commit
5ca4160a7e
7 files changed
+70
-25
No files matched your search
+12
-2
@@ -177,6 +177,7 @@ int main(int argc, char **argv) {
|
||||
double keep_presence = 0.5; // landmark presence a tracked view needs to stay
|
||||
PinchParams pinch_params;
|
||||
GripParams grip_params;
|
||||
bool gesture_log = false; // what the pinch and grip detectors measure, 10 times a second
|
||||
double record_for = 120;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string a = argv[i];
|
||||
@@ -194,6 +195,7 @@ int main(int argc, char **argv) {
|
||||
else if (a == "--pinch-palm-down" && more) pinch_params.palm_down_max = std::atof(argv[++i]);
|
||||
else if (a == "--grip-begin" && more) grip_params.begin = std::atof(argv[++i]);
|
||||
else if (a == "--grip-end" && more) grip_params.end = std::atof(argv[++i]);
|
||||
else if (a == "--gesture-log") gesture_log = true;
|
||||
else if (a == "--swap-sides") swap_sides = true;
|
||||
else if (a == "--record-only") track = publish = false;
|
||||
else if (a == "--ring" && more) ring_path = argv[++i];
|
||||
@@ -220,7 +222,7 @@ int main(int argc, char **argv) {
|
||||
" [--cams auto|mono|color|all] (auto) [--bright all|color] (all) [--bright-on L] (40) [--bright-off L] (25)\n"
|
||||
" [--color-left color_video0|color_video3] [--color-crop subtract|none]\n"
|
||||
" [--pinch-begin M] (0.020) [--pinch-end M] (0.035) [--pinch-triangulated] [--pinch-palm-down MAX] (0.6)\n"
|
||||
" [--grip-begin R] (1.2) [--grip-end R] (1.45)\n"
|
||||
" [--grip-begin R] (1.2) [--grip-end R] (1.45) [--gesture-log]\n"
|
||||
" [--contrast MODE|PALM/HAND] (clahe[:CLIP], none, stretch; default clahe:2/none)\n"
|
||||
"Recording saves every frame set for S seconds (120) to DIR/sets.bin, for ft-handreplay; SIGUSR1\n"
|
||||
"starts one in ~/.local/share/frametop/hands/rec-<time>. --record-only records without tracking, so it\n"
|
||||
@@ -339,7 +341,7 @@ int main(int argc, char **argv) {
|
||||
std::map<std::string, uint64_t> last; // per camera: the frame last used
|
||||
std::map<std::string, uint64_t> lit_seen; // per colour camera: the frame last counted for the light
|
||||
const uint64_t start = mono_ns();
|
||||
uint64_t t_status = start, next_ns = 0, t_want = 0;
|
||||
uint64_t t_status = start, next_ns = 0, t_want = 0, t_glog = 0;
|
||||
double cpu0 = cpu_seconds();
|
||||
std::vector<double> lat;
|
||||
double hands_sum = 0, resid_sum = 0;
|
||||
@@ -541,6 +543,14 @@ int main(int argc, char **argv) {
|
||||
const std::vector<Seen> views = tracker.views_now();
|
||||
grip.update(hands, views, int64_t(capture));
|
||||
pinch.update(hands, views, int64_t(capture), grip.gripping());
|
||||
if (gesture_log && capture - t_glog >= 100'000'000) {
|
||||
t_glog = capture;
|
||||
for (int k = 0; k < 2; ++k)
|
||||
if (pinch.world_d[k] >= 0)
|
||||
std::printf("gesture %s d world %.3f tri %.3f palm-down %.2f curl %.2f%s\n", k ? "right" : "left ",
|
||||
pinch.world_d[k], pinch.tri_d[k], pinch.palm_down[k], grip.curl[k],
|
||||
pinch.side(k).flags & FH_PINCH_DOWN ? " PINCH" : grip.side(k).flags & FH_PINCH_DOWN ? " GRIP" : "");
|
||||
}
|
||||
// a gesture down or closing gets the full rate, even while the palm holds still
|
||||
next_ns = tmin + uint64_t((std::min(tracker.interval(), pinch.engaged() || grip.engaged() ? 1 / 30.0 : 1.0) -
|
||||
0.005) * 1e9);
|
||||
|
||||
+15
-8
@@ -65,7 +65,11 @@ void Pinch::update(const std::vector<const Hand *> &hands, const std::vector<See
|
||||
}
|
||||
if (n) world_d[s] = sum / n * h->scale;
|
||||
const double d = p_.triangulated || world_d[s] < 0 ? tri_d[s] : world_d[s];
|
||||
const V3 point = (h->smooth[kThumbTip] + h->smooth[kIndexTip]) * 0.5;
|
||||
// Where the pinch is, for drags: the index and middle knuckles, which hold still while
|
||||
// the fingers open and close. The point between the tips moved 1-2 cm as a pinch
|
||||
// opened, so every release dragged the pointer off what it pressed (headset test,
|
||||
// 2026-09-30).
|
||||
const V3 point = (h->smooth[5] + h->smooth[9]) * 0.5;
|
||||
o.flags |= FH_PINCH_TRACKED;
|
||||
o.hand_id = uint32_t(h->id);
|
||||
o.distance = float(d);
|
||||
@@ -126,10 +130,11 @@ namespace {
|
||||
|
||||
constexpr int kWrist = 0, kFingers[4][2] = {{5, 8}, {9, 12}, {13, 16}, {17, 20}}; // knuckle, tip
|
||||
|
||||
// Each finger's curl (see GripParams): from the model's world landmarks averaged over the
|
||||
// hand's views this step, or the tracker's 3D points without any.
|
||||
bool curls(const Hand &h, const std::vector<Seen> &views, double out[4]) {
|
||||
double sum[4] = {};
|
||||
// Each finger's curl (see GripParams), and the thumb tip's distance from the index tip (m):
|
||||
// from the model's world landmarks averaged over the hand's views this step, or the
|
||||
// tracker's 3D points without any.
|
||||
bool curls(const Hand &h, const std::vector<Seen> &views, double out[4], double *thumb) {
|
||||
double sum[4] = {}, gap = 0;
|
||||
int n = 0;
|
||||
for (const Seen &v : views) {
|
||||
if (v.hand != h.id) continue;
|
||||
@@ -138,8 +143,10 @@ bool curls(const Hand &h, const std::vector<Seen> &views, double out[4]) {
|
||||
const double k = norm(p(kFingers[f][0]) - p(kWrist));
|
||||
sum[f] += k > 1e-4 ? norm(p(kFingers[f][1]) - p(kWrist)) / k : 2;
|
||||
}
|
||||
gap += norm(p(4) - p(8)) * h.scale;
|
||||
++n;
|
||||
}
|
||||
*thumb = n ? gap / n : norm(h.smooth[4] - h.smooth[8]);
|
||||
for (int f = 0; f < 4; ++f) {
|
||||
if (n) {
|
||||
out[f] = sum[f] / n;
|
||||
@@ -172,8 +179,8 @@ void Grip::update(const std::vector<const Hand *> &hands, const std::vector<Seen
|
||||
if (!h || c->frames > h->frames) h = c;
|
||||
}
|
||||
curl[s] = -1;
|
||||
double f[4];
|
||||
if (!h || !curls(*h, views, f)) {
|
||||
double f[4], thumb = 1;
|
||||
if (!h || !curls(*h, views, f, &thumb)) {
|
||||
o.flags &= ~FH_PINCH_TRACKED;
|
||||
if (down && (t_ns - seen_ns_[s]) / 1e9 > p_.grace_s) end(s, t_ns, true);
|
||||
continue;
|
||||
@@ -191,7 +198,7 @@ void Grip::update(const std::vector<const Hand *> &hands, const std::vector<Seen
|
||||
if (mean > p_.end) open_ns_[s] = t_ns;
|
||||
const bool ahead = -point[2] >= p_.min_ahead_m &&
|
||||
std::atan2(-point[1], -point[2]) * 180 / M_PI <= p_.max_down_deg;
|
||||
if (most < p_.begin && ahead && open_ns_[s] && (t_ns - open_ns_[s]) / 1e9 <= p_.armed_s) {
|
||||
if (most < p_.begin && ahead && thumb >= p_.thumb_off_m && open_ns_[s] && (t_ns - open_ns_[s]) / 1e9 <= p_.armed_s) {
|
||||
o.flags = (o.flags | FH_PINCH_DOWN) & ~FH_PINCH_LOST;
|
||||
++o.begins;
|
||||
o.begin_ns = uint64_t(t_ns);
|
||||
|
||||
+8
-1
@@ -27,7 +27,10 @@ struct PinchParams {
|
||||
// head frame; 1 turns it off). Typing curls the thumb onto the index: in the 2026-09-30
|
||||
// lit recording, pinches that began while typing had 0.69-1.00, deliberate ones 0.00-0.50.
|
||||
// Looking down tilts the head frame, which lowers the reading for a hand on a keyboard.
|
||||
double palm_down_max = 0.6;
|
||||
// Off by default since the first headset test (2026-09-30 14:55): the user's deliberate
|
||||
// pinches, hand raised in front, read 0.90-0.99 too. The pointer helper now leaves out
|
||||
// gestures that begin low (hands on a desk), which it can tell with the head's pose.
|
||||
double palm_down_max = 1.0;
|
||||
};
|
||||
|
||||
class Pinch {
|
||||
@@ -87,6 +90,10 @@ struct GripParams {
|
||||
// deliberate pinches 5-15.
|
||||
double max_down_deg = 35;
|
||||
double min_ahead_m = 0.15;
|
||||
// ...and not with the thumb on the index fingertip, closer than this (m, the pinch's
|
||||
// measure): that's a pinch with the other fingers curled, which the first headset test
|
||||
// took for a grip.
|
||||
double thumb_off_m = 0.03;
|
||||
};
|
||||
|
||||
// Grip (a closed hand) detection, per side like Pinch: press and drag.
|
||||
|
||||
Reference in new issue
Block a user