diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8b272bde7..0619ad023 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -109,20 +109,20 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets._GITHUB_TOKEN }} - name: Login to NVIDIA NGC - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: nvcr.io username: $oauthtoken diff --git "a/README_\351\203\250\347\275\262\350\257\264\346\230\216.md" "b/README_\351\203\250\347\275\262\350\257\264\346\230\216.md" index 479ecad70..3aa54ebf4 100644 --- "a/README_\351\203\250\347\275\262\350\257\264\346\230\216.md" +++ "b/README_\351\203\250\347\275\262\350\257\264\346\230\216.md" @@ -27,6 +27,7 @@ pip install -r requirements.txt ``` 或者单独安装: + ```bash pip install opencv-python numpy ultralytics pyyaml ``` @@ -37,7 +38,7 @@ pip install opencv-python numpy ultralytics pyyaml ```yaml data_source: - type: "video" # 可选: video, camera, images + type: "video" # 可选: video, camera, images video_path: "你的视频路径.mp4" fps: 24 @@ -45,7 +46,7 @@ inference: model_path: "yolov8n.pt" fps: 24 conf_threshold: 0.3 - classes: [0] # 0=人, 1=自行车, 2=汽车 等 + classes: [0] # 0=人, 1=自行车, 2=汽车 等 ``` ### 3. 运行系统 @@ -63,11 +64,13 @@ inference: #### 方式二:手动启动(Windows/Linux/Mac) **终端1(数据源):** + ```bash python data_source_shm.py ``` **终端2(推理):** + ```bash python inference_shm.py ``` @@ -148,7 +151,8 @@ Get-Content logs/app.log -Wait -Tail 20 **问题:** `无法连接到共享内存` -**解决:** +**解决:** + - 确保程序A(data_source_shm.py)先启动 - 检查两个程序的 `SHM_NAME` 配置是否一致 - 在Linux上,检查 `/dev/shm` 目录权限 @@ -158,6 +162,7 @@ Get-Content logs/app.log -Wait -Tail 20 **问题:** `无法打开摄像头` **解决:** + - 检查摄像头是否连接 - 尝试修改 `camera_index`(0, 1, 2...) - Windows上可能需要安装摄像头驱动 @@ -167,6 +172,7 @@ Get-Content logs/app.log -Wait -Tail 20 **问题:** `ModuleNotFoundError: No module named 'xxx'` **解决:** + ```bash pip install -r requirements.txt ``` @@ -187,6 +193,7 @@ pip install -r requirements.txt ## 📞 需要帮助? 如果遇到问题,请检查: + 1. 日志文件(`logs/` 目录) 2. 配置文件是否正确 3. Python依赖是否安装完整 diff --git "a/WSL2_\346\223\215\344\275\234\346\214\207\345\215\227.md" "b/WSL2_\346\223\215\344\275\234\346\214\207\345\215\227.md" index 9abe4d808..1dda5852d 100644 --- "a/WSL2_\346\223\215\344\275\234\346\214\207\345\215\227.md" +++ "b/WSL2_\346\223\215\344\275\234\346\214\207\345\215\227.md" @@ -5,6 +5,7 @@ ### 1. 确认 WSL2 和 Ubuntu 已安装 在 Windows PowerShell 中运行: + ```powershell wsl --list --verbose ``` @@ -12,6 +13,7 @@ wsl --list --verbose 应该能看到 Ubuntu,并且 VERSION 是 2。 如果没安装,运行: + ```powershell wsl --install -d Ubuntu ``` @@ -44,6 +46,7 @@ pip3 install ultralytics opencv-python ``` **注意**:如果提示权限问题,可以加 `--user`: + ```bash pip3 install --user ultralytics opencv-python ``` @@ -55,6 +58,7 @@ pip3 install --user ultralytics opencv-python ### 2.1 在 WSL2 中访问 Windows 文件 你的 Windows 文件在 WSL2 中的路径是: + - `F:\` 盘 → `/mnt/f/` - 你的项目路径: ``` @@ -74,12 +78,14 @@ ls -la ``` 应该能看到: + - `data_source.py` - `inference.py` ### 2.4 确认视频文件路径 检查视频文件是否存在: + ```bash ls -la /mnt/f/Deeplearning/yolo_source8.3.163/ultralytics/datasets/make_dataset/videos/ ``` @@ -93,6 +99,7 @@ ls -la /mnt/f/Deeplearning/yolo_source8.3.163/ultralytics/datasets/make_dataset/ ### 3.1 打开两个 WSL2 终端窗口 需要两个终端窗口: + 1. **终端1**:运行 `inference.py`(程序B - 推理) 2. **终端2**:运行 `data_source.py`(程序A - 数据源) @@ -107,6 +114,7 @@ python3 inference.py ``` **预期输出**: + ``` ============================================================ 程序 B:推理进程(负责从 Socket 读取图像并做实时检测) @@ -137,6 +145,7 @@ python3 data_source.py ``` **预期输出**: + ``` ============================================================ 程序 A:数据源进程(负责读取视频/图片/摄像头,并发送给程序 B) @@ -157,6 +166,7 @@ Unix Domain Socket 路径: /tmp/yolo_image_socket ### 3.4 查看结果 当程序A连接成功后,程序B的窗口会: + 1. 显示"已连接程序 A,开始接收图像并推理。" 2. 弹出一个 OpenCV 窗口,显示检测结果(画框标注人体) 3. 在终端中显示"已推理帧数: X" @@ -166,9 +176,11 @@ Unix Domain Socket 路径: /tmp/yolo_image_socket ## 🛑 停止程序 ### 方法1:关闭显示窗口 + 在 OpenCV 显示窗口中按键盘 `q` 键,程序B会退出。 ### 方法2:使用 Ctrl+C + 在任意一个终端中按 `Ctrl + C`,对应的程序会停止。 **建议**:先停止程序A(终端2),再停止程序B(终端1)。 @@ -182,11 +194,13 @@ Unix Domain Socket 路径: /tmp/yolo_image_socket **错误**:`ModuleNotFoundError: No module named 'ultralytics'` **解决**: + ```bash pip3 install --user ultralytics opencv-python ``` 如果还不行,检查 Python 版本: + ```bash python3 --version # 应该是 Python 3.8 或更高版本 @@ -199,7 +213,9 @@ python3 --version **错误**:`无法打开视频文件: /mnt/f/...` **解决**: + 1. 检查视频文件是否存在: + ```bash ls -la /mnt/f/Deeplearning/yolo_source8.3.163/ultralytics/datasets/make_dataset/videos/ ``` @@ -215,6 +231,7 @@ python3 --version **错误**:`Connection refused` 或 `No such file or directory` **解决**: + 1. 确保先运行程序B(inference.py),再运行程序A(data_source.py) 2. 检查 socket 文件: ```bash @@ -283,7 +300,7 @@ source ~/.bashrc ```bash # 测试 X11 是否可用(如果安装了 x11-apps) sudo apt install -y x11-apps -xeyes # 应该会弹出眼睛窗口 +xeyes # 应该会弹出眼睛窗口 # 或者测试 OpenCV python3 -c "import cv2; cv2.namedWindow('test'); cv2.destroyAllWindows(); print('X11 可用')" @@ -294,6 +311,7 @@ python3 -c "import cv2; cv2.namedWindow('test'); cv2.destroyAllWindows(); print( 如果仍然无法连接,需要在 Windows 防火墙中允许 VcXsrv: 在 **Windows PowerShell(管理员)** 中运行: + ```powershell New-NetFirewallRule -DisplayName "VcXsrv X11 Server" -Direction Inbound -Program "C:\Program Files\VcXsrv\vcxsrv.exe" -Action Allow ``` @@ -312,7 +330,6 @@ export DISPLAY=:0 # 安装必要的库 - # 运行程序 python3 inference.py ``` @@ -322,10 +339,11 @@ python3 inference.py 代码已支持自动检测 X11 可用性。只需设置: ```python -DISPLAY_MODE = 'auto' # 在 inference.py 中 +DISPLAY_MODE = "auto" # 在 inference.py 中 ``` 程序会自动检测: + - 如果 X11 可用 → 显示窗口 - 如果 X11 不可用 → 自动保存到文件 @@ -338,7 +356,9 @@ DISPLAY_MODE = 'auto' # 在 inference.py 中 **错误**:`FileNotFoundError: yolo12n.pt` **解决**: + 1. 检查模型文件是否存在: + ```bash ls -la /mnt/f/Deeplearning/yolo_source8.3.163/ultralytics/yolo12n.pt ``` @@ -354,6 +374,7 @@ DISPLAY_MODE = 'auto' # 在 inference.py 中 ### 更改数据源类型 编辑 `data_source.py`,修改: + ```python SOURCE_TYPE = "video" # 改为 "camera" 或 "images" ``` @@ -361,6 +382,7 @@ SOURCE_TYPE = "video" # 改为 "camera" 或 "images" ### 更改发送/推理频率 在两个文件中修改: + ```python FPS = 10 # 改为其他数字,比如 5(5Hz)或 20(20Hz) ``` @@ -368,6 +390,7 @@ FPS = 10 # 改为其他数字,比如 5(5Hz)或 20(20Hz) ### 更改视频路径 编辑 `data_source.py`,修改: + ```python VIDEO_PATH = "/mnt/f/你的/视频/路径.mp4" ``` @@ -392,13 +415,14 @@ VIDEO_PATH = "/mnt/f/你的/视频/路径.mp4" ## 🔄 下一步 现在你的系统已经可以: + - ✅ 使用 Unix Domain Socket 进行进程间通信 - ✅ 程序A持续发送图像数据 - ✅ 程序B持续接收并推理 - ✅ 实时显示检测结果 接下来可以: + 1. 尝试使用摄像头(修改 `SOURCE_TYPE = "camera"`) 2. 尝试使用图片文件夹(修改 `SOURCE_TYPE = "images"`) 3. 接入真实相机(需要配置相机驱动和路径) - diff --git a/camera_detect.py b/camera_detect.py index dc90be0a2..95221a3c6 100644 --- a/camera_detect.py +++ b/camera_detect.py @@ -1,8 +1,9 @@ -from ultralytics import YOLO import cv2 +from ultralytics import YOLO + # 加载训练好的模型 -model = YOLO(r'runs\detect\train11\weights\best.pt') +model = YOLO(r"runs\detect\train11\weights\best.pt") # 打开摄像头(0表示默认摄像头,如果是外接摄像头可以尝试1、2等) cap = cv2.VideoCapture(0) @@ -19,20 +20,20 @@ if not ret: print("无法读取摄像头画面,请检查摄像头是否连接正常") break - + # 使用模型进行预测 # conf=0.5 表示置信度阈值,只有置信度大于0.5的检测结果才会显示 # 你可以根据需要调整这个值(0.0-1.0之间) results = model(frame, conf=0.25, imgsz=1280) - + # 在画面上绘制检测结果(包括边界框、类别名称、置信度) annotated_frame = results[0].plot() - + # 显示结果 - cv2.imshow('摄像头实时检测 - kunkun/fan/xiang', annotated_frame) - + cv2.imshow("摄像头实时检测 - kunkun/fan/xiang", annotated_frame) + # 按 'q' 键退出 - if cv2.waitKey(1) & 0xFF == ord('q'): + if cv2.waitKey(1) & 0xFF == ord("q"): print("正在退出...") break @@ -40,4 +41,3 @@ cap.release() cv2.destroyAllWindows() print("程序已退出") - diff --git a/config.yaml b/config.yaml index 59875338d..17e9150b5 100644 --- a/config.yaml +++ b/config.yaml @@ -5,16 +5,16 @@ data_source: # 数据源类型: video(视频文件), camera(摄像头), images(图片文件夹) type: "video" - + # 视频文件路径(当 type="video" 时使用) video_path: "/mnt/f/Deeplearning/yolo_source8.3.163/ultralytics/datasets/make_dataset/videos/result.mp4" - + # 摄像头索引(当 type="camera" 时使用,0表示第一个摄像头) camera_index: 0 - + # 图片文件夹路径(当 type="images" 时使用) images_dir: "/mnt/f/Deeplearning/yolo_source8.3.163/ultralytics/datasets/make_dataset/images" - + # 发送频率(Hz):每秒发送多少帧图像 fps: 24 @@ -22,38 +22,38 @@ data_source: shared_memory: # 共享内存名称:程序A和程序B通过这个名称找到同一块内存 name: "yolo_image_shm" - + # 最大图像尺寸:共享内存需要预先分配固定大小 - max_width: 1920 # 最大宽度(像素) - max_height: 1080 # 最大高度(像素) - max_channels: 3 # 通道数(RGB图像是3通道) + max_width: 1920 # 最大宽度(像素) + max_height: 1080 # 最大高度(像素) + max_channels: 3 # 通道数(RGB图像是3通道) # ========== 推理配置 ========== inference: # YOLO模型文件路径 model_path: "yolov8n.pt" - + # 推理频率(Hz):每秒处理多少帧(建议和data_source.fps一致) fps: 24 - + # 置信度阈值:只有置信度大于这个值的检测结果才保留(0.0-1.0) conf_threshold: 0.3 - + # IoU阈值:用于非极大值抑制(NMS),去除重复框(0.0-1.0) iou_threshold: 0.5 - + # 检测类别:COCO数据集类别ID列表 # 0=person(人), 1=bicycle(自行车), 2=car(汽车), 3=motorcycle(摩托车) 等 # 空列表 [] 表示检测所有类别 - classes: [0] # 只检测"人"这个类别 + classes: [0] # 只检测"人"这个类别 # ========== 日志配置 ========== logging: # 日志级别: DEBUG(详细), INFO(信息), WARNING(警告), ERROR(错误) level: "INFO" - + # 日志文件路径(None表示不保存到文件,只输出到控制台) file: "logs/app.log" - + # 是否输出到控制台 console: true diff --git a/config_loader.py b/config_loader.py index 200f47ce8..2c953b9e1 100644 --- a/config_loader.py +++ b/config_loader.py @@ -1,72 +1,72 @@ """ -配置文件加载模块 +配置文件加载模块. 作用:读取 config.yaml 配置文件,并提供给程序使用 类比:像服务员,把菜单(配置文件)的内容告诉厨房(程序) """ -import yaml + from pathlib import Path -from typing import Dict, Any, Optional +from typing import Any + +import yaml class Config: - """配置类,用于加载和管理配置""" - + """配置类,用于加载和管理配置.""" + def __init__(self, config_path: str = "config.yaml"): - """ - 加载配置文件 - + """加载配置文件. + 参数: config_path: 配置文件路径 """ self.config_path = Path(config_path) self._config = self._load_config() - - def _load_config(self) -> Dict[str, Any]: - """加载YAML配置文件""" + + def _load_config(self) -> dict[str, Any]: + """加载YAML配置文件.""" if not self.config_path.exists(): raise FileNotFoundError(f"配置文件不存在: {self.config_path}") - - with open(self.config_path, 'r', encoding='utf-8') as f: + + with open(self.config_path, encoding="utf-8") as f: config = yaml.safe_load(f) - + if config is None: config = {} - + return config - + @property - def data_source(self) -> Dict[str, Any]: - """获取数据源配置""" - return self._config.get('data_source', {}) - + def data_source(self) -> dict[str, Any]: + """获取数据源配置.""" + return self._config.get("data_source", {}) + @property - def shared_memory(self) -> Dict[str, Any]: - """获取共享内存配置""" - return self._config.get('shared_memory', {}) - + def shared_memory(self) -> dict[str, Any]: + """获取共享内存配置.""" + return self._config.get("shared_memory", {}) + @property - def inference(self) -> Dict[str, Any]: - """获取推理配置""" - return self._config.get('inference', {}) - + def inference(self) -> dict[str, Any]: + """获取推理配置.""" + return self._config.get("inference", {}) + @property - def logging(self) -> Dict[str, Any]: - """获取日志配置""" - return self._config.get('logging', {}) - + def logging(self) -> dict[str, Any]: + """获取日志配置.""" + return self._config.get("logging", {}) + def get(self, key: str, default=None): - """ - 获取配置值(支持点号分隔的嵌套键,如 'data_source.type') - + """获取配置值(支持点号分隔的嵌套键,如 'data_source.type'). + 参数: key: 配置键,支持点号分隔(如 'data_source.type') default: 默认值(如果键不存在) - + 返回: 配置值 """ - keys = key.split('.') + keys = key.split(".") value = self._config for k in keys: if isinstance(value, dict): diff --git a/data_source_shm.py b/data_source_shm.py index 9864bbed7..a9dcd76d1 100644 --- a/data_source_shm.py +++ b/data_source_shm.py @@ -1,5 +1,5 @@ """ -程序A:数据源(使用共享内存版本) +程序A:数据源(使用共享内存版本). 这个程序的作用: 1. 从视频文件/摄像头/图片文件夹读取图像帧 @@ -14,18 +14,21 @@ 工作流程: 视频/摄像头 → 读取帧 → 写入共享内存 → 程序B读取并推理 """ + # ========== 导入必要的库 ========== -import cv2 # OpenCV,用于读取视频/图片/摄像头 -import time # 时间库,用于控制帧率 -import numpy as np # NumPy,用于处理图像数组 -from pathlib import Path # 路径处理库,用于处理文件路径 +import struct # 结构体库,用于将整数转换为字节(二进制数据) +import time # 时间库,用于控制帧率 from multiprocessing import shared_memory # 共享内存库,用于进程间通信 -import struct # 结构体库,用于将整数转换为字节(二进制数据) +from pathlib import Path # 路径处理库,用于处理文件路径 + +import cv2 # OpenCV,用于读取视频/图片/摄像头 +import numpy as np # NumPy,用于处理图像数组 # ========== 导入配置和日志模块 ========== try: from config_loader import Config from logger_setup import setup_logger + USE_CONFIG = True except ImportError: # 如果导入失败,使用默认配置(向后兼容) @@ -40,23 +43,23 @@ config = Config("config.yaml") logger = setup_logger( name="data_source", - log_level=config.logging.get('level', 'INFO'), - log_file=config.logging.get('file'), - console=config.logging.get('console', True) + log_level=config.logging.get("level", "INFO"), + log_file=config.logging.get("file"), + console=config.logging.get("console", True), ) logger.info("成功加载配置文件") - + # 从配置文件读取参数 - SOURCE_TYPE = config.data_source.get('type', 'video') - VIDEO_PATH = config.data_source.get('video_path', '') - CAMERA_INDEX = config.data_source.get('camera_index', 0) - IMAGES_DIR = config.data_source.get('images_dir', '') - FPS = config.data_source.get('fps', 24) - - SHM_NAME = config.shared_memory.get('name', 'yolo_image_shm') - MAX_WIDTH = config.shared_memory.get('max_width', 1920) - MAX_HEIGHT = config.shared_memory.get('max_height', 1080) - MAX_CHANNELS = config.shared_memory.get('max_channels', 3) + SOURCE_TYPE = config.data_source.get("type", "video") + VIDEO_PATH = config.data_source.get("video_path", "") + CAMERA_INDEX = config.data_source.get("camera_index", 0) + IMAGES_DIR = config.data_source.get("images_dir", "") + FPS = config.data_source.get("fps", 24) + + SHM_NAME = config.shared_memory.get("name", "yolo_image_shm") + MAX_WIDTH = config.shared_memory.get("max_width", 1920) + MAX_HEIGHT = config.shared_memory.get("max_height", 1080) + MAX_CHANNELS = config.shared_memory.get("max_channels", 3) except Exception as e: print(f"警告:加载配置失败,使用默认配置: {e}") USE_CONFIG = False @@ -69,11 +72,17 @@ except: # 如果日志模块也失败,使用print class SimpleLogger: - def info(self, msg): print(f"[INFO] {msg}") - def warning(self, msg): print(f"[WARNING] {msg}") - def error(self, msg): print(f"[ERROR] {msg}") + def info(self, msg): + print(f"[INFO] {msg}") + + def warning(self, msg): + print(f"[WARNING] {msg}") + + def error(self, msg): + print(f"[ERROR] {msg}") + logger = SimpleLogger() - + # 默认配置(保持原有代码) SOURCE_TYPE = "video" VIDEO_PATH = "/mnt/f/Deeplearning/yolo_source8.3.163/ultralytics/datasets/make_dataset/videos/result.mp4" @@ -97,12 +106,10 @@ def error(self, msg): print(f"[ERROR] {msg}") def iter_frames(): - """ - 根据 SOURCE_TYPE 不断产生图像帧(numpy 数组) - - 这是一个生成器函数(generator),使用 yield 关键字 - 每次调用 next() 时,会返回下一帧图像 - + """根据 SOURCE_TYPE 不断产生图像帧(numpy 数组). + + 这是一个生成器函数(generator),使用 yield 关键字 每次调用 next() 时,会返回下一帧图像 + 返回:numpy 数组,形状为 (高度, 宽度, 通道数),例如 (1080, 1920, 3) """ if SOURCE_TYPE == "video": @@ -112,7 +119,7 @@ def iter_frames(): # 检查是否成功打开 if not cap.isOpened(): raise RuntimeError(f"无法打开视频文件: {VIDEO_PATH}") - + # 无限循环读取视频帧 while True: ret, frame = cap.read() # ret表示是否成功,frame是图像数据 @@ -128,7 +135,7 @@ def iter_frames(): cap = cv2.VideoCapture(CAMERA_INDEX) if not cap.isOpened(): raise RuntimeError(f"无法打开摄像头: {CAMERA_INDEX}") - + # 无限循环读取摄像头画面 while True: ret, frame = cap.read() @@ -142,15 +149,13 @@ def iter_frames(): img_dir = Path(IMAGES_DIR) # 将路径字符串转换为Path对象 if not img_dir.is_dir(): raise RuntimeError(f"图片文件夹不存在: {IMAGES_DIR}") - + # 找到文件夹中所有的图片文件(jpg, jpeg, png, bmp) # sorted() 按文件名排序,确保顺序一致 - image_paths = sorted( - [p for p in img_dir.iterdir() if p.suffix.lower() in [".jpg", ".jpeg", ".png", ".bmp"]] - ) + image_paths = sorted([p for p in img_dir.iterdir() if p.suffix.lower() in [".jpg", ".jpeg", ".png", ".bmp"]]) if not image_paths: raise RuntimeError(f"图片文件夹中没有找到图片: {IMAGES_DIR}") - + # 循环播放图片 idx = 0 # 当前图片的索引 while True: @@ -165,63 +170,61 @@ def iter_frames(): def write_frame_to_shm(shm, frame): - """ - 将图像帧写入共享内存 - + """将图像帧写入共享内存. + 参数: shm: 共享内存对象 frame: numpy数组,形状为 (高度, 宽度, 通道数) - + 返回: True: 写入成功 False: 写入失败(图像尺寸超出限制) - + 共享内存布局(内存中的排列方式): [0-3字节] 宽度 (width),4字节整数 [4-7字节] 高度 (height),4字节整数 [8-11字节] 通道数 (channels),4字节整数 [12字节开始] 图像数据 (BGR格式,每个像素3个值:蓝、绿、红) - + 为什么要这样布局? - 程序B需要知道图像的尺寸,才能正确读取数据 - 先写尺寸信息(元数据),再写图像数据 """ # 获取图像的尺寸:h=高度, w=宽度, c=通道数 h, w, c = frame.shape - + # 检查图像尺寸是否超出限制 # 如果超出,无法写入(因为共享内存大小是固定的) if w > MAX_WIDTH or h > MAX_HEIGHT or c > MAX_CHANNELS: logger.warning(f"图像尺寸 {w}x{h}x{c} 超出限制 {MAX_WIDTH}x{MAX_HEIGHT}x{MAX_CHANNELS},将跳过") return False - + # ========== 将共享内存转换为numpy数组 ========== # 这样可以直接用numpy的方式操作内存 # buffer=shm.buf 表示使用共享内存的缓冲区 shm_array = np.ndarray((SHM_TOTAL_SIZE,), dtype=np.uint8, buffer=shm.buf) - + # ========== 写入元数据(图像尺寸信息)========== # struct.pack("I", w) 将整数w转换为4字节的二进制数据 # "I" 表示无符号整数(uint32),占4字节 # np.frombuffer() 将二进制数据转换为numpy数组 - shm_array[0:4] = np.frombuffer(struct.pack("I", w), dtype=np.uint8) # 写入宽度 - shm_array[4:8] = np.frombuffer(struct.pack("I", h), dtype=np.uint8) # 写入高度 + shm_array[0:4] = np.frombuffer(struct.pack("I", w), dtype=np.uint8) # 写入宽度 + shm_array[4:8] = np.frombuffer(struct.pack("I", h), dtype=np.uint8) # 写入高度 shm_array[8:12] = np.frombuffer(struct.pack("I", c), dtype=np.uint8) # 写入通道数 - + # ========== 写入图像数据(从第12字节开始)========== data_start = SHM_META_SIZE # 数据开始位置:12字节(元数据之后) data_end = data_start + w * h * c # 数据结束位置:开始位置 + 图像大小 # frame.flatten() 将二维/三维图像数组展平成一维数组 # 例如:(1080, 1920, 3) -> (6220800,) 一维数组 shm_array[data_start:data_end] = frame.flatten() - + return True def main(): - """ - 主函数:程序的入口点 - + """主函数:程序的入口点. + 工作流程: 1. 打印配置信息 2. 创建共享内存 @@ -247,11 +250,11 @@ def main(): logger.info(f"✅ 创建共享内存成功: {SHM_NAME}") except FileExistsError: # 如果共享内存已存在(比如上次程序异常退出),先删除再创建 - logger.warning(f"共享内存已存在,正在清理...") + logger.warning("共享内存已存在,正在清理...") try: # 连接到旧的共享内存 old_shm = shared_memory.SharedMemory(name=SHM_NAME) - old_shm.close() # 关闭连接 + old_shm.close() # 关闭连接 old_shm.unlink() # 删除共享内存 except: pass # 如果删除失败,忽略错误 @@ -271,32 +274,32 @@ def main(): while True: # 从生成器获取下一帧图像 frame = next(frame_gen) - + # 检查是否读取成功 if frame is None: logger.warning("读取到的帧为 None,跳过") time.sleep(interval) # 等待一段时间后继续 continue - + # 打印第一帧的信息(用于调试) if frame_count == 0: logger.info(f"✅ 第一帧图像尺寸: {frame.shape}") - + # 将图像写入共享内存 if write_frame_to_shm(shm, frame): frame_count += 1 # 成功写入,计数器+1 if frame_count == 1: - logger.info(f"✅ 成功写入第一帧到共享内存!") + logger.info("✅ 成功写入第一帧到共享内存!") # 每3秒打印一次进度(FPS * 3 = 30帧) if frame_count % (FPS * 3) == 0: logger.info(f"已写入帧数: {frame_count}, 当前图像尺寸: {frame.shape}") else: # 写入失败(通常是图像尺寸超出限制) logger.warning(f"写入帧失败,图像尺寸: {frame.shape}") - + # 等待一段时间,控制发送频率 time.sleep(interval) - + except KeyboardInterrupt: # 用户按 Ctrl+C 中断程序 logger.info("\n检测到 Ctrl+C,准备退出...") @@ -304,18 +307,19 @@ def main(): # 发生其他错误 logger.error(f"写入过程中发生错误: {e}") import traceback + logger.error(traceback.format_exc()) # 记录详细的错误信息 finally: # ========== 清理资源 ========== # finally 块中的代码无论是否出错都会执行 logger.info("\n正在清理资源...") - + try: shm.close() # 关闭共享内存连接 logger.info("✅ 共享内存连接已关闭") except Exception as e: logger.warning(f"关闭共享内存连接时出错: {e}") - + try: shm.unlink() # 删除共享内存(释放系统资源) logger.info("✅ 共享内存已删除") @@ -325,7 +329,7 @@ def main(): except Exception as e: # 如果删除失败(比如程序B还在使用),忽略错误 logger.warning(f"删除共享内存时出错(可能程序B还在使用): {e}") - + logger.info("程序 A 已退出。") @@ -333,4 +337,3 @@ def main(): # 当直接运行这个文件时(而不是被其他文件导入),执行main()函数 if __name__ == "__main__": main() - diff --git a/inference_shm.py b/inference_shm.py index dee329098..a4439e6ef 100644 --- a/inference_shm.py +++ b/inference_shm.py @@ -1,5 +1,5 @@ """ -程序B:推理引擎(使用共享内存版本) +程序B:推理引擎(使用共享内存版本). 这个程序的作用: 1. 从共享内存读取图像数据(由程序A写入) @@ -15,18 +15,22 @@ - 程序B负责从共享内存读取数据并进行推理 - 两个程序通过共享内存名称(SHM_NAME)连接 """ + # ========== 导入必要的库 ========== -import cv2 # OpenCV,用于显示图像和处理图像 -import time # 时间库,用于控制推理频率 -import numpy as np # NumPy,用于处理图像数组 -import struct # 结构体库,用于将字节转换为整数 +import struct # 结构体库,用于将字节转换为整数 +import time # 时间库,用于控制推理频率 from multiprocessing import shared_memory # 共享内存库,用于读取共享内存 + +import cv2 # OpenCV,用于显示图像和处理图像 +import numpy as np # NumPy,用于处理图像数组 + from ultralytics import YOLO # YOLO模型库,用于目标检测 # ========== 导入配置和日志模块 ========== try: from config_loader import Config from logger_setup import setup_logger + USE_CONFIG = True except ImportError: # 如果导入失败,使用默认配置(向后兼容) @@ -42,23 +46,23 @@ config = Config("config.yaml") logger = setup_logger( name="inference", - log_level=config.logging.get('level', 'INFO'), - log_file=config.logging.get('file'), - console=config.logging.get('console', True) + log_level=config.logging.get("level", "INFO"), + log_file=config.logging.get("file"), + console=config.logging.get("console", True), ) logger.info("成功加载配置文件") - + # 从配置文件读取参数 - SHM_NAME = config.shared_memory.get('name', 'yolo_image_shm') - MAX_WIDTH = config.shared_memory.get('max_width', 1920) - MAX_HEIGHT = config.shared_memory.get('max_height', 1080) - MAX_CHANNELS = config.shared_memory.get('max_channels', 3) - - MODEL_PATH = config.inference.get('model_path', 'yolov8n.pt') - FPS = config.inference.get('fps', 10) - CONF_THRESHOLD = config.inference.get('conf_threshold', 0.3) - IOU_THRESHOLD = config.inference.get('iou_threshold', 0.5) - CLASSES = config.inference.get('classes', [0]) # 默认只检测人 + SHM_NAME = config.shared_memory.get("name", "yolo_image_shm") + MAX_WIDTH = config.shared_memory.get("max_width", 1920) + MAX_HEIGHT = config.shared_memory.get("max_height", 1080) + MAX_CHANNELS = config.shared_memory.get("max_channels", 3) + + MODEL_PATH = config.inference.get("model_path", "yolov8n.pt") + FPS = config.inference.get("fps", 10) + CONF_THRESHOLD = config.inference.get("conf_threshold", 0.3) + IOU_THRESHOLD = config.inference.get("iou_threshold", 0.5) + CLASSES = config.inference.get("classes", [0]) # 默认只检测人 except Exception as e: print(f"警告:加载配置失败,使用默认配置: {e}") USE_CONFIG = False @@ -71,11 +75,17 @@ except: # 如果日志模块也失败,使用print class SimpleLogger: - def info(self, msg): print(f"[INFO] {msg}") - def warning(self, msg): print(f"[WARNING] {msg}") - def error(self, msg): print(f"[ERROR] {msg}") + def info(self, msg): + print(f"[INFO] {msg}") + + def warning(self, msg): + print(f"[WARNING] {msg}") + + def error(self, msg): + print(f"[ERROR] {msg}") + logger = SimpleLogger() - + # 默认配置(保持原有代码) SHM_NAME = "yolo_image_shm" MAX_WIDTH = 1920 @@ -96,17 +106,16 @@ def error(self, msg): print(f"[ERROR] {msg}") def read_frame_from_shm(shm, debug=False): - """ - 从共享内存读取一帧图像 - + """从共享内存读取一帧图像. + 参数: shm: 共享内存对象 debug: 是否打印调试信息 - + 返回: numpy数组:图像数据,形状为 (高度, 宽度, 通道数) None:如果读取失败或数据无效 - + 共享内存布局(和程序A写入的格式一致): [0-3字节] 宽度 (width),4字节整数 [4-7字节] 高度 (height),4字节整数 @@ -116,30 +125,30 @@ def read_frame_from_shm(shm, debug=False): # ========== 将共享内存转换为numpy数组 ========== # 这样可以直接用numpy的方式读取数据 shm_array = np.ndarray((SHM_TOTAL_SIZE,), dtype=np.uint8, buffer=shm.buf) - + # ========== 读取元数据(图像尺寸信息)========== # struct.unpack("I", ...) 将4字节的二进制数据转换为整数 # "I" 表示无符号整数(uint32) # tobytes() 将numpy数组转换为字节 - w = struct.unpack("I", shm_array[0:4].tobytes())[0] # 读取宽度 - h = struct.unpack("I", shm_array[4:8].tobytes())[0] # 读取高度 + w = struct.unpack("I", shm_array[0:4].tobytes())[0] # 读取宽度 + h = struct.unpack("I", shm_array[4:8].tobytes())[0] # 读取高度 c = struct.unpack("I", shm_array[8:12].tobytes())[0] # 读取通道数 - + # 打印调试信息(如果需要) - #if debug: + # if debug: # print(f"调试:读取到的尺寸 w={w}, h={h}, c={c}") - + # ========== 检查尺寸是否有效 ========== # 如果尺寸为0或超出限制,说明数据无效 if w == 0 or h == 0 or c == 0 or w > MAX_WIDTH or h > MAX_HEIGHT: if debug: - print(f"调试:尺寸无效,返回 None") + print("调试:尺寸无效,返回 None") return None - + # ========== 读取图像数据 ========== data_start = SHM_META_SIZE # 数据开始位置:12字节(元数据之后) data_end = data_start + w * h * c # 数据结束位置:开始位置 + 图像大小 - + # ========== 将一维数组重塑为图像形状 ========== try: # reshape((h, w, c)) 将一维数组重新排列成三维数组 @@ -155,9 +164,8 @@ def read_frame_from_shm(shm, debug=False): def main(): - """ - 主函数:程序的入口点 - + """主函数:程序的入口点. + 工作流程: 1. 加载YOLO模型 2. 连接到共享内存(等待程序A创建) @@ -186,7 +194,7 @@ def main(): logger.info(f"正在连接共享内存 {SHM_NAME}...") max_retries = 10 # 最多重试10次 retry_count = 0 - + # 循环尝试连接共享内存(因为程序A可能还没启动) while retry_count < max_retries: try: @@ -212,7 +220,7 @@ def main(): # ========== 初始化变量 ========== last_frame = None # 保存上一帧图像(如果读取失败,显示上一帧) - frame_count = 0 # 计数器:已推理的帧数 + frame_count = 0 # 计数器:已推理的帧数 interval = 1.0 / FPS # 每次循环的间隔时间(秒) no_frame_count = 0 # 统计连续未收到帧的次数 @@ -223,7 +231,7 @@ def main(): while True: # 从共享内存读取图像(前3次失败时打印调试信息) frame = read_frame_from_shm(shm, debug=(no_frame_count < 3)) - + if frame is None: # ========== 读取失败的处理 ========== no_frame_count += 1 @@ -234,7 +242,7 @@ def main(): logger.warning(" 2. 视频文件路径是否正确?") elif no_frame_count % 30 == 0: # 每30次(约3秒)提示一次 logger.warning(f"仍在等待图像数据... (已等待 {no_frame_count} 次)") - + # 如果没有收到新帧,使用上一帧(维持画面,避免黑屏) if last_frame is None: # 如果连上一帧都没有,等待后继续 @@ -252,11 +260,11 @@ def main(): # ========== YOLO推理 ========== # model.predict() 对图像进行目标检测 results = model.predict( - frame, # 输入图像 - conf=CONF_THRESHOLD, # 置信度阈值 - iou=IOU_THRESHOLD, # IoU阈值:用于非极大值抑制(NMS) + frame, # 输入图像 + conf=CONF_THRESHOLD, # 置信度阈值 + iou=IOU_THRESHOLD, # IoU阈值:用于非极大值抑制(NMS) classes=CLASSES if CLASSES else None, # 检测类别(空列表表示所有类别) - verbose=False, # 不打印详细信息 + verbose=False, # 不打印详细信息 ) # ========== 绘制检测结果 ========== @@ -269,7 +277,7 @@ def main(): # 窗口标题使用英文避免乱码 cv2.imshow("YOLO Real-time Inference (Shared Memory)", annotated) frame_count += 1 # 推理成功,计数器+1 - + # 每3秒打印一次进度(FPS * 3 = 30帧) if frame_count % (FPS * 3) == 0: logger.info(f"已推理帧数: {frame_count}, 图像尺寸: {annotated.shape}") @@ -292,18 +300,19 @@ def main(): # 发生其他错误 logger.error(f"❌ 推理过程中发生错误: {e}") import traceback + logger.error(traceback.format_exc()) # 记录详细的错误信息 finally: # ========== 清理资源 ========== # finally 块中的代码无论是否出错都会执行 logger.info("\n正在清理资源...") - + try: shm.close() # 关闭共享内存连接 logger.info("✅ 共享内存连接已关闭") except Exception as e: logger.warning(f"关闭共享内存连接时出错: {e}") - + # 尝试删除共享内存(如果程序A已经退出,或者这是最后一个连接) try: shm.unlink() # 删除共享内存 @@ -311,15 +320,15 @@ def main(): except FileNotFoundError: # 共享内存已经被程序A删除,这是正常的 pass - except Exception as e: + except Exception: # 其他错误(比如程序A还在使用),忽略 pass - + try: cv2.destroyAllWindows() # 关闭所有OpenCV窗口 except: pass - + logger.info("程序 B 已退出。") @@ -327,4 +336,3 @@ def main(): # 当直接运行这个文件时(而不是被其他文件导入),执行main()函数 if __name__ == "__main__": main() - diff --git a/logger_setup.py b/logger_setup.py index ef304b164..cb5eec418 100644 --- a/logger_setup.py +++ b/logger_setup.py @@ -1,34 +1,32 @@ """ -日志系统设置模块 +日志系统设置模块. 作用:设置日志系统,记录程序运行信息并保存到文件 类比:像记录员,记录重要信息并保存 """ + +from __future__ import annotations + import logging import sys -from pathlib import Path from logging.handlers import RotatingFileHandler -from typing import Optional +from pathlib import Path def setup_logger( - name: str = "yolo_shm", - log_level: str = "INFO", - log_file: Optional[str] = None, - console: bool = True + name: str = "yolo_shm", log_level: str = "INFO", log_file: str | None = None, console: bool = True ) -> logging.Logger: - """ - 设置日志系统 - + """设置日志系统. + 参数: name: 日志器名称(用于区分不同模块的日志) log_level: 日志级别 (DEBUG, INFO, WARNING, ERROR) log_file: 日志文件路径(None表示不保存到文件) console: 是否输出到控制台 - + 返回: 配置好的日志器 - + 使用示例: logger = setup_logger("data_source", log_file="logs/app.log") logger.info("程序启动") @@ -37,34 +35,31 @@ def setup_logger( """ # 创建日志器 logger = logging.getLogger(name) - + # 设置日志级别 level = getattr(logging, log_level.upper(), logging.INFO) logger.setLevel(level) - + # 避免重复添加处理器(如果已经配置过,直接返回) if logger.handlers: return logger - + # 日志格式:时间 - 名称 - 级别 - 消息 - formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + # 控制台输出处理器 if console: console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(level) console_handler.setFormatter(formatter) logger.addHandler(console_handler) - + # 文件输出处理器 if log_file: # 创建日志目录(如果不存在) log_path = Path(log_file) log_path.parent.mkdir(parents=True, exist_ok=True) - + # 使用RotatingFileHandler,自动轮转日志文件 # maxBytes: 单个日志文件最大10MB # backupCount: 保留5个备份文件 @@ -72,10 +67,10 @@ def setup_logger( log_file, maxBytes=10 * 1024 * 1024, # 10MB backupCount=5, # 保留5个备份文件 - encoding='utf-8' + encoding="utf-8", ) file_handler.setLevel(level) file_handler.setFormatter(formatter) logger.addHandler(file_handler) - + return logger diff --git a/mypredict.py b/mypredict.py index c7a2209cc..d36115b3b 100644 --- a/mypredict.py +++ b/mypredict.py @@ -12,7 +12,7 @@ save_txt=True, conf=0.01, project="runs/detect", - name="predict_val" + name="predict_val", ) # 统计验证集检测结果 @@ -20,12 +20,12 @@ for result in val_results: detections = len(result.boxes) total_detections += detections - img_name = str(result.path).split('\\')[-1] if isinstance(result.path, str) else str(result.path) + img_name = str(result.path).split("\\")[-1] if isinstance(result.path, str) else str(result.path) print(f"图像 {img_name}: 检测到 {detections} 个目标") if detections > 0: for i, box in enumerate(result.boxes[:5]): # 只显示前5个 - conf_value = float(box.conf.item()) if hasattr(box.conf, 'item') else float(box.conf) - print(f" - 目标{i+1}: 类别={result.names[int(box.cls)]}, 置信度={conf_value:.3f}") + conf_value = float(box.conf.item()) if hasattr(box.conf, "item") else float(box.conf) + print(f" - 目标{i + 1}: 类别={result.names[int(box.cls)]}, 置信度={conf_value:.3f}") print(f"\n验证集总共检测到 {total_detections} 个目标") @@ -33,7 +33,7 @@ print("\n" + "=" * 50) print("在测试图像上预测...") test_results = model.predict( - source=r"F:\Deeplearning\yolo_source8.3.163\make_dataset\images", + source=r"F:\Deeplearning\yolo_source8.3.163\make_dataset\images", save=True, show=False, save_txt=True, @@ -45,12 +45,11 @@ for result in test_results: detections = len(result.boxes) total_test_detections += detections - img_name = str(result.path).split('\\')[-1] if isinstance(result.path, str) else str(result.path) + img_name = str(result.path).split("\\")[-1] if isinstance(result.path, str) else str(result.path) print(f"图像 {img_name}: 检测到 {detections} 个目标") if detections > 0: for i, box in enumerate(result.boxes[:5]): # 只显示前5个 - conf_value = float(box.conf.item()) if hasattr(box.conf, 'item') else float(box.conf) - print(f" - 目标{i+1}: 类别={result.names[int(box.cls)]}, 置信度={conf_value:.3f}") + conf_value = float(box.conf.item()) if hasattr(box.conf, "item") else float(box.conf) + print(f" - 目标{i + 1}: 类别={result.names[int(box.cls)]}, 置信度={conf_value:.3f}") print(f"\n测试集总共检测到 {total_test_detections} 个目标") - diff --git a/mytrain.py b/mytrain.py index 432c1de91..de38e6670 100644 --- a/mytrain.py +++ b/mytrain.py @@ -8,6 +8,5 @@ imgsz=640, batch=2, cache="ram", - workers=1, + workers=1, ) - diff --git a/myvideo_predict.py b/myvideo_predict.py index ee5a30283..26a3960f3 100644 --- a/myvideo_predict.py +++ b/myvideo_predict.py @@ -1,15 +1,14 @@ from ultralytics import YOLO # 加载训练好的模型 -model = YOLO(r'F:\Deeplearning\yolo_source8.3.163\ultralytics\runs\detect\train11\weights\best.pt') +model = YOLO(r"F:\Deeplearning\yolo_source8.3.163\ultralytics\runs\detect\train11\weights\best.pt") # 检测视频文件 model.predict( - source=r'F:\Deeplearning\yolo_source8.3.163\ultralytics\datasets\make_dataset\videos\001.mp4', - save=True, # 保存标注后的视频 - show=False, # 不实时显示(设为True会在处理时显示) - conf=0.25, # 置信度阈值(与训练时一致) - imgsz=1280, # 输入尺寸(与训练时一致) - save_txt=False, # 视频不需要保存txt(如果需要可以设为True,会保存每帧的检测结果) + source=r"F:\Deeplearning\yolo_source8.3.163\ultralytics\datasets\make_dataset\videos\001.mp4", + save=True, # 保存标注后的视频 + show=False, # 不实时显示(设为True会在处理时显示) + conf=0.25, # 置信度阈值(与训练时一致) + imgsz=1280, # 输入尺寸(与训练时一致) + save_txt=False, # 视频不需要保存txt(如果需要可以设为True,会保存每帧的检测结果) ) - diff --git a/start.sh b/start.sh index 71a50adc7..d9218e105 100644 --- a/start.sh +++ b/start.sh @@ -13,23 +13,23 @@ mkdir -p config # 检查配置文件是否存在 if [ ! -f "config.yaml" ]; then - echo "⚠️ 警告:配置文件 config.yaml 不存在!" - echo "将使用代码中的默认配置" + echo "⚠️ 警告:配置文件 config.yaml 不存在!" + echo "将使用代码中的默认配置" else - echo "✅ 配置文件存在" + echo "✅ 配置文件存在" fi # 检查Python是否安装 if ! command -v python3 &> /dev/null; then - echo "❌ 错误:未找到 python3,请先安装Python" - exit 1 + echo "❌ 错误:未找到 python3,请先安装Python" + exit 1 fi # 检查必要的Python模块 echo "检查Python依赖..." -python3 -c "import cv2, numpy, yaml" 2>/dev/null +python3 -c "import cv2, numpy, yaml" 2> /dev/null if [ $? -ne 0 ]; then - echo "⚠️ 警告:缺少必要的Python模块,请运行: pip install -r requirements.txt" + echo "⚠️ 警告:缺少必要的Python模块,请运行: pip install -r requirements.txt" fi # 启动数据源程序(后台运行) diff --git a/stop.sh b/stop.sh index 77ccbed50..987d96d43 100644 --- a/stop.sh +++ b/stop.sh @@ -8,42 +8,42 @@ echo "==========================================" # 停止数据源程序 if [ -f "logs/data_source.pid" ]; then - PID=$(cat logs/data_source.pid) + PID=$(cat logs/data_source.pid) + if ps -p $PID > /dev/null 2>&1; then + echo "停止数据源程序 (PID: $PID)..." + kill $PID 2> /dev/null + sleep 1 + # 如果还在运行,强制杀死 if ps -p $PID > /dev/null 2>&1; then - echo "停止数据源程序 (PID: $PID)..." - kill $PID 2>/dev/null - sleep 1 - # 如果还在运行,强制杀死 - if ps -p $PID > /dev/null 2>&1; then - kill -9 $PID 2>/dev/null - fi - echo "✅ 数据源程序已停止" - else - echo "⚠️ 数据源程序未运行" + kill -9 $PID 2> /dev/null fi - rm -f logs/data_source.pid + echo "✅ 数据源程序已停止" + else + echo "⚠️ 数据源程序未运行" + fi + rm -f logs/data_source.pid else - echo "⚠️ 未找到数据源程序PID文件" + echo "⚠️ 未找到数据源程序PID文件" fi # 停止推理程序 if [ -f "logs/inference.pid" ]; then - PID=$(cat logs/inference.pid) + PID=$(cat logs/inference.pid) + if ps -p $PID > /dev/null 2>&1; then + echo "停止推理程序 (PID: $PID)..." + kill $PID 2> /dev/null + sleep 1 + # 如果还在运行,强制杀死 if ps -p $PID > /dev/null 2>&1; then - echo "停止推理程序 (PID: $PID)..." - kill $PID 2>/dev/null - sleep 1 - # 如果还在运行,强制杀死 - if ps -p $PID > /dev/null 2>&1; then - kill -9 $PID 2>/dev/null - fi - echo "✅ 推理程序已停止" - else - echo "⚠️ 推理程序未运行" + kill -9 $PID 2> /dev/null fi - rm -f logs/inference.pid + echo "✅ 推理程序已停止" + else + echo "⚠️ 推理程序未运行" + fi + rm -f logs/inference.pid else - echo "⚠️ 未找到推理程序PID文件" + echo "⚠️ 未找到推理程序PID文件" fi echo "" diff --git a/ultralytics/cfg/datasets/kunkun_xz.yaml b/ultralytics/cfg/datasets/kunkun_xz.yaml index 2b409cd82..8b18a63ef 100644 --- a/ultralytics/cfg/datasets/kunkun_xz.yaml +++ b/ultralytics/cfg/datasets/kunkun_xz.yaml @@ -1,13 +1,11 @@ - # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path: F:\Deeplearning\yolo_source8.3.163\ultralytics\datasets\xz_dataset -train: images/train +train: images/train val: images/val -test: +test: # Classes names: 0: kunkun 1: fan 2: xiang - \ No newline at end of file diff --git a/ultralytics/data/split.py b/ultralytics/data/split.py index af8492558..c0c5b6edc 100644 --- a/ultralytics/data/split.py +++ b/ultralytics/data/split.py @@ -135,4 +135,4 @@ def autosplit( if __name__ == "__main__": - split_classify_dataset("caltech101") \ No newline at end of file + split_classify_dataset("caltech101") diff --git a/ultralytics/trackers/bot_sort.py b/ultralytics/trackers/bot_sort.py index 9a75c08f7..9a06a122d 100644 --- a/ultralytics/trackers/bot_sort.py +++ b/ultralytics/trackers/bot_sort.py @@ -71,7 +71,7 @@ def __init__( self.curr_feat = None if feat is not None: self.update_features(feat) - self.features = deque([], maxlen=feat_history) + self.features = deque(maxlen=feat_history) self.alpha = 0.9 def update_features(self, feat: np.ndarray) -> None: diff --git "a/\351\200\232\344\277\241\346\226\271\345\274\217\350\257\264\346\230\216.md" "b/\351\200\232\344\277\241\346\226\271\345\274\217\350\257\264\346\230\216.md" index 0182dfc94..556738d74 100644 --- "a/\351\200\232\344\277\241\346\226\271\345\274\217\350\257\264\346\230\216.md" +++ "b/\351\200\232\344\277\241\346\226\271\345\274\217\350\257\264\346\230\216.md" @@ -27,31 +27,33 @@ ### 关键代码位置 #### 程序A发送数据(data_source.py 第91-108行) + ```python def send_frame(conn, frame): # 1. 将图像编码为JPEG格式(压缩) encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90] - success, encoded = cv2.imencode(".jpg", frame, encode_param) - + _success, encoded = cv2.imencode(".jpg", frame, encode_param) + # 2. 用pickle序列化(转换为字节流) data = pickle.dumps(encoded, protocol=pickle.HIGHEST_PROTOCOL) size = len(data) - + # 3. 先发送数据长度(4字节),再发送实际数据 conn.sendall(struct.pack("!I", size)) # 发送长度 - conn.sendall(data) # 发送数据 + conn.sendall(data) # 发送数据 ``` #### 程序B接收数据(inference.py 第40-59行) + ```python def recv_frame(conn): # 1. 先接收4字节的长度信息 size_data = recv_exact(conn, 4) (size,) = struct.unpack("!I", size_data) - + # 2. 根据长度接收图像数据 data = recv_exact(conn, size) - + # 3. 反序列化并解码 encoded = pickle.loads(data) frame = cv2.imdecode(encoded, cv2.IMREAD_COLOR) @@ -76,6 +78,7 @@ def recv_frame(conn): ## 🔄 共享内存 vs Socket 的区别 ### Socket(当前方式) + ``` 程序A的内存 → 序列化 → 内核缓冲区 → 网络栈 → 内核缓冲区 → 反序列化 → 程序B的内存 ↑ ↓ @@ -83,6 +86,7 @@ def recv_frame(conn): ``` ### 共享内存(更高效) + ``` 程序A的内存 ←→ 共享内存区域 ←→ 程序B的内存 ↑ ↓ @@ -90,6 +94,7 @@ def recv_frame(conn): ``` **共享内存的优势**: + - ⚡ **速度更快**:不需要复制数据,直接访问同一块内存 - 💾 **内存效率**:只占用一份内存空间 - 🚀 **适合大数据**:图像数据很大,共享内存避免复制开销 @@ -124,4 +129,3 @@ def recv_frame(conn): - 共享内存方式:两个程序直接访问同一块内存,无复制开销 如果你需要更高的性能(特别是高分辨率图像),可以考虑改为共享内存实现。 -