3. 图像处理与CUDA加速:让摄像头“看懂”世界

各位同学,欢迎来到第三章。这一章,我们开始真正动手处理图像数据了。

说实话,在自动驾驶感知系统里,摄像头就是车的“眼睛”。但这双眼睛天生有缺陷——镜头畸变、光照不均、色彩偏差……如果不做预处理,后面的目标检测、车道线识别全都会跑偏。

我个人习惯把图像预处理比作“给眼睛戴眼镜”。戴对了,世界清晰;戴错了,全是重影。今天我们就来聊聊怎么戴好这副“眼镜”,顺便用CUDA给它加速。

3.1 图像预处理三板斧

3.1.1 去畸变:把弯曲的拉直

你想想看,广角镜头拍出来的照片,边缘都是弯的。这在自动驾驶里很要命——明明是一条直线车道,拍出来却成了弧线。

去畸变的原理其实不复杂。说白了就是建立一个映射关系:
从畸变图像上的点 (u, v),映射到理想图像上的点 (u', v')。

数学上,我们通常用径向畸变和切向畸变模型来描述:

// 径向畸变模型
x_distorted = x * (1 + k1*r² + k2*r⁴ + k3*r⁶)
y_distorted = y * (1 + k1*r² + k2*r⁴ + k3*r⁶)

// 切向畸变模型
x_distorted = x + (2*p1*x*y + p2*(r² + 2*x²))
y_distorted = y + (p1*(r² + 2*y²) + 2*p2*x*y)

我在项目中遇到过一个问题:标定板拍得不规范,导致畸变参数算出来全是错的。后来我学乖了——标定板一定要占满画面各个区域,尤其是四个角。

我的小技巧: 去畸变后,图像边缘会有黑边。别急着裁掉,先看看黑边里有没有关键信息。有一次,一个行人正好站在黑边区域,差点漏检。

3.1.2 白平衡:让白色回归白色

为什么阴天拍的照片偏蓝?为什么黄昏拍的照片偏黄?

因为不同光源的色温不同。白平衡的目的,就是让白色物体在任何光源下都呈现白色。

常用的方法有:

  • 灰度世界法: 假设场景中所有颜色的平均值是灰色
  • 完美反射法: 假设图像中最亮的点是白色
  • 色温估计法: 通过统计色温分布来校正

嗯,这里要注意:自动驾驶场景变化太快,从隧道出来到阳光直射,色温可能瞬间变化。所以白平衡参数得动态调整,不能一套参数打天下。

3.1.3 直方图均衡化:让暗处亮起来

夜间行车时,摄像头拍到的图像往往对比度很低。直方图均衡化就是用来解决这个问题的。

它的核心思想很简单:把像素值的分布“拉平”。原本集中在某个区间的像素,被映射到整个0-255范围。

我曾经在夜间测试时发现,直方图均衡化后,路边的行人轮廓清晰了很多。但要注意——它也会放大噪声。所以建议先做去噪,再做均衡化。

3.2 CUDA编程基础:让GPU干活

图像处理是典型的“数据并行”任务。一张1920x1080的图,每个像素的处理逻辑都一样。这种活儿,CPU干起来太慢,得让GPU上。

CUDA编程的核心概念就三个:

  • 线程(Thread): 最小的执行单元,每个线程处理一个像素
  • 线程块(Block): 一组线程,共享一块内存
  • 网格(Grid): 所有线程块的集合

一个典型的CUDA程序长这样:

// 核函数:每个线程处理一个像素
__global__ void processImage(unsigned char* input, 
                             unsigned char* output, 
                             int width, int height) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    
    if (x < width && y < height) {
        int idx = y * width + x;
        // 这里写你的处理逻辑
        output[idx] = input[idx] * 1.2;  // 举例:亮度增强
    }
}

// 调用核函数
dim3 blockSize(16, 16);
dim3 gridSize((width + 15) / 16, (height + 15) / 16);
processImage<<<gridSize, blockSize>>>(d_input, d_output, width, height);
避坑指南: 我曾经把blockSize设成(32, 32),结果在旧显卡上跑不起来。后来查资料才知道,每个block的线程数不能超过1024。所以(32, 32)=1024,刚好卡在边界上。建议用(16, 16)或(32, 8)。

3.3 使用CUDA实现图像卷积加速

图像卷积是很多算法的基石——边缘检测、高斯模糊、锐化……本质上都是卷积操作。

卷积的数学定义:

output(x, y) = Σᵢ Σⱼ input(x+i, y+j) * kernel(i, j)

在CPU上,一个3x3的卷积核处理1080p图像,大概要跑几十毫秒。用CUDA呢?不到1毫秒。

为什么这么快?因为每个像素的卷积计算是独立的,可以并行执行。

CUDA实现卷积的关键代码:

__global__ void convolution2D(unsigned char* input,
                               unsigned char* output,
                               int width, int height,
                               float* kernel, int kSize) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    
    if (x >= width || y >= height) return;
    
    float sum = 0.0f;
    int half = kSize / 2;
    
    for (int i = -half; i <= half; i++) {
        for (int j = -half; j <= half; j++) {
            int curX = min(max(x + j, 0), width - 1);
            int curY = min(max(y + i, 0), height - 1);
            sum += input[curY * width + curX] * 
                   kernel[(i + half) * kSize + (j + half)];
        }
    }
    
    output[y * width + x] = (unsigned char)sum;
}

这里有个细节:边界处理。我习惯用“clamp to edge”的方式,就是把超出边界的像素用最近的边界像素代替。这样不会出现黑边,效果比较自然。

3.4 NPP库的使用:别重复造轮子

NVIDIA Performance Primitives(NPP)是英伟达提供的一套图像处理库。说白了,就是帮你把常用的图像处理函数都写好了,你直接调用就行。

我刚开始做CUDA开发时,总喜欢自己手写所有函数。后来发现,NPP库的性能比我手写的好得多——人家是英伟达的工程师优化过的。

常用的NPP函数:

功能 NPP函数 说明
图像缩放 nppiResize_8u_C3R 支持多种插值方式
色彩空间转换 nppiRGBToGray_8u_C3C1R RGB转灰度图
直方图计算 nppiHistogram_8u_C1R 计算单通道直方图
滤波操作 nppiFilter_8u_C1R 支持自定义卷积核

使用NPP库的示例:

#include <nppi.h>

// 分配设备内存
Npp8u* d_src, * d_dst;
cudaMalloc(&d_src, width * height * 3);
cudaMalloc(&d_dst, width * height);

// 调用NPP函数:RGB转灰度
NppiSize roiSize = {width, height};
nppiRGBToGray_8u_C3C1R(d_src, width * 3, 
                        d_dst, width, 
                        roiSize);

// 释放内存
cudaFree(d_src);
cudaFree(d_dst);
我的建议: NPP库虽然好用,但有些特殊场景还是得自己写核函数。比如自定义的非线性滤波、特定业务逻辑等。我的原则是:能用NPP的用NPP,NPP搞不定的再手写。

小结

这一章我们聊了图像预处理的三板斧——去畸变、白平衡、直方图均衡化。然后深入了CUDA编程,从线程模型到卷积加速,最后介绍了NPP库这个“偷懒神器”。

说实话,这些内容在自动驾驶感知系统里只是第一步。但这一步走稳了,后面的路就好走了。下一章,我们会把这些预处理后的图像数据,喂给深度学习模型做目标检测。

嗯,到时候你会发现——预处理做得好,模型训练事半功倍。

公众号:蓝海资料掘金营,微信deep3321