使用 darkSCNN 和 Caffe 进行车道线检测

使用 darkSCNN 和 Caffe 进行车道线检测

    • 一、引言:车道线检测的重要性
    • [二、darkSCNN_caffe 原理解析](#二、darkSCNN_caffe 原理解析)
      • [2.1 什么是 darkSCNN?](#2.1 什么是 darkSCNN?)
      • [2.2 模型工作原理](#2.2 模型工作原理)
    • 三、效果展示
    • 四、详细操作步骤
      • [4.1 环境准备:创建 Docker 容器](#4.1 环境准备:创建 Docker 容器)
      • [4.2 安装必要的依赖包](#4.2 安装必要的依赖包)
      • [4.3 下载预训练模型](#4.3 下载预训练模型)
      • [4.4 获取测试视频](#4.4 获取测试视频)
      • [4.5 编译推理程序](#4.5 编译推理程序)
      • [4.6 运行车道线检测](#4.6 运行车道线检测)
      • [4.7 生成结果 GIF(便于展示)](#4.7 生成结果 GIF(便于展示))
    • 五、代码详解
      • [5.1 `caffe_inference.cpp` 核心代码解析](#5.1 caffe_inference.cpp 核心代码解析)
    • [六、附加内容:从源码编译 Caffe](#六、附加内容:从源码编译 Caffe)
      • [6.1 安装编译依赖](#6.1 安装编译依赖)
      • [6.2 下载 Caffe 源码](#6.2 下载 Caffe 源码)
      • [6.3 配置和编译](#6.3 配置和编译)
    • 七、总结

一、引言:车道线检测的重要性

车道线检测是自动驾驶和高级驾驶辅助系统(ADAS)中的关键技术之一。它能够帮助车辆识别道路上的车道标记,从而实现车道保持、偏离预警等功能。本文介绍了如何使用 darkSCNN 模型和 Caffe 框架对视频中的车道线进行检测。

二、darkSCNN_caffe 原理解析

2.1 什么是 darkSCNN?

darkSCNN 是一种基于卷积神经网络(CNN)的车道线检测模型

2.2 模型工作原理

darkSCNN 通过深度学习的方式学习车道线的特征。其基本流程如下:

  1. 特征提取:使用卷积层从输入图像中提取多层次的特征
  2. 像素级分类:对每个像素进行分类,判断它是否属于车道线以及属于哪条车道线

三、效果展示

四、详细操作步骤

4.1 环境准备:创建 Docker 容器

我们使用 Docker 容器来确保环境一致性,避免依赖问题。

bash 复制代码
# 创建项目目录
mkdir darkSCNN_caffe_DEMO
cd darkSCNN_caffe_DEMO/

# 清理可能存在的旧容器
docker stop darkSCNN_caffe
docker rm darkSCNN_caffe

# 创建新容器,设置文件映射和网络
docker run -it --privileged --net=host \
	-v $PWD:/home -w /home --name darkSCNN_caffe ubuntu:18.04 /bin/bash

4.2 安装必要的依赖包

在容器内执行以下命令安装所需软件:

bash 复制代码
# 更新软件包列表
apt update

# 安装核心依赖
apt install caffe-cpu -y
apt install wget -y
apt install unzip -y
apt install python3-pip -y
apt install locales -y

# 安装视频处理相关工具
apt install ffmpeg -y

# 安装计算机视觉库
apt install libopencv-dev -y

# 安装数学运算和深度学习依赖
apt install libopenblas-dev -y
apt install libcaffe-cpu-dev -y
apt install libboost1.62-all-dev -y

# 安装日志和序列化库
apt install libgflags-dev -y
apt install libgoogle-glog-dev -y
apt install libprotobuf-dev -y

# 安装视频下载工具
pip3 install you-get

# 设置 locale(解决可能出现的字符编码问题)
locale-gen
locale-gen en_US.UTF-8
export LC_CTYPE=en_US.UTF-8

4.3 下载预训练模型

bash 复制代码
# 下载 darkSCNN 的预训练模型
wget https://apollo-pkg-beta.cdn.bcebos.com/perception_model/darkSCNN_caffe.zip

# 解压模型文件
unzip darkSCNN_caffe.zip

4.4 获取测试视频

bash 复制代码
# 从网络下载测试视频(这里以B站视频为例)
you-get https://www.bilibili.com/video/BV19J411t7Uy/ -O test

# 从视频中提取一帧作为测试图像
ffmpeg -i test.mp4 -ss 00:00:01 -vframes 1 test.jpg

4.5 编译推理程序

bash 复制代码
g++ -std=c++11 -DCPU_ONLY -o caffe_inference caffe_inference.cpp \
	-lcaffe -lglog -lboost_system -lopenblas `pkg-config --libs opencv`

4.6 运行车道线检测

bash 复制代码
# 设置日志级别(减少不必要的输出)
export GLOG_minloglevel=7
export GLOG_alsologtostderr=1

# 运行推理程序,处理视频
./caffe_inference test.mp4 darkSCNN_caffe.mp4

4.7 生成结果 GIF(便于展示)

bash 复制代码
# 将输出的视频转换为GIF格式,便于在网页上展示
ffmpeg -i darkSCNN_caffe.mp4 -vf "fps=5,scale=640:-1:flags=lanczos,split [s0][s1];[s0] palettegen=stats_mode=diff [p];[s1][p] paletteuse=dither=none:diff_mode=rectangle" darkSCNN_caffe.gif

五、代码详解

5.1 caffe_inference.cpp 核心代码解析

c 复制代码
#include <cblas.h>
#include <opencv2/opencv.hpp>
#include <caffe/caffe.hpp>
#include <algorithm>
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cmath>

using namespace caffe;
using std::string;

int main(int argc, char** argv) {
    // 设置CPU模式
    Caffe::set_mode(Caffe::CPU);    
    // 模型文件路径
    string model_file = "darkSCNN_caffe/deploy.prototxt";
    string trained_file = "darkSCNN_caffe/deploy.caffemodel";
    
    // 加载网络
    Net<float> net(model_file, TEST);
    net.CopyTrainedLayersFrom(trained_file);    
    // 获取输入blob
    Blob<float>* input_blob = net.input_blobs()[0];
    input_blob->Reshape(1, 3, 480, 640);  // 根据实际情况调整形状	
	float* input_data_ptr = input_blob->mutable_cpu_data();
    // 打开视频文件
    cv::VideoCapture cap(argv[1]);
    if (!cap.isOpened()) {
        std::cerr << "Error: Could not open video file." << std::endl;
        return -1;
    }
	
	// 创建视频写入器,用于保存处理结果
    cv::VideoWriter writer;
    int out_width = 640;
    int out_height = 480*3; // 高度为3倍,用于垂直拼接三种视图
    writer.open(argv[2], cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 5, cv::Size(out_width, out_height));
    if (!writer.isOpened()) {
        std::cerr << "Error: Could not create video writer." << std::endl;
        return -1;
    }

    cv::Mat frame;
    int frame_count = 0;   
	int process_count=0;
	
	// 主循环:处理视频每一帧
    while (true) {
        cap >> frame;  // 读取一帧
        if (frame.empty()) break;		
        frame_count++;        
		if(frame_count%25!=0) continue;	 // 每25帧处理一帧,提高处理速度
		
		process_count+=1;
		if (process_count>25) break;  // 最多处理25帧,避免处理时间过长
		
		std::cout << "Processing frame " << process_count << std::endl;
		// 调整图像尺寸为640x480
		cv::Mat resize_image;
		cv::resize(frame, resize_image, cv::Size(640, 480));

		// 将图像数据类型转换为float32
		cv::Mat float_image;
		resize_image.convertTo(float_image, CV_32FC3, 1.0);

		// 注意:如果输入图像实际上是BGR格式,则可以跳过此步骤
		cv::cvtColor(float_image, float_image, cv::COLOR_RGB2BGR);

		// 分离通道以便逐个处理
		std::vector<cv::Mat> channels;
		cv::split(float_image, channels);

		// 均值减法(均值顺序为BGR)
		cv::Scalar mean(95.0, 99.0, 96.0); // BGR均值
		channels[0] -= mean[0]; // B通道
		channels[1] -= mean[1]; // G通道
		channels[2] -= mean[2]; // R通道

		// 为每个通道创建新的形状 [1, height, width]
		for (auto& channel : channels) {
			channel = channel.reshape(1, 1); // 1个通道,1行,列数自动计算
		}		
		// 合并通道 [channels, height, width]
		cv::Mat chw_image;
		cv::vconcat(channels, chw_image);
		memcpy(input_data_ptr,chw_image.data,1*3*480*640*sizeof(float));			
		
		// 前向传播
		net.Forward();
		
		// 获取输出
		Blob<float>* output_blob = net.output_blobs()[1];
		const float* output_data = output_blob->cpu_data();
		
		int output0_Size=1*13*480*640;	

		int num_lanes_=13;
		int lane_output_height_=480;
		int lane_output_width_=640;
		float confidence_threshold_lane_=0.95;
		// 创建一个向量来存储每个车道的掩码
		std::vector<cv::Mat> masks;
		
		// 从Blob中提取每个车道的置信度图
		for (int i = 0; i < num_lanes_; ++i) {
			cv::Mat tmp(lane_output_height_, lane_output_width_, CV_32FC1);        
			// 计算当前车道数据在Blob中的起始位置
			const float* channel_data = output_data +i * lane_output_height_ * lane_output_width_;        
			// 将数据复制到OpenCV矩阵中
			memcpy(tmp.data, channel_data,lane_output_width_ * lane_output_height_ * sizeof(float));        
			masks.push_back(tmp);
		}        
		// 初始化像素计数器和彩色掩码图像
		std::vector<int> cnt_pixels(num_lanes_, 0);
		cv::Mat mask_color(lane_output_height_, lane_output_width_, CV_32FC1);
		mask_color.setTo(cv::Scalar(0)); // 初始化为0(背景)    
		// 遍历所有车道类别
		for (int c = 0; c < num_lanes_; ++c) {
			// 获取当前车道的置信度图
			cv::Mat& lane_mask = masks[c];        
			// 遍历每个像素
			for (int h = 0; h < lane_mask.rows; ++h) {
				for (int w = 0; w < lane_mask.cols; ++w) {
					// 如果置信度超过阈值
					if (lane_mask.at<float>(h, w) >= confidence_threshold_lane_) {
						// 在彩色掩码中设置对应的类别值
						mask_color.at<float>(h, w) = static_cast<float>(c);
						// 增加该类的像素计数
						cnt_pixels[c]++;
					}
				}
			}
		}  
		// 将浮点型掩码转换为8位图像以便显示和保存
		cv::Mat mask_color_8u;
		mask_color.convertTo(mask_color_8u, CV_8U, 255.0 / (num_lanes_ - 1));    
		// 应用颜色映射使不同类别显示为不同颜色
		cv::Mat colored_output;
		cv::applyColorMap(mask_color_8u, colored_output, cv::COLORMAP_JET);    
		
		cv::Mat overlay;
		double alpha = 0.2; // 透明度
		cv::addWeighted(resize_image, 1 - alpha, colored_output, alpha, 0.0, overlay);

		// 垂直拼接三种视图:原始图像、分割结果和叠加结果
		cv::Mat result0;
		cv::vconcat(resize_image, colored_output, result0);	

		cv::Mat result1;
		cv::vconcat(result0, overlay, result1);
		
		// 写入结果帧到输出视频
		writer.write(result1);
	}
	// 释放资源
    cap.release();
    writer.release();	
	std::cout << "Video processing completed. Processed " << process_count << " frames." << std::endl;
    return 0;
}

六、附加内容:从源码编译 Caffe

6.1 安装编译依赖

bash 复制代码
# 安装基础编译工具和库
apt install libopenblas-dev -y
apt install git -y
apt install cmake -y
apt install protobuf-compiler -y

# 安装数据库支持(可选)
apt install libhdf5-dev -y
apt install liblmdb-dev -y
apt install libleveldb-dev -y
apt install libsnappy-dev -y

# 安装数学库
apt install libatlas-base-dev -y

6.2 下载 Caffe 源码

bash 复制代码
# 克隆 Caffe 官方仓库
git clone https://github.com/BVLC/caffe.git
cd caffe

# 切换到稳定版本(1.0版本)
git checkout 1.0

6.3 配置和编译

bash 复制代码
# 创建配置文件
cat > Makefile.config << 'EOF'
CPU_ONLY := 1
USE_OPENCV := 0
USE_LEVELDB := 0
USE_LMDB := 0
BLAS := atlas
PYTHON_INCLUDE := /usr/include/python2.7 /usr/lib/python2.7/dist-packages/numpy/core/include
PYTHON_LIB := /usr/lib
INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial
LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu/hdf5/serial
BUILD_DIR := build
DISTRIBUTE_DIR := distribute
Q ?= @
EOF

# 开始编译
make

七、总结

本文介绍了如何使用 darkSCNN 模型和 Caffe 框架进行车道线检测,包括环境搭建、模型下载、代码编译和实际运行的全过程。通过这个示例,您可以了解到:

  1. 该模型的基本应用流程
  2. Caffe 框架的基本使用方法
  3. 计算机视觉任务中的常见预处理和后处理技术
  4. 如何将深度学习模型应用到视频处理中

注意:本文提供的代码和步骤在 Ubuntu 18.04 环境下测试通过,其他环境可能需要适当调整。

相关推荐
NAGNIP1 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab2 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab2 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP6 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年6 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼6 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS6 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区8 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈8 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang8 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx