ADD ALOT OF POLISHING AND UPDATES...

This commit is contained in:
Wowieee4
2026-05-26 05:52:12 +07:00
parent 2e3ecdf1d1
commit b8ad8df993
53 changed files with 3194 additions and 3860 deletions
@@ -23,12 +23,14 @@ class DetectionResult {
final double confidence;
final ObstacleDirection direction;
final String estimatedDistance;
final BoundingBox? box;
const DetectionResult({
required this.label,
required this.confidence,
required this.direction,
required this.estimatedDistance,
this.box,
});
String get directionName => direction.name.toUpperCase();
@@ -39,7 +41,7 @@ class DetectionResult {
ObstacleDirection.center => 'tengah',
ObstacleDirection.right => 'kanan',
};
return 'Hati-hati, $label di $area. Jarak $estimatedDistance.';
return 'Hati-hati, ${ObstacleAnalyzer.spokenLabel(label)} di $area. Jarak $estimatedDistance.';
}
}
@@ -68,7 +70,7 @@ class ObstacleAnalyzer {
ObstacleDirection.center => 'depan',
ObstacleDirection.right => 'kanan',
};
return 'Hati-hati, ${result.label} di $directionLabel. '
return 'Hati-hati, ${spokenLabel(result.label)} di $directionLabel. '
'Jarak ${result.estimatedDistance}.';
}
@@ -90,7 +92,12 @@ class ObstacleAnalyzer {
final bi = order.indexOf(b.estimatedDistance);
final aRank = ai == -1 ? order.length : ai;
final bRank = bi == -1 ? order.length : bi;
return aRank.compareTo(bRank);
final distanceCompare = aRank.compareTo(bRank);
if (distanceCompare != 0) return distanceCompare;
final directionCompare =
_directionRisk(a.direction).compareTo(_directionRisk(b.direction));
if (directionCompare != 0) return directionCompare;
return b.confidence.compareTo(a.confidence);
});
return sorted.first;
}
@@ -102,15 +109,43 @@ class ObstacleAnalyzer {
return detections.where((d) => d.confidence >= threshold).toList();
}
DetectionResult analyzeFallback({
String label = 'person',
double confidence = 0.86,
}) {
return DetectionResult(
label: label,
confidence: confidence,
direction: ObstacleDirection.center,
estimatedDistance: 'Close (1-2m)',
);
int _directionRisk(ObstacleDirection direction) {
return switch (direction) {
ObstacleDirection.center => 0,
ObstacleDirection.left => 1,
ObstacleDirection.right => 1,
};
}
static String spokenLabel(String label) {
return switch (label.toLowerCase()) {
'person' => 'orang',
'bicycle' => 'sepeda',
'car' => 'mobil',
'motorcycle' => 'motor',
'airplane' => 'pesawat',
'bus' => 'bus',
'train' => 'kereta',
'truck' => 'truk',
'boat' => 'perahu',
'traffic light' => 'lampu lalu lintas',
'fire hydrant' => 'hidran',
'stop sign' => 'rambu berhenti',
'parking meter' => 'meter parkir',
'bench' => 'bangku',
'chair' => 'kursi',
'couch' => 'sofa',
'potted plant' => 'pot tanaman',
'dining table' => 'meja',
'tv' => 'televisi',
'backpack' => 'tas',
'umbrella' => 'payung',
'handbag' => 'tas tangan',
'suitcase' => 'koper',
'skateboard' => 'papan skate',
'surfboard' => 'papan selancar',
'sports ball' => 'bola',
_ => label,
};
}
}
@@ -18,11 +18,31 @@ class YoloDetector {
YoloTensorType? _outputType;
bool _ready = false;
String? _lastError;
int _lastDecodedCount = 0;
int _lastConfidenceCount = 0;
int _lastObstacleCount = 0;
int _lastKeptCount = 0;
int _lastInferenceMs = 0;
String? _lastBestLabel;
double? _lastBestConfidence;
YoloDetector(this._analyzer);
bool get isReady => _ready && _runtime != null;
String? get lastError => _lastError;
String get diagnosticsSummary {
if (!isReady) {
return _lastError == null
? 'YOLO runtime belum aktif'
: 'YOLO runtime error: $_lastError';
}
final bestLabel = _lastBestLabel;
final bestConfidence = _lastBestConfidence;
final bestText = bestLabel == null || bestConfidence == null
? 'best: none'
: 'best: $bestLabel ${(bestConfidence * 100).toStringAsFixed(0)}%';
return 'frames ok, decoded $_lastDecodedCount, >=threshold $_lastConfidenceCount, obstacle $_lastObstacleCount, kept $_lastKeptCount, $bestText, ${_lastInferenceMs}ms';
}
Future<void> init() async {
dispose();
@@ -47,55 +67,64 @@ class YoloDetector {
_ready = true;
} catch (e) {
_lastError = e.toString();
debugPrint('YOLO fallback mode: $e');
debugPrint('YOLO runtime initialization skipped: $e');
_ready = false;
}
}
Future<DetectionResult?> detect(
CameraImage image, {
double confidenceThreshold = 0.45,
double confidenceThreshold = 0.25,
}) async {
if (!isReady) return detectFallback();
if (!isReady) return null;
try {
final stopwatch = Stopwatch()..start();
final input = _buildCameraInput(image);
final output = Uint8List(_runtime!.outputByteLength);
_runtime!.run(input, output);
final detections = _decodeDetections(output);
_recordDecoded(detections);
final filtered =
_analyzer.filterByConfidence(detections, confidenceThreshold);
return _analyzer.prioritize(filtered);
final kept = _nonMaxSuppression(filtered);
_lastInferenceMs = stopwatch.elapsedMilliseconds;
_lastConfidenceCount = filtered.length;
_lastObstacleCount =
filtered.where((d) => _isObstacleLabel(d.label)).length;
_lastKeptCount = kept.length;
return _analyzer.prioritize(kept);
} catch (e) {
_lastError = e.toString();
debugPrint('YOLO inference fallback: $e');
return detectFallback();
debugPrint('YOLO inference skipped: $e');
return null;
}
}
Future<DetectionResult?> detectSynthetic({
double confidenceThreshold = 0.25,
}) async {
if (!isReady) return detectFallback();
if (!isReady) return null;
try {
final input = _buildSyntheticInput();
final output = Uint8List(_runtime!.outputByteLength);
_runtime!.run(input, output);
final detections = _decodeDetections(output);
_recordDecoded(detections);
final filtered =
_analyzer.filterByConfidence(detections, confidenceThreshold);
return _analyzer.prioritize(filtered) ?? detectFallback();
final kept = _nonMaxSuppression(filtered);
_lastConfidenceCount = filtered.length;
_lastObstacleCount =
filtered.where((d) => _isObstacleLabel(d.label)).length;
_lastKeptCount = kept.length;
return _analyzer.prioritize(kept);
} catch (e) {
_lastError = e.toString();
debugPrint('YOLO synthetic fallback: $e');
return detectFallback();
debugPrint('YOLO synthetic skipped: $e');
return null;
}
}
Future<DetectionResult?> detectFallback() async {
final label = _labels.isNotEmpty ? _labels.first : 'person';
return _analyzer.analyzeFallback(label: label);
}
void dispose() {
_runtime?.close();
_runtime = null;
@@ -104,6 +133,26 @@ class YoloDetector {
_inputType = null;
_outputType = null;
_ready = false;
_lastDecodedCount = 0;
_lastConfidenceCount = 0;
_lastObstacleCount = 0;
_lastKeptCount = 0;
_lastInferenceMs = 0;
_lastBestLabel = null;
_lastBestConfidence = null;
}
void _recordDecoded(List<DetectionResult> detections) {
_lastDecodedCount = detections.length;
_lastBestLabel = null;
_lastBestConfidence = null;
for (final detection in detections) {
if (_lastBestConfidence == null ||
detection.confidence > _lastBestConfidence!) {
_lastBestLabel = detection.label;
_lastBestConfidence = detection.confidence;
}
}
}
Uint8List _buildCameraInput(CameraImage image) {
@@ -292,7 +341,7 @@ class YoloDetector {
}
if (type == YoloTensorType.float16) {
// Most exported YOLOv8 TFLite files use float32 output. Float16 is rare;
// fallback keeps the app usable if an unsupported variant is selected.
// Keep preprocessing explicit if an unsupported variant is selected.
throw StateError('Float16 YOLO output is not supported yet');
}
throw StateError('Unsupported YOLO output type: $type');
@@ -484,19 +533,164 @@ class YoloDetector {
confidence: confidence,
direction: _analyzer.analyzeDirection(box),
estimatedDistance: _analyzer.estimateDistance(box),
box: box,
);
}
List<DetectionResult> _nonMaxSuppression(
List<DetectionResult> detections, {
double iouThreshold = 0.45,
int maxDetections = 8,
}) {
final candidates = detections
.where((d) => d.box != null && _isObstacleLabel(d.label))
.toList()
..sort((a, b) => b.confidence.compareTo(a.confidence));
final selected = <DetectionResult>[];
for (final detection in candidates) {
final box = detection.box;
if (box == null) continue;
final overlaps = selected.any((kept) =>
kept.label == detection.label &&
kept.box != null &&
_iou(box, kept.box!) > iouThreshold);
if (overlaps) continue;
selected.add(detection);
if (selected.length >= maxDetections) break;
}
return selected;
}
bool _isObstacleLabel(String label) {
return _walkGuideObstacleLabels.contains(label.toLowerCase());
}
double _iou(BoundingBox a, BoundingBox b) {
final left = math.max(a.left, b.left);
final top = math.max(a.top, b.top);
final right = math.min(a.right, b.right);
final bottom = math.min(a.bottom, b.bottom);
final width = math.max(0.0, right - left);
final height = math.max(0.0, bottom - top);
final intersection = width * height;
final union = a.width * a.height + b.width * b.height - intersection;
if (union <= 0) return 0;
return intersection / union;
}
}
const Set<String> _walkGuideObstacleLabels = {
'person',
'bicycle',
'car',
'motorcycle',
'bus',
'train',
'truck',
'traffic light',
'fire hydrant',
'stop sign',
'parking meter',
'bench',
'backpack',
'umbrella',
'handbag',
'suitcase',
'chair',
'couch',
'potted plant',
'dining table',
'tv',
'laptop',
'cell phone',
'bottle',
'cup',
'book',
};
const Map<int, String> _cocoObstacleLabels = {
0: 'person',
1: 'bicycle',
2: 'car',
3: 'motorcycle',
4: 'airplane',
5: 'bus',
6: 'train',
7: 'truck',
8: 'boat',
9: 'traffic light',
10: 'fire hydrant',
11: 'stop sign',
12: 'parking meter',
13: 'bench',
14: 'bird',
15: 'cat',
16: 'dog',
17: 'horse',
18: 'sheep',
19: 'cow',
20: 'elephant',
21: 'bear',
22: 'zebra',
23: 'giraffe',
24: 'backpack',
25: 'umbrella',
26: 'handbag',
27: 'tie',
28: 'suitcase',
29: 'frisbee',
30: 'skis',
31: 'snowboard',
32: 'sports ball',
33: 'kite',
34: 'baseball bat',
35: 'baseball glove',
36: 'skateboard',
37: 'surfboard',
38: 'tennis racket',
39: 'bottle',
40: 'wine glass',
41: 'cup',
42: 'fork',
43: 'knife',
44: 'spoon',
45: 'bowl',
46: 'banana',
47: 'apple',
48: 'sandwich',
49: 'orange',
50: 'broccoli',
51: 'carrot',
52: 'hot dog',
53: 'pizza',
54: 'donut',
55: 'cake',
56: 'chair',
57: 'couch',
58: 'potted plant',
59: 'bed',
60: 'dining table',
61: 'toilet',
62: 'tv',
63: 'laptop',
64: 'mouse',
65: 'remote',
66: 'keyboard',
67: 'cell phone',
68: 'microwave',
69: 'oven',
70: 'toaster',
71: 'sink',
72: 'refrigerator',
73: 'book',
74: 'clock',
75: 'vase',
76: 'scissors',
77: 'teddy bear',
78: 'hair drier',
79: 'toothbrush',
};
class _InputInfo {