diff --git a/.gitignore b/.gitignore index 9dd24eb..a6b240c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea logs -test* \ No newline at end of file +test* +saved_images \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9dd24eb..a6b240c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea logs -test* \ No newline at end of file +test* +saved_images \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b195a6 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## TCP消息格式: + +- 示例数据: 0,2,139,383,155,392,640,480,FA +- 消息格式为字符串 + +| 位数 | 含义 | 示例值 | +|-----|----------|--------| +| 0 | 摄像头ID | 0 机械臂摄像头,1 机器狗前置摄像头 | +| 1 | 目标类别ID | 2 表示井眼 | +| 2 | 左上角 x 坐标 | 139 | +| 3 | 左上角 y 坐标 | 383 | +| 4 | 右下角 x 坐标 | 155 | +| 5 | 右下角 y 坐标 | 392 | +| 6 | 视频图像宽度 | 640 | +| 7 | 视频图像高度 | 480 | +| 8 | 标志位 | FA 表示结束 | + +## 安装 +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server ultralytics/ultralytics:latest-jetson-jetpack5 +``` + +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server go-algo-server:v1.0 +``` \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9dd24eb..a6b240c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea logs -test* \ No newline at end of file +test* +saved_images \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b195a6 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## TCP消息格式: + +- 示例数据: 0,2,139,383,155,392,640,480,FA +- 消息格式为字符串 + +| 位数 | 含义 | 示例值 | +|-----|----------|--------| +| 0 | 摄像头ID | 0 机械臂摄像头,1 机器狗前置摄像头 | +| 1 | 目标类别ID | 2 表示井眼 | +| 2 | 左上角 x 坐标 | 139 | +| 3 | 左上角 y 坐标 | 383 | +| 4 | 右下角 x 坐标 | 155 | +| 5 | 右下角 y 坐标 | 392 | +| 6 | 视频图像宽度 | 640 | +| 7 | 视频图像高度 | 480 | +| 8 | 标志位 | FA 表示结束 | + +## 安装 +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server ultralytics/ultralytics:latest-jetson-jetpack5 +``` + +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server go-algo-server:v1.0 +``` \ No newline at end of file diff --git a/camera_processor.py b/camera_processor.py index 3dd5277..93277e8 100644 --- a/camera_processor.py +++ b/camera_processor.py @@ -4,6 +4,7 @@ import time import asyncio import json +import os from global_logger import logger @@ -42,6 +43,20 @@ self.frame_height = None self.frame_fps = None + self.frames_detected = 0 + self.fps_ts = None + + # 添加图像保存相关参数 + self.save_annotated_images = self.camera_config.get('save_annotated_images', False) + self.save_path = self.camera_config.get('save_path', './saved_images') + self.save_interval = self.camera_config.get('save_interval', 25) # 每隔多少帧保存一次 + self.save_count = 0 + + # 确保保存目录存在 + if self.save_annotated_images: + os.makedirs(self.save_path, exist_ok=True) + logger.info(f"图像将保存到: {self.save_path}") + def _open_camera(self): """尝试打开摄像头,返回是否成功""" try: @@ -49,8 +64,8 @@ self.cap.release() # 确保释放之前的资源 logger.info(f"摄像头 {self.cam_id} 正在打开,源: {self.gst_str}") - self.cap = cv2.VideoCapture(self.gst_str) - # self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) + # self.cap = cv2.VideoCapture(self.gst_str) + self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) if not self.cap.isOpened(): logger.error(f"摄像头 {self.cam_id} 打开失败") @@ -116,14 +131,17 @@ for idx, (boxes, frame_info) in enumerate(zip(batch_boxes, frame_infos)): frame = frames[idx] timestamp = frame_info["timestamp"] + + # 用于保存的带标注图像 + annotated_frame = frame.copy() if self.save_annotated_images else None + for box in boxes: x1, y1, x2, y2 = box.xyxy.cpu().squeeze() cls = int(box.cls) conf = float(box.conf) - label = self.model_wrapper.get_label(cls) # print(label) - + # 只处理需要通过TCP发送的目标类别 if label in self.tcp_send_cls: data = f"{self.cam_id},{cls},{int(x1)},{int(y1)},{int(x2)},{int(y2)}," \ @@ -135,6 +153,40 @@ self.loop ) + # 在复制的图像上绘制标注 + if self.save_annotated_images: + self._draw_box_on_image(annotated_frame, int(x1), int(y1), int(x2), int(y2), cls, conf) + + # 保存带标注的图像 + if self.save_annotated_images: + self.save_count += 1 + if self.save_count % self.save_interval == 0: + self._save_annotated_image(annotated_frame, timestamp) + + def _draw_box_on_image(self, image, x1, y1, x2, y2, label, conf): + """在图像上绘制标注框""" + # 绘制矩形框 + cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) + + # 添加标签和置信度 + text = f"{label}: {conf:.2f}" + cv2.putText(image, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) + + return image + + def _save_annotated_image(self, image, timestamp): + """保存带标注的图像""" + try: + # 生成文件名:摄像头ID_时间戳.jpg + filename = f"{self.cam_id}_{int(timestamp)}.jpg" + filepath = f"{self.save_path}/{filename}" + + # 保存图像 + cv2.imwrite(filepath, image) + logger.info(f"摄像头 {self.cam_id} 已保存标注图像: {filepath}") + except Exception as e: + logger.exception(f"保存图像时发生错误: {e}") + def _add_to_batch(self, frame): """将帧添加到批处理缓冲区""" self.frame_buffer.append(frame) @@ -145,9 +197,21 @@ # 当缓冲区达到批处理大小时处理批次 if len(self.frame_buffer) >= self.batch_size: self._process_batch(self.frame_buffer, self.frame_info_buffer) + self.log_fps(len(self.frame_buffer)) self.frame_buffer = [] self.frame_info_buffer = [] + + def log_fps(self, frame_count): + self.frames_detected += frame_count + current_time = time.time() + # 每秒输出 FPS + if self.fps_ts is None or current_time - self.fps_ts >= 10: + fps = self.frames_detected / 10.0 + self.frames_detected = 0 + logger.info(f"FPS (detect) for cam {self.cam_id}: {fps}") + self.fps_ts = current_time + def run(self): """摄像头处理主循环""" logger.info(f"摄像头处理线程 {self.cam_id} 启动") diff --git a/.gitignore b/.gitignore index 9dd24eb..a6b240c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea logs -test* \ No newline at end of file +test* +saved_images \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b195a6 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## TCP消息格式: + +- 示例数据: 0,2,139,383,155,392,640,480,FA +- 消息格式为字符串 + +| 位数 | 含义 | 示例值 | +|-----|----------|--------| +| 0 | 摄像头ID | 0 机械臂摄像头,1 机器狗前置摄像头 | +| 1 | 目标类别ID | 2 表示井眼 | +| 2 | 左上角 x 坐标 | 139 | +| 3 | 左上角 y 坐标 | 383 | +| 4 | 右下角 x 坐标 | 155 | +| 5 | 右下角 y 坐标 | 392 | +| 6 | 视频图像宽度 | 640 | +| 7 | 视频图像高度 | 480 | +| 8 | 标志位 | FA 表示结束 | + +## 安装 +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server ultralytics/ultralytics:latest-jetson-jetpack5 +``` + +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server go-algo-server:v1.0 +``` \ No newline at end of file diff --git a/camera_processor.py b/camera_processor.py index 3dd5277..93277e8 100644 --- a/camera_processor.py +++ b/camera_processor.py @@ -4,6 +4,7 @@ import time import asyncio import json +import os from global_logger import logger @@ -42,6 +43,20 @@ self.frame_height = None self.frame_fps = None + self.frames_detected = 0 + self.fps_ts = None + + # 添加图像保存相关参数 + self.save_annotated_images = self.camera_config.get('save_annotated_images', False) + self.save_path = self.camera_config.get('save_path', './saved_images') + self.save_interval = self.camera_config.get('save_interval', 25) # 每隔多少帧保存一次 + self.save_count = 0 + + # 确保保存目录存在 + if self.save_annotated_images: + os.makedirs(self.save_path, exist_ok=True) + logger.info(f"图像将保存到: {self.save_path}") + def _open_camera(self): """尝试打开摄像头,返回是否成功""" try: @@ -49,8 +64,8 @@ self.cap.release() # 确保释放之前的资源 logger.info(f"摄像头 {self.cam_id} 正在打开,源: {self.gst_str}") - self.cap = cv2.VideoCapture(self.gst_str) - # self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) + # self.cap = cv2.VideoCapture(self.gst_str) + self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) if not self.cap.isOpened(): logger.error(f"摄像头 {self.cam_id} 打开失败") @@ -116,14 +131,17 @@ for idx, (boxes, frame_info) in enumerate(zip(batch_boxes, frame_infos)): frame = frames[idx] timestamp = frame_info["timestamp"] + + # 用于保存的带标注图像 + annotated_frame = frame.copy() if self.save_annotated_images else None + for box in boxes: x1, y1, x2, y2 = box.xyxy.cpu().squeeze() cls = int(box.cls) conf = float(box.conf) - label = self.model_wrapper.get_label(cls) # print(label) - + # 只处理需要通过TCP发送的目标类别 if label in self.tcp_send_cls: data = f"{self.cam_id},{cls},{int(x1)},{int(y1)},{int(x2)},{int(y2)}," \ @@ -135,6 +153,40 @@ self.loop ) + # 在复制的图像上绘制标注 + if self.save_annotated_images: + self._draw_box_on_image(annotated_frame, int(x1), int(y1), int(x2), int(y2), cls, conf) + + # 保存带标注的图像 + if self.save_annotated_images: + self.save_count += 1 + if self.save_count % self.save_interval == 0: + self._save_annotated_image(annotated_frame, timestamp) + + def _draw_box_on_image(self, image, x1, y1, x2, y2, label, conf): + """在图像上绘制标注框""" + # 绘制矩形框 + cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) + + # 添加标签和置信度 + text = f"{label}: {conf:.2f}" + cv2.putText(image, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) + + return image + + def _save_annotated_image(self, image, timestamp): + """保存带标注的图像""" + try: + # 生成文件名:摄像头ID_时间戳.jpg + filename = f"{self.cam_id}_{int(timestamp)}.jpg" + filepath = f"{self.save_path}/{filename}" + + # 保存图像 + cv2.imwrite(filepath, image) + logger.info(f"摄像头 {self.cam_id} 已保存标注图像: {filepath}") + except Exception as e: + logger.exception(f"保存图像时发生错误: {e}") + def _add_to_batch(self, frame): """将帧添加到批处理缓冲区""" self.frame_buffer.append(frame) @@ -145,9 +197,21 @@ # 当缓冲区达到批处理大小时处理批次 if len(self.frame_buffer) >= self.batch_size: self._process_batch(self.frame_buffer, self.frame_info_buffer) + self.log_fps(len(self.frame_buffer)) self.frame_buffer = [] self.frame_info_buffer = [] + + def log_fps(self, frame_count): + self.frames_detected += frame_count + current_time = time.time() + # 每秒输出 FPS + if self.fps_ts is None or current_time - self.fps_ts >= 10: + fps = self.frames_detected / 10.0 + self.frames_detected = 0 + logger.info(f"FPS (detect) for cam {self.cam_id}: {fps}") + self.fps_ts = current_time + def run(self): """摄像头处理主循环""" logger.info(f"摄像头处理线程 {self.cam_id} 启动") diff --git a/config.py b/config.py index e02d781..b6177c9 100644 --- a/config.py +++ b/config.py @@ -1,38 +1,50 @@ # config.py CAMERAS = [ - # { - # "cam_id": 0, - # "gst_str": ( - # "v4l2src device=/dev/video0 ! " - # "image/jpeg, width=1280, height=720, framerate=30/1 ! " - # "jpegdec ! videoconvert ! appsink" - # ), - # "tcp_send_cls": ["井盖眼"], - # "remark": "机械臂摄像头" - # }, - # { - # "source": 1, - # "gst_str": ( - # "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " - # "application/x-rtp, media=video, encoding-name=H264 ! " - # "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " - # "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" - # ), - # "tcp_send_cls": [], - # "remark": "机器狗前置摄像头" - # }, { "cam_id": 0, - "gst_str": 0, + "gst_str": ( + "v4l2src device=/dev/video0 ! " + "image/jpeg, width=1280, height=720, framerate=30/1 ! " + "jpegdec ! videoconvert ! appsink" + ), + # "gst_str": ( + # "v4l2src device=/dev/video0 ! " + # "image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! appsink" + # ), + # "gst_str": ( + # "v4l2src device=/dev/video0 ! image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! video/x-raw, format=BGR ! appsink drop=true sync=false" + # ), "tcp_send_cls": ["井盖眼"], - "frame_interval": 5, - "remark": "本地测试摄像头" + "remark": "机械臂摄像头", + "save_annotated_images": True }, + { + "cam_id": 1, + "gst_str": ( + "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " + "application/x-rtp, media=video, encoding-name=H264 ! " + "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " + "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" + ), + "tcp_send_cls": [], + "remark": "机器狗前置摄像头", + "save_annotated_images": True + }, + # { + # "cam_id": 0, + # "gst_str": 0, + # "tcp_send_cls": ["井盖眼"], + # "frame_interval": 5, + # "remark": "本地测试摄像头", + # "save_annotated_images": True + # }, ] TCP_SERVER = { "host": "127.0.0.1", - "port": 9000 + "port": 8888 } HTTP_SERVER = { diff --git a/.gitignore b/.gitignore index 9dd24eb..a6b240c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea logs -test* \ No newline at end of file +test* +saved_images \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b195a6 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## TCP消息格式: + +- 示例数据: 0,2,139,383,155,392,640,480,FA +- 消息格式为字符串 + +| 位数 | 含义 | 示例值 | +|-----|----------|--------| +| 0 | 摄像头ID | 0 机械臂摄像头,1 机器狗前置摄像头 | +| 1 | 目标类别ID | 2 表示井眼 | +| 2 | 左上角 x 坐标 | 139 | +| 3 | 左上角 y 坐标 | 383 | +| 4 | 右下角 x 坐标 | 155 | +| 5 | 右下角 y 坐标 | 392 | +| 6 | 视频图像宽度 | 640 | +| 7 | 视频图像高度 | 480 | +| 8 | 标志位 | FA 表示结束 | + +## 安装 +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server ultralytics/ultralytics:latest-jetson-jetpack5 +``` + +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server go-algo-server:v1.0 +``` \ No newline at end of file diff --git a/camera_processor.py b/camera_processor.py index 3dd5277..93277e8 100644 --- a/camera_processor.py +++ b/camera_processor.py @@ -4,6 +4,7 @@ import time import asyncio import json +import os from global_logger import logger @@ -42,6 +43,20 @@ self.frame_height = None self.frame_fps = None + self.frames_detected = 0 + self.fps_ts = None + + # 添加图像保存相关参数 + self.save_annotated_images = self.camera_config.get('save_annotated_images', False) + self.save_path = self.camera_config.get('save_path', './saved_images') + self.save_interval = self.camera_config.get('save_interval', 25) # 每隔多少帧保存一次 + self.save_count = 0 + + # 确保保存目录存在 + if self.save_annotated_images: + os.makedirs(self.save_path, exist_ok=True) + logger.info(f"图像将保存到: {self.save_path}") + def _open_camera(self): """尝试打开摄像头,返回是否成功""" try: @@ -49,8 +64,8 @@ self.cap.release() # 确保释放之前的资源 logger.info(f"摄像头 {self.cam_id} 正在打开,源: {self.gst_str}") - self.cap = cv2.VideoCapture(self.gst_str) - # self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) + # self.cap = cv2.VideoCapture(self.gst_str) + self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) if not self.cap.isOpened(): logger.error(f"摄像头 {self.cam_id} 打开失败") @@ -116,14 +131,17 @@ for idx, (boxes, frame_info) in enumerate(zip(batch_boxes, frame_infos)): frame = frames[idx] timestamp = frame_info["timestamp"] + + # 用于保存的带标注图像 + annotated_frame = frame.copy() if self.save_annotated_images else None + for box in boxes: x1, y1, x2, y2 = box.xyxy.cpu().squeeze() cls = int(box.cls) conf = float(box.conf) - label = self.model_wrapper.get_label(cls) # print(label) - + # 只处理需要通过TCP发送的目标类别 if label in self.tcp_send_cls: data = f"{self.cam_id},{cls},{int(x1)},{int(y1)},{int(x2)},{int(y2)}," \ @@ -135,6 +153,40 @@ self.loop ) + # 在复制的图像上绘制标注 + if self.save_annotated_images: + self._draw_box_on_image(annotated_frame, int(x1), int(y1), int(x2), int(y2), cls, conf) + + # 保存带标注的图像 + if self.save_annotated_images: + self.save_count += 1 + if self.save_count % self.save_interval == 0: + self._save_annotated_image(annotated_frame, timestamp) + + def _draw_box_on_image(self, image, x1, y1, x2, y2, label, conf): + """在图像上绘制标注框""" + # 绘制矩形框 + cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) + + # 添加标签和置信度 + text = f"{label}: {conf:.2f}" + cv2.putText(image, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) + + return image + + def _save_annotated_image(self, image, timestamp): + """保存带标注的图像""" + try: + # 生成文件名:摄像头ID_时间戳.jpg + filename = f"{self.cam_id}_{int(timestamp)}.jpg" + filepath = f"{self.save_path}/{filename}" + + # 保存图像 + cv2.imwrite(filepath, image) + logger.info(f"摄像头 {self.cam_id} 已保存标注图像: {filepath}") + except Exception as e: + logger.exception(f"保存图像时发生错误: {e}") + def _add_to_batch(self, frame): """将帧添加到批处理缓冲区""" self.frame_buffer.append(frame) @@ -145,9 +197,21 @@ # 当缓冲区达到批处理大小时处理批次 if len(self.frame_buffer) >= self.batch_size: self._process_batch(self.frame_buffer, self.frame_info_buffer) + self.log_fps(len(self.frame_buffer)) self.frame_buffer = [] self.frame_info_buffer = [] + + def log_fps(self, frame_count): + self.frames_detected += frame_count + current_time = time.time() + # 每秒输出 FPS + if self.fps_ts is None or current_time - self.fps_ts >= 10: + fps = self.frames_detected / 10.0 + self.frames_detected = 0 + logger.info(f"FPS (detect) for cam {self.cam_id}: {fps}") + self.fps_ts = current_time + def run(self): """摄像头处理主循环""" logger.info(f"摄像头处理线程 {self.cam_id} 启动") diff --git a/config.py b/config.py index e02d781..b6177c9 100644 --- a/config.py +++ b/config.py @@ -1,38 +1,50 @@ # config.py CAMERAS = [ - # { - # "cam_id": 0, - # "gst_str": ( - # "v4l2src device=/dev/video0 ! " - # "image/jpeg, width=1280, height=720, framerate=30/1 ! " - # "jpegdec ! videoconvert ! appsink" - # ), - # "tcp_send_cls": ["井盖眼"], - # "remark": "机械臂摄像头" - # }, - # { - # "source": 1, - # "gst_str": ( - # "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " - # "application/x-rtp, media=video, encoding-name=H264 ! " - # "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " - # "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" - # ), - # "tcp_send_cls": [], - # "remark": "机器狗前置摄像头" - # }, { "cam_id": 0, - "gst_str": 0, + "gst_str": ( + "v4l2src device=/dev/video0 ! " + "image/jpeg, width=1280, height=720, framerate=30/1 ! " + "jpegdec ! videoconvert ! appsink" + ), + # "gst_str": ( + # "v4l2src device=/dev/video0 ! " + # "image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! appsink" + # ), + # "gst_str": ( + # "v4l2src device=/dev/video0 ! image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! video/x-raw, format=BGR ! appsink drop=true sync=false" + # ), "tcp_send_cls": ["井盖眼"], - "frame_interval": 5, - "remark": "本地测试摄像头" + "remark": "机械臂摄像头", + "save_annotated_images": True }, + { + "cam_id": 1, + "gst_str": ( + "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " + "application/x-rtp, media=video, encoding-name=H264 ! " + "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " + "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" + ), + "tcp_send_cls": [], + "remark": "机器狗前置摄像头", + "save_annotated_images": True + }, + # { + # "cam_id": 0, + # "gst_str": 0, + # "tcp_send_cls": ["井盖眼"], + # "frame_interval": 5, + # "remark": "本地测试摄像头", + # "save_annotated_images": True + # }, ] TCP_SERVER = { "host": "127.0.0.1", - "port": 9000 + "port": 8888 } HTTP_SERVER = { diff --git a/config.py.bak b/config.py.bak new file mode 100644 index 0000000..2471916 --- /dev/null +++ b/config.py.bak @@ -0,0 +1,79 @@ +# config.py +CAMERAS = [ + { + "cam_id": 0, + # "gst_str": ( + # "v4l2src device=/dev/video0 ! " + # "image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! appsink" + # ), + "gst_str": ( + "v4l2src device=/dev/video0 ! image/jpeg, width=1280, height=720, framerate=30/1 ! " + "jpegparse ! jpegdec ! videoconvert ! video/x-raw, format=BGR ! appsink drop=true sync=false" + ), + "tcp_send_cls": ["井盖眼"], + "remark": "机械臂摄像头", + "save_annotated_images": True + }, + { + "cam_id": 1, + "gst_str": ( + "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " + "application/x-rtp, media=video, encoding-name=H264 ! " + "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " + "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" + ), + "tcp_send_cls": [], + "remark": "机器狗前置摄像头", + "save_annotated_images": True + }, + # { + # "cam_id": 0, + # "gst_str": 0, + # "tcp_send_cls": ["井盖眼"], + # "frame_interval": 5, + # "remark": "本地测试摄像头", + # "save_annotated_images": True + # }, +] + +TCP_SERVER = { + "host": "127.0.0.1", + "port": 8888 +} + +HTTP_SERVER = { + "url": "http://127.0.0.1:8000/alert", + "timeout": 5 # 超时重试 +} + +MODEL_CLASS = { + 0: '井盖', + 1: '井盖塌陷', + 2: '井盖眼', + 3: '井盖破损', + 4: '井盖移位', + 5: '井盖缺失', + 6: '人', + 7: '压路机', + 8: '反光衣', + 9: '土堆', + 10: '土方车', + 11: '头', + 12: '安全帽', + 13: '挖掘机', + 14: '推土机', + 15: '施工路牌', + 16: '水马', + 17: '路锥', + 18: '铁锹', + 19: '防护栏', + 20: '风镐' +} + +MODEL = { + "path": "weights/go-v8s-20250117.pt", + "size": 640, + "class_map": MODEL_CLASS, + "batch_size": 1, +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9dd24eb..a6b240c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea logs -test* \ No newline at end of file +test* +saved_images \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b195a6 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## TCP消息格式: + +- 示例数据: 0,2,139,383,155,392,640,480,FA +- 消息格式为字符串 + +| 位数 | 含义 | 示例值 | +|-----|----------|--------| +| 0 | 摄像头ID | 0 机械臂摄像头,1 机器狗前置摄像头 | +| 1 | 目标类别ID | 2 表示井眼 | +| 2 | 左上角 x 坐标 | 139 | +| 3 | 左上角 y 坐标 | 383 | +| 4 | 右下角 x 坐标 | 155 | +| 5 | 右下角 y 坐标 | 392 | +| 6 | 视频图像宽度 | 640 | +| 7 | 视频图像高度 | 480 | +| 8 | 标志位 | FA 表示结束 | + +## 安装 +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server ultralytics/ultralytics:latest-jetson-jetpack5 +``` + +```angular2html +docker run -d --ipc=host --runtime=nvidia --network=host --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v ~/docker_data:/data/ -v ~/docker_src:/code/ --restart=always --entrypoint /code/go-algo-server/run.sh --name algo-server go-algo-server:v1.0 +``` \ No newline at end of file diff --git a/camera_processor.py b/camera_processor.py index 3dd5277..93277e8 100644 --- a/camera_processor.py +++ b/camera_processor.py @@ -4,6 +4,7 @@ import time import asyncio import json +import os from global_logger import logger @@ -42,6 +43,20 @@ self.frame_height = None self.frame_fps = None + self.frames_detected = 0 + self.fps_ts = None + + # 添加图像保存相关参数 + self.save_annotated_images = self.camera_config.get('save_annotated_images', False) + self.save_path = self.camera_config.get('save_path', './saved_images') + self.save_interval = self.camera_config.get('save_interval', 25) # 每隔多少帧保存一次 + self.save_count = 0 + + # 确保保存目录存在 + if self.save_annotated_images: + os.makedirs(self.save_path, exist_ok=True) + logger.info(f"图像将保存到: {self.save_path}") + def _open_camera(self): """尝试打开摄像头,返回是否成功""" try: @@ -49,8 +64,8 @@ self.cap.release() # 确保释放之前的资源 logger.info(f"摄像头 {self.cam_id} 正在打开,源: {self.gst_str}") - self.cap = cv2.VideoCapture(self.gst_str) - # self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) + # self.cap = cv2.VideoCapture(self.gst_str) + self.cap = cv2.VideoCapture(self.gst_str, cv2.CAP_GSTREAMER) if not self.cap.isOpened(): logger.error(f"摄像头 {self.cam_id} 打开失败") @@ -116,14 +131,17 @@ for idx, (boxes, frame_info) in enumerate(zip(batch_boxes, frame_infos)): frame = frames[idx] timestamp = frame_info["timestamp"] + + # 用于保存的带标注图像 + annotated_frame = frame.copy() if self.save_annotated_images else None + for box in boxes: x1, y1, x2, y2 = box.xyxy.cpu().squeeze() cls = int(box.cls) conf = float(box.conf) - label = self.model_wrapper.get_label(cls) # print(label) - + # 只处理需要通过TCP发送的目标类别 if label in self.tcp_send_cls: data = f"{self.cam_id},{cls},{int(x1)},{int(y1)},{int(x2)},{int(y2)}," \ @@ -135,6 +153,40 @@ self.loop ) + # 在复制的图像上绘制标注 + if self.save_annotated_images: + self._draw_box_on_image(annotated_frame, int(x1), int(y1), int(x2), int(y2), cls, conf) + + # 保存带标注的图像 + if self.save_annotated_images: + self.save_count += 1 + if self.save_count % self.save_interval == 0: + self._save_annotated_image(annotated_frame, timestamp) + + def _draw_box_on_image(self, image, x1, y1, x2, y2, label, conf): + """在图像上绘制标注框""" + # 绘制矩形框 + cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) + + # 添加标签和置信度 + text = f"{label}: {conf:.2f}" + cv2.putText(image, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) + + return image + + def _save_annotated_image(self, image, timestamp): + """保存带标注的图像""" + try: + # 生成文件名:摄像头ID_时间戳.jpg + filename = f"{self.cam_id}_{int(timestamp)}.jpg" + filepath = f"{self.save_path}/{filename}" + + # 保存图像 + cv2.imwrite(filepath, image) + logger.info(f"摄像头 {self.cam_id} 已保存标注图像: {filepath}") + except Exception as e: + logger.exception(f"保存图像时发生错误: {e}") + def _add_to_batch(self, frame): """将帧添加到批处理缓冲区""" self.frame_buffer.append(frame) @@ -145,9 +197,21 @@ # 当缓冲区达到批处理大小时处理批次 if len(self.frame_buffer) >= self.batch_size: self._process_batch(self.frame_buffer, self.frame_info_buffer) + self.log_fps(len(self.frame_buffer)) self.frame_buffer = [] self.frame_info_buffer = [] + + def log_fps(self, frame_count): + self.frames_detected += frame_count + current_time = time.time() + # 每秒输出 FPS + if self.fps_ts is None or current_time - self.fps_ts >= 10: + fps = self.frames_detected / 10.0 + self.frames_detected = 0 + logger.info(f"FPS (detect) for cam {self.cam_id}: {fps}") + self.fps_ts = current_time + def run(self): """摄像头处理主循环""" logger.info(f"摄像头处理线程 {self.cam_id} 启动") diff --git a/config.py b/config.py index e02d781..b6177c9 100644 --- a/config.py +++ b/config.py @@ -1,38 +1,50 @@ # config.py CAMERAS = [ - # { - # "cam_id": 0, - # "gst_str": ( - # "v4l2src device=/dev/video0 ! " - # "image/jpeg, width=1280, height=720, framerate=30/1 ! " - # "jpegdec ! videoconvert ! appsink" - # ), - # "tcp_send_cls": ["井盖眼"], - # "remark": "机械臂摄像头" - # }, - # { - # "source": 1, - # "gst_str": ( - # "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " - # "application/x-rtp, media=video, encoding-name=H264 ! " - # "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " - # "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" - # ), - # "tcp_send_cls": [], - # "remark": "机器狗前置摄像头" - # }, { "cam_id": 0, - "gst_str": 0, + "gst_str": ( + "v4l2src device=/dev/video0 ! " + "image/jpeg, width=1280, height=720, framerate=30/1 ! " + "jpegdec ! videoconvert ! appsink" + ), + # "gst_str": ( + # "v4l2src device=/dev/video0 ! " + # "image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! appsink" + # ), + # "gst_str": ( + # "v4l2src device=/dev/video0 ! image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! video/x-raw, format=BGR ! appsink drop=true sync=false" + # ), "tcp_send_cls": ["井盖眼"], - "frame_interval": 5, - "remark": "本地测试摄像头" + "remark": "机械臂摄像头", + "save_annotated_images": True }, + { + "cam_id": 1, + "gst_str": ( + "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " + "application/x-rtp, media=video, encoding-name=H264 ! " + "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " + "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" + ), + "tcp_send_cls": [], + "remark": "机器狗前置摄像头", + "save_annotated_images": True + }, + # { + # "cam_id": 0, + # "gst_str": 0, + # "tcp_send_cls": ["井盖眼"], + # "frame_interval": 5, + # "remark": "本地测试摄像头", + # "save_annotated_images": True + # }, ] TCP_SERVER = { "host": "127.0.0.1", - "port": 9000 + "port": 8888 } HTTP_SERVER = { diff --git a/config.py.bak b/config.py.bak new file mode 100644 index 0000000..2471916 --- /dev/null +++ b/config.py.bak @@ -0,0 +1,79 @@ +# config.py +CAMERAS = [ + { + "cam_id": 0, + # "gst_str": ( + # "v4l2src device=/dev/video0 ! " + # "image/jpeg, width=1280, height=720, framerate=30/1 ! " + # "jpegparse ! jpegdec ! videoconvert ! appsink" + # ), + "gst_str": ( + "v4l2src device=/dev/video0 ! image/jpeg, width=1280, height=720, framerate=30/1 ! " + "jpegparse ! jpegdec ! videoconvert ! video/x-raw, format=BGR ! appsink drop=true sync=false" + ), + "tcp_send_cls": ["井盖眼"], + "remark": "机械臂摄像头", + "save_annotated_images": True + }, + { + "cam_id": 1, + "gst_str": ( + "udpsrc address=230.1.1.1 port=1720 multicast-iface=eth0 ! " + "application/x-rtp, media=video, encoding-name=H264 ! " + "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! " + "video/x-raw,width=1280,height=720,format=BGR ! appsink drop=1" + ), + "tcp_send_cls": [], + "remark": "机器狗前置摄像头", + "save_annotated_images": True + }, + # { + # "cam_id": 0, + # "gst_str": 0, + # "tcp_send_cls": ["井盖眼"], + # "frame_interval": 5, + # "remark": "本地测试摄像头", + # "save_annotated_images": True + # }, +] + +TCP_SERVER = { + "host": "127.0.0.1", + "port": 8888 +} + +HTTP_SERVER = { + "url": "http://127.0.0.1:8000/alert", + "timeout": 5 # 超时重试 +} + +MODEL_CLASS = { + 0: '井盖', + 1: '井盖塌陷', + 2: '井盖眼', + 3: '井盖破损', + 4: '井盖移位', + 5: '井盖缺失', + 6: '人', + 7: '压路机', + 8: '反光衣', + 9: '土堆', + 10: '土方车', + 11: '头', + 12: '安全帽', + 13: '挖掘机', + 14: '推土机', + 15: '施工路牌', + 16: '水马', + 17: '路锥', + 18: '铁锹', + 19: '防护栏', + 20: '风镐' +} + +MODEL = { + "path": "weights/go-v8s-20250117.pt", + "size": 640, + "class_map": MODEL_CLASS, + "batch_size": 1, +} \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..c7b37fb --- /dev/null +++ b/run.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e +set -x + +export PYTHONIOENCODING=utf-8 +export LANG=C.UTF-8 +export LC_ALL=C.UTF-8 + +cd /code/go-algo-server +exec python3 app.py +#tail -f /dev/null \ No newline at end of file