跳到主要内容STM32 摄像头颜色识别实战:OV7725/OV2640 驱动与算法解析 | 极客日志C算法
STM32 摄像头颜色识别实战:OV7725/OV2640 驱动与算法解析
STM32 结合 OV7725/OV2640 摄像头实现颜色识别。通过 DCMI 接口采集图像数据,利用 DMA 降低 CPU 占用。核心算法采用 RGB 阈值法或 HSV 空间转换,通过设定色相、饱和度及亮度范围筛选目标颜色。代码包含图像预处理、颜色匹配及坐标追踪逻辑,适用于嵌入式视觉跟踪场景。
217728380128 浏览 STM32 摄像头颜色识别实战
本文分享如何使用 STM32 单片机配合 OV7725/OV2640 彩色摄像头采集图像数据,并进行分析处理,最终实现颜色的识别和检测。
一、什么是颜色识别
颜色识别(Color Recognition)是一种通过分析图像中像素的颜色信息来区分特定颜色的技术。它通常用于图像处理领域,通过将图像的每个像素或区域的颜色信息与预设标准进行比较,从而判断该区域属于何种颜色。
1. 图像采集识别的基本概念
像素(Pixel)
像素是图像中最小的单元,代表图像的基本组成部分。每个像素包含颜色信息,通常是 RGB(红、绿、蓝)或灰度值。图像由多个像素按矩阵排列而成,分辨率通常以宽度和高度的像素数表示,例如 1920x1080。
分辨率(Resolution)
分辨率表示图像的细节水平,指水平和垂直方向上的像素数。分辨率越高,图像包含的信息越多,细节越清晰。常见的如 HD(1280x720)和 FHD(1920x1080)。
帧率(Frame Rate)
帧率是指每秒钟显示的图像帧数,单位为 FPS。在动态视频处理中,帧率越高,画面越流畅。常见标准包括电影制作的 24 FPS、电视广播的 30 FPS 以及游戏视频的 60 FPS。
颜色深度(Color Depth)
颜色深度指每个像素所能表示颜色的位数。8 位颜色可表示 256 种颜色;24 位真彩色由 RGB 三种颜色的 8 位组成,可显示约 1677 万种颜色;32 位则通常包含 24 位颜色加 8 位透明度通道。
图像处理(Image Processing)
图像处理是对图像进行算法处理的过程,用于增强、分析或转换数据。常见技术包括去噪、对比度调整、边缘检测(如 Sobel、Canny)及图像分割。
图像采集设备
包括静态相机、动态摄像头模块及深度相机。摄像头模块通常用于获取连续帧图像数据,支持视频录制。
亮度与色度
亮度表示明暗程度,色度描述颜色信息(色调和饱和度)。在某些格式中,两者分开存储有助于高效压缩。
图像编码与压缩
压缩用于减少文件大小。无损压缩(如 PNG)可恢复原图,有损压缩(如 JPEG)会丢失部分数据但大幅减小体积。
图像识别
利用计算机视觉技术从图像中提取目标特征,包括分类、检测等。卷积神经网络(CNN)是其中广泛应用的技术。
延迟(Latency)
指图像采集到结果输出的时间差。在自动驾驶、视频监控等实时应用中,低延迟至关重要。
二、OV7725 简介
OV7725 是一款 VGA(640x480)分辨率的 CMOS 图像传感器,支持彩色图像采集。
基本特性
- 分辨率:支持 VGA(640x480),适合低分辨率视觉应用。
- 传感器类型:CMOS 技术,低功耗且成本低。
- 像素尺寸:3.6 µm,低光照下表现较好。
- 输出格式:支持 YUV422 和 RGB565。
主要特点
具备自动曝光控制(AEC)、自动白平衡(AWB)、自动亮度控制(AGC)及动态范围控制(DRC),能在不同光照条件下保持图像质量,并具备一定的噪声抑制能力。
输出接口
- Parallel 接口:8 位并行输出,速度快,适合高带宽需求。
- I2C 接口:用于配置传感器参数,简化连接。
三、单片机如何驱动 OV7725
关于具体的代码驱动细节较多,这里重点讲解两种主要的硬件接口方式及其差异。
1. GPIO 驱动
GPIO(通用输入输出)可用于基本的控制和数据交互。在 OV7725 驱动中,GPIO 涉及复位信号、电源使能、同步信号(VSYNC/HSYNC)及像素数据传输。时钟信号(PCLK)也通过 GPIO 生成。
- 性能限制:频率较低,传输速率受限,不适合高分辨率或高帧率应用。
- 资源消耗:占用大量 MCU 引脚,影响其他功能。
2. DCMI 驱动
DCMI(Digital Camera Memory Interface)是 STM32 等微控制器中专门用于连接图像传感器的硬件接口。
- 高效传输:专为高速图像数据传输设计,支持 DMA 直接内存访问,不占用 CPU 资源。
- 自动化:自动管理时序和数据流,稳定性高。
- 多格式支持:支持 RGB、YCbCr 等多种格式。
| 特性 | GPIO 驱动 | DCMI 驱动 |
|---|
| 功能 | 简单信号控制、低速传输 | 高效图像数据传输、支持 DMA |
| 接口 | GPIO 引脚 | 专用数字摄像头接口 |
| 速率 | 受限,适合低分辨率 | 高速,适合高分辨率 |
| CPU 占用 | 较高 | 较低 |
| 场景 | 简单模块、低速采集 | 高清视频、高性能采集 |
四、颜色识别的步骤和原理
1. RGB 阈值法
RGB 阈值法通过设定每个颜色通道(R、G、B)的数值范围来识别颜色。每个像素用 (R, G, B) 表示,分量值通常在 0 到 255 之间。
原理:
预设阈值范围来判断颜色。例如识别红色:R 大于某阈值,G 和 B 小于某阈值。
- 硬件连接:确保 STM32 与 OV7725 正确连接,配置 DCMI 接口。
- 捕获数据:使用 DCMI 驱动捕获图像,通过 DMA 传输到内存。
- 遍历处理:读取像素值,提取 RGB 分量。
- 阈值匹配:比较像素值与预设范围。
void process_image(uint16_t *frame_buffer, uint32_t width, uint32_t height) {
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
uint16_t pixel = frame_buffer[y * width + x];
uint8_t R = (pixel >> 11) & 0x1F;
uint8_t G = (pixel >> 5) & 0x3F;
uint8_t B = pixel & 0x1F;
R = (R * 255) / 31;
G = (G * 255) / 63;
B = (B * 255) / 31;
if (R > 200 && G < 100 && B < 100) {
}
}
}
}
- 红色:R: [200, 255], G: [0, 100], B: [0, 100]
- 绿色:R: [0, 100], G: [200, 255], B: [0, 100]
- 蓝色:R: [0, 100], G: [0, 100], B: [200, 255]
2. HSV 空间转换
HSV(色相、饱和度、明度)空间更接近人类感知,对光照变化更鲁棒。
- 色相(H):0°~360°,代表颜色种类。
- 饱和度(S):0%~100%,代表纯度。
- 明度(V):0%~100%,代表亮度。
RGB 到 HSV 转换公式:
需先将 RGB 归一化至 0~1,计算最大值、最小值及色差,再根据公式推导 H、S、V。
- 图像遍历:获取每个像素的 RGB 值。
- 空间转换:调用转换函数得到 HSV 值。
- 定义范围:设定目标颜色的 H、S、V 区间。
- 判断标记:若像素值在范围内,标记为目标颜色。
void RGB_to_HSV(uint8_t R, uint8_t G, uint8_t B, float *H, float *S, float *V) {
float r = R / 255.0f;
float g = G / 255.0f;
float b = B / 255.0f;
float Cmax = fmaxf(fmaxf(r, g), b);
float Cmin = fminf(fminf(r, g), b);
float delta = Cmax - Cmin;
if (delta == 0) { *H = 0; }
else if (Cmax == r) { *H = 60 * fmodf(((g - b) / delta), 6); }
else if (Cmax == g) { *H = 60 * (((b - r) / delta) + 2); }
else { *H = 60 * (((r - g) / delta) + 4); }
if (*H < 0) { *H += 360; }
if (Cmax == 0) { *S = 0; }
else { *S = (delta / Cmax) * 100; }
*V = Cmax * 100;
}
void identify_color_with_HSV(uint16_t *frame_buffer, uint32_t width, uint32_t height) {
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
uint16_t pixel = frame_buffer[y * width + x];
uint8_t R = (pixel >> 11) & 0x1F;
uint8_t G = (pixel >> 5) & 0x3F;
uint8_t B = pixel & 0x1F;
float H, S, V;
RGB_to_HSV(R * 255 / 31, G * 255 / 63, B * 255 / 31, &H, &S, &V);
if ((H >= 0 && H <= 10 || H >= 350 && H <= 360) && S >= 50 && V >= 50) {
frame_buffer[y * width + x] = 0xF800;
}
}
}
}
3. 颜色滤波器
颜色滤波器基于颜色空间转换与筛选,提取特定颜色区域。常用 RGB 和 HSV 空间,其中 HSV 更适合滤波。
- 读取图像:加载数据到内存。
- 转换空间:RGB 转 HSV。
- 设定范围:定义感兴趣的颜色区间。
- 像素检测:遍历像素,判断是否在范围内。
- 输出结果:保留符合条件的像素,过滤背景。
五、代码工程开源
以下是 STM32 单片机驱动摄像头对颜色识别的关键代码片段,整体工程逻辑参考以下结构。
#ifndef EASY_TRACERED_H
#define EASY_TRACERED_H
#define IMG_X 0
#define IMG_Y 0
#define IMG_W 240
#define IMG_H 300
#define ALLOW_FAIL_PER 3
#define ITERATE_NUM 7
typedef struct{
unsigned char H_MIN;
unsigned char H_MAX;
unsigned char S_MIN;
unsigned char S_MAX;
unsigned char L_MIN;
unsigned char L_MAX;
unsigned int WIDTH_MIN;
unsigned int HIGHT_MIN;
unsigned int WIDTH_MAX;
unsigned int HIGHT_MAX;
}TARGET_CONDI;
typedef struct{
unsigned int x;
unsigned int y;
unsigned int w;
unsigned int h;
}RESULT;
int Trace(const TARGET_CONDI *Condition, RESULT *Resu);
#endif
#include "EasyTracer_color.h"
#include "LCD.h"
#define min3v(v1, v2, v3) ((v1)>(v2)? ((v2)>(v3)?(v3):(v2)):((v1)>(v3)?(v3):(v1)))
#define max3v(v1, v2, v3) ((v1)<(v2)? ((v2)<(v3)?(v3):(v2)):((v1)<(v3)?(v3):(v1)))
typedef struct {
unsigned char red;
unsigned char green;
unsigned char blue;
}COLOR_RGB;
typedef struct {
unsigned char hue;
unsigned char saturation;
unsigned char luminance;
}COLOR_HSL;
typedef struct {
unsigned int X_Start;
unsigned int X_End;
unsigned int Y_Start;
unsigned int Y_End;
}SEARCH_AREA;
extern unsigned short LCD_ReadPoint(unsigned short x,unsigned short y);
static void ReadColor(unsigned int x,unsigned int y,COLOR_RGB *Rgb) {
unsigned short C16;
C16 = LCD_ReadPoint(x,y);
Rgb->red = (unsigned char)((C16&0xf800)>>8);
Rgb->green = (unsigned char)((C16&0x07e0)>>3);
Rgb->blue = (unsigned char)((C16&0x001f)<<3);
}
static void RGBtoHSL(const COLOR_RGB *Rgb, COLOR_HSL *Hsl) {
int h,s,l,maxVal,minVal,difVal;
int r = Rgb->red;
int g = Rgb->green;
int b = Rgb->blue;
maxVal = max3v(r, g, b);
minVal = min3v(r, g, b);
difVal = maxVal-minVal;
l = (maxVal+minVal)*240/255/2;
if(maxVal == minVal){
h = 0; s = 0;
} else {
if(maxVal==r) {
if(g>=b) h = 40*(g-b)/(difVal);
else h = 40*(g-b)/(difVal) + 240;
} else if(maxVal==g) h = 40*(b-r)/(difVal) + 80;
else if(maxVal==b) h = 40*(r-g)/(difVal) + 160;
if(l == 0) s=0;
else if(l<=120) s = (difVal)*240/(maxVal+minVal);
else s = (difVal)*240/(511 - (maxVal+minVal));
}
Hsl->hue = (unsigned char)(((h>240)? 240 : ((h<0)?0:h)));
Hsl->saturation = (unsigned char)(((s>240)? 240 : ((s<0)?0:s)));
Hsl->luminance = (unsigned char)(((l>240)? 240 : ((l<0)?0:l)));
}
static int ColorMatch(const COLOR_HSL *Hsl,const TARGET_CONDI *Condition) {
if( Hsl->hue > Condition->H_MIN && Hsl->hue < Condition->H_MAX &&
Hsl->saturation > Condition->S_MIN && Hsl->saturation < Condition->S_MAX &&
Hsl->luminance > Condition->L_MIN && Hsl->luminance < Condition->L_MAX )
return 1;
else return 0;
}
static int SearchCentre(unsigned int *x,unsigned int *y,const TARGET_CONDI *Condition,const SEARCH_AREA *Area) {
unsigned int SpaceX,SpaceY,i,j,k,FailCount=0;
COLOR_RGB Rgb;
COLOR_HSL Hsl;
SpaceX = Condition->WIDTH_MIN/3;
SpaceY = Condition->HIGHT_MIN/3;
for(i=Area->Y_Start;i<Area->Y_End;i+=SpaceY){
for(j=Area->X_Start;j<Area->X_End;j+=SpaceX){
FailCount=0;
for(k=0;k<SpaceX+SpaceY;k++){
if(k<SpaceX) ReadColor(j+k,i+SpaceY/2,&Rgb);
else ReadColor(j+SpaceX/2,i+(k-SpaceX),&Rgb);
RGBtoHSL(&Rgb,&Hsl);
if(!ColorMatch(&Hsl,Condition)) FailCount++;
if(FailCount>((SpaceX+SpaceY)>>ALLOW_FAIL_PER)) break;
}
if(k==SpaceX+SpaceY){
*x = j+SpaceX/2;
*y = i+SpaceY/2;
return 1;
}
}
}
return 0;
}
static int Corrode(unsigned int oldx,unsigned int oldy,const TARGET_CONDI *Condition,RESULT *Resu) {
unsigned int Xmin,Xmax,Ymin,Ymax,i,FailCount=0;
COLOR_RGB Rgb;
COLOR_HSL Hsl;
for(i=oldx;i>IMG_X;i--){
ReadColor(i,oldy,&Rgb);
RGBtoHSL(&Rgb,&Hsl);
if(!ColorMatch(&Hsl,Condition)) FailCount++;
if(FailCount>(((Condition->WIDTH_MIN+Condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER)) break;
}
Xmin=i;
FailCount=0;
for(i=oldx;i<IMG_X+IMG_W;i++){
ReadColor(i,oldy,&Rgb);
RGBtoHSL(&Rgb,&Hsl);
if(!ColorMatch(&Hsl,Condition)) FailCount++;
if(FailCount>(((Condition->WIDTH_MIN+Condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER)) break;
}
Xmax=i;
FailCount=0;
for(i=oldy;i>IMG_Y;i--){
ReadColor(oldx,i,&Rgb);
RGBtoHSL(&Rgb,&Hsl);
if(!ColorMatch(&Hsl,Condition)) FailCount++;
if(FailCount>(((Condition->HIGHT_MIN+Condition->HIGHT_MAX)>>2)>>ALLOW_FAIL_PER)) break;
}
Ymin=i;
FailCount=0;
for(i=oldy;i<IMG_Y+IMG_H;i++){
ReadColor(oldx,i,&Rgb);
RGBtoHSL(&Rgb,&Hsl);
if(!ColorMatch(&Hsl,Condition)) FailCount++;
if(FailCount>(((Condition->HIGHT_MIN+Condition->HIGHT_MAX)>>2)>>ALLOW_FAIL_PER)) break;
}
Ymax=i;
Resu->x = (Xmin+Xmax)/2;
Resu->y = (Ymin+Ymax)/2;
Resu->w = Xmax-Xmin;
Resu->h = Ymax-Ymin;
if( ((Xmax-Xmin)>(Condition->WIDTH_MIN)) && ((Ymax-Ymin)>(Condition->HIGHT_MIN)) &&
((Xmax-Xmin)<(Condition->WIDTH_MAX)) && ((Ymax-Ymin)<(Condition->HIGHT_MAX)) )
return 1;
else return 0;
}
int Trace(const TARGET_CONDI *Condition,RESULT *Resu) {
unsigned int i;
static unsigned int x0,y0,flag=0;
static SEARCH_AREA Area={IMG_X,IMG_X+IMG_W,IMG_Y,IMG_Y+IMG_H};
RESULT Result;
if(flag==0){
if(SearchCentre(&x0,&y0,Condition,&Area)) flag=1;
else {
Area.X_Start= IMG_X ; Area.X_End = IMG_X+IMG_W ;
Area.Y_Start= IMG_Y ; Area.Y_End = IMG_Y+IMG_H ;
if(SearchCentre(&x0,&y0,Condition,&Area)){ flag=1; return 1; }
else { flag=0; return 0; }
}
}
Result.x = x0;
Result.y = y0;
for(i=0;i<ITERATE_NUM;i++) Corrode(Result.x,Result.y,Condition,&Result);
if(Corrode(Result.x,Result.y,Condition,&Result)){
x0=Result.x; y0=Result.y;
Resu->x=Result.x; Resu->y=Result.y;
Resu->w=Result.w; Resu->h=Result.h;
flag=1;
Area.X_Start= Result.x - ((Result.w)<<1);
Area.X_End = Result.x + ((Result.w)<<1);
Area.Y_Start= Result.y - ((Result.h)<<1);
Area.Y_End = Result.y + ((Result.h)<<1);
return 1;
} else{
flag=0; return 0;
}
}
#include <rgb565.h>
#include "EasyTracer_color.h"
#include "lcd.h"
const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"};
int x=0,y=0;
TARGET_CONDI Conditionred={215,240,20,240,30,160,15,15,200,200};
RESULT Resured;
u8 ov_frame=0;
void rgb565_test(void) {
OV2640_RGB565_Mode();
My_DCMI_Init();
DCMI_DMA_Init((u32)&LCD->LCD_RAM,1,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);
OV2640_OutSize_Set(240,300);
DCMI_Start();
while(1){ delay_ms(10); }
}
void DCMI_IRQHandler(void) {
if(DCMI_GetITStatus(DCMI_IT_FRAME)==SET){
DCMI_Stop();
if(Trace(&Conditionred,&Resured)){
LCD_Fill(Resured.x-Resured.w/2,Resured.y-Resured.h/2,Resured.x+Resured.w/2,Resured.y-Resured.h/2+1,0xf800);
LCD_Fill(Resured.x-Resured.w/2,Resured.y-Resured.h/2,Resured.x-Resured.w/2+1,Resured.y+Resured.h/2,0xf800);
LCD_Fill(Resured.x-Resured.w/2,Resured.y+Resured.h/2,Resured.x+Resured.w/2,Resured.y+Resured.h/2+1,0xf800);
LCD_Fill(Resured.x+Resured.w/2,Resured.y-Resured.h/2,Resured.x+Resured.w/2+1,Resured.y+Resured.h/2,0xf800);
LCD_Fill(Resured.x-2,Resured.y-2,Resured.x+2,Resured.y+2,0xf800);
x=Resured.x; y=Resured.y;
}
ov_frame++;
DCMI_Start();
DCMI_ClearITPendingBit(DCMI_IT_FRAME);
LED1=!LED1;
}
}
void TIM3_IRQHandler(void) {
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET){
printf("framerate is %d \r\n",ov_frame);ov_frame=0;
printf("x is %d ,y is %d \r\n",x,y);
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online