大家好,我是良许。
作为一名嵌入式程序员,我在工作中经常会接触到图像处理和计算机视觉相关的项目。
特别是在汽车电子领域,像自动驾驶辅助系统(ADAS)、车道偏离预警、行人检测等功能,都离不开图像处理和计算机视觉技术。
今天就和大家聊聊这两个既相关又有区别的技术领域。
1. 图像处理与计算机视觉的区别与联系
1.1 什么是图像处理
图像处理(Image Processing)主要关注的是对图像本身进行操作和变换,目的是改善图像质量、提取图像特征或者为后续处理做准备。
简单来说,图像处理的输入和输出都是图像。
比如我之前做过一个车载摄像头项目,原始图像经常会受到光照、噪声等影响,这时候就需要用到图像处理技术。
我们会对图像进行去噪、增强对比度、边缘检测等操作,让图像变得更清晰,更适合后续的分析处理。
常见的图像处理操作包括:
- 图像滤波(去噪)
- 图像增强(调整亮度、对比度)
- 图像变换(旋转、缩放、裁剪)
- 边缘检测
- 形态学操作(膨胀、腐蚀)
1.2 什么是计算机视觉
计算机视觉(Computer Vision)则更进一步,它的目标是让计算机能够"理解"图像中的内容。
计算机视觉不仅仅是处理图像,更重要的是从图像中提取有意义的信息,做出判断和决策。
举个例子,在我参与的车道偏离预警系统中,摄像头拍摄到的道路图像需要经过一系列处理:首先用图像处理技术对图像进行预处理,然后通过计算机视觉算法识别出车道线的位置,判断车辆是否偏离车道,最后给出预警信号。
这整个过程就是计算机视觉的应用。
计算机视觉的典型应用包括:
- 物体检测与识别
- 人脸识别
- 目标跟踪
- 场景理解
- 三维重建
1.3 两者的关系
可以这样理解:图像处理是计算机视觉的基础,计算机视觉是图像处理的高级应用。
图像处理提供了各种工具和方法来操作图像,而计算机视觉则利用这些工具来实现更高层次的理解和决策。
在实际项目中,这两者往往是结合使用的。
比如在做行人检测时,我们首先需要对图像进行预处理(图像处理),然后使用深度学习模型来识别行人(计算机视觉),最后可能还需要对检测结果进行后处理优化(图像处理)。
2. 嵌入式系统中的图像处理实践
2.1 硬件平台选择
在嵌入式系统中做图像处理,硬件平台的选择非常关键。
我接触过的平台主要有以下几种:
2.1.1 基于STM32的方案
对于一些简单的图像处理任务,STM32系列MCU配合摄像头模块就能胜任。
比如我曾经用STM32F429做过一个简单的二维码识别项目,虽然处理速度不快,但对于低成本、低功耗的应用场景来说已经足够了。
下面是一个使用STM32 HAL库读取摄像头数据的示例代码:
// 摄像头初始化
void Camera_Init(void)
{
// 配置DCMI接口
DCMI_HandleTypeDef hdcmi;
hdcmi.Instance = DCMI;
hdcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE;
hdcmi.Init.PCKPolarity = DCMI_PCKPOLARITY_RISING;
hdcmi.Init.VSPolarity = DCMI_VSPOLARITY_LOW;
hdcmi.Init.HSPolarity = DCMI_HSPOLARITY_LOW;
hdcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME;
hdcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B;
if (HAL_DCMI_Init(&hdcmi) != HAL_OK)
{
Error_Handler();
}
}
// 启动图像采集
void Camera_Start_Capture(uint32_t *pData, uint32_t Length)
{
HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS,
(uint32_t)pData, Length);
}
// DMA传输完成回调
void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
{
// 图像采集完成,可以进行处理
Image_Process();
}
2.1.2 基于Linux的方案
对于更复杂的图像处理和计算机视觉任务,我通常会选择运行Linux的嵌入式平台,比如树莓派、NVIDIA Jetson系列等。
这些平台性能更强,而且可以方便地使用OpenCV等成熟的图像处理库。
在我目前的工作中,我们使用的是基于ARM Cortex-A系列处理器的平台,运行嵌入式Linux系统。
这样的平台既有足够的计算能力,又能保持相对较低的功耗和成本。
2.2 常用图像处理算法实现
2.2.1 图像滤波
图像滤波是最基础也是最常用的图像处理操作。
在嵌入式系统中,我们经常需要对摄像头采集的图像进行去噪处理。
下面是一个简单的均值滤波实现:
// 3x3均值滤波
void Mean_Filter(uint8_t *src, uint8_t *dst, int width, int height)
{
int i, j, m, n;
int sum;
for (i = 1; i < height - 1; i++)
{
for (j = 1; j < width - 1; j++)
{
sum = 0;
// 计算3x3邻域的平均值
for (m = -1; m <= 1; m++)
{
for (n = -1; n <= 1; n++)
{
sum += src[(i + m) * width + (j + n)];
}
}
dst[i * width + j] = sum / 9;
}
}
}
在实际项目中,为了提高处理速度,我们通常会使用硬件加速或者SIMD指令来优化这些算法。
2.2.2 边缘检测
边缘检测在很多应用中都非常重要,比如车道线检测、物体轮廓提取等。
Sobel算子是一种常用的边缘检测方法:
// Sobel边缘检测
void Sobel_Edge_Detection(uint8_t *src, uint8_t *dst,
int width, int height)
{
int i, j;
int gx, gy, gradient;
// Sobel算子
int sobel_x[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
int sobel_y[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
for (i = 1; i < height - 1; i++)
{
for (j = 1; j < width - 1; j++)
{
gx = 0;
gy = 0;
// 计算x方向和y方向的梯度
for (int m = -1; m <= 1; m++)
{
for (int n = -1; n <= 1; n++)
{
int pixel = src[(i + m) * width + (j + n)];
gx += pixel * sobel_x[m + 1][n + 1];
gy += pixel * sobel_y[m + 1][n + 1];
}
}
// 计算梯度幅值
gradient = (int)sqrt(gx * gx + gy * gy);
// 限制在0-255范围内
if (gradient > 255) gradient = 255;
if (gradient < 0) gradient = 0;
dst[i * width + j] = (uint8_t)gradient;
}
}
}
2.3 使用OpenCV库
在嵌入式Linux平台上,OpenCV是最常用的图像处理库。
它提供了丰富的图像处理和计算机视觉算法,而且经过了充分的优化。
下面是一个使用OpenCV进行图像处理的示例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 打开摄像头
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "无法打开摄像头" << endl;
return -1;
}
Mat frame, gray, edges;
while (true)
{
// 读取一帧图像
cap >> frame;
if (frame.empty())
break;
// 转换为灰度图
cvtColor(frame, gray, COLOR_BGR2GRAY);
// 高斯滤波去噪
GaussianBlur(gray, gray, Size(5, 5), 1.5);
// Canny边缘检测
Canny(gray, edges, 50, 150);
// 显示结果
imshow("原始图像", frame);
imshow("边缘检测", edges);
// 按ESC键退出
if (waitKey(30) == 27)
break;
}
return 0;
}
3. 计算机视觉在嵌入式系统中的应用
3.1 目标检测
目标检测是计算机视觉中最重要的应用之一。
在汽车电子领域,我们需要检测行人、车辆、交通标志等各种目标。
3.1.1 传统方法
在深度学习普及之前,我们主要使用传统的目标检测方法,比如HOG(方向梯度直方图)+ SVM(支持向量机)。
这种方法的优点是计算量相对较小,适合在资源受限的嵌入式系统上运行。
3.1.2 深度学习方法
现在,深度学习已经成为目标检测的主流方法。
YOLO、SSD、Faster R-CNN等算法在准确率和速度上都有很大优势。
但是,这些算法通常需要较强的计算能力,所以在嵌入式系统上部署时需要进行模型优化。
我们在项目中使用的是YOLOv5的轻量级版本,通过模型量化和剪枝,可以在嵌入式平台上实现实时检测。下面是一个简化的推理代码示例:
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
using namespace cv;
using namespace cv::dnn;
// 加载YOLO模型
Net net = readNetFromONNX("yolov5s.onnx");
// 目标检测函数
void Detect_Objects(Mat &frame)
{
Mat blob;
// 预处理:调整大小并归一化
blobFromImage(frame, blob, 1/255.0, Size(640, 640),
Scalar(0,0,0), true, false);
// 设置输入
net.setInput(blob);
// 前向推理
vector<Mat> outputs;
net.forward(outputs, net.getUnconnectedOutLayersNames());
// 后处理:解析检测结果
float conf_threshold = 0.5;
float nms_threshold = 0.4;
vector<Rect> boxes;
vector<float> confidences;
vector<int> class_ids;
for (size_t i = 0; i < outputs.size(); i++)
{
float* data = (float*)outputs[i].data;
for (int j = 0; j < outputs[i].rows; j++, data += outputs[i].cols)
{
float confidence = data[4];
if (confidence >= conf_threshold)
{
// 提取边界框和类别信息
int center_x = (int)(data[0] * frame.cols);
int center_y = (int)(data[1] * frame.rows);
int width = (int)(data[2] * frame.cols);
int height = (int)(data[3] * frame.rows);
int left = center_x - width / 2;
int top = center_y - height / 2;
boxes.push_back(Rect(left, top, width, height));
confidences.push_back(confidence);
// 获取类别ID
Mat scores = outputs[i].row(j).colRange(5, outputs[i].cols);
Point class_id_point;
double max_class_score;
minMaxLoc(scores, 0, &max_class_score, 0, &class_id_point);
class_ids.push_back(class_id_point.x);
}
}
}
// 非极大值抑制
vector<int> indices;
NMSBoxes(boxes, confidences, conf_threshold, nms_threshold, indices);
// 绘制检测结果
for (size_t i = 0; i < indices.size(); i++)
{
int idx = indices[i];
Rect box = boxes[idx];
rectangle(frame, box, Scalar(0, 255, 0), 2);
}
}
3.2 图像分类
图像分类是判断图像属于哪个类别的任务。
在嵌入式系统中,我们可能需要识别交通标志、判断道路类型等。
对于嵌入式平台,我们通常会使用MobileNet、SqueezeNet等轻量级网络模型。
这些模型在保持较高准确率的同时,大大减少了计算量和模型大小。
3.3 目标跟踪
在很多应用场景中,我们不仅需要检测目标,还需要跟踪目标的运动轨迹。
比如在车辆防碰撞系统中,需要持续跟踪前方车辆的位置和速度。
常用的跟踪算法包括KCF(核相关滤波)、SORT(简单在线实时跟踪)等。
OpenCV提供了多种跟踪器的实现:
#include <opencv2/tracking.hpp>
// 创建跟踪器
Ptr<Tracker> tracker = TrackerKCF::create();
// 初始化跟踪器
Rect2d bbox = selectROI(frame); // 选择要跟踪的目标
tracker->init(frame, bbox);
// 在后续帧中更新跟踪
while (true)
{
cap >> frame;
// 更新跟踪器
bool ok = tracker->update(frame, bbox);
if (ok)
{
// 绘制跟踪框
rectangle(frame, bbox, Scalar(255, 0, 0), 2);
}
imshow("跟踪", frame);
if (waitKey(1) == 27) break;
}
4. 性能优化技巧
4.1 算法优化
在嵌入式系统中,性能优化至关重要。
以下是我在实际项目中总结的一些优化技巧:
4.1.1 降低图像分辨率
很多时候,我们不需要处理全分辨率的图像。
通过降低分辨率,可以大幅减少计算量。
比如原本1920x1080的图像,降采样到640x480后,计算量减少到原来的1/9左右。
4.1.2 感兴趣区域(ROI)处理
只处理图像中感兴趣的区域,而不是整幅图像。
比如在车道线检测中,我们只需要处理图像下半部分的道路区域。
4.1.3 多级处理策略
先用简单快速的算法进行粗检测,再对可疑区域进行精细处理。
这样可以在保证准确率的同时提高处理速度。
4.2 硬件加速
4.2.1 使用GPU
如果平台支持GPU,可以利用CUDA、OpenCL等技术进行加速。
OpenCV的很多函数都支持GPU加速,只需要简单修改代码即可。
4.2.2 使用DSP或NPU
一些嵌入式平台集成了DSP(数字信号处理器)或NPU(神经网络处理单元),专门用于加速图像处理和深度学习推理。
充分利用这些硬件资源可以大幅提升性能。
4.2.3 使用DMA
在数据传输过程中使用DMA(直接内存访问),可以减少CPU的负担,提高数据传输效率。
前面STM32的示例代码中就使用了DMA来传输摄像头数据。
4.3 代码优化
4.3.1 使用定点运算代替浮点运算
在资源受限的嵌入式系统中,定点运算通常比浮点运算快得多。
可以将浮点数转换为定点数进行计算。
4.3.2 循环展开
对于一些关键的循环,可以进行循环展开优化,减少循环控制的开销。
4.3.3 使用查找表
对于一些复杂的数学运算,可以预先计算好结果存储在查找表中,使用时直接查表,避免重复计算。
5. 实际项目经验分享
在我参与的汽车电子项目中,有一个车道偏离预警系统的案例,可以很好地展示图像处理和计算机视觉的综合应用。
整个系统的处理流程如下:
5.1 图像采集
使用前置摄像头采集道路图像,帧率为30fps,分辨率为1280x720。
5.2 图像预处理
首先对图像进行灰度化处理,然后使用高斯滤波去噪。
接着提取图像下半部分作为感兴趣区域,因为车道线主要出现在这个区域。
5.3 边缘检测
使用Canny算子检测图像中的边缘,车道线通常表现为强边缘。
5.4 车道线检测
使用霍夫变换检测直线,筛选出符合车道线特征的直线。
对于弯道情况,我们使用多项式拟合来检测曲线车道线。
5.5 车道偏离判断
根据检测到的车道线位置,计算车辆在车道中的位置。
如果车辆偏离车道中心超过阈值,就发出预警信号。
5.6 结果输出
将检测结果通过CAN总线发送给其他控制单元,同时在显示屏上绘制车道线和预警信息。
这个项目的难点在于如何在有限的计算资源下实现实时处理,同时保证足够的准确率。
我们通过算法优化、硬件加速、多线程并行等手段,最终实现了稳定可靠的系统性能。
6. 未来发展趋势
图像处理和计算机视觉技术还在不断发展,特别是在嵌入式领域,有以下几个值得关注的趋势:
6.1 边缘计算
越来越多的计算任务从云端转移到边缘设备,这对嵌入式系统的计算能力提出了更高要求。
同时也推动了专用AI芯片的发展。
6.2 轻量化模型
为了在嵌入式设备上部署深度学习模型,研究人员开发了各种轻量化技术,如模型压缩、知识蒸馏、神经网络架构搜索等。
6.3 多模态融合
单一的视觉信息有时不够准确,未来会更多地融合雷达、激光雷达等多种传感器数据,提高系统的鲁棒性。
6.4 实时性要求提升
随着自动驾驶等应用的发展,对实时性的要求越来越高。
这需要我们在算法和硬件两方面都进行优化。
作为嵌入式程序员,我们需要不断学习新技术,同时也要深入理解底层原理,才能在这个快速发展的领域中保持竞争力。
图像处理和计算机视觉技术正在改变我们的生活,而嵌入式系统则是这些技术落地的重要载体。
希望这篇文章能够帮助大家更好地理解和应用这些技术。
更多编程学习资源
随时随地看视频