ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于YOLOv8与OpenCV的实时车速检测系统实现

基于YOLOv8与OpenCV的实时车速检测系统实现 简介一套面向计算机视觉毕业设计与智能交通场景的完整工程包基于OpenCV和YOLOv8实现实时车辆检测、多目标跟踪与车速测算适合具备Python和深度学习基础的学生、开发者作为项目参考或二次开发起点。压缩包共16个文件主要包含Python核心脚本、YOLOv8模型标签与配置文件、项目说明文档以及演示视频可直观了解从模型调用、车辆识别到运动分析与速度计算的完整流程包体约87.89MB。资源中提供了车辆跟踪实现代码、依赖清单和演示素材并辅以说明文档有助于快速还原实验环境、理解目标检测与目标跟踪的模块分工也能为超速监测、交通流量分析等场景提供实现思路。目前已有140人学习下载对毕业设计选题或计算机视觉方向的项目落地具有实用参考价值。1. 实时车速检测到底在解决什么一辆车从画面中驶过怎么从视频算出它的速度如果一段固定视角的交通监控视频摆在你面前最直观的需求是“认出车”和“算出车速”但真正动手做计算机毕业设计时你会发现这两件事的难度完全不在一个量级。目标检测有公开的 YOLOv8 预训练模型可以直接用而测速涉及坐标系换算、跟踪 ID 稳定性、视频帧率误差这些检测模型本身不关心的问题。这套以 OpenCV 和 YOLOv8 为核心的实时车速检测与车辆检测跟踪系统价值恰恰在于YOLOv8 负责把车辆框出来OpenCV 负责图像解码、绘制和交互你在中间补上跟踪与测速逻辑最终输出带速度值的可视化结果。它适合想走应用型路线、手头有一段固定机位道路视频、愿意花时间调参数和验证精度的计算机视觉方向毕设选题。2. 系统拆解与测速原理检测、跟踪、像素换算成 km/h 的逻辑先立住2.1 任务拆解检测、跟踪、测速是三件事别指望一个模型全包很多同学会把“实时车速检测”理解为训练一个能直接输出速度的深度学习模型这其实是把问题想窄了。车速是物理量模型输出的是像素坐标中间的换算必须由你自己实现。常见做法是把系统拆成三段检测、跟踪、测速。检测用 YOLOv8。选择它的理由很直接一阶段 anchor-free 结构在检测精度和推理速度之间平衡得比较好公开的 COCO 预训练权重覆盖了 car、motorcycle、bus、truck 这些车辆类别拿来跑车辆检测几乎不用自己做数据集。对比传统的 Background Subtraction 或 HOG SVMYOLOv8 对光照变化和阴影的鲁棒性好很多这也是以深度学习为核心卖点的毕设最愿意看到的。跟踪是把每一帧的检测框关联成同一条车辆轨迹。YOLOv8 本身只做单帧检测帧与帧之间没有记忆需要额外的跟踪器。常见做法是接 ByteTrack 或 SORTYOLOv8 官方接口里也内置了这两种配置调用起来不复杂。跟踪的价值不是炫技而是为测速提供连续的时间戳同一辆车在画面中什么时候经过第一根线、什么时候经过第二根线全靠 ID 串起来。OpenCV 的作用容易被低估。它负责 VideoCapture 读取视频、图像缩放、画框画线画轨迹、最终结果可视化还能顺带做 ROI 区域掩码和亮度归一化。没有 OpenCVYOLOv8 的推理结果只是一堆张量构不成一个能演示的系统。2.2 像素坐标怎么变成真实速度标定线、透视和帧率三条路单目摄像头拍摄的视频没有深度信息单帧画面里你根本不知道一个像素代表多少厘米必须借助时间信息算出速度。最基本的原理是找到道路画面中两条物理距离已知的线记录车辆通过两条线的时间差然后用距离除以时间。速度公式很简单v D / (t2 - t1)其中 D 是两条标定线之间的真实道路距离单位米t1 和 t2 是车辆底部中心分别越过两条线的时刻单位秒。得到的结果是米每秒乘以 3.6 就是 km/h。这里有一个关键细节为什么用车辆底部中心而不是检测框的中心因为框的中心会随着车辆高度和遮挡关系上下移动而底部中心更贴近地面地面上的点与道路平面的投射关系更稳定。测速时用底部中心作为参考点误差会小很多。但你必须清楚单目测速的边界。摄像头如果是斜向俯拍画面中近处和远处的像素尺度不一样直接拿两条图像水平线当作真实距离会有明显偏差。毕业设计里最稳妥的标定方法是在拍摄地点量好两条真实平行线之间的距离让这两条线在画面中的位置尽量靠近画面中间区域车辆行驶方向尽量垂直于这两条线。如果你想要更严谨可以用cv2.getPerspectiveTransform把道路平面投影成俯视图再测速但做毕设不急于一步到位先把双线法跑通再考虑透视修正。帧率是另一个决定测速精度的变量。视频的 FPS 必须从视频元数据里读取不要在代码里写死。同样一辆车如果 FPS 读取成 30 而实际视频是 25测出来的速度会系统性偏高约 20%而且这种误差很难靠事后调整找补。2.3 选型决策模型大小、CPU 还是 GPU、要不要自己训练在你开始敲代码之前先把运行环境这件事定下来。很多同学用的是普通笔记本没有 N 卡或只有集显想跑 YOLOv8 会有心理负担。实测下来只要选对模型和输入尺寸CPU 也不是不能用。yolov8n 配合 640 输入分辨率在近几年的笔记本 CPU 上能跑到十几帧每秒对测速演示来说够用。如果你是 Ubuntu 20.04 的 CPU 环境照着官方文档把 ultralytics 装好选择 yolov8n.pt 权重同样能跑只是别开过高的置信度阈值以免漏检。如果是 GTX 1660 Ti 级别的显卡选择空间就大很多。yolov8s 或 yolov8m 都能流畅运行检测精度明显好于 n 版本。再往上有 RTX 30 系或 40 系可以直接挑战 yolov8l但毕设场景通常没必要因为性能过剩不会体现在测速准确性上反而拖慢开发迭代速度。关于是否自己训练数据集我的建议是先不训练。COCO 预训练权重对车辆检测已经很成熟直接用它做测速逻辑开发把精力放在跟踪和速度换算上。等系统整个跑通、答辩演示稳定后如果想在论文里增加“改进”亮点再用 Labelme 标注自己的场景视频微调一个 yolov8 模型这样工作量和创新点都更可控。3. 搭一个能跑起来的代码骨架YOLOv8 车辆检测加跟踪的最小实现3.1 环境安装一套在 CPU 上也能跑的命令环境问题尽量避免手动编译直接用 pip 装轮子。Python 版本建议 3.9 或 3.10太新的 Python 版本有时会碰上某些依赖库尚未适配的情况。# 建议先建独立的虚拟环境, 避免和系统 Python 纠缠 python -m venv speed_env source speed_env/bin/activate # OpenCV 装 CPU 版就够, 不需要额外装 CUDA 版 pip install opencv-python4.8.1.78 # ultralytics 会自动带上对应的 pytorch 环境 pip install ultralytics # 验证安装 python -c import cv2; import torch; print(cv2.__version__, torch.__version__)这里要说明三点。第一opencv-python默认是 CPU 版安装体积不大运行也不需要 CUDA选 4.8 系列是因为跟 ultralytics 的兼容性比较稳定不推荐一上来就装最新的 OpenCV 5.x 预览版。第二ultralytics会拉取 PyTorch 依赖没有独立显卡时 pip 会自动安装 CPU 版 PyTorch这对推理 yolov8n 足够如果后续想自训数据集再单独按 CUDA 版本重装对应 torch。第三验证安装的输出结果不需要很华丽能看到版本号就说明底层环境没问题。如果出现No module named cv2基本就是虚拟环境没激活或者 pip 安装到了其他 Python 解释器里先which python确认路径。3.2 最小检测加跟踪代码检测框、ID、轨迹一起出环境中最重要的函数是model.track它把检测和跟踪封装在同一个调用里。下面这段代码是能跑通的最小框架不需要额外写 SORT 或 DeepSORT 的逻辑。import cv2 from ultralytics import YOLO # 无 GPU 用 yolov8n, 有独立显卡可以换 yolov8s model YOLO(yolov8n.pt) cap cv2.VideoCapture(road.mp4) fps cap.get(cv2.CAP_PROP_FPS) # 帧率必须从视频文件里读 while cap.isOpened(): ok, frame cap.read() if not ok: break # classes: 2car, 3motorcycle, 5bus, 7truck # bytetrack 比默认的 sort 能更好应对目标重叠和短暂遮挡 results model.track(frame, persistTrue, classes[2, 3, 5, 7], conf0.35, iou0.5, trackerbytetrack.yaml) if results[0].boxes is not None and results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.int().cpu().numpy() for box, track_id in zip(boxes, ids): x1, y1, x2, y2 box.astype(int) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID:{track_id}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(track-demo, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码的逻辑分四层读取视频、YOLOv8 检测加跟踪、从结果中取框和 ID、绘制并显示。关键在model.track的参数设置。persistTrue表示跨帧保持跟踪轨迹如果漏掉这个参数每一帧的 ID 会重新编号跟踪就失效了。trackerbytetrack.yaml指定跟踪器配置ByteTrack 在车辆密集和交叉场景下比 SORT 更稳这一点在毕设演示车辆较多时差异很明显。classes[2, 3, 5, 7]过滤出车辆类别避免行人、自行车这些目标干扰测速结果。参数说明里值得留意的是conf0.35。置信度门槛不是越高越好调高到 0.6 以上会漏掉远处较小或部分被遮挡的车辆调太低又会有很多误检框。实际开发时建议先看打印出的检测效果再按目标最远距离定位到合适区间。3.3 把跟踪结果落成结构化数据每个 ID 的轨迹数组实时界面跑通只是第一步测速需要的是每个车辆 ID 的连续轨迹。建议在帧循环里维护一个字典以 track_id 为 key把每一帧的底部中心坐标、帧号和时刻记录下来。tracks {} # track_id - list[(cx, bottom_y, frame_idx, timestamp)] frame_idx 0 while cap.isOpened(): ok, frame cap.read() if not ok: break timestamp frame_idx / fps # 当前帧对应的视频时刻, 单位秒 results model.track(frame, persistTrue, classes[2, 3, 5, 7], conf0.35, iou0.5, trackerbytetrack.yaml) if results[0].boxes is not None and results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.int().cpu().numpy() for box, track_id in zip(boxes, ids): x1, y1, x2, y2 box cx (x1 x2) / 2.0 bottom_y y2 # 用检测框底边中心作为车辆地面参考点 tracks.setdefault(track_id, []).append((cx, bottom_y, frame_idx, timestamp)) frame_idx 1为什么要维护这样一个轨迹数组因为测速逻辑不只看当前帧还要回看车辆轨迹中哪一帧越过标定线。轨迹数组相当于给每个 ID 建立了一份历史档案后续计算速度时直接遍历这个 ID 对应的轨迹即可。bottom_y取y2而不是(y1 y2) / 2是因为检测框的底边中心更接近车辆与地面的接触点能减少车型高低带来的影响。时间戳用frame_idx / fps计算比用time.time()更可靠因为程序处理速度不稳定会影响真实时间读数而帧间隔在固定帧率视频里是稳定的。这段代码本身不输出速度但它定义了测速数据的基础格式。后续所有速度计算都建立在tracks字典之上建议在框架阶段就把坐标系和时间戳的格式定清楚不然后面返工改数据结构会让你怀疑人生。4. 从标定到输出速度双线越线测速的完整实现与参数调优4.1 视频采集与帧率处理别让源头数据坑了后面所有逻辑测速系统的误差源头往往不在算法而在输入视频本身。做毕设时最容易翻车的操作是随便找一段网上视频既不知道原始帧率也不清楚是否有编辑过的跳帧。这类视频做演示没问题但用来当作论文的测量依据数据经不起推敲。我的建议是自拍视频或请同学帮忙拍摄。固定一个三脚架或倚靠在稳定的护栏上让镜头视角能同时看到足够长的道路段分辨率至少 1080p帧率 30 或 60。拍摄时在道路上找到两个天然标志物比如路灯间距、斑马线边缘、井盖位置用卷尺量出它们的实际距离这就是你的真实标定距离 D。没有卷尺时可以用已知车长约 4.5 米估算但精度会差一些论文里建议写明估算方法。另一个容易被忽略的点是视频后期不要做慢放或变速处理。导出视频时保持原始帧率不然测出来的速度会整体偏大或偏小。代码里统一用cap.get(cv2.CAP_PROP_FPS)读取帧率不要在代码里硬编码。4.2 双线越线测速实现记录越线帧号并计算瞬时速度这一步是整个测速系统的核心逻辑。在画面中设定两条水平线车辆底部中心从线的一侧跨越到另一侧时记录当前时间戳。第一条线和第二条线的时间差就是车辆通过已知距离 D 所用的时间。import cv2 from ultralytics import YOLO # 参数区: 根据实际画面调整 LINE1_Y 300 # 第一根测速线的 y 坐标 LINE2_Y 450 # 第二根测速线的 y 坐标 REAL_DISTANCE 12.0 # 两根线对应的真实道路距离, 单位: 米 LOWER_SPEED 5.0 # 低于该速度视为异常, 过滤 UPPER_SPEED 200.0 # 高于该速度视为异常 model YOLO(yolov8n.pt) cap cv2.VideoCapture(road.mp4) fps cap.get(cv2.CAP_PROP_FPS) tracks {} first_cross_time {} # track_id - 经过 LINE1 的时刻 speeds {} # track_id - 计算出的速度 frame_idx 0 while cap.isOpened(): ok, frame cap.read() if not ok: break timestamp frame_idx / fps results model.track(frame, persistTrue, classes[2, 3, 5, 7], conf0.35, iou0.5, trackerbytetrack.yaml) if results[0].boxes is not None and results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.int().cpu().numpy() for box, track_id in zip(boxes, ids): x1, y1, x2, y2 box bottom_y y2 # 记录当前帧位置 if track_id not in tracks: prev_bottom_y bottom_y else: prev_bottom_y tracks[track_id][-1][1] tracks.setdefault(track_id, []).append((timestamp, bottom_y)) # 越线判断: 上一帧在线之上, 当前帧在线之下 if track_id not in first_cross_time: if prev_bottom_y LINE1_Y bottom_y: first_cross_time[track_id] timestamp elif track_id not in speeds: if prev_bottom_y LINE2_Y bottom_y: t2 timestamp t1 first_cross_time[track_id] delta_t t2 - t1 if delta_t 0: speed_kmh REAL_DISTANCE / delta_t * 3.6 if LOWER_SPEED speed_kmh UPPER_SPEED: speeds[track_id] speed_kmh print(f车辆 {track_id}: {speed_kmh:.1f} km/h) # 绘制标定线和速度结果 cv2.line(frame, (0, LINE1_Y), (frame.shape[1], LINE1_Y), (0, 0, 255), 2) cv2.line(frame, (0, LINE2_Y), (frame.shape[1], LINE2_Y), (0, 255, 0), 2) cv2.putText(frame, fD{REAL_DISTANCE}m, (10, LINE1_Y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imshow(speed-detection, frame) if cv2.waitKey(1) 0xFF ord(q): break frame_idx 1 cap.release() cv2.destroyAllWindows()这段代码的越线判断值得拆开讲。prev_bottom_y LINE1_Y bottom_y这个连比表达式语义是“上一帧的底部中心在线的上方当前帧的底部中心到达线或越过线”只有从上方穿越到线及以下的瞬间才触发记录。这比单纯比较当前帧底部位置要可靠因为车辆在画面中长时间停留在线下时不会反复触发越线事件。时间差的计算用t2 - t1其中 t1 和 t2 都是从视频帧索引换算出的时间戳不是程序运行时的墙上时间。这个设计很关键因为模型推理耗时不稳定程序每帧处理时间会有波动如果用time.time()记录时刻测出来的速度可能时快时慢。速度计算之后加了上下限过滤。低于 5 km/h 的通常是误检或静止物体被跟踪高于 200 km/h 的是异常跳变直接丢弃不让它污染统计结果。4.3 速度结果平滑与误差区间把跳变压下去双线法测得的速度本质上是这一段路程的平均速度但一次的测量结果容易受越线帧采样误差影响。举例来说30 FPS 视频下车辆如果以 60 km/h 通过 12 米标定距离耗时 0.72 秒约 21 帧。越线瞬间早一帧或晚一帧时间误差约 0.033 秒速度误差在 2-3 km/h 左右。这是系统固有误差无法完全消除但可以减少波动。常见的处理方法是加权平均。对同一辆车如果它多次经过画面比如绕一圈回到镜头前取多次速度的中位数而不是平均值因为中位数对个别异常值不敏感。如果视频里车辆只经过一次则不做平滑直接输出双线法结果。另一个实用技巧是输出结果时保留一位小数显示但统计误差时用原始浮点值。论文里的表格不建议只放速度截图最好把视频帧号、t1、t2、delta_t、速度都列出来方便老师溯源。这些中间数据也是论文“实验设计”章节的重要素材尽量保留到 CSV 里。5. 避坑与常见问题排查误检、抖框、ID 跳变、视角差引起的 5 个典型坑5.1 案例一树木和阴影被当成车辆速度值乱跳现象画面里出现大量静止或缓慢移动的检测框ID 不断变化测出的速度分布跨度很大有些只有几 km/h有些却有几十 km/h。原因COCO 预训练模型对“像车”的物体比较敏感树叶阴影、路边停靠的车辆轮廓、广告牌上的车辆图案都可能被识别为车辆。这些误检框的底部中心并不在地面上跟踪它们会得到毫无意义的速度值。解决给测速区域画一个 ROI 多边形掩码只保留框中心落在路面区域内的检测结果。这是最直接有效的手段。import numpy as np # 手动标出画面中的路面区域 roi_points np.array([(150, 280), (850, 280), (1000, 720), (0, 720)], dtypenp.int32) mask np.zeros(frame.shape[:2], dtypenp.uint8) cv2.fillPoly(mask, [roi_points], 255) # 在过滤框时叠加 ROI 判断 if mask[int(bottom_y), int(cx)] 255: # 保留该检测框用于跟踪和测速 passROI 之外的一切检测结果直接丢弃这能消灭大部分误检。同时把conf提高到 0.4-0.45可以进一步过滤低置信度目标。5.2 案例二检测框底部抖动导致越线时间忽早忽晚现象同一辆车重复测试多次速度结果分散最大值和最小值相差超过 10 km/h而且没有明显规律。原因YOLOv8 输出的边界框是锚框回归结果车辆在移动过程中框的底部边缘会随车辆姿态、遮挡和图片压缩产生一两个像素的抖动。双线法对越线帧号非常敏感一帧的偏差在低帧率视频里就可能放大成几 km/h 的误差。解决对底部 y 坐标做平滑处理。常见做法是对最近 5 帧的底部 y 取中值或者用指数移动平均。越线判断改为“连续 3 帧都在线下方”才算真正越线而不是单帧触发。# 指数移动平均, alpha 越小越平滑 smoothed_y alpha * bottom_y (1 - alpha) * prev_smoothed_y这里alpha取 0.3-0.5 比较合适太小会让跟随延迟明显车辆实际越线时间被推后。5.3 案例三跟踪 ID 频繁切换一辆车算成两段速度现象画面中一辆车被遮挡或短暂漏检几帧后重新出现的 ID 变了原来的轨迹断掉测速逻辑中first_cross_time永远等不到第二条线。原因这是跟踪器的固有局限。ByteTrack 对短期遮挡的恢复能力已经比 SORT 好很多但遇到大面积遮挡或置信度骤降时跟踪框丢失后无法关联到原 ID。另外conf阈值设得过高会让车辆后半段漏检同样打断轨迹。解决优先确认用的是bytetrack.yaml而不是默认的 sort再把conf降低到 0.25-0.3保证低质量帧也能检出车辆。如果画面中大量目标是密集小物体可以适当降低iou到 0.4。实现层面给跨线逻辑加一个超时判断如果某辆车记录过一次越线但长时间没有第二次越线超过 5 秒后清除该 ID 的记录避免内存无限增长。5.4 案例四夜间或逆光场景漏检严重速度直接归零现象白天效果正常换到傍晚或逆光视频后车辆检测率大幅下降很多车被漏掉或只检到半截车身测速结果稀疏且不稳定。原因YOLOv8 预训练权重对常规光照泛化不错但低照度和高反差场景超出训练分布。摄像头自动曝光也会让暗部车辆与路面混在一起模型难以分辨轮廓。解决推理前做图像增强。cv2.normalize或 CLAHE 都能提升暗部细节让模型更容易检出目标。效果不够好时终极方案是采集自己的场景帧用 Labelme 标注后微调 yolov8 模型把自建数据集融入训练流程这也是答辩讲“针对实际场景优化”的加分点。# CLAHE 提升暗部对比度 lab cv2.cvtColor(frame, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l clahe.apply(l) frame cv2.cvtColor(cv2.merge([l, a, b]), cv2.COLOR_LAB2BGR)5.5 案例五透视造成的远近速度不一致现象同一辆车在画面近处和远处测得的速度相差很大远处偏慢、近处偏快且这种误差是系统性的不是随机抖动。原因斜向俯拍视角下图像 y 坐标与真实路面距离不是线性关系。近处一个像素对应几厘米远处一个像素可能对应几十厘米直接用图像 y 方向的像素高度差当作真实距离当然会出错。双线法如果只取两条水平线没有考虑透视缩放本质上是把一个线性模型套到了非线性关系上。解决最简单的办法是把两条测速线放在画面中车辆轨迹比较集中的区域且两条线之间距离不要太远减小透视带来的非线性差异。更严谨的做法是用单应性变换把道路平面映射为俯视图再测速在图像中选取道路上的四个点对应真实世界的矩形坐标求透视变换矩阵后把整个道路区域投影成正视图然后再做双线越线计算。这个方法前期工作量大但能明显提升测速精度放在论文里也比只画两条线更有说服力。6. 交出一份能答辩的毕业设计精度验证、演示界面的习惯总结6.1 精度验证没有测速仪也能算出误差很多同学做完整套系统后不知道怎么证明测速准。最实用的方法是参考车辆仪表盘请同学开一辆车在拍摄路段以固定速度行驶同时用手机记录仪表盘读数事后在视频回放中找到相应时间段和系统输出对比。整个过程重复 5-8 次覆盖不同速度和不同车道最后统计平均绝对误差和误差率。习惯上平均绝对误差在 5 km/h 以内可以算作优秀8 km/h 以内也可以接受超出这个范围就要检查标定距离或帧率设置。6.2 演示界面别让现场推理性能拖后腿答辩现场最怕两种情况视频卡成幻灯片或者模型加载时间过长。我的建议是准备两个形式的演示材料一份是原视频一份是系统处理后的输出视频用播放器左右对比展示。处理好的视频可以反复播放不依赖现场 GPU 性能。如果你想要可交互的界面最省事的是用 OpenCV 窗口加键盘控制或做一个简单的 Streamlit 页面上传视频即可处理但务必先本地录好演示视频作为兜底方案。6.3 答辩前自查清单检查模型加载是否只执行一次不要在每一帧都重复初始化权重。把检测、跟踪、速度计算分模块写在函数里老师问起框架结构时能讲清楚。如果你的工作量需要再拔高用 Labelme 标注几十张本场景截图微调 YOLOv8 或引入注意力机制改进有明显增益再写进论文不要为了“改进”而堆砌模块。输出测速结果时保留中间数据到 CSV 文件这是答辩时最有说服力的原始材料。最后现场演示前重启一次 Python 进程确认模型文件路径是绝对路径而不是相对路径避免工作目录变化导致加载失败。我有一次就是图省事在 Jupyter Notebook 里把推理循环跑通后直接带去演示结果答辩电脑上没有安装对应的 Python 包现场花了五分钟装环境整个节奏被打乱。从那以后我养成了两个习惯所有依赖写进 requirements.txt所有演示视频先渲染成成品。这两个习惯大概也是这套毕设系统能顺利交付最重要的保障希望帮到你。本文还有配套的精品资源点击获取
返回列表