api_version: cmvr.edge.ai/v1 runtime: # Decoder, two YOLO models and viewer drawing share this bounded pool. thread_workers: 4 shutdown_timeout_s: 8 endpoints: cmvr_es: transport: grpc # Remote cmvr-es address. Keep this aligned with the robot being viewed. target: 192.168.0.119:50052 tls: false timeout_s: 5 options: max_receive_mb: 32 pipelines: detection_show: enabled: true nodes: camera: uses: cmvr.grpc.camera_rgb_stream@1 with: endpoint: cmvr_es device_id: wrist_cam pixel_format: BGR8 reconnect: true reconnect_initial_s: 0.5 reconnect_max_s: 10 stream_log_interval_s: 5 decoder: uses: media.video_decoder.pyav@1 frame_fanout: # One shared fan-out node chooses the latest decoded frame before both # blocking detector queues. This keeps the two model inputs identical. uses: core.passthrough@1 detector: uses: detection.model@1 with: model: construction-ppe-yolov8@2 detect_labels: - No-Boots - No-Ear-Protection - No-Glass - No-Glove - No-Helmet - No-Mask - No-Vest confidence: 0.50 inference_log_interval_s: 5 # Required so the viewer receives the exact decoded inference frame. attach_frame: true model_options: # Repository-relative path; launch the viewer from the repository root. weights: models/detection/construction-ppe-yolov8/v2/model.onnx providers: [CPUExecutionProvider] intra_op_threads: 1 inter_op_threads: 1 imgsz: 640 iou: 0.70 max_det: 100 phone_detector: uses: detection.model@1 with: model: yolov8n-mobile-phone@2 detect_labels: - mobile_phone confidence: 0.50 inference_log_interval_s: 5 attach_frame: true model_options: weights: models/detection/yolov8n-mobile-phone/v2/model.onnx providers: [CPUExecutionProvider] intra_op_threads: 1 inter_op_threads: 1 imgsz: 640 iou: 0.70 max_det: 100 viewer: uses: demo.opencv_detection_viewer@1 with: window_name: CMVR PPE + Phone Detection window_width: 1280 window_height: 720 wait_key_ms: 1 box_thickness: 2 font_scale: 0.6 show_stats: true # Render only after both results for the exact same ImageFrame arrive. expected_inputs: [ppe, phone] max_pending_frames: 8 edges: # H264/H265 packets must remain contiguous until decoding. - from: camera.frames to: decoder.frames qos: profile: video_contiguous capacity: 8 overflow: block # Drop only before the shared fan-out, so both models see the same # surviving decoded frame rather than independently dropping frames. - from: decoder.frames to: frame_fanout.input qos: profile: realtime_latest capacity: 1 overflow: drop_oldest # These two blocking edges preserve the same frame set and ordering for # both models. Do not add independent detector max_fps limits here. - from: frame_fanout.output to: detector.frames qos: profile: telemetry capacity: 1 overflow: block - from: frame_fanout.output to: phone_detector.frames qos: profile: telemetry capacity: 1 overflow: block # The viewer joins both result streams by frame identity before drawing. - from: detector.detections to: viewer.ppe qos: profile: telemetry capacity: 2 overflow: block - from: phone_detector.detections to: viewer.phone qos: profile: telemetry capacity: 2 overflow: block