From f36e6564c2a14d24fcabd671dc855255ef44db76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=97=AB=E6=B5=A9=E9=9C=96?= <2103200855@qq.com> Date: Mon, 26 Aug 2024 16:50:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=E4=B8=9C=E5=8C=97?= =?UTF-8?q?=E5=A4=A7=E5=AD=A6=E7=A7=A6=E7=9A=87=E5=B2=9B=E5=88=86=E6=A0=A1?= =?UTF-8?q?=5F=E7=99=BD=E5=A1=94=E5=B2=AD143=E9=98=9F=5F=E7=99=BE=E5=BA=A6?= =?UTF-8?q?=E6=99=BA=E6=85=A7=E4=BA=A4=E9=80=9A.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...Œ—大学秦皇岛分校_白塔岭143队_百度智慧交通.md | 240 +++++++++++++++++- 1 file changed, 237 insertions(+), 3 deletions(-) diff --git a/东北大学秦皇岛分校_白塔岭143队_百度智慧交通.md b/东北大学秦皇岛分校_白塔岭143队_百度智慧交通.md index 2b2bf4b..b50f66f 100644 --- a/东北大学秦皇岛分校_白塔岭143队_百度智慧交通.md +++ b/东北大学秦皇岛分校_白塔岭143队_百度智慧交通.md @@ -81,12 +81,14 @@ z 轴使用带éœå°”ç¼–ç å™¨çš„蜗æ†ç”µæœºï¼Œè¯¥ç”µæœºå‡é€Ÿæœºå…·æœ‰è‡ªé”特 在程åºå®žé™…è¿è¡Œä¸­ï¼Œæˆ‘们å‘现当推ç†å¼€å¯æ—¶ï¼Œä¸€ä¸ªæ¨¡åž‹çš„进程需è¦å ç”¨ 2G å·¦å³çš„内存,å³ä½¿æ˜¯åœ¨ä»£ç ä¸­å¼€å¯äº†å†…存优化,并且指定大å°ï¼Œå®žé™…å ç”¨å¹¶æ²¡æœ‰æ˜Žæ˜¾åŒºåˆ«ã€‚当开å¯å¤šä¸ªæŽ¨ç†è¿›ç¨‹æ—¶ï¼Œä¼šå¯¼è‡´å†…å­˜å æ»¡ï¼Œä½¿å¾—æŸäº›è¿›ç¨‹è¢« kill 掉。由于规则é™åˆ¶æœ€å¤§åªèƒ½ä½¿ç”¨ 8G 内存的 nano æ¿å¡ï¼Œæ‰€ä»¥æŒ‚载了一个较大的 swap 分区,使用ç£ç›˜ç¼“å­˜ä¿è¯ä¸ä¼šå‡ºçŽ°ç‰©ç†å†…å­˜å ç”¨è¿‡é«˜çš„æƒ…å†µï¼Œå®žé™…æµ‹è¯•ä¸‹ï¼Œæœªè§æ˜Žæ˜¾çš„æ€§èƒ½å½±å“。 -此外,为了ä¿è¯å¼€æœºè‡ªå¯çš„æ•ˆæžœï¼Œæˆ‘们构建了一个守护进程,使用 systemctl 管ç†ã€‚ +此外,为了ä¿è¯å¼€æœºè‡ªå¯çš„æ•ˆæžœï¼Œæˆ‘们构建了一个守护进程,使用 systemctl ç®¡ç†æˆ‘们的人机交互程åºã€‚ ### ä¸Šä½æœºç¨‹åºæ€»ä½“设计 为了ä¿è¯ç¨‹åºçš„高效è¿è¡Œï¼Œä¸Šä½æœºç¨‹åºæ€»ä½“采用“生产者-æ¶ˆè´¹è€…â€æ¨¡åž‹ï¼Œæ‰€æœ‰è¿›ç¨‹ç”±å®ˆæŠ¤è¿›ç¨‹ç®¡ç†ï¼Œé¦–先由图åƒé‡‡é›†è¿›ç¨‹é‡‡é›†å›¾åƒå¹¶å‚¨å­˜åœ¨ç¼“冲区中;推ç†è¿›ç¨‹ä»Žç¼“冲区获å–图åƒï¼ŒæŽ¨ç†å®ŒæˆåŽå°†æŽ¨ç†ç»“æžœå‚¨å­˜ï¼›æŽ§åˆ¶çº¿ç¨‹éœ€è¦æŽ¨ç†ç»“æžœæ—¶ï¼Œå‘æŽ¨ç†æœåŠ¡å™¨è¯·æ±‚æš‚å­˜çš„æŽ¨ç†ç»“æžœå³å¯ï¼Œæ‰€æœ‰è¿›ç¨‹é—´è¯·æ±‚å‡ä½¿ç”¨ zmq çš„ “请求-应答†模å¼ã€‚通过上述的结构模å¼ï¼Œå¯ä»¥ä¿è¯èŽ·å–æ•°æ®æœ€æ–°ä¸”请求速度较快。 +![](/Users/snow/Desktop/百度智慧交通/TechDoc-BC2024/无标题-2024-08-26-1601.png) + ### 执行机构接å£ç¨‹åºè®¾è®¡ 执行机构接å£ä½œä¸º python 模å—导入控制进程的程åºä¸­ï¼Œæœ¬ä½“基于 C 编译æˆåЍæ€é“¾æŽ¥åº“,然åŽé€šè¿‡ cython æž„å»ºæˆ python 模å—。 @@ -95,15 +97,247 @@ z 轴使用带éœå°”ç¼–ç å™¨çš„蜗æ†ç”µæœºï¼Œè¯¥ç”µæœºå‡é€Ÿæœºå…·æœ‰è‡ªé”特 ### 图åƒé‡‡é›†æœåŠ¡å™¨ç¨‹åºè®¾è®¡ -图åƒé‡‡é›†æœåŠ¡å™¨ç¨‹åºåŸºäºŽ C/C++ 编写,是一个独立的进程。该进程使用 opencv è¯»å–æ‘„åƒå¤´å›¾åƒï¼Œæ¯ä¸ªæ‘„åƒå¤´å¯¹è±¡æž„造一个采集和应答线程,ä¿è¯èŽ·å–å’Œå“åº”çš„å›¾åƒæœ€æ–°ã€‚ +图åƒé‡‡é›†æœåŠ¡å™¨ç¨‹åºåŸºäºŽ C/C++ 编写,是一个独立的进程。该进程使用 opencv è¯»å–æ‘„åƒå¤´å›¾åƒï¼Œæ¯ä¸ªæ‘„åƒå¤´å¯¹è±¡æž„造一个采集和应答线程,ä¿è¯èŽ·å–å’Œå“åº”çš„å›¾åƒæœ€æ–°ã€‚使用了多语言支æŒçš„zeromq消æ¯ä¼ é€’库建立一个请求-å“应模型的 socket æœåŠ¡å™¨ã€‚ç›¸å…³ä»£ç å¦‚下: + +```c++ +cap = new cv::VideoCapture(index, cv::CAP_V4L2); +sleep(2); +cap->set(cv::CAP_PROP_FRAME_WIDTH, width); +cap->set(cv::CAP_PROP_FRAME_HEIGHT, height); +cap->set(cv::CAP_PROP_FPS, fps); + +context = new zmq::context_t(1); +socket = new zmq::socket_t(*context, ZMQ_REP); +char zmq_bind_port[10] = {0}; +sprintf(zmq_bind_port, "%d", port); +strcat(zmq_bind_addr, zmq_bind_port); +log_info("设置 %d zmq åœ°å€ %s", index, zmq_bind_addr); +socket->bind(zmq_bind_addr); +``` + + ### æŽ¨ç†æœåŠ¡å™¨ç¨‹åºè®¾è®¡ +在备赛过程中,我们注æ„到在å°è½¦é€šè¿‡å²”è·¯å£æ—¶éœ€è¦ä½¿ç”¨ç›®æ ‡æ£€æµ‹æ£€æµ‹å‰æ–¹çš„转å‘牌从而确定下一步å‘哪一方å‘å‰è¿›ï¼ŒåŒæ—¶å·¡çº¿æ¨¡åž‹è¿˜éœ€è¦ä¸æ–­è¯·æ±‚å‰å‘æ‘„åƒå¤´ä»¥èŽ·å–转å‘åˆ†é‡æ•°æ®ï¼Œä¸ºäº†è§£å†³è¿™ç§ä¸åŒçš„æ·±åº¦æ¨¡åž‹éœ€è¦è¯·æ±‚ä¸åŒçš„å›¾åƒæºçš„é—®é¢˜ï¼Œæˆ‘ä»¬è®¾è®¡äº†æŽ¨ç†æœåŠ¡å™¨å±‚ã€‚ + +![](无标题-2024-08-26-1600.png) + +以lane_inferå·¡çº¿æŽ¨ç† server 为例,相关代ç å¦‚下: + +```python +while True: + camera_socket.send_string("") + message = camera_socket.recv() + np_array = np.frombuffer(message, dtype=np.uint8) + frame = cv2.imdecode(np_array, cv2.IMREAD_COLOR) + frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) + result = predictor.infer(frame) + with lock: + response['data'] = result +``` + +```python +# å¤„ç† server å“åº”æ•°æ® +def server_resp(lane_infer_port): + global response + logger.info("lane server thread init success") + + context = zmq.Context() + socket = context.socket(zmq.REP) + socket.bind(f"tcp://*:{lane_infer_port}") + logger.info("lane infer server init success") + while True: + message = socket.recv_string() + with lock: + socket.send_pyobj(response) +``` + +对于yolo_infer目标检测serverï¼Œæœ‰ä¸€ä¸ªåˆ‡æ¢æ‘„åƒå¤´æºçš„过程,相关代ç å¦‚下: + +```python +def server_resp(yolo_infer_port): + logger.info("yolo server thread init success") + global response + global src_camera_id + + context = zmq.Context() + socket = context.socket(zmq.REP) + socket.bind(f"tcp://*:{yolo_infer_port}") + logger.info("yolo infer server init success") + while not exit_event.is_set(): + try: + message = socket.recv_string() + if message != '': + with lock1: + logger.error(message) + src_camera_id = int(message) + logger.info("switch camera") + socket.send_pyobj(response) + else: + with lock2: + socket.send_pyobj(response) + response['data'] = np.array([]) + except zmq.Again: + time.sleep(0.01) + + socket.close() + context.term() + +while not exit_event.is_set(): + with lock1: + try: + if src_camera_id == 1: + camera1_socket.send_string("") + message = camera1_socket.recv() + else: + camera2_socket.send_string("") + message = camera2_socket.recv() + np_array = np.frombuffer(message, dtype=np.uint8) + with lock3: + frame = cv2.imdecode(np_array, cv2.IMREAD_COLOR) + start = True + except: + time.sleep(0.01) +``` + +模型推ç†ï¼Œæˆ‘们使用原生 PaddleInference,以目标检测为例,官方的 PaddleDection 套件带有许多冗余代ç ï¼Œé™ä½Žæ‰§è¡Œæ•ˆçŽ‡ï¼Œæ‰€ä»¥æˆ‘ä»¬è‡ªå·±å®žçŽ°é¢„å¤„ç†-推ç†ç­‰è¿‡ç¨‹ã€‚ + +```python +import paddle.inference as paddle_infer +import numpy as np +import cv2 +class Yolo_model_infer: + def __init__(self, model_dir="./ppyoloe_plus_crn_t_auxhead_320_300e_coco", target_size=[320, 320]): + # åˆå§‹åŒ– paddle æŽ¨ç† + self.model_dir = model_dir + self.config = paddle_infer.Config(model_dir + "/model.pdmodel", model_dir + "/model.pdiparams") + self.config.disable_glog_info() + self.config.enable_use_gpu(500, 0) + self.config.enable_memory_optim() + self.config.switch_ir_optim() + self.config.switch_use_feed_fetch_ops(False) + self.predictor = paddle_infer.create_predictor(self.config) + self.input_names = self.predictor.get_input_names() + self.input_handle = self.predictor.get_input_handle(self.input_names[0]) + self.input_handle1 = self.predictor.get_input_handle(self.input_names[1]) + self.output_names = self.predictor.get_output_names() + self.output_handle = self.predictor.get_output_handle(self.output_names[0]) + + self.target_size = target_size + origin_shape = (240, 320) + resize_h, resize_w = self.target_size + self.im_scale_y = resize_h / float(origin_shape[0]) + self.im_scale_x = resize_w / float(origin_shape[1]) + self.scale_info = np.array([[self.im_scale_y, self.im_scale_x]]).astype('float32') + def infer(self,src) -> np.ndarray: + image = self.preprocess(src) + self.input_handle.copy_from_cpu(image) + self.input_handle1.copy_from_cpu(self.scale_info) + self.predictor.run() + results = self.output_handle.copy_to_cpu() + return results + def preprocess(self,src): + # resize + # keep_ratio=0 + img = cv2.resize( + src, + None, + None, + fx=self.im_scale_x, + fy=self.im_scale_y, + interpolation=2) + # NormalizeImage + img = img.astype(np.float32, copy=False) + scale = 1.0 / 255.0 + img *= scale + # Permute + img = img.transpose((2, 0, 1)) + img = np.array((img, )) + # .astype('float32') + return img + +``` + ### èµ›é“线回归模型概述 +官方采用的方案是采集摄åƒå¤´ç¬¬ä¸€è§†è§’+é¥æŽ§å™¨æ•°æ®æ¥å®žçŽ°æ·±åº¦å·¡çº¿ï¼Œä½†å®žé™…ä¸Šæ¨¡åž‹å¾ˆéš¾é€šè¿‡å›¾åƒæ•°æ®å­¦ä¹ åˆ°é¥æŽ§å™¨æ•°æ®çš„特å¾ï¼Œå¹¶ä¸”官方采用的模型结构过于简å•ï¼šåªæœ‰ç®€å•çš„å·ç§¯å±‚ã€æ± åŒ–层,é¢å¯¹å¤æ‚的光线æ¡ä»¶æ¨¡åž‹çš„æ³›åŒ–èƒ½åŠ›è¾ƒå·®ã€‚æˆ‘ä»¬çš„ä¸Šä½æœºé€‰ç”¨Jetson Orin Nano,内存和 GPU 推ç†èƒ½åŠ›éƒ½å¾ˆå¼ºï¼Œå› æ­¤ä¸éœ€è¦è¿‡äºŽæ‹…å¿ƒæ¨¡åž‹å‚æ•°å¤ªå¤§çš„é—®é¢˜ï¼Œæ‰€ä»¥åº”å½“é‡‡ç”¨æ¨¡åž‹å‚æ•°æ›´å¤§å’Œç»“构更加先进的网络,é¢å¯¹ä¸åŒçš„光线环境æ‰èƒ½æœ‰è¾ƒå¥½çš„æ•ˆæžœã€‚ + + + +我们设计了一个网络,输入的图片ç»è¿‡ç‰¹å¾æå–层和全连接层åŽè¾“å‡ºä¸¤ä¸ªå€¼ï¼šè·Ÿè¸ªç‚¹çš„æ¨ªçºµåæ ‡ï¼Œå›žå½’出å°è½¦éœ€è¦è·Ÿè¸ªçš„èµ›é“中点的 [x, y]åæ ‡ã€‚å› ä¸ºä¸­ç‚¹åæ ‡å’Œå›¾åƒæ•°æ®æœ‰ç€å¼ºç›¸å…³çš„特å¾ï¼Œç›¸æ¯”é¥æŽ§å™¨æ•°æ®ï¼Œæ›´åŠ å®¹æ˜“è¢«æ¨¡åž‹å­¦ä¹ åˆ°è¿™ä¸ªç‰¹å¾ã€‚并且使用 paddlepaddle,å¯ä»¥éžå¸¸æ–¹ä¾¿çš„使用很多性能更好的 backbone。 + +```python +import paddle.vision.models as models +feature_extractor = models.resnet18(pretrained=True,num_classes=0) +feature_extractor = models.resnet34(pretrained=True,num_classes=0) +feature_extractor = models.mobilenet_v3_large(pretrained=True,num_classes=0) +``` + +æˆ‘ä»¬ä½¿ç”¨æ€§èƒ½æ›´å¥½çš„æ¨¡åž‹çš„ç‰¹å¾æå–层作为我们网络的 backbone,并且通过 paddle å¯ä»¥è‡ªåŠ¨åŠ è½½é¢„è®­ç»ƒå‚æ•°ï¼Œåœ¨è®­ç»ƒç½‘络时å¯ä»¥åŠ å¿«æ”¶æ•›ã€‚ + +æ•°æ®é›†çš„åˆ¶ä½œè¿‡ç¨‹æ¯”è¾ƒå¤æ‚,首先需è¦å®žçް饿ާå°è½¦çš„åŠŸèƒ½ï¼ŒåŒæ—¶æ‰“开摄åƒå¤´ä¿å­˜æ¯ä¸€å¸§çš„图片。利用ä¿å­˜çš„图片和我们实现的打标软件标注赛é“跟踪点。 + +![](iShot_2024-08-26_15.18.21.png) + +导出的 json æ ¼å¼å¦‚下所示[{"img_path": "200.jpg", "state": [126, 191]}, {"img_path": "201.jpg", "state": [128, 191]}] + +ç»è¿‡å¤šæ¬¡å°è¯•ä¸åŒçš„ç‰¹å¾æå–层以åŠä¸åŒçš„è¶…å‚æ•°ï¼Œæœ€åŽé€‰æ‹©mobilenet_v3_largeä½œä¸ºç‰¹å¾æå–å±‚ï¼Œåœ¨æ¨¡åž‹å‚æ•°é‡å’Œæ¨¡åž‹æ€§èƒ½ä¸Šå–得最平衡的效果。 + ### 任务主程åºè®¾è®¡ 任务主程åºå°†æ¯ä¸ªä»»åŠ¡å°è£…æˆå¯¹è±¡ï¼Œæ ¹æ®å®žé™…éœ€æ±‚æ”¾å…¥é˜Ÿåˆ—ã€‚ç¨‹åºæ‰§è¡Œæ—¶ï¼Œä»»åŠ¡å¯¹è±¡ä¾æ¬¡å‡ºé˜Ÿã€‚任务的执行按照 “æœå¯»-执行-åŽå¤„ç†â€ 的方å¼è¿è¡Œï¼Œå½“执行æŸä¸€ä»»åŠ¡æ—¶ï¼Œå…ˆå¾ªçŽ¯æœå¯»ä»»åŠ¡æ ‡å¿—ï¼Œå¾…æœå¯»åˆ°å¯ä»¥è¿›å…¥ä»»åŠ¡çš„æ¡ä»¶åŽï¼Œè¿›å…¥ä»»åŠ¡æ‰§è¡Œå‡½æ•°ï¼Œå‡½æ•°å†…æ‰§è¡Œæ ¡å‡†ã€å¤¹æŒã€ä¼¸å±•等任务。执行完æˆåŽï¼Œè¿›å…¥åŽå¤„ç†é˜¶æ®µï¼Œè¯¥é˜¶æ®µå¯ä»¥è®¾ç½®ä¸‹ä¸€ä»»åŠ¡çš„ç›¸å…³å‚æ•°ï¼Œå¹¶å°†æ‰§è¡Œæœºæž„è¿åŠ¨åˆ°ä¸‹ä¸€ä»»åŠ¡çš„é¢„å¤‡ä½ç½®ã€‚如果该任务ä¸è¢«å¼€å¯ï¼Œåˆ™æ‰§è¡Œä¸€ä¸ªä»»åŠ¡ä¸å¼€å¯æ—¶æ‰è°ƒç”¨çš„函数,使执行机构è¿åŠ¨åˆ°ä¸­ç«‹ä½ç½®ï¼Œä¿è¯ä¸‹ä¸€ä»»åС开坿—¶ä¸ä¼šå‘生冲撞。 -### 守护和交互程åºè®¾è®¡ +### 交互程åºè®¾è®¡ + +为了满足日常调试的远程开å¯ã€å…³é—­å’Œæ—¥å¿—å®žæ—¶æŸ¥çœ‹ä»¥åŠæ­£å¼æ¯”赛时的自动å¯åŠ¨ç­‰éœ€æ±‚ï¼Œæˆ‘ä»¬åŸºäºŽ python flask ã€vue å’Œ element ui å¼€å‘了人机交互程åºï¼ŒåŠŸèƒ½åŒ…æ‹¬æŽ¨ç†æœåŠ¡å™¨çš„æ—¥å¿—æŸ¥çœ‹ã€å¼€å¯å’Œå…³é—­ä»¥åŠä»»åŠ¡ç¨‹åºçš„æ—¥å¿—查看ã€å¼€å¯å’Œå…³é—­ç­‰åŠŸèƒ½ã€‚ + +æŽ¨ç†æœåŠ¡å™¨å’Œä»»åŠ¡ç¨‹åºçš„å¼€å¯å’Œå…³é—­é€šè¿‡ç®¡ç†è¿›ç¨‹å®žçŽ°ï¼Œç›¸å…³ä»£ç å¦‚下: + +```python +elif data['type'] == 'operate_task': + # 任务函数 + if data['content'] == 'run': + task_run_flag.set() + # å¼€å¯ task 进程å‰å…ˆå…³é—­æ‰€æœ‰åކå²è¿›ç¨‹ + if task_process != None: + task_process.terminate() + time_record = time.perf_counter() + task_process = Process(target=main_func, args=(task_run_flag,queue)) + task_process.start() + logger.info("å¼€å¯ task") + elif data['content'] == 'stop': + task_run_flag.clear() + if task_process != None: + task_process.terminate() + logger.info(f"ä»»åŠ¡ç»“æŸ ç”¨æ—¶{time.perf_counter() - time_record}s") + logger.info("关闭 task") + elif data['content'] == 'restart': + pass +``` + +我们通过é‡å®šå‘loguru日志库,并且通过 WebSocket å®žçŽ°å¤šäººåŒæ—¶æŸ¥çœ‹çš„实时日志系统,ä¸åŒè¿›ç¨‹ä¹‹é—´ä½¿ç”¨å†…存安全的共享队列传输日志信æ¯ï¼Œç›¸å…³ä»£ç å¦‚下: + +```python +class Handler(logging.Handler): + def emit(self, record): + log_entry = self.format(record) + _queue.put({'level': record.levelname.lower(), 'content': log_entry}) +logger.remove() +handler = Handler() +logger.add(handler, format="{time:MM-DD HH:mm:ss} {message}", level="DEBUG") +``` + +```python +def thread_function(): + global queue + while True: + try: + log = queue.get() + socketio.emit('log', log) + except multiprocessing.Queue.Empty: + pass +``` + +