☰
Keras+YOLO车辆检测实战:从环境搭建到TensorRT加速全流程
2026/10/11 21:17:07 网站建设 项目流程

简介:本资源面向计算机视觉与深度学习方向的初学者及进阶开发者,提供一套基于Keras框架与YOLO目标检测算法的车辆检测完整实战项目,可用于智能交通、自动驾驶环境感知等场景的学习与复现。压缩包共15个文件,约27.42MB,包含2个Python源码文件、1个Jupyter Notebook、1个Markdown说明文档,以及7张jpg与2张png效果图、1段mp4演示视频和1个txt说明文件,覆盖从模型构建、训练到检测效果展示的完整链路。目前已有74人学习下载。通过源码与效果展示,读者可掌握Keras搭建YOLO网络结构、模型训练与参数调整、模型评估等关键步骤,并借助检测结果图与演示视频直观理解算法在车辆检测任务中的实际表现,适合作为课程设计、毕业设计或项目练手的参考案例。

1. 车辆检测项目拆解:Keras+YOLO 这套组合到底能跑出什么效果

路上跑的每一辆车,从监控画面到被框选出来,中间隔着一整套检测流程。这个项目标题里的关键词很明确:车辆检测、Keras、YOLO、项目源码、效果展示。说白了,就是用 Keras 深度学习框架去实现 YOLO 目标检测算法,专门用来识别画面中的车辆。适合谁看?做智能交通、停车场管理、道路监控的开发者,或者想拿一个完整项目练手目标检测的算法入门者。很多人搜 yolo入门、yolo项目源码、keras安装教程,本质诉求就一个:有没有一套能跑通、能改、能看到实际检测画面的代码。这个项目正好卡在这个需求点上。它不追求 SOTA 精度,而是给你一条从数据准备到模型推理的完整链路,让你先跑起来,再谈优化。接下来我会按实际落地顺序,把 Keras 搭 YOLO 做车辆检测这件事拆开讲清楚。

2. 为什么车辆检测选 Keras 搭 YOLO:选型逻辑与版本对齐

2.1 Keras 做目标检测的利与弊

Keras 最大的好处是 API 干净,搭网络像搭积木,对刚接触目标检测的人非常友好。你不需要一上来就写几百行 TensorFlow 底层代码,用 Keras 的Conv2D、BatchNormalization、LeakyReLU就能把 YOLO 的骨干网络拼出来。但要注意,Keras 在高性能推理上不如原生 TensorFlow 或 PyTorch 灵活,尤其是自定义损失函数和 NMS(非极大值抑制)部分,需要你手动实现或借助tf.image里的工具。车辆检测这个场景,输入分辨率通常不会太低(416×416 或 608×608),Keras 的model.predict在批量推理时效率尚可,但如果你要上 TensorRT 做加速,就得先把 Keras 模型转成 ONNX 或 SavedModel,这一步后面会讲。

另一个现实问题是版本。Keras 现在分两条线:tf.keras和独立的 Keras 3。做 YOLO 车辆检测,我建议直接用tf.keras,因为社区里绝大多数 YOLO 实现和预训练权重都是基于 TensorFlow 2.x 的。如果你搜 keras安装教程,装完发现import keras报错,大概率是版本没对齐。下面这个环境配置是我反复验证过的组合:

# 创建虚拟环境,避免污染主环境 python -m venv vehicle_yolo_env source vehicle_yolo_env/bin/activate # Windows 用 vehicle_yolo_env\Scripts\activate # 安装 TensorFlow 2.10 和对应 Keras pip install tensorflow==2.10.0 pip install opencv-python==4.8.0.74 pip install numpy==1.24.3 pip install matplotlib==3.7.1

逻辑说明:TensorFlow 2.10 是最后一个原生支持 Windows GPU 的版本,对大多数车辆检测项目足够用。OpenCV 用来读视频流和画框,numpy 锁 1.24 是因为 1.25 以上和 TensorFlow 2.10 有兼容问题。参数上,如果你用 Linux 且显卡较新,可以上 TensorFlow 2.13+,但 Keras 版本要对应改成 2.13。装完后跑一句import tensorflow as tf; print(tf.__version__)确认没报错。

2.2 YOLO 版本选择:v3、v4 还是 v5

标题没写具体 YOLO 版本,但车辆检测项目里最常见的是 YOLOv3 和 YOLOv4。YOLOv3 结构简单,Keras 实现多,适合入门;YOLOv4 精度更高,但 Keras 原生实现少,多数是 Darknet 权重转过来。如果你搜 yolo模型、yolo算法,会发现现在讨论多的是 YOLOv5/v8,但那些默认用 PyTorch。用 Keras 做车辆检测,我一般推荐从 YOLOv3 开始,因为它的三个尺度输出(13×13、26×26、52×52)对大小车辆都能覆盖,而且 Keras 代码可读性强,改起来不费劲。

选 YOLOv3 还有一个实际原因:预训练权重好找。Darknet 的.weights文件可以转成 Keras 的.h5,转换脚本网上有成熟实现。你不需要自己从头训练,拿 COCO 预训练权重做迁移学习,只训练车辆这一类,收敛快很多。车辆检测数据集可以用 KITTI、BDD100K 或者自己标注,标注格式转成 YOLO 的 txt 格式即可。

2.3 车辆检测的数据准备与标注格式

YOLO 要求每个图像对应一个 txt 文件,每行格式:类别 id 中心 x 中心 y 宽 高,所有坐标归一化到 0~1。车辆检测通常只关心一类(car),所以类别 id 就是 0。如果你用 LabelImg 标注,导出时选 YOLO 格式,它会自动生成。但要注意,LabelImg 导出的坐标是左上角和右下角,需要自己转成中心点加宽高。下面这个转换脚本我经常用:

import os import cv2 def convert_bbox(img_w, img_h, box): """将左上角右下角坐标转为 YOLO 中心点宽高格式""" x1, y1, x2, y2 = box # 计算中心点 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h # 计算宽高 w = (x2 - x1) / img_w h = (y2 - y1) / img_h return cx, cy, w, h # 假设标注文件里每行是:类别 x1 y1 x2 y2 def convert_annotation(txt_path, img_dir, out_path): img_name = os.path.basename(txt_path).replace('.txt', '.jpg') img_path = os.path.join(img_dir, img_name) img = cv2.imread(img_path) if img is None: return h, w = img.shape[:2] with open(txt_path, 'r') as f: lines = f.readlines() with open(out_path, 'w') as f: for line in lines: parts = line.strip().split() cls_id = parts[0] box = list(map(float, parts[1:5])) cx, cy, bw, bh = convert_bbox(w, h, box) f.write(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")

逻辑说明:convert_bbox做归一化,convert_annotation读原图拿宽高,再逐行写新格式。参数上,img_dir是原图目录,out_path是输出 txt 路径。注意图像格式要和标注文件同名,否则读不到。这个脚本跑完,你的车辆检测数据集就符合 YOLO 输入要求了。

3. 用 Keras 搭 YOLOv3 车辆检测网络:骨干、损失与训练

3.1 Darknet-53 骨干网络的 Keras 实现

YOLOv3 的骨干是 Darknet-53,由 53 个卷积层组成,大量使用 3×3 和 1×1 卷积交替,每个卷积后接 BN 和 LeakyReLU。用 Keras 实现时,我习惯把卷积块封装成一个函数:

from tensorflow.keras.layers import Conv2D, BatchNormalization, LeakyReLU, Add, Input from tensorflow.keras.models import Model def conv_block(x, filters, kernel_size, strides=1): """标准卷积块:Conv2D + BN + LeakyReLU""" x = Conv2D(filters, kernel_size, strides=strides, padding='same', use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.1)(x) return x def residual_block(x, filters): """残差块:两个卷积后与输入相加""" shortcut = x x = conv_block(x, filters // 2, 1) x = conv_block(x, filters, 3) x = Add()([shortcut, x]) return x # 构建 Darknet-53 主干(简化版,只列关键部分) inputs = Input(shape=(416, 416, 3)) x = conv_block(inputs, 32, 3) x = conv_block(x, 64, 3, strides=2) # ... 中间层省略,按 Darknet-53 结构堆叠 # 最终输出三个尺度特征图

逻辑说明:conv_block里use_bias=False是因为 BN 层会减去均值,偏置冗余。LeakyReLU的 alpha 设 0.1 是 YOLO 原论文参数。残差块先降维再升维,减少计算量。参数上,输入尺寸 416×416 是 YOLOv3 的标准输入,如果你显卡显存小,可以改成 320×320,但小目标车辆检测会掉点。

3.2 多尺度检测头与锚框设置

YOLOv3 在三个尺度上做检测,每个尺度分配 3 个锚框。车辆检测的锚框需要根据你的数据集重新聚类,不能直接用 COCO 的。常见做法是用 K-means 对标注框的宽高聚类,得到 9 个锚框。下面是一个聚类脚本的核心部分:

import numpy as np def kmeans_anchors(boxes, k=9): """对标注框宽高做 K-means 聚类,返回 k 个锚框""" # boxes 是 N×2 数组,每行是归一化后的宽高 np.random.seed(42) # 随机选 k 个初始中心 centers = boxes[np.random.choice(len(boxes), k, replace=False)] for _ in range(100): # 计算每个框到各中心的距离(用 1-IoU 作为距离) distances = 1 - iou(boxes, centers) labels = np.argmin(distances, axis=1) # 更新中心 for i in range(k): if np.sum(labels == i) > 0: centers[i] = np.mean(boxes[labels == i], axis=0) return centers def iou(boxes, centers): """计算框与锚框的 IoU""" # 省略具体实现,注意宽高格式转换 pass

逻辑说明:K-means 聚类让锚框更贴合你的车辆数据,比默认锚框召回率高。参数上,k=9 对应三个尺度各 3 个锚框。聚类前要把所有标注框的宽高提取出来并归一化。跑完得到 9 个值,按面积从小到大排序,分给三个尺度。

3.3 损失函数:坐标、置信度与类别损失

YOLOv3 的损失由三部分组成:坐标损失、置信度损失、类别损失。坐标损失用 MSE,置信度和类别用二值交叉熵。Keras 里自定义损失需要小心,因为三个尺度的输出要分别计算。我一般写一个yolo_loss函数,用tf.keras.backend里的操作:

import tensorflow as tf def yolo_loss(y_true, y_pred): """YOLOv3 损失函数,y_true 和 y_pred 形状为 (batch, grid, grid, anchors, 5+num_classes)""" # 拆分预测值 pred_xy, pred_wh, pred_conf, pred_cls = tf.split(y_pred, [2, 2, 1, num_classes], axis=-1) # 拆分真实值 true_xy, true_wh, true_conf, true_cls = tf.split(y_true, [2, 2, 1, num_classes], axis=-1) # 坐标损失:只计算有目标的框 obj_mask = tf.squeeze(true_conf, axis=-1) xy_loss = tf.reduce_sum(tf.square(true_xy - pred_xy) * tf.expand_dims(obj_mask, -1)) wh_loss = tf.reduce_sum(tf.square(true_wh - pred_wh) * tf.expand_dims(obj_mask, -1)) # 置信度损失:有目标和无目标都算 conf_loss = tf.reduce_sum(tf.square(true_conf - pred_conf)) # 类别损失 cls_loss = tf.reduce_sum(tf.square(true_cls - pred_cls) * tf.expand_dims(obj_mask, -1)) total_loss = xy_loss + wh_loss + conf_loss + cls_loss return total_loss

逻辑说明:obj_mask用来区分哪些格子有车辆,只有有目标的格子才计算坐标和类别损失。置信度损失对所有格子计算,但无目标格子的权重通常调低。参数上,num_classes车辆检测设为 1。实际训练时,损失值会很大,建议用tf.reduce_mean代替reduce_sum,并加一个缩放系数。

3.4 训练流程与关键超参数

训练车辆检测模型,我一般分两阶段:先冻结骨干只训练检测头,再解冻全部微调。优化器用 Adam,学习率从 1e-3 开始,后期降到 1e-4。Batch size 根据显存来,8G 显存跑 416×416 可以设 8 或 16。下面是一个训练循环的骨架:

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss=yolo_loss) # 第一阶段:冻结骨干 for layer in model.layers[:200]: layer.trainable = False history = model.fit(train_dataset, validation_data=val_dataset, epochs=50, callbacks=[ tf.keras.callbacks.ModelCheckpoint('best_model.h5', save_best_only=True), tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5) ]) # 第二阶段:解冻全部,小学习率微调 for layer in model.layers: layer.trainable = True model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4), loss=yolo_loss) model.fit(train_dataset, validation_data=val_dataset, epochs=30)

逻辑说明:冻结骨干是为了防止预训练权重被随机初始化的检测头破坏。ReduceLROnPlateau在验证损失不降时自动降学习率。参数上,patience=5表示 5 个 epoch 没改善就降。注意ModelCheckpoint只保存最好的模型,避免过拟合。

4. 车辆检测推理与效果验证:从单张图到视频流

4.1 单张图像推理与 NMS 后处理

模型训练完,推理时输出的是每个格子的预测值,需要解码成实际框坐标,再做 NMS 去重。下面是一个完整的单图推理函数:

import cv2 import numpy as np def detect_image(model, img_path, anchors, num_classes=1, conf_thresh=0.5, iou_thresh=0.4): """对单张图像做车辆检测""" img = cv2.imread(img_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_input = cv2.resize(img_rgb, (416, 416)) / 255.0 img_input = np.expand_dims(img_input, axis=0) # 模型预测 preds = model.predict(img_input) # 解码三个尺度的输出 boxes = [] for pred, anchor in zip(preds, anchors): # pred 形状 (1, grid, grid, 3, 5+num_classes) grid_h, grid_w = pred.shape[1:3] pred = pred.reshape(-1, 5 + num_classes) # 解码 xywh 和置信度 # ... 省略具体解码步骤 boxes.extend(decode_boxes(pred, anchor, grid_w, grid_h)) # NMS boxes = nms(boxes, iou_thresh) # 画框 for box in boxes: if box[4] > conf_thresh: x1, y1, x2, y2 = box[:4] cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite('result.jpg', img)

逻辑说明:conf_thresh过滤低置信度框,iou_thresh控制 NMS 合并程度。参数上,车辆检测置信度阈值一般设 0.5,IoU 设 0.4。如果漏检多,降置信度到 0.3;如果误检多,升到 0.6。

4.2 视频流车辆检测与帧率优化

视频流检测就是把每一帧当单图处理,但要注意帧率。用 OpenCV 读视频,逐帧推理,再写回视频。如果帧率太低,可以跳帧或降低输入分辨率。下面是一个视频检测的骨架:

cap = cv2.VideoCapture('traffic.mp4') fps = cap.get(cv2.CAP_PROP_FPS) writer = cv2.VideoWriter('output.mp4', cv2.VideoWriter_fourcc(*'mp4v'), fps, (416, 416)) while cap.isOpened(): ret, frame = cap.read() if not ret: break # 检测逻辑同上,返回画框后的 frame result_frame = detect_frame(model, frame, anchors) writer.write(result_frame) cap.release() writer.release()

逻辑说明:VideoWriter的帧率和分辨率要和输入一致,否则输出视频会变形。参数上,如果推理速度跟不上,可以每两帧检测一次,中间帧用上一帧结果。

4.3 效果展示与指标解读

车辆检测的效果展示通常包括:检测框可视化、mAP(平均精度均值)、召回率。mAP 计算需要跑验证集,用 IoU 阈值 0.5 判断是否检测正确。下面是一个简单的 mAP 计算逻辑:

def compute_map(pred_boxes, true_boxes, iou_thresh=0.5): """计算 mAP,pred_boxes 和 true_boxes 都是列表,每个元素是 [x1,y1,x2,y2,conf,cls]""" # 按置信度排序 pred_boxes.sort(key=lambda x: x[4], reverse=True) tp = np.zeros(len(pred_boxes)) fp = np.zeros(len(pred_boxes)) for i, pred in enumerate(pred_boxes): best_iou = 0 best_gt = None for gt in true_boxes: iou_val = iou_single(pred, gt) if iou_val > best_iou: best_iou = iou_val best_gt = gt if best_iou >= iou_thresh: tp[i] = 1 else: fp[i] = 1 # 计算 precision 和 recall tp_cum = np.cumsum(tp) fp_cum = np.cumsum(fp) precision = tp_cum / (tp_cum + fp_cum + 1e-6) recall = tp_cum / len(true_boxes) # 计算 AP ap = np.trapz(precision, recall) return ap

逻辑说明:tp和fp分别记录真正例和假正例,precision和recall逐点计算,最后用梯形法求 AP。参数上,iou_thresh通常设 0.5。车辆检测的 mAP 能到 0.7 以上就算不错,具体看数据集难度。

5. 车辆检测项目避坑:从环境到部署的 5 个翻车现场

5.1 现象:训练损失不降,一直震荡

原因:学习率太大,或者锚框和数据集不匹配。YOLO 对锚框敏感,如果锚框尺寸和车辆实际尺寸差太多,坐标损失很难降。解决:先用 K-means 重新聚类锚框,再把学习率降到 1e-4,加ReduceLROnPlateau回调。

5.2 现象:推理时框全叠在一起,NMS 没生效

原因:NMS 的 IoU 阈值设太高,或者解码时坐标没还原到原图尺寸。YOLO 输出的是归一化坐标,要乘以原图宽高。解决:检查解码步骤,确保x1 = (cx - w/2) * img_w,NMS 阈值设 0.4 左右。

5.3 现象:Keras 加载模型报Unknown layer错误

原因:自定义损失函数或自定义层没有注册。解决:加载时用custom_objects参数,把yolo_loss和自定义层传进去。例如load_model('model.h5', custom_objects={'yolo_loss': yolo_loss})。

5.4 现象:视频检测帧率只有个位数

原因:Keras 默认用 GPU 但没开tf.function,或者每帧都重新加载模型。解决:把模型加载提到循环外,用@tf.function装饰推理函数,或者转成 TensorRT 引擎。如果搜 yolo 640分辨率检测可以支持多少路,答案取决于硬件,T4 上 YOLOv3 416×416 大概能跑 30 路左右。

5.5 现象:自己标注的数据训练后 mAP 很低

原因:标注质量差,或者类别不均衡。车辆检测如果只标了 car,但画面里有 truck、bus,模型会混淆。解决:要么把 truck、bus 也标上并设不同类别,要么在数据增强时多裁剪车辆区域。另外,标注框要贴紧车辆边缘,不要留太多背景。

6. 把 Keras YOLO 车辆检测推到实用:模型导出与 TensorRT 加速

训练完的 Keras 模型直接推理,在服务器上勉强够用,但如果你要部署到边缘设备或者处理多路视频,就得做模型导出和加速。我一般先把.h5转成 SavedModel,再转 ONNX,最后用 TensorRT 生成引擎。下面是一个导出 ONNX 的脚本:

import tensorflow as tf import tf2onnx model = tf.keras.models.load_model('best_model.h5', custom_objects={'yolo_loss': yolo_loss}) # 指定输入签名 input_signature = [tf.TensorSpec([1, 416, 416, 3], tf.float32, name='input')] onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature, opset=13) with open('yolo_vehicle.onnx', 'wb') as f: f.write(onnx_model.SerializeToString())

逻辑说明:opset=13兼容大多数 TensorRT 版本。导出后可以用trtexec转成 TensorRT 引擎,命令类似trtexec --onnx=yolo_vehicle.onnx --saveEngine=yolo_vehicle.trt --fp16。参数上,--fp16开启半精度,速度能提升一倍左右,精度掉点通常在 1% 以内。

验证 TensorRT 引擎是否正常,可以跑一个对比脚本,用同一张图分别跑 Keras 和 TensorRT,看输出框的 IoU 是否大于 0.95。如果差异大,检查预处理是否一致,比如归一化方式、通道顺序。

最后说一个我踩过的坑:导出 ONNX 时如果模型里有tf.split或tf.reshape的动态形状,TensorRT 可能不支持。解决办法是把这些操作固定成静态形状,或者在导出时指定dynamic_axes。这个项目做下来,最大的体会是:车辆检测的精度上限不取决于网络多深,而取决于你的数据质量和锚框匹配度。我习惯在训练前先可视化一批锚框和真实框的重叠情况,重叠率低于 0.5 就重新聚类。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询