diff --git a/best_cxn.onnx b/best_cxn.onnx new file mode 100644 index 0000000..2485b00 Binary files /dev/null and b/best_cxn.onnx differ diff --git a/check0_base_optimize.onnx b/check0_base_optimize.onnx new file mode 100644 index 0000000..a928d19 Binary files /dev/null and b/check0_base_optimize.onnx differ diff --git a/check1_fold_constant.onnx b/check1_fold_constant.onnx new file mode 100644 index 0000000..906df31 Binary files /dev/null and b/check1_fold_constant.onnx differ diff --git a/check2_correct_ops.onnx b/check2_correct_ops.onnx new file mode 100644 index 0000000..8ad780c Binary files /dev/null and b/check2_correct_ops.onnx differ diff --git a/check3_fuse_ops.onnx b/check3_fuse_ops.onnx new file mode 100644 index 0000000..d04958b Binary files /dev/null and b/check3_fuse_ops.onnx differ diff --git a/configs/sample_cam2.json.last_good.json b/configs/sample_cam2.json.last_good.json new file mode 100644 index 0000000..8436eaa --- /dev/null +++ b/configs/sample_cam2.json.last_good.json @@ -0,0 +1 @@ +{"graphs":[{"edges":[["in_cam1","pre_cam1"],["in_cam1","pre_face_cam1"],["pre_cam1","yolo_cam1"],["yolo_cam1","trk_cam1"],["trk_cam1","osd_cam1"],["osd_cam1","post_cam1"],["post_cam1","pub_cam1"],["pub_cam1","alarm_cam1"],["pre_face_cam1","face_det_cam1"],["face_det_cam1","face_recog_cam1"],["face_recog_cam1","alarm_face_cam1"]],"name":"cam1_sample_full_pipeline","nodes":[{"enable":true,"force_tcp":true,"fps":30,"height":720,"id":"in_cam1","reconnect_backoff_max_sec":30,"reconnect_sec":5,"role":"source","type":"input_rtsp","url":"rtsp://10.0.0.49:8554/cam","use_ffmpeg":false,"use_mpp":true,"width":1280},{"dst_format":"rgb","dst_h":640,"dst_packed":true,"dst_w":640,"enable":true,"id":"pre_cam1","keep_ratio":false,"rga_gate":"cam1_sample_full_pipeline","role":"filter","type":"preprocess","use_rga":true},{"class_filter":[],"conf":0.35,"enable":true,"id":"yolo_cam1","infer_fps":10,"model_path":"./models/yolov5s-640-640.rknn","model_version":"v5","nms":0.45,"num_classes":80,"role":"filter","type":"ai_yolo"},{"conf":0.7,"enable":true,"id":"face_det_cam1","input_format":"rgb","max_faces":10,"model_path":"./models/RetinaFace_mobile320.rknn","nms":0.4,"output_landmarks":true,"role":"filter","type":"ai_face_det"},{"align":true,"emit_embedding":false,"enable":true,"gallery":{"backend":"sqlite","dtype":"auto","expected_dim":512,"load_on_start":true,"path":"./models/face_gallery.db"},"id":"face_recog_cam1","input_dtype":"uint8","input_format":"rgb","max_faces":10,"model_path":"./models/mobilefacenet_arcface.rknn","role":"filter","threshold":{"accept":0.45,"margin":0.05},"type":"ai_face_recog"},{"allowed_models":["yolov5","yolov8"],"debug":{"stats":false,"stats_interval":200},"enable":true,"high_th":0.5,"id":"trk_cam1","ignore_classes":[],"iou_th":0.3,"low_th":0.1,"max_age_ms":1500,"max_tracks":128,"min_hits":2,"mode":"bytetrack_lite","per_class":true,"role":"filter","state_key":"cam1_sample_full_pipeline","track_classes":[0],"type":"tracker"},{"dst_format":"rgb","dst_h":0,"dst_packed":true,"dst_w":0,"enable":true,"id":"pre_face_cam1","keep_ratio":false,"rga_gate":"cam1_sample_full_pipeline","role":"filter","type":"preprocess","use_rga":true},{"draw_bbox":true,"draw_face_bbox":false,"draw_face_det":false,"draw_text":true,"enable":true,"font_scale":1,"id":"osd_cam1","labels":[],"line_width":2,"role":"filter","type":"osd"},{"dst_format":"nv12","dst_h":720,"dst_w":1280,"enable":true,"id":"post_cam1","keep_ratio":false,"rga_gate":"cam1_sample_full_pipeline","role":"filter","type":"preprocess","use_rga":true},{"bitrate_kbps":2000,"codec":"h264","enable":true,"fps":30,"gop":60,"id":"pub_cam1","outputs":[{"path":"/live/cam1","port":8555,"proto":"rtsp_server"},{"path":"./web/hls/cam1/index.m3u8","proto":"hls","segment_sec":2}],"role":"filter","type":"publish","use_ffmpeg_mux":true,"use_mpp":true},{"actions":{"clip":{"enable":true,"format":"mp4","fps":30,"post_sec":10,"pre_sec":5,"upload":{"access_key":"your-access-key","bucket":"vi","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"your-secret-key","type":"minio"}},"external_api":{"channelNo":"${vod_channelNo}","enable":true,"getTokenUrl":"http://127.0.0.1:8080/api/getToken","include_media_url":true,"putMessageUrl":"http://127.0.0.1:8080/api/putMessage","tenantCode":"32","timeout_ms":3000,"token_cache_sec":1200,"token_header":"X-Access-Token","token_json_path":"responseBody.token"},"http":{"enable":false,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":true,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"your-access-key","bucket":"vi","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"your-secret-key","type":"minio"}}},"enable":true,"eval_fps":10,"face_rules":[],"id":"alarm_cam1","labels":[],"role":"sink","rules":[{"class_ids":[0],"cooldown_ms":5000,"hit_window_ms":1500,"min_box_area_ratio":0.02,"min_duration_ms":1500,"min_hits":3,"min_score":0.4,"name":"person_in_view","per_track_cooldown_ms":5000,"require_track_id":true,"roi":{"h":1,"w":1,"x":0,"y":0}}],"type":"alarm"},{"actions":{"clip":{"enable":false},"http":{"enable":true,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":false,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"your-access-key","bucket":"vi","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"your-secret-key","type":"minio"}}},"enable":true,"eval_fps":5,"face_rules":[{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.35,"name":"unknown_face","type":"unknown"},{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.6,"name":"known_person","type":"person"}],"id":"alarm_face_cam1","labels":[],"role":"sink","rules":[],"type":"alarm"}]}],"queue":{"size":8,"strategy":"drop_oldest"}} \ No newline at end of file diff --git a/configs/sample_cam3.json b/configs/sample_cam3.json new file mode 100644 index 0000000..99df80b --- /dev/null +++ b/configs/sample_cam3.json @@ -0,0 +1,281 @@ +{ + "queue": { "size": 8, "strategy": "drop_oldest" }, + "graphs": [ + { + "name": "cam1_sample_full_pipeline", + "nodes": [ + { + "id": "in_cam1", + "type": "input_rtsp", + "role": "source", + "enable": true, + "url": "rtsp://10.0.0.49:8554/cam", + "fps": 30, + "width": 1280, + "height": 720, + "use_mpp": true, + "use_ffmpeg": false, + "force_tcp": true, + "reconnect_sec": 5, + "reconnect_backoff_max_sec": 30 + }, + { + "id": "pre_cam1", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 640, + "dst_h": 640, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "cam1_sample_full_pipeline", + "use_rga": true + }, + { + "id": "yolo_cam1", + "type": "ai_yolo", + "role": "filter", + "enable": true, + "infer_fps": 10, + "model_path": "./models/yolov5s-640-640.rknn", + "model_version": "v5", + "num_classes": 80, + "conf": 0.35, + "nms": 0.45, + "class_filter": [] + }, + { + "id": "face_det_cam1", + "type": "ai_face_det", + "role": "filter", + "enable": true, + "model_path": "./models/RetinaFace_mobile320.rknn", + "conf": 0.7, + "nms": 0.4, + "max_faces": 10, + "output_landmarks": true, + "input_format": "rgb" + }, + { + "id": "face_recog_cam1", + "type": "ai_face_recog", + "role": "filter", + "enable": true, + "model_path": "./models/mobilefacenet_arcface.rknn", + "align": true, + "emit_embedding": false, + "max_faces": 10, + "input_format": "rgb", + "input_dtype": "uint8", + "threshold": { "accept": 0.45, "margin": 0.05 }, + "gallery": { + "backend": "sqlite", + "path": "./models/face_gallery.db", + "load_on_start": true, + "expected_dim": 512, + "dtype": "auto" + } + }, + { + "id": "trk_cam1", + "type": "tracker", + "role": "filter", + "enable": true, + "mode": "bytetrack_lite", + "per_class": true, + "state_key": "cam1_sample_full_pipeline", + "track_classes": [0], + "ignore_classes": [], + "allowed_models": ["yolov5", "yolov8"], + "high_th": 0.5, + "low_th": 0.1, + "iou_th": 0.3, + "max_age_ms": 1500, + "min_hits": 2, + "max_tracks": 128, + "debug": { "stats": false, "stats_interval": 200 } + }, + { + "id": "pre_face_cam1", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 0, + "dst_h": 0, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "cam1_sample_full_pipeline", + "use_rga": true + }, + { + "id": "osd_cam1", + "type": "osd", + "role": "filter", + "enable": true, + "draw_bbox": true, + "draw_text": true, + "draw_face_det": false, + "draw_face_bbox": false, + "line_width": 2, + "font_scale": 1, + "use_rga_bbox": false, + "labels": [] + }, + { + "id": "post_cam1", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 1280, + "dst_h": 720, + "dst_format": "nv12", + "keep_ratio": false, + "rga_gate": "cam1_sample_full_pipeline", + "use_rga": true + }, + { + "id": "pub_cam1", + "type": "publish", + "role": "filter", + "enable": true, + "codec": "h264", + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "use_mpp": true, + "use_ffmpeg_mux": true, + "outputs": [ + { "proto": "rtsp_server", "port": 8555, "path": "/live/cam1" }, + { "proto": "hls", "path": "./web/hls/cam1/index.m3u8", "segment_sec": 2 } + ] + }, + { + "id": "alarm_cam1", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 10, + "labels": [], + "rules": [ + { + "name": "person_in_view", + "class_ids": [0], + "roi": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 }, + "min_score": 0.4, + "min_box_area_ratio": 0.02, + "require_track_id": true, + "min_duration_ms": 1500, + "min_hits": 3, + "hit_window_ms": 1500, + "cooldown_ms": 5000, + "per_track_cooldown_ms": 5000 + } + ], + "face_rules": [], + "actions": { + "log": { "enable": true, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "myminio", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { + "enable": true, + "pre_sec": 5, + "post_sec": 10, + "format": "mp4", + "fps": 30, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "myminio", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "http": { + "enable": false, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + }, + "external_api": { + "enable": true, + "getTokenUrl": "http://127.0.0.1:8080/api/getToken", + "putMessageUrl": "http://127.0.0.1:8080/api/putMessage", + "tenantCode": "32", + "channelNo": "${vod_channelNo}", + "timeout_ms": 3000, + "include_media_url": true, + "token_header": "X-Access-Token", + "token_json_path": "responseBody.token", + "token_cache_sec": 1200 + } + } + }, + { + "id": "alarm_face_cam1", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 5, + "labels": [], + "rules": [], + "face_rules": [ + { "name": "unknown_face", "type": "unknown", "cooldown_ms": 7000, "min_sim": 0.35, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 }, + { "name": "known_person", "type": "person", "cooldown_ms": 7000, "min_sim": 0.6, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 } + ], + "actions": { + "log": { "enable": false, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "myminio", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { "enable": false }, + "http": { + "enable": true, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + } + } + } + ], + "edges": [ + ["in_cam1", "pre_cam1"], + ["in_cam1", "pre_face_cam1"], + ["pre_cam1", "yolo_cam1"], + ["yolo_cam1", "trk_cam1"], + ["trk_cam1", "osd_cam1"], + ["osd_cam1", "post_cam1"], + ["post_cam1", "pub_cam1"], + ["pub_cam1", "alarm_cam1"], + ["pre_face_cam1", "face_det_cam1"], + ["face_det_cam1", "face_recog_cam1"], + ["face_recog_cam1", "alarm_face_cam1"] + ] + } + ] +} diff --git a/configs/sample_cam3.json.last_good.json b/configs/sample_cam3.json.last_good.json new file mode 100644 index 0000000..6f544f9 --- /dev/null +++ b/configs/sample_cam3.json.last_good.json @@ -0,0 +1 @@ +{"graphs":[{"edges":[["in_cam1","pre_cam1"],["in_cam1","pre_face_cam1"],["pre_cam1","yolo_cam1"],["yolo_cam1","trk_cam1"],["trk_cam1","osd_cam1"],["osd_cam1","post_cam1"],["post_cam1","pub_cam1"],["pub_cam1","alarm_cam1"],["pre_face_cam1","face_det_cam1"],["face_det_cam1","face_recog_cam1"],["face_recog_cam1","alarm_face_cam1"]],"name":"cam1_sample_full_pipeline","nodes":[{"enable":true,"force_tcp":true,"fps":30,"height":720,"id":"in_cam1","reconnect_backoff_max_sec":30,"reconnect_sec":5,"role":"source","type":"input_rtsp","url":"rtsp://10.0.0.49:8554/cam","use_ffmpeg":false,"use_mpp":true,"width":1280},{"dst_format":"rgb","dst_h":640,"dst_packed":true,"dst_w":640,"enable":true,"id":"pre_cam1","keep_ratio":false,"rga_gate":"cam1_sample_full_pipeline","role":"filter","type":"preprocess","use_rga":true},{"class_filter":[],"conf":0.35,"enable":true,"id":"yolo_cam1","infer_fps":10,"model_path":"./models/yolov5s-640-640.rknn","model_version":"v5","nms":0.45,"num_classes":80,"role":"filter","type":"ai_yolo"},{"conf":0.7,"enable":true,"id":"face_det_cam1","input_format":"rgb","max_faces":10,"model_path":"./models/RetinaFace_mobile320.rknn","nms":0.4,"output_landmarks":true,"role":"filter","type":"ai_face_det"},{"align":true,"emit_embedding":false,"enable":true,"gallery":{"backend":"sqlite","dtype":"auto","expected_dim":512,"load_on_start":true,"path":"./models/face_gallery.db"},"id":"face_recog_cam1","input_dtype":"uint8","input_format":"rgb","max_faces":10,"model_path":"./models/mobilefacenet_arcface.rknn","role":"filter","threshold":{"accept":0.45,"margin":0.05},"type":"ai_face_recog"},{"allowed_models":["yolov5","yolov8"],"debug":{"stats":false,"stats_interval":200},"enable":true,"high_th":0.5,"id":"trk_cam1","ignore_classes":[],"iou_th":0.3,"low_th":0.1,"max_age_ms":1500,"max_tracks":128,"min_hits":2,"mode":"bytetrack_lite","per_class":true,"role":"filter","state_key":"cam1_sample_full_pipeline","track_classes":[0],"type":"tracker"},{"dst_format":"rgb","dst_h":0,"dst_packed":true,"dst_w":0,"enable":true,"id":"pre_face_cam1","keep_ratio":false,"rga_gate":"cam1_sample_full_pipeline","role":"filter","type":"preprocess","use_rga":true},{"draw_bbox":true,"draw_face_bbox":false,"draw_face_det":false,"draw_text":true,"enable":true,"font_scale":1,"id":"osd_cam1","labels":[],"line_width":2,"role":"filter","type":"osd","use_rga_bbox":false},{"dst_format":"nv12","dst_h":720,"dst_w":1280,"enable":true,"id":"post_cam1","keep_ratio":false,"rga_gate":"cam1_sample_full_pipeline","role":"filter","type":"preprocess","use_rga":true},{"bitrate_kbps":2000,"codec":"h264","enable":true,"fps":30,"gop":60,"id":"pub_cam1","outputs":[{"path":"/live/cam1","port":8555,"proto":"rtsp_server"},{"path":"./web/hls/cam1/index.m3u8","proto":"hls","segment_sec":2}],"role":"filter","type":"publish","use_ffmpeg_mux":true,"use_mpp":true},{"actions":{"clip":{"enable":true,"format":"mp4","fps":30,"post_sec":10,"pre_sec":5,"upload":{"access_key":"minioadmin","bucket":"myminio","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}},"external_api":{"channelNo":"${vod_channelNo}","enable":true,"getTokenUrl":"http://127.0.0.1:8080/api/getToken","include_media_url":true,"putMessageUrl":"http://127.0.0.1:8080/api/putMessage","tenantCode":"32","timeout_ms":3000,"token_cache_sec":1200,"token_header":"X-Access-Token","token_json_path":"responseBody.token"},"http":{"enable":false,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":true,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"myminio","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":10,"face_rules":[],"id":"alarm_cam1","labels":[],"role":"sink","rules":[{"class_ids":[0],"cooldown_ms":5000,"hit_window_ms":1500,"min_box_area_ratio":0.02,"min_duration_ms":1500,"min_hits":3,"min_score":0.4,"name":"person_in_view","per_track_cooldown_ms":5000,"require_track_id":true,"roi":{"h":1,"w":1,"x":0,"y":0}}],"type":"alarm"},{"actions":{"clip":{"enable":false},"http":{"enable":true,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":false,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"myminio","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":5,"face_rules":[{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.35,"name":"unknown_face","type":"unknown"},{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.6,"name":"known_person","type":"person"}],"id":"alarm_face_cam1","labels":[],"role":"sink","rules":[],"type":"alarm"}]}],"queue":{"size":8,"strategy":"drop_oldest"}} \ No newline at end of file diff --git a/configs/stress_test_4ch_shared_source.json b/configs/stress_test_4ch_shared_source.json new file mode 100644 index 0000000..6af1891 --- /dev/null +++ b/configs/stress_test_4ch_shared_source.json @@ -0,0 +1,1105 @@ +{ + "queue": { "size": 8, "strategy": "drop_oldest" }, + "graphs": [ + { + "name": "stress_cam1", + "nodes": [ + { + "id": "in_cam1", + "type": "input_rtsp", + "role": "source", + "enable": true, + "url": "rtsp://10.0.0.49:8554/cam", + "fps": 30, + "width": 1280, + "height": 720, + "use_mpp": true, + "use_ffmpeg": false, + "force_tcp": true, + "reconnect_sec": 5, + "reconnect_backoff_max_sec": 30 + }, + { + "id": "pre_cam1", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 640, + "dst_h": 640, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam1", + "use_rga": true + }, + { + "id": "yolo_cam1", + "type": "ai_yolo", + "role": "filter", + "enable": true, + "infer_fps": 10, + "model_path": "./models/yolov5s-640-640.rknn", + "model_version": "v8", + "num_classes": 80, + "conf": 0.35, + "nms": 0.45, + "class_filter": [] + }, + { + "id": "face_det_cam1", + "type": "ai_face_det", + "role": "filter", + "enable": true, + "model_path": "./models/RetinaFace_mobile320.rknn", + "conf": 0.7, + "nms": 0.4, + "max_faces": 10, + "output_landmarks": true, + "input_format": "rgb" + }, + { + "id": "face_recog_cam1", + "type": "ai_face_recog", + "role": "filter", + "enable": true, + "model_path": "./models/mobilefacenet_arcface.rknn", + "align": true, + "emit_embedding": false, + "max_faces": 10, + "input_format": "rgb", + "input_dtype": "uint8", + "threshold": { "accept": 0.45, "margin": 0.05 }, + "gallery": { + "backend": "sqlite", + "path": "./models/face_gallery.db", + "load_on_start": true, + "expected_dim": 512, + "dtype": "auto" + } + }, + { + "id": "trk_cam1", + "type": "tracker", + "role": "filter", + "enable": true, + "mode": "bytetrack_lite", + "per_class": true, + "state_key": "stress_cam1", + "track_classes": [0], + "ignore_classes": [], + "allowed_models": ["yolov5", "yolov8"], + "high_th": 0.5, + "low_th": 0.1, + "iou_th": 0.3, + "max_age_ms": 1500, + "min_hits": 2, + "max_tracks": 128, + "debug": { "stats": false, "stats_interval": 200 } + }, + { + "id": "pre_face_cam1", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 0, + "dst_h": 0, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam1", + "use_rga": true + }, + { + "id": "osd_cam1", + "type": "osd", + "role": "filter", + "enable": true, + "draw_bbox": true, + "draw_text": true, + "draw_face_det": false, + "draw_face_bbox": false, + "line_width": 2, + "font_scale": 1, + "labels": [] + }, + { + "id": "post_cam1", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 1280, + "dst_h": 720, + "dst_format": "nv12", + "keep_ratio": false, + "rga_gate": "stress_cam1", + "use_rga": true + }, + { + "id": "pub_cam1", + "type": "publish", + "role": "filter", + "enable": true, + "codec": "h264", + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "use_mpp": true, + "use_ffmpeg_mux": true, + "outputs": [ + { "proto": "rtsp_server", "port": 8555, "path": "/live/cam1" }, + { "proto": "hls", "path": "./web/hls/cam1/index.m3u8", "segment_sec": 2 } + ] + }, + { + "id": "alarm_cam1", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 10, + "labels": [], + "rules": [ + { + "name": "person_in_view", + "class_ids": [0], + "roi": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 }, + "min_score": 0.4, + "min_box_area_ratio": 0.02, + "require_track_id": true, + "min_duration_ms": 1500, + "min_hits": 3, + "hit_window_ms": 1500, + "cooldown_ms": 5000, + "per_track_cooldown_ms": 5000 + } + ], + "face_rules": [], + "actions": { + "log": { "enable": true, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { + "enable": true, + "pre_sec": 5, + "post_sec": 10, + "format": "mp4", + "fps": 30, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "http": { + "enable": false, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + }, + "external_api": { + "enable": false, + "getTokenUrl": "http://127.0.0.1:8080/api/getToken", + "putMessageUrl": "http://127.0.0.1:8080/api/putMessage", + "tenantCode": "32", + "channelNo": "${vod_channelNo}", + "timeout_ms": 3000, + "include_media_url": true, + "token_header": "X-Access-Token", + "token_json_path": "responseBody.token", + "token_cache_sec": 1200 + } + } + }, + { + "id": "alarm_face_cam1", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 5, + "labels": [], + "rules": [], + "face_rules": [ + { "name": "unknown_face", "type": "unknown", "cooldown_ms": 7000, "min_sim": 0.35, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 }, + { "name": "known_person", "type": "person", "cooldown_ms": 7000, "min_sim": 0.6, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 } + ], + "actions": { + "log": { "enable": false, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { "enable": false }, + "http": { + "enable": true, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + } + } + } + ], + "edges": [ + ["in_cam1", "pre_cam1"], + ["in_cam1", "pre_face_cam1"], + ["pre_cam1", "yolo_cam1"], + ["yolo_cam1", "trk_cam1"], + ["trk_cam1", "osd_cam1"], + ["osd_cam1", "post_cam1"], + ["post_cam1", "pub_cam1"], + ["pub_cam1", "alarm_cam1"], + ["pre_face_cam1", "face_det_cam1"], + ["face_det_cam1", "face_recog_cam1"], + ["face_recog_cam1", "alarm_face_cam1"] + ] + }, + { + "name": "stress_cam2", + "nodes": [ + { + "id": "in_cam2", + "type": "input_rtsp", + "role": "source", + "enable": true, + "url": "rtsp://10.0.0.49:8554/cam", + "fps": 30, + "width": 1280, + "height": 720, + "use_mpp": true, + "use_ffmpeg": false, + "force_tcp": true, + "reconnect_sec": 5, + "reconnect_backoff_max_sec": 30 + }, + { + "id": "pre_cam2", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 640, + "dst_h": 640, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam2", + "use_rga": true + }, + { + "id": "yolo_cam2", + "type": "ai_yolo", + "role": "filter", + "enable": true, + "infer_fps": 10, + "model_path": "./models/yolov5s-640-640.rknn", + "model_version": "v8", + "num_classes": 80, + "conf": 0.35, + "nms": 0.45, + "class_filter": [] + }, + { + "id": "face_det_cam2", + "type": "ai_face_det", + "role": "filter", + "enable": true, + "model_path": "./models/RetinaFace_mobile320.rknn", + "conf": 0.7, + "nms": 0.4, + "max_faces": 10, + "output_landmarks": true, + "input_format": "rgb" + }, + { + "id": "face_recog_cam2", + "type": "ai_face_recog", + "role": "filter", + "enable": true, + "model_path": "./models/mobilefacenet_arcface.rknn", + "align": true, + "emit_embedding": false, + "max_faces": 10, + "input_format": "rgb", + "input_dtype": "uint8", + "threshold": { "accept": 0.45, "margin": 0.05 }, + "gallery": { + "backend": "sqlite", + "path": "./models/face_gallery.db", + "load_on_start": true, + "expected_dim": 512, + "dtype": "auto" + } + }, + { + "id": "trk_cam2", + "type": "tracker", + "role": "filter", + "enable": true, + "mode": "bytetrack_lite", + "per_class": true, + "state_key": "stress_cam2", + "track_classes": [0], + "ignore_classes": [], + "allowed_models": ["yolov5", "yolov8"], + "high_th": 0.5, + "low_th": 0.1, + "iou_th": 0.3, + "max_age_ms": 1500, + "min_hits": 2, + "max_tracks": 128, + "debug": { "stats": false, "stats_interval": 200 } + }, + { + "id": "pre_face_cam2", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 0, + "dst_h": 0, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam2", + "use_rga": true + }, + { + "id": "osd_cam2", + "type": "osd", + "role": "filter", + "enable": true, + "draw_bbox": true, + "draw_text": true, + "draw_face_det": false, + "draw_face_bbox": false, + "line_width": 2, + "font_scale": 1, + "labels": [] + }, + { + "id": "post_cam2", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 1280, + "dst_h": 720, + "dst_format": "nv12", + "keep_ratio": false, + "rga_gate": "stress_cam2", + "use_rga": true + }, + { + "id": "pub_cam2", + "type": "publish", + "role": "filter", + "enable": true, + "codec": "h264", + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "use_mpp": true, + "use_ffmpeg_mux": true, + "outputs": [ + { "proto": "rtsp_server", "port": 8556, "path": "/live/cam2" }, + { "proto": "hls", "path": "./web/hls/cam2/index.m3u8", "segment_sec": 2 } + ] + }, + { + "id": "alarm_cam2", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 10, + "labels": [], + "rules": [ + { + "name": "person_in_view", + "class_ids": [0], + "roi": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 }, + "min_score": 0.4, + "min_box_area_ratio": 0.02, + "require_track_id": true, + "min_duration_ms": 1500, + "min_hits": 3, + "hit_window_ms": 1500, + "cooldown_ms": 5000, + "per_track_cooldown_ms": 5000 + } + ], + "face_rules": [], + "actions": { + "log": { "enable": true, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { + "enable": true, + "pre_sec": 5, + "post_sec": 10, + "format": "mp4", + "fps": 30, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "http": { + "enable": false, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + }, + "external_api": { + "enable": false, + "getTokenUrl": "http://127.0.0.1:8080/api/getToken", + "putMessageUrl": "http://127.0.0.1:8080/api/putMessage", + "tenantCode": "32", + "channelNo": "${vod_channelNo}", + "timeout_ms": 3000, + "include_media_url": true, + "token_header": "X-Access-Token", + "token_json_path": "responseBody.token", + "token_cache_sec": 1200 + } + } + }, + { + "id": "alarm_face_cam2", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 5, + "labels": [], + "rules": [], + "face_rules": [ + { "name": "unknown_face", "type": "unknown", "cooldown_ms": 7000, "min_sim": 0.35, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 }, + { "name": "known_person", "type": "person", "cooldown_ms": 7000, "min_sim": 0.6, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 } + ], + "actions": { + "log": { "enable": false, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { "enable": false }, + "http": { + "enable": true, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + } + } + } + ], + "edges": [ + ["in_cam2", "pre_cam2"], + ["in_cam2", "pre_face_cam2"], + ["pre_cam2", "yolo_cam2"], + ["yolo_cam2", "trk_cam2"], + ["trk_cam2", "osd_cam2"], + ["osd_cam2", "post_cam2"], + ["post_cam2", "pub_cam2"], + ["pub_cam2", "alarm_cam2"], + ["pre_face_cam2", "face_det_cam2"], + ["face_det_cam2", "face_recog_cam2"], + ["face_recog_cam2", "alarm_face_cam2"] + ] + }, + { + "name": "stress_cam3", + "nodes": [ + { + "id": "in_cam3", + "type": "input_rtsp", + "role": "source", + "enable": true, + "url": "rtsp://10.0.0.49:8554/cam", + "fps": 30, + "width": 1280, + "height": 720, + "use_mpp": true, + "use_ffmpeg": false, + "force_tcp": true, + "reconnect_sec": 5, + "reconnect_backoff_max_sec": 30 + }, + { + "id": "pre_cam3", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 640, + "dst_h": 640, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam3", + "use_rga": true + }, + { + "id": "yolo_cam3", + "type": "ai_yolo", + "role": "filter", + "enable": true, + "infer_fps": 10, + "model_path": "./models/yolov5s-640-640.rknn", + "model_version": "v8", + "num_classes": 80, + "conf": 0.35, + "nms": 0.45, + "class_filter": [] + }, + { + "id": "face_det_cam3", + "type": "ai_face_det", + "role": "filter", + "enable": true, + "model_path": "./models/RetinaFace_mobile320.rknn", + "conf": 0.7, + "nms": 0.4, + "max_faces": 10, + "output_landmarks": true, + "input_format": "rgb" + }, + { + "id": "face_recog_cam3", + "type": "ai_face_recog", + "role": "filter", + "enable": true, + "model_path": "./models/mobilefacenet_arcface.rknn", + "align": true, + "emit_embedding": false, + "max_faces": 10, + "input_format": "rgb", + "input_dtype": "uint8", + "threshold": { "accept": 0.45, "margin": 0.05 }, + "gallery": { + "backend": "sqlite", + "path": "./models/face_gallery.db", + "load_on_start": true, + "expected_dim": 512, + "dtype": "auto" + } + }, + { + "id": "trk_cam3", + "type": "tracker", + "role": "filter", + "enable": true, + "mode": "bytetrack_lite", + "per_class": true, + "state_key": "stress_cam3", + "track_classes": [0], + "ignore_classes": [], + "allowed_models": ["yolov5", "yolov8"], + "high_th": 0.5, + "low_th": 0.1, + "iou_th": 0.3, + "max_age_ms": 1500, + "min_hits": 2, + "max_tracks": 128, + "debug": { "stats": false, "stats_interval": 200 } + }, + { + "id": "pre_face_cam3", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 0, + "dst_h": 0, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam3", + "use_rga": true + }, + { + "id": "osd_cam3", + "type": "osd", + "role": "filter", + "enable": true, + "draw_bbox": true, + "draw_text": true, + "draw_face_det": false, + "draw_face_bbox": false, + "line_width": 2, + "font_scale": 1, + "labels": [] + }, + { + "id": "post_cam3", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 1280, + "dst_h": 720, + "dst_format": "nv12", + "keep_ratio": false, + "rga_gate": "stress_cam3", + "use_rga": true + }, + { + "id": "pub_cam3", + "type": "publish", + "role": "filter", + "enable": true, + "codec": "h264", + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "use_mpp": true, + "use_ffmpeg_mux": true, + "outputs": [ + { "proto": "rtsp_server", "port": 8557, "path": "/live/cam3" }, + { "proto": "hls", "path": "./web/hls/cam3/index.m3u8", "segment_sec": 2 } + ] + }, + { + "id": "alarm_cam3", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 10, + "labels": [], + "rules": [ + { + "name": "person_in_view", + "class_ids": [0], + "roi": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 }, + "min_score": 0.4, + "min_box_area_ratio": 0.02, + "require_track_id": true, + "min_duration_ms": 1500, + "min_hits": 3, + "hit_window_ms": 1500, + "cooldown_ms": 5000, + "per_track_cooldown_ms": 5000 + } + ], + "face_rules": [], + "actions": { + "log": { "enable": true, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { + "enable": true, + "pre_sec": 5, + "post_sec": 10, + "format": "mp4", + "fps": 30, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "http": { + "enable": false, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + }, + "external_api": { + "enable": false, + "getTokenUrl": "http://127.0.0.1:8080/api/getToken", + "putMessageUrl": "http://127.0.0.1:8080/api/putMessage", + "tenantCode": "32", + "channelNo": "${vod_channelNo}", + "timeout_ms": 3000, + "include_media_url": true, + "token_header": "X-Access-Token", + "token_json_path": "responseBody.token", + "token_cache_sec": 1200 + } + } + }, + { + "id": "alarm_face_cam3", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 5, + "labels": [], + "rules": [], + "face_rules": [ + { "name": "unknown_face", "type": "unknown", "cooldown_ms": 7000, "min_sim": 0.35, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 }, + { "name": "known_person", "type": "person", "cooldown_ms": 7000, "min_sim": 0.6, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 } + ], + "actions": { + "log": { "enable": false, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { "enable": false }, + "http": { + "enable": true, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + } + } + } + ], + "edges": [ + ["in_cam3", "pre_cam3"], + ["in_cam3", "pre_face_cam3"], + ["pre_cam3", "yolo_cam3"], + ["yolo_cam3", "trk_cam3"], + ["trk_cam3", "osd_cam3"], + ["osd_cam3", "post_cam3"], + ["post_cam3", "pub_cam3"], + ["pub_cam3", "alarm_cam3"], + ["pre_face_cam3", "face_det_cam3"], + ["face_det_cam3", "face_recog_cam3"], + ["face_recog_cam3", "alarm_face_cam3"] + ] + }, + { + "name": "stress_cam4", + "nodes": [ + { + "id": "in_cam4", + "type": "input_rtsp", + "role": "source", + "enable": true, + "url": "rtsp://10.0.0.49:8554/cam", + "fps": 30, + "width": 1280, + "height": 720, + "use_mpp": true, + "use_ffmpeg": false, + "force_tcp": true, + "reconnect_sec": 5, + "reconnect_backoff_max_sec": 30 + }, + { + "id": "pre_cam4", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 640, + "dst_h": 640, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam4", + "use_rga": true + }, + { + "id": "yolo_cam4", + "type": "ai_yolo", + "role": "filter", + "enable": true, + "infer_fps": 10, + "model_path": "./models/yolov5s-640-640.rknn", + "model_version": "v8", + "num_classes": 80, + "conf": 0.35, + "nms": 0.45, + "class_filter": [] + }, + { + "id": "face_det_cam4", + "type": "ai_face_det", + "role": "filter", + "enable": true, + "model_path": "./models/RetinaFace_mobile320.rknn", + "conf": 0.7, + "nms": 0.4, + "max_faces": 10, + "output_landmarks": true, + "input_format": "rgb" + }, + { + "id": "face_recog_cam4", + "type": "ai_face_recog", + "role": "filter", + "enable": true, + "model_path": "./models/mobilefacenet_arcface.rknn", + "align": true, + "emit_embedding": false, + "max_faces": 10, + "input_format": "rgb", + "input_dtype": "uint8", + "threshold": { "accept": 0.45, "margin": 0.05 }, + "gallery": { + "backend": "sqlite", + "path": "./models/face_gallery.db", + "load_on_start": true, + "expected_dim": 512, + "dtype": "auto" + } + }, + { + "id": "trk_cam4", + "type": "tracker", + "role": "filter", + "enable": true, + "mode": "bytetrack_lite", + "per_class": true, + "state_key": "stress_cam4", + "track_classes": [0], + "ignore_classes": [], + "allowed_models": ["yolov5", "yolov8"], + "high_th": 0.5, + "low_th": 0.1, + "iou_th": 0.3, + "max_age_ms": 1500, + "min_hits": 2, + "max_tracks": 128, + "debug": { "stats": false, "stats_interval": 200 } + }, + { + "id": "pre_face_cam4", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 0, + "dst_h": 0, + "dst_format": "rgb", + "dst_packed": true, + "keep_ratio": false, + "rga_gate": "stress_cam4", + "use_rga": true + }, + { + "id": "osd_cam4", + "type": "osd", + "role": "filter", + "enable": true, + "draw_bbox": true, + "draw_text": true, + "draw_face_det": false, + "draw_face_bbox": false, + "line_width": 2, + "font_scale": 1, + "labels": [] + }, + { + "id": "post_cam4", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 1280, + "dst_h": 720, + "dst_format": "nv12", + "keep_ratio": false, + "rga_gate": "stress_cam4", + "use_rga": true + }, + { + "id": "pub_cam4", + "type": "publish", + "role": "filter", + "enable": true, + "codec": "h264", + "fps": 30, + "gop": 60, + "bitrate_kbps": 2000, + "use_mpp": true, + "use_ffmpeg_mux": true, + "outputs": [ + { "proto": "rtsp_server", "port": 8558, "path": "/live/cam4" }, + { "proto": "hls", "path": "./web/hls/cam4/index.m3u8", "segment_sec": 2 } + ] + }, + { + "id": "alarm_cam4", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 10, + "labels": [], + "rules": [ + { + "name": "person_in_view", + "class_ids": [0], + "roi": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 }, + "min_score": 0.4, + "min_box_area_ratio": 0.02, + "require_track_id": true, + "min_duration_ms": 1500, + "min_hits": 3, + "hit_window_ms": 1500, + "cooldown_ms": 5000, + "per_track_cooldown_ms": 5000 + } + ], + "face_rules": [], + "actions": { + "log": { "enable": true, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { + "enable": true, + "pre_sec": 5, + "post_sec": 10, + "format": "mp4", + "fps": 30, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "http": { + "enable": false, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + }, + "external_api": { + "enable": false, + "getTokenUrl": "http://127.0.0.1:8080/api/getToken", + "putMessageUrl": "http://127.0.0.1:8080/api/putMessage", + "tenantCode": "32", + "channelNo": "${vod_channelNo}", + "timeout_ms": 3000, + "include_media_url": true, + "token_header": "X-Access-Token", + "token_json_path": "responseBody.token", + "token_cache_sec": 1200 + } + } + }, + { + "id": "alarm_face_cam4", + "type": "alarm", + "role": "sink", + "enable": true, + "eval_fps": 5, + "labels": [], + "rules": [], + "face_rules": [ + { "name": "unknown_face", "type": "unknown", "cooldown_ms": 7000, "min_sim": 0.35, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 }, + { "name": "known_person", "type": "person", "cooldown_ms": 7000, "min_sim": 0.6, "min_hits": 2, "hit_window_ms": 1500, "min_face_area_ratio": 0.01, "min_face_aspect": 0.6, "max_face_aspect": 1.6 } + ], + "actions": { + "log": { "enable": false, "level": "info" }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://10.0.0.49:9000", + "bucket": "stress-test", + "region": "us-east-1", + "access_key": "minioadmin", + "secret_key": "minioadmin" + } + }, + "clip": { "enable": false }, + "http": { + "enable": true, + "url": "http://127.0.0.1:8080/api/alarm", + "timeout_ms": 3000, + "include_media_url": true, + "method": "POST" + } + } + } + ], + "edges": [ + ["in_cam4", "pre_cam4"], + ["in_cam4", "pre_face_cam4"], + ["pre_cam4", "yolo_cam4"], + ["yolo_cam4", "trk_cam4"], + ["trk_cam4", "osd_cam4"], + ["osd_cam4", "post_cam4"], + ["post_cam4", "pub_cam4"], + ["pub_cam4", "alarm_cam4"], + ["pre_face_cam4", "face_det_cam4"], + ["face_det_cam4", "face_recog_cam4"], + ["face_recog_cam4", "alarm_face_cam4"] + ] + } + ] +} diff --git a/configs/stress_test_4ch_shared_source.json.last_good.json b/configs/stress_test_4ch_shared_source.json.last_good.json new file mode 100644 index 0000000..db0cc8a --- /dev/null +++ b/configs/stress_test_4ch_shared_source.json.last_good.json @@ -0,0 +1 @@ +{"graphs":[{"edges":[["in_cam1","pre_cam1"],["in_cam1","pre_face_cam1"],["pre_cam1","yolo_cam1"],["yolo_cam1","trk_cam1"],["trk_cam1","osd_cam1"],["osd_cam1","post_cam1"],["post_cam1","pub_cam1"],["pub_cam1","alarm_cam1"],["pre_face_cam1","face_det_cam1"],["face_det_cam1","face_recog_cam1"],["face_recog_cam1","alarm_face_cam1"]],"name":"stress_cam1","nodes":[{"enable":true,"force_tcp":true,"fps":30,"height":720,"id":"in_cam1","reconnect_backoff_max_sec":30,"reconnect_sec":5,"role":"source","type":"input_rtsp","url":"rtsp://10.0.0.49:8554/cam","use_ffmpeg":false,"use_mpp":true,"width":1280},{"dst_format":"rgb","dst_h":640,"dst_packed":true,"dst_w":640,"enable":true,"id":"pre_cam1","keep_ratio":false,"rga_gate":"stress_cam1","role":"filter","type":"preprocess","use_rga":true},{"class_filter":[],"conf":0.35,"enable":true,"id":"yolo_cam1","infer_fps":10,"model_path":"./models/yolov5s-640-640.rknn","model_version":"v8","nms":0.45,"num_classes":80,"role":"filter","type":"ai_yolo"},{"conf":0.7,"enable":true,"id":"face_det_cam1","input_format":"rgb","max_faces":10,"model_path":"./models/RetinaFace_mobile320.rknn","nms":0.4,"output_landmarks":true,"role":"filter","type":"ai_face_det"},{"align":true,"emit_embedding":false,"enable":true,"gallery":{"backend":"sqlite","dtype":"auto","expected_dim":512,"load_on_start":true,"path":"./models/face_gallery.db"},"id":"face_recog_cam1","input_dtype":"uint8","input_format":"rgb","max_faces":10,"model_path":"./models/mobilefacenet_arcface.rknn","role":"filter","threshold":{"accept":0.45,"margin":0.05},"type":"ai_face_recog"},{"allowed_models":["yolov5","yolov8"],"debug":{"stats":false,"stats_interval":200},"enable":true,"high_th":0.5,"id":"trk_cam1","ignore_classes":[],"iou_th":0.3,"low_th":0.1,"max_age_ms":1500,"max_tracks":128,"min_hits":2,"mode":"bytetrack_lite","per_class":true,"role":"filter","state_key":"stress_cam1","track_classes":[0],"type":"tracker"},{"dst_format":"rgb","dst_h":0,"dst_packed":true,"dst_w":0,"enable":true,"id":"pre_face_cam1","keep_ratio":false,"rga_gate":"stress_cam1","role":"filter","type":"preprocess","use_rga":true},{"draw_bbox":true,"draw_face_bbox":false,"draw_face_det":false,"draw_text":true,"enable":true,"font_scale":1,"id":"osd_cam1","labels":[],"line_width":2,"role":"filter","type":"osd"},{"dst_format":"nv12","dst_h":720,"dst_w":1280,"enable":true,"id":"post_cam1","keep_ratio":false,"rga_gate":"stress_cam1","role":"filter","type":"preprocess","use_rga":true},{"bitrate_kbps":2000,"codec":"h264","enable":true,"fps":30,"gop":60,"id":"pub_cam1","outputs":[{"path":"/live/cam1","port":8555,"proto":"rtsp_server"},{"path":"./web/hls/cam1/index.m3u8","proto":"hls","segment_sec":2}],"role":"filter","type":"publish","use_ffmpeg_mux":true,"use_mpp":true},{"actions":{"clip":{"enable":true,"format":"mp4","fps":30,"post_sec":10,"pre_sec":5,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}},"external_api":{"channelNo":"${vod_channelNo}","enable":false,"getTokenUrl":"http://127.0.0.1:8080/api/getToken","include_media_url":true,"putMessageUrl":"http://127.0.0.1:8080/api/putMessage","tenantCode":"32","timeout_ms":3000,"token_cache_sec":1200,"token_header":"X-Access-Token","token_json_path":"responseBody.token"},"http":{"enable":false,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":true,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":10,"face_rules":[],"id":"alarm_cam1","labels":[],"role":"sink","rules":[{"class_ids":[0],"cooldown_ms":5000,"hit_window_ms":1500,"min_box_area_ratio":0.02,"min_duration_ms":1500,"min_hits":3,"min_score":0.4,"name":"person_in_view","per_track_cooldown_ms":5000,"require_track_id":true,"roi":{"h":1,"w":1,"x":0,"y":0}}],"type":"alarm"},{"actions":{"clip":{"enable":false},"http":{"enable":true,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":false,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":5,"face_rules":[{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.35,"name":"unknown_face","type":"unknown"},{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.6,"name":"known_person","type":"person"}],"id":"alarm_face_cam1","labels":[],"role":"sink","rules":[],"type":"alarm"}]},{"edges":[["in_cam2","pre_cam2"],["in_cam2","pre_face_cam2"],["pre_cam2","yolo_cam2"],["yolo_cam2","trk_cam2"],["trk_cam2","osd_cam2"],["osd_cam2","post_cam2"],["post_cam2","pub_cam2"],["pub_cam2","alarm_cam2"],["pre_face_cam2","face_det_cam2"],["face_det_cam2","face_recog_cam2"],["face_recog_cam2","alarm_face_cam2"]],"name":"stress_cam2","nodes":[{"enable":true,"force_tcp":true,"fps":30,"height":720,"id":"in_cam2","reconnect_backoff_max_sec":30,"reconnect_sec":5,"role":"source","type":"input_rtsp","url":"rtsp://10.0.0.49:8554/cam","use_ffmpeg":false,"use_mpp":true,"width":1280},{"dst_format":"rgb","dst_h":640,"dst_packed":true,"dst_w":640,"enable":true,"id":"pre_cam2","keep_ratio":false,"rga_gate":"stress_cam2","role":"filter","type":"preprocess","use_rga":true},{"class_filter":[],"conf":0.35,"enable":true,"id":"yolo_cam2","infer_fps":10,"model_path":"./models/yolov5s-640-640.rknn","model_version":"v8","nms":0.45,"num_classes":80,"role":"filter","type":"ai_yolo"},{"conf":0.7,"enable":true,"id":"face_det_cam2","input_format":"rgb","max_faces":10,"model_path":"./models/RetinaFace_mobile320.rknn","nms":0.4,"output_landmarks":true,"role":"filter","type":"ai_face_det"},{"align":true,"emit_embedding":false,"enable":true,"gallery":{"backend":"sqlite","dtype":"auto","expected_dim":512,"load_on_start":true,"path":"./models/face_gallery.db"},"id":"face_recog_cam2","input_dtype":"uint8","input_format":"rgb","max_faces":10,"model_path":"./models/mobilefacenet_arcface.rknn","role":"filter","threshold":{"accept":0.45,"margin":0.05},"type":"ai_face_recog"},{"allowed_models":["yolov5","yolov8"],"debug":{"stats":false,"stats_interval":200},"enable":true,"high_th":0.5,"id":"trk_cam2","ignore_classes":[],"iou_th":0.3,"low_th":0.1,"max_age_ms":1500,"max_tracks":128,"min_hits":2,"mode":"bytetrack_lite","per_class":true,"role":"filter","state_key":"stress_cam2","track_classes":[0],"type":"tracker"},{"dst_format":"rgb","dst_h":0,"dst_packed":true,"dst_w":0,"enable":true,"id":"pre_face_cam2","keep_ratio":false,"rga_gate":"stress_cam2","role":"filter","type":"preprocess","use_rga":true},{"draw_bbox":true,"draw_face_bbox":false,"draw_face_det":false,"draw_text":true,"enable":true,"font_scale":1,"id":"osd_cam2","labels":[],"line_width":2,"role":"filter","type":"osd"},{"dst_format":"nv12","dst_h":720,"dst_w":1280,"enable":true,"id":"post_cam2","keep_ratio":false,"rga_gate":"stress_cam2","role":"filter","type":"preprocess","use_rga":true},{"bitrate_kbps":2000,"codec":"h264","enable":true,"fps":30,"gop":60,"id":"pub_cam2","outputs":[{"path":"/live/cam2","port":8556,"proto":"rtsp_server"},{"path":"./web/hls/cam2/index.m3u8","proto":"hls","segment_sec":2}],"role":"filter","type":"publish","use_ffmpeg_mux":true,"use_mpp":true},{"actions":{"clip":{"enable":true,"format":"mp4","fps":30,"post_sec":10,"pre_sec":5,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}},"external_api":{"channelNo":"${vod_channelNo}","enable":false,"getTokenUrl":"http://127.0.0.1:8080/api/getToken","include_media_url":true,"putMessageUrl":"http://127.0.0.1:8080/api/putMessage","tenantCode":"32","timeout_ms":3000,"token_cache_sec":1200,"token_header":"X-Access-Token","token_json_path":"responseBody.token"},"http":{"enable":false,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":true,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":10,"face_rules":[],"id":"alarm_cam2","labels":[],"role":"sink","rules":[{"class_ids":[0],"cooldown_ms":5000,"hit_window_ms":1500,"min_box_area_ratio":0.02,"min_duration_ms":1500,"min_hits":3,"min_score":0.4,"name":"person_in_view","per_track_cooldown_ms":5000,"require_track_id":true,"roi":{"h":1,"w":1,"x":0,"y":0}}],"type":"alarm"},{"actions":{"clip":{"enable":false},"http":{"enable":true,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":false,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":5,"face_rules":[{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.35,"name":"unknown_face","type":"unknown"},{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.6,"name":"known_person","type":"person"}],"id":"alarm_face_cam2","labels":[],"role":"sink","rules":[],"type":"alarm"}]},{"edges":[["in_cam3","pre_cam3"],["in_cam3","pre_face_cam3"],["pre_cam3","yolo_cam3"],["yolo_cam3","trk_cam3"],["trk_cam3","osd_cam3"],["osd_cam3","post_cam3"],["post_cam3","pub_cam3"],["pub_cam3","alarm_cam3"],["pre_face_cam3","face_det_cam3"],["face_det_cam3","face_recog_cam3"],["face_recog_cam3","alarm_face_cam3"]],"name":"stress_cam3","nodes":[{"enable":true,"force_tcp":true,"fps":30,"height":720,"id":"in_cam3","reconnect_backoff_max_sec":30,"reconnect_sec":5,"role":"source","type":"input_rtsp","url":"rtsp://10.0.0.49:8554/cam","use_ffmpeg":false,"use_mpp":true,"width":1280},{"dst_format":"rgb","dst_h":640,"dst_packed":true,"dst_w":640,"enable":true,"id":"pre_cam3","keep_ratio":false,"rga_gate":"stress_cam3","role":"filter","type":"preprocess","use_rga":true},{"class_filter":[],"conf":0.35,"enable":true,"id":"yolo_cam3","infer_fps":10,"model_path":"./models/yolov5s-640-640.rknn","model_version":"v8","nms":0.45,"num_classes":80,"role":"filter","type":"ai_yolo"},{"conf":0.7,"enable":true,"id":"face_det_cam3","input_format":"rgb","max_faces":10,"model_path":"./models/RetinaFace_mobile320.rknn","nms":0.4,"output_landmarks":true,"role":"filter","type":"ai_face_det"},{"align":true,"emit_embedding":false,"enable":true,"gallery":{"backend":"sqlite","dtype":"auto","expected_dim":512,"load_on_start":true,"path":"./models/face_gallery.db"},"id":"face_recog_cam3","input_dtype":"uint8","input_format":"rgb","max_faces":10,"model_path":"./models/mobilefacenet_arcface.rknn","role":"filter","threshold":{"accept":0.45,"margin":0.05},"type":"ai_face_recog"},{"allowed_models":["yolov5","yolov8"],"debug":{"stats":false,"stats_interval":200},"enable":true,"high_th":0.5,"id":"trk_cam3","ignore_classes":[],"iou_th":0.3,"low_th":0.1,"max_age_ms":1500,"max_tracks":128,"min_hits":2,"mode":"bytetrack_lite","per_class":true,"role":"filter","state_key":"stress_cam3","track_classes":[0],"type":"tracker"},{"dst_format":"rgb","dst_h":0,"dst_packed":true,"dst_w":0,"enable":true,"id":"pre_face_cam3","keep_ratio":false,"rga_gate":"stress_cam3","role":"filter","type":"preprocess","use_rga":true},{"draw_bbox":true,"draw_face_bbox":false,"draw_face_det":false,"draw_text":true,"enable":true,"font_scale":1,"id":"osd_cam3","labels":[],"line_width":2,"role":"filter","type":"osd"},{"dst_format":"nv12","dst_h":720,"dst_w":1280,"enable":true,"id":"post_cam3","keep_ratio":false,"rga_gate":"stress_cam3","role":"filter","type":"preprocess","use_rga":true},{"bitrate_kbps":2000,"codec":"h264","enable":true,"fps":30,"gop":60,"id":"pub_cam3","outputs":[{"path":"/live/cam3","port":8557,"proto":"rtsp_server"},{"path":"./web/hls/cam3/index.m3u8","proto":"hls","segment_sec":2}],"role":"filter","type":"publish","use_ffmpeg_mux":true,"use_mpp":true},{"actions":{"clip":{"enable":true,"format":"mp4","fps":30,"post_sec":10,"pre_sec":5,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}},"external_api":{"channelNo":"${vod_channelNo}","enable":false,"getTokenUrl":"http://127.0.0.1:8080/api/getToken","include_media_url":true,"putMessageUrl":"http://127.0.0.1:8080/api/putMessage","tenantCode":"32","timeout_ms":3000,"token_cache_sec":1200,"token_header":"X-Access-Token","token_json_path":"responseBody.token"},"http":{"enable":false,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":true,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":10,"face_rules":[],"id":"alarm_cam3","labels":[],"role":"sink","rules":[{"class_ids":[0],"cooldown_ms":5000,"hit_window_ms":1500,"min_box_area_ratio":0.02,"min_duration_ms":1500,"min_hits":3,"min_score":0.4,"name":"person_in_view","per_track_cooldown_ms":5000,"require_track_id":true,"roi":{"h":1,"w":1,"x":0,"y":0}}],"type":"alarm"},{"actions":{"clip":{"enable":false},"http":{"enable":true,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":false,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":5,"face_rules":[{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.35,"name":"unknown_face","type":"unknown"},{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.6,"name":"known_person","type":"person"}],"id":"alarm_face_cam3","labels":[],"role":"sink","rules":[],"type":"alarm"}]},{"edges":[["in_cam4","pre_cam4"],["in_cam4","pre_face_cam4"],["pre_cam4","yolo_cam4"],["yolo_cam4","trk_cam4"],["trk_cam4","osd_cam4"],["osd_cam4","post_cam4"],["post_cam4","pub_cam4"],["pub_cam4","alarm_cam4"],["pre_face_cam4","face_det_cam4"],["face_det_cam4","face_recog_cam4"],["face_recog_cam4","alarm_face_cam4"]],"name":"stress_cam4","nodes":[{"enable":true,"force_tcp":true,"fps":30,"height":720,"id":"in_cam4","reconnect_backoff_max_sec":30,"reconnect_sec":5,"role":"source","type":"input_rtsp","url":"rtsp://10.0.0.49:8554/cam","use_ffmpeg":false,"use_mpp":true,"width":1280},{"dst_format":"rgb","dst_h":640,"dst_packed":true,"dst_w":640,"enable":true,"id":"pre_cam4","keep_ratio":false,"rga_gate":"stress_cam4","role":"filter","type":"preprocess","use_rga":true},{"class_filter":[],"conf":0.35,"enable":true,"id":"yolo_cam4","infer_fps":10,"model_path":"./models/yolov5s-640-640.rknn","model_version":"v8","nms":0.45,"num_classes":80,"role":"filter","type":"ai_yolo"},{"conf":0.7,"enable":true,"id":"face_det_cam4","input_format":"rgb","max_faces":10,"model_path":"./models/RetinaFace_mobile320.rknn","nms":0.4,"output_landmarks":true,"role":"filter","type":"ai_face_det"},{"align":true,"emit_embedding":false,"enable":true,"gallery":{"backend":"sqlite","dtype":"auto","expected_dim":512,"load_on_start":true,"path":"./models/face_gallery.db"},"id":"face_recog_cam4","input_dtype":"uint8","input_format":"rgb","max_faces":10,"model_path":"./models/mobilefacenet_arcface.rknn","role":"filter","threshold":{"accept":0.45,"margin":0.05},"type":"ai_face_recog"},{"allowed_models":["yolov5","yolov8"],"debug":{"stats":false,"stats_interval":200},"enable":true,"high_th":0.5,"id":"trk_cam4","ignore_classes":[],"iou_th":0.3,"low_th":0.1,"max_age_ms":1500,"max_tracks":128,"min_hits":2,"mode":"bytetrack_lite","per_class":true,"role":"filter","state_key":"stress_cam4","track_classes":[0],"type":"tracker"},{"dst_format":"rgb","dst_h":0,"dst_packed":true,"dst_w":0,"enable":true,"id":"pre_face_cam4","keep_ratio":false,"rga_gate":"stress_cam4","role":"filter","type":"preprocess","use_rga":true},{"draw_bbox":true,"draw_face_bbox":false,"draw_face_det":false,"draw_text":true,"enable":true,"font_scale":1,"id":"osd_cam4","labels":[],"line_width":2,"role":"filter","type":"osd"},{"dst_format":"nv12","dst_h":720,"dst_w":1280,"enable":true,"id":"post_cam4","keep_ratio":false,"rga_gate":"stress_cam4","role":"filter","type":"preprocess","use_rga":true},{"bitrate_kbps":2000,"codec":"h264","enable":true,"fps":30,"gop":60,"id":"pub_cam4","outputs":[{"path":"/live/cam4","port":8558,"proto":"rtsp_server"},{"path":"./web/hls/cam4/index.m3u8","proto":"hls","segment_sec":2}],"role":"filter","type":"publish","use_ffmpeg_mux":true,"use_mpp":true},{"actions":{"clip":{"enable":true,"format":"mp4","fps":30,"post_sec":10,"pre_sec":5,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}},"external_api":{"channelNo":"${vod_channelNo}","enable":false,"getTokenUrl":"http://127.0.0.1:8080/api/getToken","include_media_url":true,"putMessageUrl":"http://127.0.0.1:8080/api/putMessage","tenantCode":"32","timeout_ms":3000,"token_cache_sec":1200,"token_header":"X-Access-Token","token_json_path":"responseBody.token"},"http":{"enable":false,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":true,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":10,"face_rules":[],"id":"alarm_cam4","labels":[],"role":"sink","rules":[{"class_ids":[0],"cooldown_ms":5000,"hit_window_ms":1500,"min_box_area_ratio":0.02,"min_duration_ms":1500,"min_hits":3,"min_score":0.4,"name":"person_in_view","per_track_cooldown_ms":5000,"require_track_id":true,"roi":{"h":1,"w":1,"x":0,"y":0}}],"type":"alarm"},{"actions":{"clip":{"enable":false},"http":{"enable":true,"include_media_url":true,"method":"POST","timeout_ms":3000,"url":"http://127.0.0.1:8080/api/alarm"},"log":{"enable":false,"level":"info"},"snapshot":{"enable":true,"format":"jpg","quality":85,"upload":{"access_key":"minioadmin","bucket":"stress-test","endpoint":"http://10.0.0.49:9000","region":"us-east-1","secret_key":"minioadmin","type":"minio"}}},"enable":true,"eval_fps":5,"face_rules":[{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.35,"name":"unknown_face","type":"unknown"},{"cooldown_ms":7000,"hit_window_ms":1500,"max_face_aspect":1.6,"min_face_area_ratio":0.01,"min_face_aspect":0.6,"min_hits":2,"min_sim":0.6,"name":"known_person","type":"person"}],"id":"alarm_face_cam4","labels":[],"role":"sink","rules":[],"type":"alarm"}]}],"queue":{"size":8,"strategy":"drop_oldest"}} \ No newline at end of file diff --git a/docs/requirements/32视频内容识别集成方案3.docx b/docs/requirements/32视频内容识别集成方案3.docx new file mode 100644 index 0000000..3b3345a Binary files /dev/null and b/docs/requirements/32视频内容识别集成方案3.docx differ diff --git a/docs/requirements/guide.md b/docs/requirements/guide.md index 8e70142..6fe01c3 100644 --- a/docs/requirements/guide.md +++ b/docs/requirements/guide.md @@ -8,6 +8,12 @@ winget install Gyan.FFmpeg - 安装完成后,关闭并重新打开终端,验证: ffmpeg -version +-查看本地摄像头信息 +ffmpeg -list_devices true -f dshow -i dummy + +- 本地运行RTSP服务器 +mediamtx.exe + - 推流到RTSP服务器(设置摄像头的分辨率为720P) ffmpeg -f dshow -rtbufsize 100M -video_size 1280x720 -framerate 30 -vcodec mjpeg -i video="4K AutoFocus Webcam" -c:v libx264 -preset ultrafast -pix_fmt yuv420p -f rtsp rtsp://localhost:8554/cam diff --git a/docs/stress_test_4ch_full_pipeline.md b/docs/stress_test_4ch_full_pipeline.md new file mode 100644 index 0000000..c4b73ba --- /dev/null +++ b/docs/stress_test_4ch_full_pipeline.md @@ -0,0 +1,526 @@ +# RK3588 4路全流程压力测试方案 + +本文档描述在单台 RK3588 设备上进行 4 路全流程视频处理的压力测试方案。 + +**方案说明:** 本方案使用单一 RTSP 视频源 (`rtsp://10.0.0.49:8554/test`),4 条流水线独立处理同一路输入,模拟 4 路摄像头并发场景。配置与 `sample_cam2.json` 完全一致。 + +--- + +## 1. 测试目标 + +### 1.1 核心指标 + +| 指标项 | 目标值 | 说明 | +|--------|--------|------| +| **并发路数** | 4 路 | 单设备同时处理 4 路视频 | +| **输入分辨率** | 1280×720 @ 30fps | 每路输入为 720p 实时视频 | +| **端到端延迟** | ≤ 500ms | 从采集到输出的完整延迟 | +| **NPU 利用率** | ≥ 70% | NPU 高效利用,但不超载 | +| **CPU 占用** | ≤ 50% | 预留系统余量 | +| **内存占用** | ≤ 2.5GB | 避免内存压力 | +| **运行稳定性** | 24 小时无崩溃 | 长稳测试 | + +### 1.2 全流程覆盖 + +测试需覆盖所有节点类型(与 `sample_cam2.json` 一致的拓扑): + +``` + [共享输入源] + │ + ┌────────────────┼────────────────┐ + │ │ │ + [Pipeline 1] [Pipeline 2] [Pipeline 3] [Pipeline 4] + │ │ │ │ + input_rtsp input_rtsp input_rtsp input_rtsp + │ │ │ │ + preprocess preprocess preprocess preprocess + │ │ │ │ + ai_yolo ──→ tracker ─┤ ai_yolo ──→ tracker (目标检测+跟踪) + │ │ │ │ + ai_face_det ──→ ai_face_recog (人脸识别) + │ │ │ │ + └──────────→ osd ←─────────────────────────────┘ + │ + preprocess (后处理) + │ + publish (RTSP/HLS 输出) + │ + ┌──────────┴──────────┐ + │ │ + alarm (目标) alarm_face (人脸) +``` + +**节点类型统计(每路,与 sample_cam2.json 一致):** +- **Source (1)**:`input_rtsp` +- **Filter (7)**:`preprocess`×2, `ai_yolo`, `ai_face_det`, `ai_face_recog`, `tracker`, `osd` +- **Sink (3)**:`alarm`×2, `publish` + +**4路总计:** +- 4 个 input_rtsp 节点(连接同一 RTSP 源) +- 28 个 Filter 节点 +- 12 个 Sink 节点 + +--- + +## 2. 测试环境要求 + +### 2.1 硬件要求 + +| 项目 | 规格 | 说明 | +|------|------|------| +| **主控芯片** | RK3588 | 4×A76@2.4GHz + 4×A55@1.8GHz | +| **NPU** | 6 TOPS@INT8 | 用于 AI 推理 | +| **内存** | ≥ 8GB LPDDR4/LPDDR5 | 建议 8GB 或以上 | +| **存储** | ≥ 32GB eMMC/SSD | 用于 HLS 切片存储 | +| **网络** | 千兆以太网 | 单路 4-8Mbps 输入,4路约 16-32Mbps | +| **散热** | 主动散热 | 长时间高负载运行 | + +### 2.2 软件要求 + +| 项目 | 版本/配置 | 说明 | +|------|-----------|------| +| **操作系统** | Ubuntu 22.04 / Debian 11 | ARM64 架构 | +| **内核** | Linux 5.10+ | 需支持 RGA、MPP、DMA-BUF | +| **NPU 驱动** | rknpu driver 0.9.6+ | 建议最新版本 | +| **CMake** | ≥ 3.20 | 构建工具 | +| **GCC** | ≥ 10.0 | 编译器 | + +### 2.3 依赖库检查 + +```bash +# 检查 NPU 驱动 +ls /dev/rknpu + +# 检查 RGA 驱动 +ls /dev/rga + +# 检查内存 +free -h + +# 检查存储空间 +df -h +``` + +### 2.4 视频源要求 + +| 项目 | 要求 | 说明 | +|------|------|------| +| **视频源类型** | RTSP Server | 建议使用 ffmpeg 或 ZLMediaKit 推流 | +| **视频源数量** | 1 路共享流 | 4 条流水线共用同一输入 | +| **视频规格** | 1280×720 @ 30fps, H.264 | 模拟真实摄像头 | +| **码率** | 4-8 Mbps | 总输入带宽 4-8 Mbps | +| **内容要求** | 包含行人和人脸 | 用于验证检测和识别功能 | + +**视频源准备命令(在 10.0.0.49 服务器执行):** + +```bash +# 使用 ffmpeg 推送 1 路 720p 测试流 +ffmpeg -re -stream_loop -1 -i test_video.mp4 \ + -c:v libx264 -preset fast -b:v 4M -r 30 -s 1280x720 \ + -f rtsp rtsp://0.0.0.0:8554/test + +# 或使用 ZLMediaKit 等 RTSP 服务器 +``` + +**测试视频内容建议:** +- 包含行人移动(验证目标检测和跟踪) +- 包含正脸人像(验证人脸识别) +- 时长 5-10 分钟,循环播放 + +### 2.5 外部服务要求 + +| 服务 | 用途 | 配置 | +|------|------|------| +| **MinIO** | 报警截图/录像存储 | 地址: `10.0.0.49:9000`
Bucket: `stress-test`
账号: `minioadmin/minioadmin` | + +**MinIO 准备(在 10.0.0.49 上执行):** + +```bash +# 创建 bucket +mc alias set myminio http://10.0.0.49:9000 minioadmin minioadmin +mc mb myminio/stress-test +``` + +--- + +## 3. 测试配置文件 + +### 3.1 主配置文件 + +配置文件路径:`configs/stress_test_4ch_shared_source.json` + +**配置特点:** +- 4 个独立 Graph(stress_cam1 ~ stress_cam4) +- 所有 Graph 的输入 RTSP URL 相同:`rtsp://10.0.0.49:8554/test` +- 每路独立输出到不同端口(8555-8558) +- **节点拓扑和参数与 `sample_cam2.json` 完全一致** + +**节点配置详情(每路,与 sample_cam2.json 一致):** + +| 节点 | 类型 | 作用 | 关键参数 | +|------|------|------|----------| +| `input_rtsp` | source | 拉取 RTSP 流 | 1280×720@30fps, use_mpp=true | +| `preprocess` | filter | 图像预处理 | 720p → 640×640 RGB, use_rga=true | +| `ai_yolo` | filter | 目标检测 | yolov8n-640.rknn, v8, conf=0.35, nms=0.45, infer_fps=10 | +| `ai_face_det` | filter | 人脸检测 | RetinaFace_mobile320.rknn, conf=0.7, max_faces=10 | +| `ai_face_recog` | filter | 人脸识别 | mobilefacenet_arcface.rknn, align=true, max_faces=10 | +| `tracker` | filter | 目标跟踪 | bytetrack_lite, max_age_ms=1500, track_classes=[0] | +| `preprocess` | filter | 人脸分支预处理 | 原始分辨率 → RGB (pre_face) | +| `osd` | filter | 屏幕显示 | draw_bbox=true, draw_text=true, line_width=2 | +| `preprocess` | filter | 后处理 | 640×640 → 720p NV12, use_rga=true | +| `publish` | sink | 视频输出 | h264, 30fps, 2000kbps, RTSP+HLS | +| `alarm` | sink | 目标报警 | eval_fps=10, MinIO上传截图+录像 | +| `alarm_face` | sink | 人脸报警 | eval_fps=5, MinIO上传截图 | + +**输出端口分配:** + +| Graph | RTSP 端口 | HLS 路径 | +|-------|-----------|----------| +| stress_cam1 | 8555 | `./web/hls/cam1/index.m3u8` | +| stress_cam2 | 8556 | `./web/hls/cam2/index.m3u8` | +| stress_cam3 | 8557 | `./web/hls/cam3/index.m3u8` | +| stress_cam4 | 8558 | `./web/hls/cam4/index.m3u8` | + +**模型文件清单:** + +| 模型文件 | 路径 | 说明 | +|----------|------|------| +| yolov8n-640.rknn | `./models/yolov8n-640.rknn` | YOLOv8 目标检测 | +| RetinaFace_mobile320.rknn | `./models/RetinaFace_mobile320.rknn` | 人脸检测 | +| mobilefacenet_arcface.rknn | `./models/mobilefacenet_arcface.rknn` | 人脸识别 | +| face_gallery.db | `./models/face_gallery.db` | 人脸库 | + +--- + +## 4. 测试步骤 + +### 4.1 环境准备 + +```bash +# 1. 检查 NPU 驱动 +ls /dev/rknpu + +# 2. 检查内存 +free -h + +# 3. 检查存储空间 +df -h + +# 4. 准备模型文件 +ls models/ +# 应有:yolov8n-640.rknn, RetinaFace_mobile320.rknn, mobilefacenet_arcface.rknn, face_gallery.db + +# 5. 创建 HLS 输出目录 +mkdir -p web/hls/cam{1,2,3,4} + +# 6. 检查网络连接 +ping 10.0.0.49 +``` + +### 4.2 启动视频源 + +在服务器(10.0.0.49)上执行: + +```bash +# 推送单路 720p 测试流 +ffmpeg -re -stream_loop -1 -i test_video.mp4 \ + -c:v libx264 -preset fast -b:v 4M -r 30 -s 1280x720 \ + -f rtsp rtsp://0.0.0.0:8554/cam1 +``` + +### 4.3 启动测试程序 + +```bash +cd /home/orangepi/apps/OrangePi3588Media + +# 启动 media-server +./build/media-server --config configs/stress_test_4ch_shared_source.json +``` + +### 4.4 监控脚本 + +创建监控脚本 `monitor_4ch.sh`: + +```bash +#!/bin/bash + +LOG_FILE="stress_test_4ch_$(date +%Y%m%d_%H%M%S).log" + +echo "Time,CPU%,MemMB,NPU%,Temp°C" > $LOG_FILE + +while true; do + TIME=$(date '+%H:%M:%S') + + # CPU 使用率 + CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1) + + # 内存使用 (MB) + MEM=$(free -m | awk 'NR==2{printf "%.0f", $3}') + + # NPU 使用率 + NPU=$(cat /sys/kernel/debug/rknpu/load 2>/dev/null || echo "0") + + # CPU 温度 + TEMP=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null | awk '{print $1/1000}') + + echo "$TIME,$CPU,$MEM,$NPU,$TEMP" >> $LOG_FILE + echo "[$TIME] CPU:${CPU}% Mem:${MEM}MB NPU:${NPU}% Temp:${TEMP}°C" + + sleep 5 +done +``` + +运行监控: +```bash +chmod +x monitor_4ch.sh +./monitor_4ch.sh +``` + +--- + +## 5. 测试结果验证方法 + +### 5.1 功能验证 + +| 验证项 | 方法 | 通过标准 | +|--------|------|----------| +| **输入连接** | 查看日志 | 4 路都显示 "connected" | +| **视频输出** | VLC 播放 | `rtsp://:8555/live/cam1` ~ `:8558/live/cam4` 都能正常播放 | +| **HLS 输出** | 浏览器/播放器 | 4 路 HLS 流可正常播放 | +| **目标检测** | 观察 OSD 输出 | 画面中出现检测框和 "person" 标签 | +| **人脸识别** | 观察 OSD 输出 | 人脸被框选,显示姓名或 "unknown" | +| **目标跟踪** | 观察 OSD 输出 | 同一目标的 ID 保持稳定 | +| **报警触发** | 查看日志 | alarm 节点输出 "person_detect" 或 "unknown_face" | +| **MinIO 上传** | 检查 MinIO | `stress-test` bucket 中有截图/录像文件 | +| **4路并发** | 同时播放4路输出 | 4路输出画面都流畅,无明显卡顿 | + +### 5.2 性能验证 + +| 指标 | 测量方法 | 通过标准 | +|------|----------|----------| +| **帧率** | 日志中 node_output_fps | ≥ 25 fps(每路) | +| **延迟** | 对比输入输出时间戳 | ≤ 500ms | +| **NPU 利用率** | `/sys/kernel/debug/rknpu/load` | 50-90% | +| **CPU 占用** | `top` 命令 | ≤ 50% | +| **内存占用** | `free` 命令 | ≤ 2.5GB | +| **队列积压** | 日志中 queue_length | 不持续增长 | + +### 5.3 稳定性验证 + +| 测试项 | 时长 | 通过标准 | +|--------|------|----------| +| **短稳测试** | 1 小时 | 无崩溃,无内存泄漏 | +| **中稳测试** | 8 小时 | 性能指标稳定,无异常重启 | +| **长稳测试** | 24 小时 | 无崩溃,内存增长 < 100MB | + +### 5.4 验证命令示例 + +```bash +# 1. 检查 4 路进程状态 +ps aux | grep media-server + +# 2. 查看 NPU 负载 +watch -n 1 cat /sys/kernel/debug/rknpu/load + +# 3. 查看资源使用 +htop + +# 4. 播放测试(另开终端) +ffplay rtsp://localhost:8555/live/cam1 & +ffplay rtsp://localhost:8556/live/cam2 & +ffplay rtsp://localhost:8557/live/cam3 & +ffplay rtsp://localhost:8558/live/cam4 & + +# 5. 检查 HLS 文件生成 +ls -la web/hls/cam*/ + +# 6. 查看日志中的报警 +tail -f media-server.log | grep alarm + +# 7. 检查 MinIO 上传(在 10.0.0.49 上) +mc ls myminio/stress-test +``` + +--- + +## 6. 预期结果 + +### 6.1 正常情况 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 4 路 720p 全流程测试预期结果 │ +├─────────────────────────────────────────────────────────┤ +│ • 4 路视频正常解码、处理、输出 │ +│ • 端到端延迟: 200-400ms │ +│ • NPU 利用率: 70-90% │ +│ • CPU 占用: 30-50% │ +│ • 内存占用: 1.5-2.5GB │ +│ • 无丢帧或轻微丢帧 (< 1%) │ +│ • 报警功能正常触发,MinIO上传成功 │ +│ • 4路输出画面质量一致 │ +└─────────────────────────────────────────────────────────┘ +``` + +### 6.2 性能数据记录表 + +| 指标 | 测试前 | 1小时 | 8小时 | 24小时 | +|------|--------|-------|-------|--------| +| CPU 平均占用 | - | | | | +| CPU 峰值占用 | - | | | | +| 内存占用 (MB) | - | | | | +| NPU 平均利用率 | - | | | | +| NPU 峰值利用率 | - | | | | +| 平均延迟 (ms) | - | | | | +| 帧率 cam1 (fps) | - | | | | +| 帧率 cam2 (fps) | - | | | | +| 帧率 cam3 (fps) | - | | | | +| 帧率 cam4 (fps) | - | | | | +| 报警次数 (目标) | - | | | | +| 报警次数 (人脸) | - | | | | +| MinIO 文件数 | - | | | | +| 异常重启次数 | - | | | | + +### 6.3 输出验证清单 + +- [ ] 4 路 RTSP 流均可正常播放 +- [ ] 4 路 HLS 流均可正常播放 +- [ ] 画面中有检测框(OSD 正常工作) +- [ ] 检测到人脸时显示姓名或 "unknown" +- [ ] 目标 ID 保持稳定(跟踪正常工作) +- [ ] 日志中有报警输出 +- [ ] MinIO `stress-test` bucket 中有文件上传 +- [ ] 无 Error/Warning 刷屏 + +--- + +## 7. 故障排查 + +### 7.1 常见问题 + +| 现象 | 可能原因 | 解决方法 | +|------|----------|----------| +| **启动失败** | NPU 驱动未加载 | `ls /dev/rknpu` 检查,重新加载驱动 | +| **拉流失败** | 网络不通/视频源问题 | 检查网络,`ping 10.0.0.49`,验证 RTSP 流 | +| **高 CPU 占用** | RGA 未启用 | 检查配置 `use_rga: true`,`use_mpp: true` | +| **高延迟** | 队列积压 | 检查 `queue.size`,适当调小 | +| **NPU 利用率低** | 推理频率设置过低 | 调整 `infer_fps` | +| **4路中某路无输出** | 端口冲突 | 检查端口 8555-8558 是否被占用 | +| **OSD 不显示** | 格式不支持 | 确保 OSD 输入为 RGB/BGR/NV12 格式 | +| **MinIO 上传失败** | 网络/认证问题 | 检查 MinIO 地址和账号密码 | + +### 7.2 调试命令 + +```bash +# 查看 NPU 状态 +cat /sys/kernel/debug/rknpu/version +cat /sys/kernel/debug/rknpu/load + +# 查看 RGA 状态 +cat /sys/kernel/debug/rga/info + +# 查看进程资源使用 +pidof media-server | xargs -I {} ps -p {} -o pid,ppid,cmd,%cpu,%mem + +# 查看线程数 +ps -eLf | grep media-server | wc -l + +# 查看端口占用 +netstat -tlnp | grep media-server + +# 查看系统日志 +sudo dmesg | tail -50 +``` + +### 7.3 日志分析 + +```bash +# 检查错误 +grep -i "error\|fail\|warn" media-server.log | head -20 + +# 检查各路口连接状态 +grep "input_rtsp\|connected\|disconnected" media-server.log + +# 检查帧率 +grep "output_fps" media-server.log | tail -20 + +# 检查报警触发 +grep "alarm" media-server.log | tail -20 + +# 检查 MinIO 上传 +grep "minio\|snapshot\|clip" media-server.log | tail -20 +``` + +--- + +## 8. 测试报告模板 + +### 8.1 基本信息 + +- **测试时间**: +- **测试人员**: +- **设备型号**: +- **固件版本**: +- **软件版本**: +- **视频源**: `rtsp://10.0.0.49:8554/test` (1280×720@30fps) +- **MinIO**: `http://10.0.0.49:9000`, bucket: `stress-test` + +### 8.2 测试结论 + +- [ ] 4 路全流程测试通过 +- [ ] 性能指标达标 +- [ ] 稳定性测试通过 +- [ ] MinIO 上传功能正常 + +### 8.3 详细数据 + +(填入第 6 章的表格数据) + +### 8.4 问题记录 + +| 序号 | 问题描述 | 严重程度 | 状态 | +|------|----------|----------|------| +| 1 | | | | +| 2 | | | | + +### 8.5 优化建议 + +(根据测试结果填写) + +--- + +## 9. 附录 + +### 9.1 快速开始 + +```bash +# 1. 准备环境 +cd /home/orangepi/apps/OrangePi3588Media +mkdir -p web/hls/cam{1,2,3,4} + +# 2. 确认模型存在 +ls models/yolov8n-640.rknn models/RetinaFace_mobile320.rknn \ + models/mobilefacenet_arcface.rknn models/face_gallery.db + +# 3. 启动测试 +./build/media-server --config configs/stress_test_4ch_shared_source.json + +# 4. 另开终端监控 +./monitor_4ch.sh + +# 5. 验证输出 +ffplay rtsp://localhost:8555/live/cam1 +``` + +### 9.2 相关文档 + +- [Readme.md](../Readme.md) - 项目总览 +- [deployment.md](deployment.md) - 部署说明 +- [dag_graph_node_edge.md](architecture/dag_graph_node_edge.md) - 架构说明 + +### 9.3 配置文件清单 + +| 文件 | 说明 | +|------|------| +| `configs/stress_test_4ch_shared_source.json` | 4路全流程测试配置(共享源,与 sample_cam2.json 一致) | +| `monitor_4ch.sh` | 资源监控脚本 | diff --git a/models/best-640.rknn b/models/best-640.rknn new file mode 100644 index 0000000..bfd23a4 Binary files /dev/null and b/models/best-640.rknn differ diff --git a/models/yolov5s-640-640.rknn b/models/yolov5s-640-640.rknn new file mode 100644 index 0000000..87419fd Binary files /dev/null and b/models/yolov5s-640-640.rknn differ diff --git a/models/yolov8n-640.rknn b/models/yolov8n-640.rknn new file mode 100644 index 0000000..7439f41 Binary files /dev/null and b/models/yolov8n-640.rknn differ diff --git a/scripts/d8_1.py b/scripts/d8_1.py new file mode 100644 index 0000000..bb7368c --- /dev/null +++ b/scripts/d8_1.py @@ -0,0 +1,1206 @@ +# 开发者 haotian +# 开发时间: 2024/9/20 21:14 +''' + v7_p1_1.5.1 的 改进版 + 将文件路径等做成可配置的 +''' + +''' + + 在1.5 的基础上修改了判断人脸的逻辑 +''' + +''' + 人脸识别直接新开一个线程算了 + +''' + +''' + 添加人脸识别,新的告警逻辑。 + 改进了告警的逻辑。 + 没写上传minio + 加上上传minio + 加上post警告,测试环境注释掉了。 + + + 测试环境 + 1.注释掉获取get_token + 2.注释掉minio上传文件 self.minio_client.put_object + 3.注释掉 send_post_request + 4.注释掉 os.remove +''' + + + + +import ctypes +import os +import shutil +import random +import sys +import threading +import time +import cv2 +import numpy as np +import pycuda.autoinit +import pycuda.driver as cuda +import tensorrt as trt +import queue +from minio import Minio +import yaml +import threading +import subprocess +import uuid +import requests +import json +import datetime + + +from compreface import CompreFace +from compreface.service import RecognitionService +# from PIL import Image + +CONF_THRESH = 0.65 +IOU_THRESHOLD = 0.4 + +with open('config.yaml', 'r') as file: + configData = yaml.safe_load(file) + +# Minio实例化, 用于云端存储文件 +client = Minio( + endpoint=configData['minioConfig']['endpoint'], + access_key=configData['minioConfig']['access_key'], + secret_key=configData['minioConfig']['secret_key'], + secure=configData['minioConfig']['secure'] +) + +# 保存token +tokenResult = {} +getTokenUrl = configData['dataConfig']['getTokenUrl'] + +# 告警信息url +putMessageUrl = configData['dataConfig']['putMessageUrl'] + +# ip和文件目录标识符 +ip = configData['video_config']['v0_ip'] + +# 所有模型类别 +categories = configData['video_config']['categories'] + +# 输出m3u8文件地址 +m3u8_path = configData['video_config']['m3u8_path'] + +# 图片/视频文件的暂存路径 +save_path = configData['video_config']['save_path'] + +# 视频源地址 +vod_path = configData['video_config']['v0_path'] + +# 人脸识别暂存文件地址 +people_save_path = configData['video_config']['people_save_path'] + + +# 模型文件地址 +engine_path = configData['engine_path'] + +vod_channelNo = configData['video_config']['v0_channelNo'] + +# 要检测的类别 +testclasses = configData['video_config']['v0_testclasses'] + +# 请求超时时间 +time_interval = configData['dataConfig']['timeInterval'] +command_mid = [ + 'ffmpeg', + '-i', '-', # 从标准输入读取视频帧 + '-c:v', 'libx264', # 使用 H.264 编码 + '-b:v', '500k', # 设置视频比特率 + '-preset', 'superfast', # 编码速度 + '-tune', 'zerolatency', # 低延迟 + '-crf', '23', # 使用 CRF 模式来控制视频质量 + '-s', '1280x720', # 设置分辨率 + '-an', # 禁用音频 + '-loglevel', 'error', + # '-f', 'flv', # 输出格式 + # 'rtmp://127.0.0.1:1935/live/1' # 输出到 RTMP 服务器 + '-hls_time', '4', + '-hls_list_size', '2', + '-hls_flags', 'delete_segments', + '-f', 'hls', + f'{m3u8_path}'+ ip + '/index.m3u8' +] + +# 人脸识别部分 + +DOMAIN: str = configData['compreface_service']['domain'] +PORT: str = configData['compreface_service']['port'] +API_KEY: str = configData['compreface_service']['api_key'] +LIMIT: str = configData['compreface_service']['limit'] +Det_prob_threshold: str = configData['compreface_service']['det_prob_threshold'] + +# 人脸识别客户端 +compre_face: CompreFace = CompreFace(DOMAIN, PORT,options={'limit': LIMIT,"det_prob_threshold":Det_prob_threshold}) +recognition: RecognitionService = compre_face.init_face_recognition(API_KEY) + + +pipeline_mid = subprocess.Popen(command_mid, shell=False, stdin=subprocess.PIPE) + +frames = [None] * 6 +# 拉流缓存 +rtsp_frame_buffer = queue.Queue(maxsize=300) + +# 全局人名字典,每天0点清空?? +d_face = dict() + + +# 获取token和对应时间 存入字典 +def get_token(tokenResult): + if 'token' in tokenResult and 'current_time' in tokenResult: + token_time = datetime.datetime.strptime(tokenResult['current_time'], + "%Y-%m-%d %H:%M:%S") + current_time = datetime.datetime.now() + time_diff = current_time - token_time + if time_diff.total_seconds() > 20 * 60: + # 过期重新请求 token + # print("token 已过期") + response = requests.post(getTokenUrl) + if response.status_code == 200: + data = json.loads(response.text) + if 'retCode' in data and data['retCode'] == '200': + token = data['responseBody']['token'] + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + tokenResult['token'] = token + tokenResult['current_time'] = current_time + else: + tokenResult['error'] = data['errorDesc'] + else: + tokenResult['error'] = response.status_code + token = tokenResult['token'] + return token + + +def send_post_request(url, token, msg, picUrl, videoUrl): + payload = { + "tenantCode": "32", + "channelNo": vod_channelNo, + "alarmContent": msg, + "alarmTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "picInfo": [ + {"url": picUrl} + ], + "videoInfo": [ + {"url": videoUrl} + ] + } + headers = { + 'X-Access-Token': token, + 'Content-Type': 'application/json' + } + # print(url) + # print(headers) + # print(payload) + response = requests.post(url, headers=headers, data=json.dumps(payload)) + #print(response) + + +def clear_folder(ip): + folder_path1 = m3u8_path+ip + # 判断文件夹是否存在 + if not os.path.exists(folder_path1): + print(f"文件夹 {folder_path1} 不存在!") + os.mkdir(folder_path1) + # return + + # 判断文件夹是否为空 + if not os.listdir(folder_path1): + print(f"文件夹 {folder_path1} 为空,无需清空!") + else: + # 清空文件夹1 + for filename in os.listdir(folder_path1): + file_path = os.path.join(folder_path1, filename) + if os.path.isfile(file_path): + os.remove(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + print(f"已清空文件夹 {folder_path1} 的内容!") + + +def restart_program(): + """重新启动当前程序""" + python = sys.executable # 获取当前 Python 解释器的路径 + # print("Restarting program...") + time.sleep(1) # 可选延迟,确保用户看到提示 + os.execl(python, python, *sys.argv) # 使用相同的参数重新启动当前脚本 + + +def verify_bbox_class(classid_list, save_flag): + if "face" in classid_list: + save_flag.append("face") + return True + if "shoe" in classid_list: + save_flag.append("shoe") + return True + if "phone" in classid_list: + save_flag.append("phone") + return True + return False + + +# after_time 距离 before_time 是否在 time_num秒 之内,若在 返回true 不在返回false +def verify_timenum(before_time, after_time, time_num): + if before_time is None: + return True + time_difference = after_time - before_time + if time_difference < time_num: + return True + else: + return False + + +# 该方法用于判断两个帧是否相同 +def compare_frames(frame1, frame2, threshold): + difference = cv2.absdiff(frame1, frame2) + diff_gray = cv2.cvtColor(difference, cv2.COLOR_BGR2GRAY) + _, thresholded_diff = cv2.threshold(diff_gray, threshold, 350, cv2.THRESH_BINARY) + + return np.sum(thresholded_diff) == 0 + + +def white_color_ratio(image): + image = image.copy() + # 将图像从BGR颜色空间转换为HSV颜色空间 + hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) + # 定义白色的HSV范围(在此示例中使用了一组简单的范围) + lower_white = np.array([0, 0, 200], dtype=np.uint8) + upper_white = np.array([180, 30, 255], dtype=np.uint8) + # 创建遮罩,将白色和白灰色区域设置为白色,其他区域设置为黑色 + white_mask = cv2.inRange(hsv_image, lower_white, upper_white) + # 计算白色和白灰色区域的像素数 + white_pixels = np.count_nonzero(white_mask) + # 计算图像中白色和白灰色的占比 + total_pixels = image.shape[0] * image.shape[1] + white_ratio = white_pixels / total_pixels + return white_ratio + + +def shoe_color_ratio(image): + # 将图像从BGR颜色空间转换为HSV颜色空间 + hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) + # 定义黑色的HSV范围(在此示例中使用了一组简单的范围) + lower_black = np.array([0, 0, 0], dtype=np.uint8) + upper_black = np.array([180, 255, 80], dtype=np.uint8) + # 创建遮罩,将黑色区域设置为白色,其他区域设置为黑色 + black_mask = cv2.inRange(hsv_image, lower_black, upper_black) + # 计算黑色区域的像素数 + black_pixels = np.count_nonzero(black_mask) + # 计算图像中黑色的占比 + total_pixels = image.shape[0] * image.shape[1] + black_ratio = black_pixels / total_pixels + return black_ratio + +def skin_color_ratio(image): + # 将图像从BGR颜色空间转换为HSV颜色空间 + # 定义肤色的HSV范围(在此示例中使用了一组简单的范围) + hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) + lower_skin = np.array([0, 20, 70], dtype=np.uint8) + upper_skin = np.array([20, 255, 255], dtype=np.uint8) + # 创建遮罩,将肤色区域设置为白色,其他区域设置为黑色 + skin_mask = cv2.inRange(hsv_image, lower_skin, upper_skin) + # 计算肤色区域的像素数 + skin_pixels = np.count_nonzero(skin_mask) + # 计算图像中人体肤色的占比 + total_pixels = image.shape[0] * image.shape[1] + skin_ratio = skin_pixels / total_pixels + return skin_ratio + + +def get_img_path_batches(batch_size, img_dir): + ret = [] + batch = [] + for root, dirs, files in os.walk(img_dir): + for name in files: + if len(batch) == batch_size: + ret.append(batch) + batch = [] + batch.append(os.path.join(root, name)) + if len(batch) > 0: + ret.append(batch) + return ret + + +# 画框 返回True 为需要报警的选项,返回Flase,为不需要报警的 +def plot_one_box(x, img, color=[0, 255, 0], label=None, line_thickness=2): + + # print("label: ", label) + + c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3])) + # msg = '' + + if label == 'shoe': + print('识别到鞋子') + region_of_interest = img[c1[1]:c2[1], c1[0]:c2[0]] + source = shoe_color_ratio(region_of_interest) + print(f'识别到鞋子, 比率:{source}') + # white_sorce = white_color_ratio(region_of_interest) + # 鞋子黑色面积必须大于整体框的百分之50 并且框的宽度小于40,高度小于30 + if source < 0.4: + # 画框 + color = [0, 0, 255] + cv2.rectangle(img, c1, c2, color, thickness=line_thickness, lineType=cv2.LINE_AA) + return [1, '未穿戴劳保鞋'] + + if label == 'face': + + region_of_interest = img[c1[1]:c2[1], c1[0]:c2[0]] + face_source = skin_color_ratio(region_of_interest) + + print("识别到人脸, 人脸比率: ", face_source) + # white_sorce = white_color_ratio(region_of_interest) + # print("判断存在脸,且肤色为" + str(face_source)) + # 肤色面积必须大于整体框的百分之70 框的宽度大于10 + if face_source < 0.8: + # 画框 + color = [0, 0, 255] + cv2.rectangle(img, c1, c2, color, thickness=1, lineType=cv2.LINE_AA) + + print("face") + + return [1, '未佩戴口罩'] + if label == 'phone': + + print("phone") + color = [0, 0, 255] + cv2.rectangle(img, c1, c2, color, thickness=line_thickness, lineType=cv2.LINE_AA) + return [1, '识别到手机'] + + if label == 'e-bike': + print("e-bike") + color = [0, 0, 255] + cv2.rectangle(img, c1, c2, color, thickness=line_thickness, lineType=cv2.LINE_AA) + return [1, '识别到电动车'] + + if label == 'non-Helmet': + print("non-Helmet") + color = [0, 0, 255] + cv2.rectangle(img, c1, c2, color, thickness=line_thickness, lineType=cv2.LINE_AA) + return [1, '识别到未佩戴安全帽'] + + return [0, ""] + + +# def check_save_flag(save_flag): +# # 定义需要检查的类别 +# categories = ["shoe"] +# # 找出在save_flag中的类别 +# matched_categories = [category for category in categories if category in save_flag] +# # 用'-'连接匹配的类别并返回 +# return "-".join(matched_categories) + + +# 计算图像的模糊度 +def calculate_blur(frame): + # 将图片转换为灰度图 + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + # 计算灰度图的方差 + blur_value = cv2.Laplacian(gray, cv2.CV_64F).var() + return blur_value + + +# 先前陌生人数 +p_s_num = 0 +class FaceRecUpload(threading.Thread): + def __init__(self, ip, frame, token): + threading.Thread.__init__(self) + self.ip = ip + self.frame = frame.copy() + # self.result = result + self.new_face = False + self.token = token + self.minio_client = client + + def run(self): + global p_s_num + + # blur = calculate_blur(self.frame) + + # print(f'\n模糊度:{blur}') + et, jpeg_frame = cv2.imencode('.jpg', self.frame) + # self.ffmpeg_frame_buffer.put(jpeg_frame.tobytes()) + now_time = time.time() + result = recognition.recognize(jpeg_frame.tobytes()) + + if 'message' not in result: + + warning = list() + + # 人脸识别检测 + for i in range(len(result['result'])): + # print(self.result['result'][i]['subjects'][0]['subject'], self.result['result'][i]['subjects'][0]['similarity']) + # print(self.result['result'][i]['box']) + x1, y1, x2, y2 = result['result'][i]['box']['x_min'], result['result'][i]['box']['y_min'], \ + result['result'][i]['box']['x_max'], result['result'][i]['box']['y_max'] + + if self.frame[x1:x2+1, y1:y2+1].size > 0: + blur = calculate_blur(self.frame[x1:x2+1, y1:y2+1]) + else: + + blur = 0 + # print(f'人脸模糊度{blur}') + if blur > 10: + if 0.3 < result['result'][i]['subjects'][0]['similarity'] < 0.4: + # 设置文本参数 + font = cv2.FONT_HERSHEY_SIMPLEX + font_scale = 1 + color = (255, 0, 0) # BGR颜色 + thickness = 2 + cv2.rectangle(self.frame, (x1, y1), (x2, y2), (255, 0, 0), 2) + cv2.putText(self.frame, f'warning {result["result"][i]["subjects"][0]["similarity"]}', + (x1, y1 - 20), font, font_scale, color, thickness, + cv2.LINE_AA) + warning.append([(x1, y1), (x2, y2)]) + # 垃圾帧丢弃 + elif result['result'][i]['subjects'][0]['similarity'] <= 0.3: + # print('\n丢弃的人脸识别帧') + pass + else: + if result['result'][i]['subjects'][0]['subject'] not in d_face: + # 记录每人第一次人脸识别记录 + d_face[result['result'][i]['subjects'][0]['subject']] = time.time() + self.new_face = True + print( + f'\n新人打卡, 员工名:{result["result"][i]["subjects"][0]["subject"]} ,相似度:{result["result"][i]["subjects"][0]["similarity"]},打卡时间{datetime.datetime.now()}') + uuid_str = str(uuid.uuid4())[:6] + str(int(time.time())) + img_object_name = f"{save_path}{result['result'][i]['subjects'][0]['subject']}_{uuid_str}_{vod_channelNo}.jpg" + cv2.imwrite(img_object_name, self.frame) + # print(f'\n/home/admin-root/haotian/jingzhu1.1/tensorrtx-master/yolov8/attendance/{datetime.datetime.now().date()}.txt') + with open( + f'{people_save_path}{datetime.datetime.now().date()}.txt', + 'a') as f: + f.write( + f'新人打卡, 员工名:{result["result"][i]["subjects"][0]["subject"]} ,相似度:{result["result"][i]["subjects"][0]["similarity"]},打卡时间{datetime.datetime.now()} \n') + else: + pass + # print('\r模糊帧丢弃', end='') + + if len(warning) > p_s_num: + p_s_num = len(warning) + uuid_str = str(uuid.uuid4())[:6] + str(int(time.time())) # 生成UUID的前6位,前6位,有不小的概率重复,再加上时间戳。 + img_object_name = f"{save_path}face_{uuid_str}_{vod_channelNo}.jpg" + cv2.imwrite(img_object_name, self.frame) + + # with open(img_object_name, 'rb') as file_data: + # file_data.seek(0, os.SEEK_END) + # file_size = file_data.tell() + # file_data.seek(0) + # self.minio_client.put_object(configData['minioConfig']['bucket_name'], + # f'{uuid_str}_{vod_channelNo}_stranger_.jpg', file_data, + # file_size) + + # os.remove(img_object_name) + upload_http_url_img = configData['minioConfig'][ + 'bucket_name'] + f'/{uuid_str}_{vod_channelNo}_stranger_.jpg' + # send_post_request(putMessageUrl, self.token, '陌生人警告', upload_http_url_img, '') + print('\n陌生人警告') + #print('陌生人图片保存完成') + elif len(warning) < p_s_num: + p_s_num = len(warning) + + self.new_face = False + + #print(f'\r人脸识别任务完成,完成时间:{time.time() - now_time}', end='') + # if blur > 750: + # + # else: + # print('\r模糊帧不做人脸识别处理', end='') + + + +class NewSaveAndUploadMP4Thread(threading.Thread): + def __init__(self, ip, frame_buffer, save_type, token, msg_list = None): + ''' + + :param ip: 摄像头标识 + :param frame_buffer: 异常帧缓存 + :param save_type: 保存类型 + ''' + threading.Thread.__init__(self) + self.ip = ip + # 初始化时将异常帧转换为队列,你单纯的赋值其实是一个对象引用,若源对象变了,这里也会变。 + # 线程start后可能不会立即得到执行,若没执行,源队列被清空了,这里的队列也会变空? + # 所以这里先将其转换成list存储。 + self.frames = list(frame_buffer.queue) + self.save_type = save_type + self.minio_client = client + self.token = token + self.msg_list = msg_list + + def run(self): + # 所以这里是可能出现重复的啊,300次生成有10次会重复。。加个时间戳吧 + now_time = str(int(time.time())) + uuid_str = str(uuid.uuid4())[:6] + now_time # 生成UUID的前6位 + + + + # 将异常帧存储到列表中 + if self.save_type == 'picture': + print("图片上传消息", self.msg_list, "视频长度: ", len(self.frames)) + + first_frame = self.frames[0] + img_file_path = f"{save_path}{uuid_str}_{vod_channelNo}_.jpg" + cv2.imwrite(img_file_path, first_frame) + + # with open(img_file_path, 'rb') as file_data: + # file_data.seek(0, os.SEEK_END) + # file_size = file_data.tell() + # file_data.seek(0) + # self.minio_client.put_object(configData['minioConfig']['bucket_name'], f'{uuid_str}_{vod_channelNo}_.jpg', file_data, + # file_size) + + upload_http_url_img = configData['minioConfig']['bucket_name'] + f'/{uuid_str}_{vod_channelNo}_.jpg' + + msg = self.msg_list[0] if len(self.msg_list) > 0 else "" + + # upload_http_url_img = configData['minioConfig']['bucket_name'] + f'/{uuid_str}_{vod_channelNo}_.jpg' + # send_post_request(putMessageUrl, self.token, msg, upload_http_url_img, '') + # os.remove(img_object_name) + print('\n上传图片完成') + self.msg_list.clear() + elif self.save_type == 'video': + print("视频上传消息", self.msg_list, "视频长度: ", len(self.frames)) + + mp4_file_path = f'{save_path}{uuid_str}_{vod_channelNo}_.mp4' + height, width, _ = self.frames[0].shape + + # 保存视频 + out = cv2.VideoWriter(mp4_file_path, cv2.VideoWriter_fourcc(*'mp4v'), 25, (width, height), isColor=True) + for frame in self.frames: + out.write(frame) + out.release() + + mp4_object_name = f"{uuid_str}_{vod_channelNo}_.mp4" + + # self.temp_file_path = os.path.abspath(mp4_file_path) + self.temp_file_size = os.path.getsize(mp4_file_path) + + # with open(mp4_file_path, 'rb') as file_data: + # #上传到minio + # self.minio_client.put_object(configData['minioConfig']['bucket_name'], mp4_object_name, file_data, + # self.temp_file_size) + + + upload_http_url_mp4 = configData['minioConfig']['bucket_name'] + f'/{uuid_str}_{vod_channelNo}_.mp4' + msg = self.msg_list[0] + # 上传警告。 + # send_post_request(putMessageUrl, self.token, msg, '', upload_http_url_mp4) + # os.remove(mp4_file_path) + print('\n上传视频成功') + self.msg_list.clear() + + else: + print('\n异常类型') + + + + +class FramePushThread(threading.Thread): + def __init__(self, frame_buffer, process_mid): + threading.Thread.__init__(self) + self.frame_buffer = frame_buffer + self.process_mid = process_mid + + def run(self): + while True: + frame_data = self.frame_buffer.get() + self.process_mid.stdin.write(frame_data) + + +class YoLov8TRT(object): + + def __init__(self, engine_file_path): + # Create a Context on this device, + self.ctx = cuda.Device(0).make_context() + stream = cuda.Stream() + TRT_LOGGER = trt.Logger(trt.Logger.INFO) + runtime = trt.Runtime(TRT_LOGGER) + + # Deserialize the engine from file + with open(engine_file_path, "rb") as f: + engine = runtime.deserialize_cuda_engine(f.read()) + context = engine.create_execution_context() + + host_inputs = [] + cuda_inputs = [] + host_outputs = [] + cuda_outputs = [] + bindings = [] + + for binding in engine: + print('bingding:', binding, engine.get_binding_shape(binding)) + size = trt.volume(engine.get_binding_shape(binding)) * engine.max_batch_size + dtype = trt.nptype(engine.get_binding_dtype(binding)) + # Allocate host and device buffers + host_mem = cuda.pagelocked_empty(size, dtype) + cuda_mem = cuda.mem_alloc(host_mem.nbytes) + # Append the device buffer to device bindings. + bindings.append(int(cuda_mem)) + # Append to the appropriate list. + if engine.binding_is_input(binding): + self.input_w = engine.get_binding_shape(binding)[-1] + self.input_h = engine.get_binding_shape(binding)[-2] + host_inputs.append(host_mem) + cuda_inputs.append(cuda_mem) + else: + host_outputs.append(host_mem) + cuda_outputs.append(cuda_mem) + + # Store + self.stream = stream + self.context = context + self.engine = engine + self.host_inputs = host_inputs + self.cuda_inputs = cuda_inputs + self.host_outputs = host_outputs + self.cuda_outputs = cuda_outputs + self.bindings = bindings + self.batch_size = engine.max_batch_size + + def infer(self, image_frame): + threading.Thread.__init__(self) + # Make self the active context, pushing it on top of the context stack. + self.ctx.push() + # Restore + stream = self.stream + context = self.context + engine = self.engine + host_inputs = self.host_inputs + cuda_inputs = self.cuda_inputs + host_outputs = self.host_outputs + cuda_outputs = self.cuda_outputs + bindings = self.bindings + # Do image preprocess + batch_image_raw = [] + batch_origin_h = [] + batch_origin_w = [] + batch_input_image = np.empty(shape=[self.batch_size, 3, self.input_h, self.input_w]) + # 方法遍历由 raw_image_generator 生成的图像,对每个图像执行预处理,并收集处理后的图像数据以及原始图像的尺寸。 + # 预处理可能包括调整大小、标准化等步骤,为推理准备适当的输入格式。 + # for i, image_raw in enumerate(image_frame): + input_image, image_raw, origin_h, origin_w = self.preprocess_image(image_frame) + batch_image_raw.append(image_frame) + batch_origin_h.append(origin_h) + batch_origin_w.append(origin_w) + np.copyto(batch_input_image[0], input_image) + batch_input_image = np.ascontiguousarray(batch_input_image) + + # Copy input image to host buffer + np.copyto(host_inputs[0], batch_input_image.ravel()) + # 记录推理开始时间 + # start = time.time() + # 将输入数据传输到GPU + cuda.memcpy_htod_async(cuda_inputs[0], host_inputs[0], stream) + # 执行异步推理 + context.execute_async(batch_size=self.batch_size, bindings=bindings, stream_handle=stream.handle) + # 将预测结果传回主机 + cuda.memcpy_dtoh_async(host_outputs[0], cuda_outputs[0], stream) + # 同步CUDA流 + stream.synchronize() + + # 并移除上下文 + self.ctx.pop() + output = host_outputs[0] + + # 后处理和结果解析: + for i in range(self.batch_size): + # for循环中 调用self.post_process方法进行后处理,提取结果框、得分和类别ID。 此处不需要特别注意 了解获得返回结果即可 + result_boxes, result_scores, result_classid = self.post_process( + output[i * 38001: (i + 1) * 38001], batch_origin_h[i], batch_origin_w[i] + ) + result_list = 0 + result_boxes_list = list() + msg_list = list() + for j in range(len(result_boxes)): + box = result_boxes[j] + # !!!!!!!!!!!这个推理的地方 后续可判断一张图片中是否需要保存 若保存调用保存方法 + # plot_one_box 方法在原始图像上绘制检测到的每个对象的边界框和标签 + t = int(result_classid[j]) + # print("t: ", t) + if t in testclasses: + result, msg = plot_one_box( + box, + batch_image_raw[i], + label="{}".format(categories[t]) + + ) + # print("result: ", result) + # 将检测结果保存到minio中 + result_list += result + if result == 1: + result_boxes_list.append(box) + msg_list.append(msg) + return batch_image_raw, result_list, result_boxes_list, msg_list + + def destroy(self): + # Remove any context from the top of the context stack, deactivating it. + self.ctx.pop() + + def get_raw_image_zeros(self, image_path_batch=None): + """ + description: Ready data for warmup + """ + for _ in range(self.batch_size): + yield np.zeros([self.input_h, self.input_w, 3], dtype=np.uint8) + + def preprocess_image(self, raw_bgr_image): + image_raw = raw_bgr_image + h, w, c = image_raw.shape + image = cv2.cvtColor(image_raw, cv2.COLOR_BGR2RGB) + # Calculate widht and height and paddings + r_w = self.input_w / w + r_h = self.input_h / h + if r_h > r_w: + tw = self.input_w + th = int(r_w * h) + tx1 = tx2 = 0 + ty1 = int((self.input_h - th) / 2) + ty2 = self.input_h - th - ty1 + else: + tw = int(r_h * w) + th = self.input_h + tx1 = int((self.input_w - tw) / 2) + tx2 = self.input_w - tw - tx1 + ty1 = ty2 = 0 + # Resize the image with long side while maintaining ratio + image = cv2.resize(image, (tw, th)) + # Pad the short side with (128,128,128) + image = cv2.copyMakeBorder( + image, ty1, ty2, tx1, tx2, cv2.BORDER_CONSTANT, None, (128, 128, 128) + ) + image = image.astype(np.float32) + # Normalize to [0,1] + image /= 255.0 + # HWC to CHW format: + image = np.transpose(image, [2, 0, 1]) + # CHW to NCHW format + image = np.expand_dims(image, axis=0) + # Convert the image to row-major order, also known as "C order": + image = np.ascontiguousarray(image) + return image, image_raw, h, w + + def xywh2xyxy(self, origin_h, origin_w, x): + """ + description: Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right + param: + origin_h: height of original image + origin_w: width of original image + x: A boxes numpy, each row is a box [center_x, center_y, w, h] + return: + y: A boxes numpy, each row is a box [x1, y1, x2, y2] + """ + y = np.zeros_like(x) + r_w = self.input_w / origin_w + r_h = self.input_h / origin_h + if r_h > r_w: + y[:, 0] = x[:, 0] + y[:, 2] = x[:, 2] + y[:, 1] = x[:, 1] - (self.input_h - r_w * origin_h) / 2 + y[:, 3] = x[:, 3] - (self.input_h - r_w * origin_h) / 2 + y /= r_w + else: + y[:, 0] = x[:, 0] - (self.input_w - r_h * origin_w) / 2 + y[:, 2] = x[:, 2] - (self.input_w - r_h * origin_w) / 2 + y[:, 1] = x[:, 1] + y[:, 3] = x[:, 3] + y /= r_h + + return y + + def post_process(self, output, origin_h, origin_w): + # Get the num of boxes detected + num = int(output[0]) + # Reshape to a two dimentional ndarray + pred = np.reshape(output[1:], (-1, 38))[:num, :] + # Do nms + boxes = self.non_max_suppression(pred, origin_h, origin_w, conf_thres=CONF_THRESH, nms_thres=IOU_THRESHOLD) + result_boxes = boxes[:, :4] if len(boxes) else np.array([]) + result_scores = boxes[:, 4] if len(boxes) else np.array([]) + result_classid = boxes[:, 5] if len(boxes) else np.array([]) + return result_boxes, result_scores, result_classid + + def bbox_iou(self, box1, box2, x1y1x2y2=True): + """ + description: compute the IoU of two bounding boxes + param: + box1: A box coordinate (can be (x1, y1, x2, y2) or (x, y, w, h)) + box2: A box coordinate (can be (x1, y1, x2, y2) or (x, y, w, h)) + x1y1x2y2: select the coordinate format + return: + iou: computed iou + box_iou + """ + if not x1y1x2y2: + # Transform from center and width to exact coordinates + b1_x1, b1_x2 = box1[:, 0] - box1[:, 2] / 2, box1[:, 0] + box1[:, 2] / 2 + b1_y1, b1_y2 = box1[:, 1] - box1[:, 3] / 2, box1[:, 1] + box1[:, 3] / 2 + b2_x1, b2_x2 = box2[:, 0] - box2[:, 2] / 2, box2[:, 0] + box2[:, 2] / 2 + b2_y1, b2_y2 = box2[:, 1] - box2[:, 3] / 2, box2[:, 1] + box2[:, 3] / 2 + else: + # Get the coordinates of bounding boxes + b1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3] + b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3] + + # Get the coordinates of the intersection rectangle + inter_rect_x1 = np.maximum(b1_x1, b2_x1) + inter_rect_y1 = np.maximum(b1_y1, b2_y1) + inter_rect_x2 = np.minimum(b1_x2, b2_x2) + inter_rect_y2 = np.minimum(b1_y2, b2_y2) + # Intersection area + inter_area = np.clip(inter_rect_x2 - inter_rect_x1 + 1, 0, None) * \ + np.clip(inter_rect_y2 - inter_rect_y1 + 1, 0, None) + # Union Area + b1_area = (b1_x2 - b1_x1 + 1) * (b1_y2 - b1_y1 + 1) + b2_area = (b2_x2 - b2_x1 + 1) * (b2_y2 - b2_y1 + 1) + + iou = inter_area / (b1_area + b2_area - inter_area + 1e-16) + + return iou + + def non_max_suppression(self, prediction, origin_h, origin_w, conf_thres=0.5, nms_thres=0.4): + boxes = prediction[prediction[:, 4] >= conf_thres] + boxes[:, :4] = self.xywh2xyxy(origin_h, origin_w, boxes[:, :4]) + boxes[:, 0] = np.clip(boxes[:, 0], 0, origin_w - 1) + boxes[:, 2] = np.clip(boxes[:, 2], 0, origin_w - 1) + boxes[:, 1] = np.clip(boxes[:, 1], 0, origin_h - 1) + boxes[:, 3] = np.clip(boxes[:, 3], 0, origin_h - 1) + confs = boxes[:, 4] + boxes = boxes[np.argsort(-confs)] + + keep_boxes = [] + while boxes.shape[0]: + large_overlap = self.bbox_iou(np.expand_dims(boxes[0, :4], 0), boxes[:, :4]) > nms_thres + label_match = boxes[0, -1] == boxes[:, -1] + # Indices of boxes with lower confidence scores, large IOUs and matching labels + invalid = large_overlap & label_match + keep_boxes += [boxes[0]] + boxes = boxes[~invalid] + boxes = np.stack(keep_boxes, 0) if len(keep_boxes) else np.array([]) + return boxes + +def get_attendance_p(): + # 万一今天重启了,直接从文件中读取以打卡的人数,避免重复打卡 + global d_face + try: + with open(f'{people_save_path}{datetime.datetime.now().date()}.txt','r') as f: + for line in f: + d_face[line.strip().split(' ')[1][4:]] = 1 + except: + pass + + +def connect_to_rtsp_stream(url): + """ 尝试连接到 RTSP 流 """ + cap = cv2.VideoCapture(url) + cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'HEVC')) + if not cap.isOpened(): + print(f"Failed to connect to {url}") + return None + return cap + + +class rtspInputFrame(threading.Thread): + def __init__(self, video_path, rtsp_frame_buffer): + threading.Thread.__init__(self) + self.video_path = video_path + self.rtsp_frame_buffer = rtsp_frame_buffer # rtsp输入缓冲区的队列 + + def run(self): + cap = connect_to_rtsp_stream(self.video_path) + while True: + + if cap is None: + cap = connect_to_rtsp_stream(self.video_path) + time.sleep(5) + else: + # 从视频流中读取一帧 + ret, frame = cap.read() + # 如果帧读取成功,则 ret 为 True + if not ret: + print("无法读取帧,尝试重新连接...") + # 关闭当前的 VideoCapture 对象 + cap.release() + # 尝试重新连接 + while True: + cap = connect_to_rtsp_stream(self.video_path) + if cap is not None: + break + # 等待一段时间后再尝试连接 + time.sleep(5) # 休眠5秒 + + else: + if rtsp_frame_buffer.full(): + continue # 如果队列满了,就丢弃最早的帧 这个地方可能有线程安全问题 + frame = cv2.resize(frame, (1280, 720)) + rtsp_frame_buffer.put(frame) + # 释放资源 + cap.release() + cv2.destroyAllWindows() + + +class inferThread(threading.Thread): + def __init__(self, yolov8_wrapper, video_path, ip, rtsp_frame_buffer, tokenResult, ffmpeg_buffer_size=300, + save_buffer_size=2500): + threading.Thread.__init__(self) + self.ip = ip + self.yolov8_wrapper = yolov8_wrapper + self.video_path = video_path + self.rtsp_frame_buffer = rtsp_frame_buffer + self.tokenResult = tokenResult + self.ffmpeg_buffer_size = ffmpeg_buffer_size + self.ffmpeg_frame_buffer = queue.Queue(maxsize=ffmpeg_buffer_size) # ffmpeg输出到m3u6的队列,输出切片缓冲区用于存储视频帧 + self.save_frame_buffer = queue.Queue(maxsize=save_buffer_size) # 用于保存视频的缓存 + + self.msg_buffer = list() # 保存报错原因 + + self.frame_count = 0 # 帧计数器 + self.start_warning_time = None # 记录告警开始时间 + self.latest_warning_time = None # 记录告警结束时间 + self.old_update_warning_time = time.time() - 3600 # 上一次成功告警的时间 + + # 两帧一次目标检测,5帧一次人脸识别,时间不够啊 + self.det_gap = 0 + self.face_gap = 0 + + self.p_num_error = 0 + + # 上一帧的检测框作为没有检测帧的默认结果。只要位置不发生太大变化,结果不会差别太大。 + self.p_box_list = list() + + def run(self): + + print('检测线程启动') + + frame_push_thread = FramePushThread(self.ffmpeg_frame_buffer, pipeline_mid) + frame_push_thread.start() + + # 存的是目标检测的上一次结果图片 + previous_frame = None + previous_fram_face = None + # save_flag = [] + self.tokenResult['token'] = 'token' + self.tokenResult['current_time'] = '2024-01-01 00:00:00' + + # 这里没必要每一帧都检测吧,直接隔1帧检测一帧. + while True: + frame = rtsp_frame_buffer.get() + self.face_gap += 1 + self.det_gap += 1 + # 人脸识别 + if self.face_gap > 4: + # 上传token + # token = get_token(self.tokenResult) + token = {} + # 直接将帧加入到ffmpeg缓存中 + ret, jpeg_frame = cv2.imencode('.jpg', frame) + self.ffmpeg_frame_buffer.put(jpeg_frame.tobytes()) + if previous_fram_face is not None: + result_same = compare_frames(previous_fram_face, frame, 100) + else: + result_same = False + if not result_same: + face_save = FaceRecUpload(self.ip, frame, token) + face_save.start() + # print('\r 人脸识别任务', end='') + self.face_gap = 0 + # pass + # 异常检测 + elif self.det_gap > 1: + self.det_gap = 0 + if previous_frame is not None: + result_same = compare_frames(previous_frame, frame, 100) + else: + result_same = False + + if not result_same: + + # token = get_token(self.tokenResult) + # print(token) + token = {} + + batch_image_raw, r_list, box_list, msg_list = self.yolov8_wrapper.infer(frame) + + # print("r_list: ", r_list) + # print("p_num_error", self.p_num_error) + + # 上一帧检测结果 + self.p_box_list = box_list + + previous_frame = frame + + # 将处理完的帧加入到 ffmpeg缓存中 + ret, jpeg_frame = cv2.imencode('.jpg', frame) + self.ffmpeg_frame_buffer.put(jpeg_frame.tobytes()) + + now_time = time.time() + + if r_list > self.p_num_error: + self.p_num_error = r_list + self.save_frame_buffer.put(frame) + self.msg_buffer.append(msg_list[0]) + self.frame_count += 1 + print(f'\n异常人数增长, 当前异常帧数量: {self.frame_count}', end='') + + + # 开始时间记录 + self.start_warning_time = now_time + + if self.save_frame_buffer.full(): + + print('\n告警队列满-执行保存') + save_thread = NewSaveAndUploadMP4Thread(self.ip, self.save_frame_buffer, 'video', token, self.msg_buffer) + save_thread.start() + + self.frame_count = 0 + self.save_frame_buffer.queue.clear() + # self.msg_buffer.clear() + + self.start_warning_time = None + + # 记录当前警告时间 + self.latest_warning_time = now_time + + # 当前异常人数不变。 + elif r_list == self.p_num_error and self.p_num_error != 0: + + # token = get_token(self.tokenResult) + token = {} + + # 记录当前警告时间 + self.latest_warning_time = now_time + + # 判断当前的报警时间是否在10秒内 + upload = verify_timenum(self.start_warning_time, now_time, 10) + + # 在10秒内,将当前异常帧加入异常缓存 + if upload: + self.save_frame_buffer.put(frame) + + self.frame_count += 1 + print(f'\r检测到异常,异常开始时间{self.start_warning_time} ,将当前异常帧加入异常帧缓存,当前异常数量:{self.p_num_error},当前异常帧数量: {self.frame_count} ' + f'', end='') + + # 异常帧缓存满了 + if self.save_frame_buffer.full(): + print('\n告警缓存满,保存为视频') + + # 上传视频/图片 + save_thread = NewSaveAndUploadMP4Thread(self.ip, self.save_frame_buffer, 'video', token, self.msg_buffer) + save_thread.start() + # save_flag.clear() + self.frame_count = 0 + self.save_frame_buffer.queue.clear() + # self.msg_buffer.clear() + # self.old_update_warning_time = now_time + self.start_warning_time = now_time + + else: + + #print(f'\r检测到异常帧,但异常人数没变化,当前异常人数为{r_list}。', end='') + + if not self.save_frame_buffer.empty(): + print('\n10s 视频开始保存') + save_thread = NewSaveAndUploadMP4Thread(self.ip, self.save_frame_buffer, + 'video', token, self.msg_buffer) + save_thread.start() + # save_flag.clear() + self.frame_count = 0 + self.save_frame_buffer.queue.clear() + # self.msg_buffer.clear() + # self.old_update_warning_time = now_time + self.start_warning_time = now_time - 11 + + elif r_list == self.p_num_error and self.p_num_error == 0: + pass + #print(f'\r无异常', end='') + elif r_list < self.p_num_error: + # 间隔小于4秒的认为是误判 + change_flag = verify_timenum(self.latest_warning_time, now_time, 4) + if not change_flag: + + self.p_num_error = r_list + print(f'\n异常人数减少,当前异常人数:{self.p_num_error},当前异常帧数量:{self.frame_count}') + + # 将当前缓存中的异常帧上传 + if self.save_frame_buffer.qsize()> 20: + + # 4秒后才保存视频,这时msg_list已经是空的了. + save_thread = NewSaveAndUploadMP4Thread(self.ip, self.save_frame_buffer, 'video', token, self.msg_buffer) + save_thread.start() + elif self.save_frame_buffer.qsize()> 0: + save_thread = NewSaveAndUploadMP4Thread(self.ip, self.save_frame_buffer, 'picture', token, self.msg_buffer) + save_thread.start() + + + self.frame_count = 0 + self.save_frame_buffer.queue.clear() + # self.msg_buffer.clear() + + self.start_warning_time = None + else: + pass + # print('\r程序可能的误判', end='') + else: + # 一样的帧直接加入到ffmpeg缓存中 + # print('\r相同帧不需要推理', end='') + ret, jpeg_frame = cv2.imencode('.jpg', frame) + self.ffmpeg_frame_buffer.put(jpeg_frame.tobytes()) + else: + # 什么都不干的帧,直接以上一次的推理结果画框。 + for i in range(len(self.p_box_list)): + c1, c2 = (int(self.p_box_list[i][0]), int(self.p_box_list[i][1])), (int(self.p_box_list[i][2]), int(self.p_box_list[i][3])) + cv2.rectangle(frame, c1, c2, (0, 0, 255), 2) + uuid_str = str(uuid.uuid4())[:6] + str(int(time.time())) + # img_object_name = f"/home/admin-root/haotian/jingzhu1.1/tensorrtx-master/yolov8/mp4/skip_{uuid_str}_{vod_channelNo}.jpg" + # cv2.imwrite(img_object_name, frame) + ret, jpeg_frame = cv2.imencode('.jpg', frame) + self.ffmpeg_frame_buffer.put(jpeg_frame.tobytes()) + + + + + +if __name__ == "__main__": + print( + "=============================================================================================================") + # load custom plugin and engine + PLUGIN_LIBRARY = f"{engine_path}libmyplugins.so" + engine_file_path = f"{engine_path}best.engine" + # time.sleep() + + # 执行python代码命令行 参数判断操作(不做处理) + if len(sys.argv) > 1: + engine_file_path = sys.argv[1] + if len(sys.argv) > 2: + PLUGIN_LIBRARY = sys.argv[2] + clear_folder(ip) + # 加载动态链接库 + ctypes.CDLL(PLUGIN_LIBRARY) + # 手动输入训练时的类别, 不需要了直接从配置文件中读 + print("当前类别", categories) + # categories = ["shoe"] + # 加载模型文件 + yolov8_wrapper1 = YoLov8TRT(engine_file_path) + + # 重启后自动根据文件,加载当天打卡信息。根据程序设置,每天凌晨0点更新打卡数据 + get_attendance_p() + + try: + thread_rtsp = rtspInputFrame(vod_path, + rtsp_frame_buffer) + thread_det_1 = inferThread(yolov8_wrapper1, vod_path, ip, rtsp_frame_buffer, tokenResult) + thread_rtsp.start() + thread_det_1.start() + thread_rtsp.join() + thread_det_1.join() + + finally: + # destroy the instance + thread_det_1.destroy() diff --git a/scripts/mock_alarm_server.py b/scripts/mock_alarm_server.py new file mode 100755 index 0000000..717420e --- /dev/null +++ b/scripts/mock_alarm_server.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +""" +模拟告警服务器 +用于测试 RK3588 Media Server 的 alarm 节点 external_api 功能 + +提供两个接口: +1. POST /api/getToken - 获取访问令牌 +2. POST /api/putMessage - 接收告警信息 + +使用方法: + python3 mock_alarm_server.py + +默认监听:0.0.0.0:8080 +""" + +from flask import Flask, request, jsonify +from datetime import datetime +import json +import sys + +app = Flask(__name__) + +# 模拟 token 存储 +mock_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.mock_token_for_testing" +token_expire_time = 30 * 60 # 30分钟过期时间(秒) + +# 统计信息 +stats = { + "token_requests": 0, + "alarm_requests": 0, + "last_alarm": None +} + + +@app.route('/api/getToken', methods=['POST']) +def get_token(): + """ + 模拟获取 token 接口 + 返回格式与 d8_1.py 中一致 + """ + stats["token_requests"] += 1 + + # 打印请求信息 + print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 收到 Token 请求") + print(f" 请求头: {dict(request.headers)}") + print(f" 请求体: {request.data.decode('utf-8') if request.data else 'None'}") + + response = { + "errorDesc": None, + "message": None, + "responseBody": { + "userInfo": { + "username": "szls", + "realname": "数字孪生", + "deptName": "精密铸造厂", + "id": "8a746b7d91deb3270191df35f42e000e" + }, + "expireTime": str(token_expire_time), + "token": mock_token, + "refreshToken": mock_token + "_refresh" + }, + "retCode": "200" + } + + print(f" 响应: 返回 mock token") + return jsonify(response) + + +@app.route('/api/putMessage', methods=['POST']) +def put_message(): + """ + 模拟接收告警信息接口 + 接收格式与 d8_1.py 中 send_post_request 一致 + """ + stats["alarm_requests"] += 1 + + # 获取请求头中的 token + token = request.headers.get('X-Access-Token', 'None') + + # 解析请求体 + try: + data = request.json if request.is_json else json.loads(request.data) + except: + data = {} + + # 打印告警信息 + print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 收到告警信息") + print(f" Token: {token[:30]}...") + print(f" 租户代码: {data.get('tenantCode', 'N/A')}") + print(f" 频道号: {data.get('channelNo', 'N/A')}") + print(f" 告警内容: {data.get('alarmContent', 'N/A')}") + print(f" 告警时间: {data.get('alarmTime', 'N/A')}") + + # 打印图片/视频信息 + pic_info = data.get('picInfo', []) + video_info = data.get('videoInfo', []) + + if pic_info: + print(f" 图片地址: {len(pic_info)} 张") + for i, pic in enumerate(pic_info[:3]): # 只打印前3个 + print(f" [{i+1}] {pic.get('url', 'N/A')}") + if len(pic_info) > 3: + print(f" ... 还有 {len(pic_info) - 3} 张") + + if video_info: + print(f" 视频地址: {len(video_info)} 个") + for i, video in enumerate(video_info[:3]): + print(f" [{i+1}] {video.get('url', 'N/A')}") + + # 更新统计 + stats["last_alarm"] = { + "time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + "content": data.get('alarmContent', 'N/A'), + "channel": data.get('channelNo', 'N/A') + } + + # 返回成功响应 + response = { + "responseBody": "1", + "message": None, + "retCode": "200", + "errorDesc": None + } + + print(f" 响应: 告警接收成功") + return jsonify(response) + + +@app.route('/stats', methods=['GET']) +def get_stats(): + """查看统计信息""" + return jsonify({ + "token_requests": stats["token_requests"], + "alarm_requests": stats["alarm_requests"], + "last_alarm": stats["last_alarm"] + }) + + +@app.route('/', methods=['GET']) +def index(): + """首页说明""" + return """ +

RK3588 模拟告警服务器

+

可用接口:

+ +

当前统计:

+ + """.format(**stats) + + +def main(): + host = "0.0.0.0" + port = 8080 + + print("=" * 60) + print("RK3588 模拟告警服务器") + print("=" * 60) + print(f"启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"监听地址: http://{host}:{port}") + print("") + print("接口列表:") + print(f" 1. POST http://{host}:{port}/api/getToken") + print(f" 2. POST http://{host}:{port}/api/putMessage") + print(f" 3. GET http://{host}:{port}/stats") + print("") + print("按 Ctrl+C 停止服务") + print("=" * 60) + + try: + app.run(host=host, port=port, debug=False, threaded=True) + except KeyboardInterrupt: + print("\n\n服务已停止") + print(f"总计 Token 请求: {stats['token_requests']}") + print(f"总计告警请求: {stats['alarm_requests']}") + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/yolov8n_cxn.onnx b/yolov8n_cxn.onnx index 8c802a1..c64cd0e 100644 Binary files a/yolov8n_cxn.onnx and b/yolov8n_cxn.onnx differ