Skip to content

Commit f460390

Browse files
committed
Add dual inner/outer deadzone system to PTZ tracker
Prevents PTZ overshooting with a two-zone speed reduction system: - Inner deadzone (green): Camera STOPS completely when target is inside this zone. Adjustable per-axis. - Outer deadzone (amber): Between inner and outer, camera moves at 10% of configured speed (90% reduction). This is the braking zone that prevents overshoot. - Beyond outer: Camera moves at 50% speed for controlled approach. Visual feedback: - Green dashed rectangle = inner deadzone (stop zone) - Amber dashed rectangle = outer deadzone (braking zone) - Labels on each zone for clarity UI: 4 sliders (Inner H/V, Outer H/V) with help text explaining the dual zone concept. All presets updated with tuned values. Settings persist to localStorage.
1 parent 55dbc71 commit f460390

File tree

3 files changed

+243
-74
lines changed

3 files changed

+243
-74
lines changed

PTZOptics-Moondream-Tracker/app.js

Lines changed: 114 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,32 @@ $(function () {
1515
detectionRate: 0.5,
1616
panSpeed: 3,
1717
tiltSpeed: 3,
18-
deadzoneX: 12,
19-
deadzoneY: 12
18+
deadzoneX: 15,
19+
deadzoneY: 15,
20+
outerDeadzoneX: 35,
21+
outerDeadzoneY: 35
2022
},
2123
precise: {
2224
name: 'Precise Centering',
2325
description: 'Tight centering for presentations',
2426
detectionRate: 1.5,
2527
panSpeed: 6,
2628
tiltSpeed: 6,
27-
deadzoneX: 2,
28-
deadzoneY: 2
29+
deadzoneX: 4,
30+
deadzoneY: 4,
31+
outerDeadzoneX: 12,
32+
outerDeadzoneY: 12
2933
},
3034
balanced: {
3135
name: 'Balanced',
3236
description: 'Good balance for general use',
3337
detectionRate: 1.0,
3438
panSpeed: 5,
3539
tiltSpeed: 5,
36-
deadzoneX: 5,
37-
deadzoneY: 5
40+
deadzoneX: 10,
41+
deadzoneY: 10,
42+
outerDeadzoneX: 25,
43+
outerDeadzoneY: 25
3844
},
3945
fast: {
4046
name: 'Fast Response',
@@ -43,19 +49,22 @@ $(function () {
4349
panSpeed: 8,
4450
tiltSpeed: 8,
4551
deadzoneX: 8,
46-
deadzoneY: 8
52+
deadzoneY: 8,
53+
outerDeadzoneX: 20,
54+
outerDeadzoneY: 20
4755
},
4856
minimal: {
4957
name: 'Minimal Movement',
5058
description: 'Reduce API usage and camera movement',
5159
detectionRate: 0.3,
5260
panSpeed: 4,
5361
tiltSpeed: 4,
54-
deadzoneX: 15,
55-
deadzoneY: 15
62+
deadzoneX: 20,
63+
deadzoneY: 20,
64+
outerDeadzoneX: 40,
65+
outerDeadzoneY: 40
5666
}
5767
};
58-
5968
let settings = {
6069
moondreamApiKey: localStorage.getItem('moondreamApiKey') || '',
6170
cameraIP: localStorage.getItem('cameraIP') || '192.168.1.19',
@@ -69,8 +78,10 @@ $(function () {
6978
tiltSpeed: parseInt(localStorage.getItem('tiltSpeed')) || 5,
7079
centerOffsetX: parseFloat(localStorage.getItem('centerOffsetX')) || 50,
7180
centerOffsetY: parseFloat(localStorage.getItem('centerOffsetY')) || 50,
72-
deadzoneX: parseFloat(localStorage.getItem('deadzoneX')) || 5,
73-
deadzoneY: parseFloat(localStorage.getItem('deadzoneY')) || 5,
81+
deadzoneX: parseFloat(localStorage.getItem('deadzoneX')) || 10,
82+
deadzoneY: parseFloat(localStorage.getItem('deadzoneY')) || 10,
83+
outerDeadzoneX: parseFloat(localStorage.getItem('outerDeadzoneX')) || 25,
84+
outerDeadzoneY: parseFloat(localStorage.getItem('outerDeadzoneY')) || 25,
7485
autoZoomEnabled: localStorage.getItem('autoZoomEnabled') === 'true',
7586
minHeadroom: parseFloat(localStorage.getItem('minHeadroom')) || 10,
7687
maxHeadroom: parseFloat(localStorage.getItem('maxHeadroom')) || 30,
@@ -164,6 +175,10 @@ $(function () {
164175
$('#deadzoneXValue').text(settings.deadzoneX);
165176
$('#deadzoneY').val(settings.deadzoneY);
166177
$('#deadzoneYValue').text(settings.deadzoneY);
178+
$('#outerDeadzoneX').val(settings.outerDeadzoneX);
179+
$('#outerDeadzoneXValue').text(settings.outerDeadzoneX);
180+
$('#outerDeadzoneY').val(settings.outerDeadzoneY);
181+
$('#outerDeadzoneYValue').text(settings.outerDeadzoneY);
167182

168183
$('#autoZoomEnabled').prop('checked', settings.autoZoomEnabled);
169184
$('#minHeadroom').val(settings.minHeadroom);
@@ -192,6 +207,10 @@ $(function () {
192207
horizontal: settings.deadzoneX,
193208
vertical: settings.deadzoneY
194209
});
210+
ptzController.setOuterDeadzone({
211+
horizontal: settings.outerDeadzoneX,
212+
vertical: settings.outerDeadzoneY
213+
});
195214

196215
setupEventListeners();
197216

@@ -331,6 +350,26 @@ $(function () {
331350
switchToCustomMode();
332351
renderDeadzone();
333352
});
353+
354+
$('#outerDeadzoneX').on('input', function() {
355+
const val = parseFloat($(this).val());
356+
settings.outerDeadzoneX = val;
357+
$('#outerDeadzoneXValue').text(val);
358+
localStorage.setItem('outerDeadzoneX', val.toString());
359+
ptzController.setOuterDeadzone({ horizontal: val });
360+
switchToCustomMode();
361+
renderDeadzone();
362+
});
363+
364+
$('#outerDeadzoneY').on('input', function() {
365+
const val = parseFloat($(this).val());
366+
settings.outerDeadzoneY = val;
367+
$('#outerDeadzoneYValue').text(val);
368+
localStorage.setItem('outerDeadzoneY', val.toString());
369+
ptzController.setOuterDeadzone({ vertical: val });
370+
switchToCustomMode();
371+
renderDeadzone();
372+
});
334373

335374
$('#autoZoomEnabled').on('change', function() {
336375
settings.autoZoomEnabled = $(this).is(':checked');
@@ -436,6 +475,18 @@ $(function () {
436475
$('#deadzoneYValue').text(preset.deadzoneY);
437476
localStorage.setItem('deadzoneY', preset.deadzoneY.toString());
438477
ptzController.setDeadzone({ vertical: preset.deadzoneY });
478+
479+
settings.outerDeadzoneX = preset.outerDeadzoneX;
480+
$('#outerDeadzoneX').val(preset.outerDeadzoneX);
481+
$('#outerDeadzoneXValue').text(preset.outerDeadzoneX);
482+
localStorage.setItem('outerDeadzoneX', preset.outerDeadzoneX.toString());
483+
ptzController.setOuterDeadzone({ horizontal: preset.outerDeadzoneX });
484+
485+
settings.outerDeadzoneY = preset.outerDeadzoneY;
486+
$('#outerDeadzoneY').val(preset.outerDeadzoneY);
487+
$('#outerDeadzoneYValue').text(preset.outerDeadzoneY);
488+
localStorage.setItem('outerDeadzoneY', preset.outerDeadzoneY.toString());
489+
ptzController.setOuterDeadzone({ vertical: preset.outerDeadzoneY });
439490

440491
if (isTracking) {
441492
clearInterval(detectionInterval);
@@ -641,28 +692,58 @@ $(function () {
641692

642693
const centerX = (settings.centerOffsetX / 100) * video.videoWidth;
643694
const centerY = (settings.centerOffsetY / 100) * video.videoHeight;
644-
const halfDeadzoneW = (settings.deadzoneX / 100) * video.videoWidth / 2;
645-
const halfDeadzoneH = (settings.deadzoneY / 100) * video.videoHeight / 2;
646-
647-
ctx.strokeStyle = 'rgba(42, 157, 143, 0.6)';
695+
696+
// Outer deadzone (larger zone — 50% speed reduction beyond this)
697+
const halfOuterW = (settings.outerDeadzoneX / 100) * video.videoWidth / 2;
698+
const halfOuterH = (settings.outerDeadzoneY / 100) * video.videoHeight / 2;
699+
700+
// Draw outer deadzone — amber/yellow dashed
701+
ctx.strokeStyle = 'rgba(230, 176, 50, 0.5)';
702+
ctx.lineWidth = 1;
703+
ctx.setLineDash([12, 6]);
704+
ctx.strokeRect(
705+
centerX - halfOuterW,
706+
centerY - halfOuterH,
707+
halfOuterW * 2,
708+
halfOuterH * 2
709+
);
710+
ctx.setLineDash([]);
711+
712+
// Fill outer zone (between inner and outer) — faint amber
713+
ctx.fillStyle = 'rgba(230, 176, 50, 0.05)';
714+
ctx.fillRect(
715+
centerX - halfOuterW,
716+
centerY - halfOuterH,
717+
halfOuterW * 2,
718+
halfOuterH * 2
719+
);
720+
721+
// Inner deadzone (smaller zone — camera STOPS here)
722+
const halfInnerW = (settings.deadzoneX / 100) * video.videoWidth / 2;
723+
const halfInnerH = (settings.deadzoneY / 100) * video.videoHeight / 2;
724+
725+
// Draw inner deadzone — green solid
726+
ctx.strokeStyle = 'rgba(42, 157, 143, 0.7)';
648727
ctx.lineWidth = 2;
649728
ctx.setLineDash([8, 4]);
650729
ctx.strokeRect(
651-
centerX - halfDeadzoneW,
652-
centerY - halfDeadzoneH,
653-
halfDeadzoneW * 2,
654-
halfDeadzoneH * 2
730+
centerX - halfInnerW,
731+
centerY - halfInnerH,
732+
halfInnerW * 2,
733+
halfInnerH * 2
655734
);
656735
ctx.setLineDash([]);
657-
736+
737+
// Fill inner deadzone — faint green
658738
ctx.fillStyle = 'rgba(42, 157, 143, 0.1)';
659739
ctx.fillRect(
660-
centerX - halfDeadzoneW,
661-
centerY - halfDeadzoneH,
662-
halfDeadzoneW * 2,
663-
halfDeadzoneH * 2
740+
centerX - halfInnerW,
741+
centerY - halfInnerH,
742+
halfInnerW * 2,
743+
halfInnerH * 2
664744
);
665-
745+
746+
// Center crosshair
666747
ctx.strokeStyle = 'rgba(147, 204, 234, 0.5)';
667748
ctx.lineWidth = 1;
668749
ctx.beginPath();
@@ -672,11 +753,15 @@ $(function () {
672753
ctx.lineTo(centerX, centerY + 20);
673754
ctx.stroke();
674755

675-
ctx.fillStyle = 'rgba(147, 204, 234, 0.7)';
676-
ctx.font = '12px sans-serif';
756+
// Labels
757+
ctx.fillStyle = 'rgba(42, 157, 143, 0.8)';
758+
ctx.font = '11px sans-serif';
677759
ctx.textAlign = 'left';
678760
ctx.textBaseline = 'top';
679-
ctx.fillText('Deadzone', centerX - halfDeadzoneW + 4, centerY - halfDeadzoneH + 4);
761+
ctx.fillText('Inner (stop)', centerX - halfInnerW + 4, centerY - halfInnerH + 4);
762+
763+
ctx.fillStyle = 'rgba(230, 176, 50, 0.8)';
764+
ctx.fillText('Outer (slow)', centerX - halfOuterW + 4, centerY - halfOuterH + 4);
680765
}
681766

682767
function renderDetection(detection) {

PTZOptics-Moondream-Tracker/index.html

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,31 @@ <h3 class="section-heading">Center Target Position</h3>
125125
<h3 class="section-heading">Centering Precision</h3>
126126
<div class="settings-row">
127127
<div class="control-group half">
128-
<label for="deadzoneX">Horizontal Deadzone: <span id="deadzoneXValue">5</span>%</label>
129-
<input type="range" id="deadzoneX" class="slider settings-input" min="1" max="50" step="1" value="5" />
130-
<small>Smaller = tighter centering</small>
128+
<label for="deadzoneX">Inner Deadzone H: <span id="deadzoneXValue">10</span>%</label>
129+
<input type="range" id="deadzoneX" class="slider settings-input" min="1" max="50" step="1" value="10" />
130+
<small>Camera STOPS inside this zone</small>
131131
</div>
132132
<div class="control-group half">
133-
<label for="deadzoneY">Vertical Deadzone: <span id="deadzoneYValue">5</span>%</label>
134-
<input type="range" id="deadzoneY" class="slider settings-input" min="1" max="50" step="1" value="5" />
135-
<small>Smaller = tighter centering</small>
133+
<label for="deadzoneY">Inner Deadzone V: <span id="deadzoneYValue">10</span>%</label>
134+
<input type="range" id="deadzoneY" class="slider settings-input" min="1" max="50" step="1" value="10" />
135+
<small>Camera STOPS inside this zone</small>
136+
</div>
137+
</div>
138+
<div class="settings-row">
139+
<div class="control-group half">
140+
<label for="outerDeadzoneX">Outer Deadzone H: <span id="outerDeadzoneXValue">25</span>%</label>
141+
<input type="range" id="outerDeadzoneX" class="slider settings-input" min="5" max="80" step="1" value="25" />
142+
<small>90% speed reduction in this band</small>
143+
</div>
144+
<div class="control-group half">
145+
<label for="outerDeadzoneY">Outer Deadzone V: <span id="outerDeadzoneYValue">25</span>%</label>
146+
<input type="range" id="outerDeadzoneY" class="slider settings-input" min="5" max="80" step="1" value="25" />
147+
<small>90% speed reduction in this band</small>
136148
</div>
137149
</div>
138150
<small class="help-text">
139-
<strong>Tip:</strong> Set center target to where you want the object (50% = true center).
140-
Use smaller deadzone (1-5%) for precise centering, larger (15-30%) for smoother tracking.
151+
<strong>Dual Deadzone:</strong> Inner zone (green) = camera stops. Between inner &amp; outer (amber) = 10% speed.
152+
Beyond outer = 50% speed. This prevents overshooting the target.
141153
</small>
142154

143155
<h3 class="section-heading">Auto-Zoom (Headroom Control)</h3>

0 commit comments

Comments
 (0)