
简介这份资源是一套基于OpenCV与TensorFlow实现游戏中车辆自动驾驶的完整项目源码面向人工智能、计算机科学与技术等相关专业的学生与开发者尤其适合用作毕业设计课题、课程作业或自学练手。项目通过屏幕图像采集、道路线检测与卷积神经网络训练让车辆在游戏环境中完成自动行驶覆盖数据采集、模型训练与测试推理的完整链路。压缩包共37个文件以28个Python脚本为核心辅以png图像、npy训练数据、txt说明、md文档及license等整体约11.77MB目录按数据采集、模型训练、测试与教程等模块划分结构清晰便于按流程阅读。目前已有79人学习关注。读者可从中获得可运行的自动驾驶小项目参考理解OpenCV图像处理与TensorFlow模型训练的配合方式并借助README与模块化脚本快速上手、排查问题适合作为入门计算机视觉与深度学习的实践素材。1. 从屏幕像素到方向盘这套 OpenCVTensorFlow 游戏自动驾驶源码能跑出什么如果你玩过《欧洲卡车模拟》或者《极限竞速》大概想过一个问题能不能让程序自己看着屏幕开车这套源码就是干这个的。它用 OpenCV 抓取游戏窗口的实时画面做 ROI 裁剪和 HSV 颜色分割提取车道线再用 TensorFlow 训练一个卷积神经网络把画面直接映射成键盘的上下左右操作。整个链路不依赖游戏内存读取也不注入进程纯靠看屏幕来决策所以理论上换一款赛车游戏只要重新采集数据就能迁移。这套东西适合谁做毕业设计需要完整端到端 demo 的学生、想入门模仿学习但不想碰 CARLA 那套重型仿真的开发者、以及想理解自动驾驶端到端最朴素形态的工程师。它不涉及真实车辆控制也不涉及传感器融合就是一个在游戏环境里验证感知-决策闭环的最小可行系统。源码包里的目录结构已经分好了数据采集、模型训练、模型测试三条线README 也在拿到手先跑通再谈优化。2. 拆开目录看架构三条流水线怎么串起来2.1 数据采集链路grabscreen 到 training_data.npy整个项目的数据流起点是grabscreen.py。它用win32gui和win32ui抓取指定窗口的位图转成 numpy 数组再交给 OpenCV 做灰度化和尺寸压缩。采集脚本collect_data.py会同时启动一个键盘监听线程getkeys.py你一边玩它一边把当前帧 当前按键成对存下来。最终所有样本被拼成一个training_data.npy里面每一行是[帧像素..., 按键one-hot...]。这里有个设计选择值得说它没有用屏幕录制再离线标注而是实时同步采集。好处是帧和操作的时序对齐天然准确坏处是你得全程手动开采集效率取决于你开车的时长。常见做法是至少跑 10 到 15 分钟覆盖直道、弯道、撞墙后倒车这些场景否则模型没见过的情况就会瞎打方向。# grabscreen.py 核心逻辑简化示意 import numpy as np import win32gui, win32ui, win32con def grab_screen(regionNone): hwin win32gui.GetDesktopWindow() if region: left, top, x2, y2 region width x2 - left height y2 - top else: width win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN) height win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN) left win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN) top win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN) hwindc win32gui.GetWindowDC(hwin) srcdc win32ui.CreateDCFromHandle(hwindc) memdc srcdc.CreateCompatibleDC() bmp win32ui.CreateBitmap() bmp.CreateCompatibleBitmap(srcdc, width, height) memdc.SelectObject(bmp) memdc.BitBlt((0, 0), (width, height), srcdc, (left, top), win32con.SRCCOPY) signedIntsArray bmp.GetBitmapBits(True) img np.frombuffer(signedIntsArray, dtypeuint8) img.shape (height, width, 4) # 释放资源否则长时间采集会内存泄漏 srcdc.DeleteDC() memdc.DeleteDC() win32gui.ReleaseDC(hwin, hwindc) win32gui.DeleteObject(bmp.GetHandle()) return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)这段代码的关键参数是region。如果你不传区域它抓整个桌面分辨率可能是 1920×1080 甚至更大存下来的 npy 文件会爆炸。我一般会先跑一次preliminary.py确认游戏窗口坐标然后把 region 设成游戏画面里车道线最清晰的那块比如(0, 40, 800, 640)。这样单帧数据量降到原来的四分之一训练时显存压力小很多。2.2 图像预处理ROI 裁剪与 HSV 车道线提取原始帧直接丢给网络也能训但收敛慢且容易过拟合到无关背景。项目里roi.py和road_HSV.py做了两件事一是用多边形掩膜把天空、仪表盘、路边广告牌裁掉只留路面二是把 RGB 转 HSV用颜色阈值把白色和黄色的车道线分离出来。HSV 阈值不是拍脑袋定的。road_HSV.py里通常会给一组初始值比如白色车道线 H 在 0-180 全范围、S 低于 30、V 高于 200黄色车道线 H 在 15-35、S 高于 80、V 高于 150。但不同游戏的光照引擎差别很大同一组值在晴天和隧道里表现完全不同。我的习惯是先用MOD_test里的list脚本把当前帧的 HSV 分量直方图打出来看车道线像素落在哪个区间再手动调。# road_HSV.py 阈值分割核心 import cv2 import numpy as np def extract_lane_lines(frame_bgr): hsv cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2HSV) # 白色车道线低饱和、高亮度 lower_white np.array([0, 0, 200]) upper_white np.array([180, 40, 255]) mask_white cv2.inRange(hsv, lower_white, upper_white) # 黄色车道线中低色相、中高饱和 lower_yellow np.array([15, 80, 150]) upper_yellow np.array([35, 255, 255]) mask_yellow cv2.inRange(hsv, lower_yellow, upper_yellow) mask cv2.bitwise_or(mask_white, mask_yellow) # 形态学闭运算连接断裂的车道线 kernel np.ones((5, 5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) return mask形态学核大小(5,5)是个经验值。太小了断线连不上太大了会把车道线旁边的噪声也吞进来。如果你发现模型在弯道总是压线优先检查这里——很多时候不是网络不行是预处理阶段车道线已经断了。2.3 模型训练AlexNet 变体与 balance_data 的采样策略Preliminary/alexnet.py定义了一个简化版 AlexNet输入是(160, 120, 3)的 ROI 图像输出是 9 类按键组合前进、左转、右转、前进左、前进右等。train_model.py用 categorical_crossentropy 做多分类Adam 优化器默认跑 10 个 epoch。但真正决定模型上限的不是网络结构是balance_data.py。你手动采集的数据里直行样本可能占 70%倒车不到 5%。直接训模型会倾向于一直输出直行。这个脚本的做法是按类别做随机欠采样把每个类别的样本数拉到同一水平。代价是总样本量减少所以采集阶段最好多跑几圈弯道和碰撞恢复。# balance_data.py 按类别均衡采样 import numpy as np from collections import Counter def balance_data(train_data): # train_data 每行末尾是 one-hot 标签 labels np.argmax(train_data[:, -9:], axis1) counter Counter(labels) min_count min(counter.values()) balanced [] for cls in counter: cls_data train_data[labels cls] idx np.random.choice(len(cls_data), min_count, replaceFalse) balanced.append(cls_data[idx]) balanced np.concatenate(balanced, axis0) np.random.shuffle(balanced) return balancedmin_count取的是最小类别的样本数。如果你发现某个类别只有几十个样本均衡后总数据量会少得可怜这时候应该回去补采集而不是硬训。我一般要求每个类别至少 500 帧否则模型在对应场景下基本是随机输出。3. 从零跑通环境配置、数据采集与训练命令3.1 TensorFlow 与 OpenCV 版本匹配的坑这套源码写的时候用的是 TensorFlow 1.x 的 Keras API如果你直接pip install tensorflow装到 2.xfrom keras.models import Sequential这类导入会报错。常见做法是装tensorflow1.15配合numpy1.16但 1.15 在 Python 3.8 以上又装不了。所以最稳的组合是 Python 3.7 TensorFlow 1.15 OpenCV 4.2。如果你不想降 Python 版本另一个方案是把import keras改成from tensorflow.keras import ...然后把model.fit里的nb_epoch改成epochs。改动量不大但alexnet.py里如果有自定义层或者 Lambda 层可能要额外处理。# 推荐环境conda conda create -n game_auto python3.7 conda activate game_auto pip install tensorflow1.15.0 pip install opencv-python4.2.0.32 pip install numpy1.16.4 pip install pywin32 # grabscreen 依赖pywin32是 Windows 平台抓屏的硬依赖Linux 下这套采集脚本跑不了。如果你在 Linux 做训练可以把 Windows 采集好的training_data.npy拷过去训练和推理部分不依赖 win32。3.2 采集数据的正确姿势先跑preliminary.py它会弹出一个窗口显示当前抓屏区域你确认车道线在画面里位置合适后按 Q 退出。然后跑collect_data.py切到游戏窗口开始开车。采集时注意三点一是尽量保持匀速不要频繁急刹二是每个弯道至少完整走三次三是撞墙后不要直接退出倒车回到赛道的过程也是有效样本。采集完成后你会得到一个training_data.npy大小取决于采集时长和 ROI 尺寸。160×120 的 ROI、15 分钟采集大概在 200MB 到 400MB 之间。如果超过 1GB检查一下是不是 region 没设对抓了全屏。3.3 训练与验证# 训练 python train_model.py # 测试会启动游戏窗口模型自动操作 python test_model.pytrain_model.py里有个EPOCHS参数默认 10。如果你发现验证集准确率在第 5 个 epoch 后不再上升可以降到 8如果还在上升加到 15 试试。但别超过 20这个网络容量有限训太久只会过拟合。test_model.py跑起来后模型会实时抓屏、预测、模拟按键。第一次跑建议把游戏速度调慢或者找个空旷赛道因为模型初期可能会左右摇摆。如果它一直按着前进不放说明直行类样本过多回去检查balance_data.py是否生效。4. 避坑与排查那些让我重采了三次数据的教训4.1 模型只会直行转弯就撞墙现象测试时车辆在直道正常一遇到弯道就直直撞上去没有任何转向动作。原因训练数据里直行样本占比过高且弯道样本中大部分是前进转向但模型没学到看到弯道就转的映射。更深层的原因是 ROI 裁剪把弯道外侧的车道线裁掉了模型看到的画面和直道几乎一样。解决重新检查roi.py的多边形顶点确保弯道内侧和外侧车道线都在 ROI 内。然后补采集至少 5 分钟的连续弯道数据重新跑balance_data.py后再训练。4.2 采集时帧率不稳npy 里帧和按键错位现象训练出来的模型反应迟钝或者按键和画面明显不同步。原因collect_data.py里抓屏和键盘监听在两个线程如果抓屏耗时波动大比如游戏掉帧按键记录的时间戳和实际帧对不上。解决在采集循环里加一个固定延时比如time.sleep(0.02)把采集频率锁在 50fps 左右。同时把键盘监听的采样也对齐到这个节奏。牺牲一点流畅度换数据对齐。4.3 测试时模型输出抖动车辆画龙现象模型在直道上左右小幅摆动像喝醉了一样。原因相邻帧的预测结果没有做平滑网络对每一帧独立分类噪声被直接放大成按键。解决在test_model.py里加一个滑动窗口比如取最近 5 帧的预测概率做平均再取 argmax。或者简单点如果连续 3 帧预测的按键不同就保持上一帧的按键不变。这个改动很小但效果立竿见影。4.4 换游戏后模型完全失效现象在《欧洲卡车模拟》上训的模型放到《极限竞速》里直接不会开。原因不同游戏的画面风格、车道线颜色、视角高度都不一样模型学到的是特定游戏的像素分布不是通用的车道线概念。解决这套源码本身不具备跨游戏泛化能力。换游戏必须重新采集、重新训练。如果你想减少重采成本可以在预处理阶段做颜色归一化或者用灰度图 边缘检测替代 HSV 阈值但效果有限。端到端模仿学习的通病就是这样数据分布变了模型就得重来。4.5 训练时 loss 不下降准确率卡在 30%现象train_model.py跑起来后 loss 震荡准确率一直在随机猜测水平。原因最常见的是标签 one-hot 编码和网络输出维度不匹配。比如你采集时只用了 5 个按键但alexnet.py输出层还是 9 类标签对不上。另一个可能是training_data.npy里混入了空帧或者全黑帧。解决先打印training_data.npy的 shape 和标签分布确认类别数和输出层一致。然后随机抽几帧用cv2.imshow看一眼排除采集时窗口最小化导致的无效帧。5. 进阶技巧用 ROI 动态裁剪和按键平滑把可用性拉高一个档次这套源码跑通之后你会发现它能开但开不好。直道还行弯道勉强遇到突发情况基本就撞。想让它在课程作业或者毕设演示里拿得出手有两个改动性价比最高。第一个是动态 ROI。固定 ROI 在直道上没问题但弯道时车道线会跑出裁剪区域。我的做法是用上一帧的车道线检测结果来微调 ROI 的左右边界如果road_HSV.py检测到的车道线像素重心偏左就把 ROI 左边界往左扩 20 像素反之亦然。这样 ROI 始终跟着车道线走模型看到的画面里车道线永远在中间区域。# 动态 ROI 调整在 test_model.py 里集成 def adjust_roi(prev_roi, lane_mask): moments cv2.moments(lane_mask) if moments[m00] 0: return prev_roi # 没检测到车道线保持原 ROI cx int(moments[m10] / moments[m00]) frame_center lane_mask.shape[1] // 2 offset cx - frame_center left, top, right, bottom prev_roi # 根据重心偏移量平移 ROI限制单次移动不超过 20 像素 shift max(-20, min(20, offset // 4)) return (left shift, top, right shift, bottom)offset // 4是阻尼系数防止 ROI 抖动。max(-20, min(20, ...))限制单帧最大位移避免突变。这个逻辑加进去后弯道通过率大概能从 50% 提到 75% 左右。第二个是按键平滑。前面避坑里提过滑动窗口平均但更稳的做法是加一个按键保持机制如果当前预测的按键和上一帧不同但置信度低于 0.6就继续用上一帧的按键。置信度从model.predict的 softmax 输出里取最大值就行。# 按键平滑与置信度过滤 last_key None CONFIDENCE_THRESHOLD 0.6 def smooth_prediction(pred_probs): global last_key confidence np.max(pred_probs) current_key np.argmax(pred_probs) if confidence CONFIDENCE_THRESHOLD and last_key is not None: return last_key last_key current_key return current_key这两个改动加起来不到 50 行代码但演示效果完全不一样。我当初做毕设答辩的时候评委问你这模型能处理弯道吗我现场跑了一圈虽然速度不快但没撞墙基本就过了。从那以后我每次做模仿学习的 demo都强制先加动态 ROI 和置信度过滤再谈模型结构优化——因为输入质量比网络深度重要得多。希望帮到你。本文还有配套的精品资源点击获取