图像处理和计算机视觉
大家好,我是良许。 作为一名嵌入式程序员,我在工作中经常会接触到图像处理和计算机视觉相关的项目。 特别是在汽车电子领域,像自动驾驶辅助系统(ADAS)、车道偏离预警、行人检测等功能,都离不开图像处理和计算机视觉技术。 今天就和大家聊聊这两个既相关又有区别的技术领域。 图像处理(Image Processing)主要关注的是对图像本身进行操作和变换,目的是改善图像质量、提取图像特征或者为后续处理做准备。 简单来说,图像处理的输入和输出都是图像。 比如我之前做过一个车载摄像头项目,原始图像经常会受到光照、噪声等影响,这时候就需要用到图像处理技术。 我们会对图像进行去噪、增强对比度、边缘检测等操作,让图像变得更清晰,更适合后续的分析处理。 常见的图像处理操作包括: 计算机视觉(Computer Vision)则更进一步,它的目标是让计算机能够"理解"图像中的内容。 计算机视觉不仅仅是处理图像,更重要的是从图像中提取有意义的信息,做出判断和决策。 举个例子,在我参与的车道偏离预警系统中,摄像头拍摄到的道路图像需要经过一系列处理:首先用图像处理技术对图像进行预处理,然后通过计算机视觉算法识别出车道线的位置,判断车辆是否偏离车道,最后给出预警信号。 这整个过程就是计算机视觉的应用。 计算机视觉的典型应用包括: 可以这样理解:图像处理是计算机视觉的基础,计算机视觉是图像处理的高级应用。 图像处理提供了各种工具和方法来操作图像,而计算机视觉则利用这些工具来实现更高层次的理解和决策。 在实际项目中,这两者往往是结合使用的。 比如在做行人检测时,我们首先需要对图像进行预处理(图像处理),然后使用深度学习模型来识别行人(计算机视觉),最后可能还需要对检测结果进行后处理优化(图像处理)。 在嵌入式系统中做图像处理,硬件平台的选择非常关键。 我接触过的平台主要有以下几种: 2.1.1 基于 STM32 的方案 对于一些简单的图像处理任务,STM32 系列 MCU 配合摄像头模块就能胜任。 比如我曾经用 STM32F429 做过一个简单的二维码识别项目,虽然处理速度不快,但对于低成本、低功耗的应用场景来说已经足够了。 下面是一个使用 STM32 HAL 库读取摄像头数据的示例代码: 2.1.2 基于 Linux 的方案 对于更复杂的图像处理和计算机视觉任务,我通常会选择运行 Linux 的嵌入式平台,比如树莓派、NVIDIA Jetson 系列等。 这些平台性能更强,而且可以方便地使用 OpenCV 等成熟的图像处理库。 在我目前的工作中,我们使用的是基于 ARM Cortex-A 系列处理器的平台,运行嵌入式 Linux 系统。 这样的平台既有足够的计算能力,又能保持相对较低的功耗和成本。 2.2.1 图像滤波 图像滤波是最基础也是最常用的图像处理操作。 在嵌入式系统中,我们经常需要对摄像头采集的图像进行去噪处理。 下面是一个简单的均值滤波实现: 在实际项目中,为了提高处理速度,我们通常会使用硬件加速或者 SIMD 指令来优化这些算法。 2.2.2 边缘检测 边缘检测在很多应用中都非常重要,比如车道线检测、物体轮廓提取等。 Sobel 算子是一种常用的边缘检测方法: 在嵌入式 Linux 平台上,OpenCV 是最常用的图像处理库。 它提供了丰富的图像处理和计算机视觉算法,而且经过了充分的优化。 下面是一个使用 OpenCV 进行图像处理的示例: 目标检测是计算机视觉中最重要的应用之一。 在汽车电子领域,我们需要检测行人、车辆、交通标志等各种目标。 3.1.1 传统方法 在深度学习普及之前,我们主要使用传统的目标检测方法,比如 HOG(方向梯度直方图)+ SVM(支持向量机)。 这种方法的优点是计算量相对较小,适合在资源受限的嵌入式系统上运行。 3.1.2 深度学习方法 现在,深度学习已经成为目标检测的主流方法。 YOLO、SSD、Faster R-CNN 等算法在准确率和速度上都有很大优势。 但是,这些算法通常需要较强的计算能力,所以在嵌入式系统上部署时需要进行模型优化。 我们在项目中使用的是 YOLOv5 的轻量级版本,通过模型量化和剪枝,可以在嵌入式平台上实现实时检测。下面是一个简化的推理代码示例: 图像分类是判断图像属于哪个类别的任务。 在嵌入式系统中,我们可能需要识别交通标志、判断道路类型等。 对于嵌入式平台,我们通常会使用 MobileNet、SqueezeNet 等轻量级网络模型。 这些模型在保持较高准确率的同时,大大减少了计算量和模型大小。 在很多应用场景中,我们不仅需要检测目标,还需要跟踪目标的运动轨迹。 比如在车辆防碰撞系统中,需要持续跟踪前方车辆的位置和速度。 常用的跟踪算法包括 KCF(核相关滤波)、SORT(简单在线实时跟踪)等。 OpenCV 提供了多种跟踪器的实现: 在嵌入式系统中,性能优化至关重要。 以下是我在实际项目中总结的一些优化技巧: 4.1.1 降低图像分辨率 很多时候,我们不需要处理全分辨率的图像。 通过降低分辨率,可以大幅减少计算量。 比如原本 1920x1080 的图像,降采样到 640x480 后,计算量减少到原来的 1/9 左右。 4.1.2 感兴趣区域(ROI)处理 只处理图像中感兴趣的区域,而不是整幅图像。 比如在车道线检测中,我们只需要处理图像下半部分的道路区域。 4.1.3 多级处理策略 先用简单快速的算法进行粗检测,再对可疑区域进行精细处理。 这样可以在保证准确率的同时提高处理速度。 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.1 使用定点运算代替浮点运算 在资源受限的嵌入式系统中,定点运算通常比浮点运算快得多。 可以将浮点数转换为定点数进行计算。 4.3.2 循环展开 对于一些关键的循环,可以进行循环展开优化,减少循环控制的开销。 4.3.3 使用查找表 对于一些复杂的数学运算,可以预先计算好结果存储在查找表中,使用时直接查表,避免重复计算。 在我参与的汽车电子项目中,有一个车道偏离预警系统的案例,可以很好地展示图像处理和计算机视觉的综合应用。 整个系统的处理流程如下: 5.1 图像采集 使用前置摄像头采集道路图像,帧率为 30fps,分辨率为 1280x720。 5.2 图像预处理 首先对图像进行灰度化处理,然后使用高斯滤波去噪。 接着提取图像下半部分作为感兴趣区域,因为车道线主要出现在这个区域。 5.3 边缘检测 使用 Canny 算子检测图像中的边缘,车道线通常表现为强边缘。 5.4 车道线检测 使用霍夫变换检测直线,筛选出符合车道线特征的直线。 对于弯道情况,我们使用多项式拟合来检测曲线车道线。 5.5 车道偏离判断 根据检测到的车道线位置,计算车辆在车道中的位置。 如果车辆偏离车道中心超过阈值,就发出预警信号。 5.6 结果输出 将检测结果通过 CAN 总线发送给其他控制单元,同时在显示屏上绘制车道线和预警信息。 这个项目的难点在于如何在有限的计算资源下实现实时处理,同时保证足够的准确率。 我们通过算法优化、硬件加速、多线程并行等手段,最终实现了稳定可靠的系统性能。 图像处理和计算机视觉技术还在不断发展,特别是在嵌入式领域,有以下几个值得关注的趋势: 6.1 边缘计算 越来越多的计算任务从云端转移到边缘设备,这对嵌入式系统的计算能力提出了更高要求。 同时也推动了专用 AI 芯片的发展。 6.2 轻量化模型 为了在嵌入式设备上部署深度学习模型,研究人员开发了各种轻量化技术,如模型压缩、知识蒸馏、神经网络架构搜索等。 6.3 多模态融合 单一的视觉信息有时不够准确,未来会更多地融合雷达、激光雷达等多种传感器数据,提高系统的鲁棒性。 6.4 实时性要求提升 随着自动驾驶等应用的发展,对实时性的要求越来越高。 这需要我们在算法和硬件两方面都进行优化。 作为嵌入式程序员,我们需要不断学习新技术,同时也要深入理解底层原理,才能在这个快速发展的领域中保持竞争力。 图像处理和计算机视觉技术正在改变我们的生活,而嵌入式系统则是这些技术落地的重要载体。 希望这篇文章能够帮助大家更好地理解和应用这些技术。 更多编程学习资源1. 图像处理与计算机视觉的区别与联系
1.1 什么是图像处理
1.2 什么是计算机视觉
1.3 两者的关系
2. 嵌入式系统中的图像处理实践
2.1 硬件平台选择
// 摄像头初始化
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.2 常用图像处理算法实现
// 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;
}
}
}// 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 库
#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 目标检测
#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 图像分类
3.3 目标跟踪
#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.2 硬件加速
4.3 代码优化
5. 实际项目经验分享
6. 未来发展趋势