STM32 单片机 OV7725/OV2640 摄像头颜色识别检测方案
STM32 单片机结合 OV7725 或 OV2640 摄像头实现颜色识别。通过 DCMI 接口驱动摄像头采集图像数据,利用 RGB 阈值法或 HSV 色彩空间转换进行颜色判断。文章详解了 GPIO 与 DCMI 驱动区别,RGB 转 HSV 公式,以及基于 HSL 滤波器的目标追踪代码实现。包含关键代码片段如 Trace 函数、DCMI 中断处理及 LCD 显示逻辑,适用于嵌入式视觉开发参考。

STM32 单片机结合 OV7725 或 OV2640 摄像头实现颜色识别。通过 DCMI 接口驱动摄像头采集图像数据,利用 RGB 阈值法或 HSV 色彩空间转换进行颜色判断。文章详解了 GPIO 与 DCMI 驱动区别,RGB 转 HSV 公式,以及基于 HSL 滤波器的目标追踪代码实现。包含关键代码片段如 Trace 函数、DCMI 中断处理及 LCD 显示逻辑,适用于嵌入式视觉开发参考。

本篇文章分享关于如何使用 STM32 单片机对彩色摄像头(OV7725/OV2640)采集的图像数据进行分析处理,最后实现颜色的识别和检测。

颜色识别(Color Recognition)是一种通过分析图像中像素的颜色信息来识别或区分特定颜色的技术。它通常用于图像处理和计算机视觉领域,通过将图像的每个像素或区域的颜色信息与预设的颜色标准进行比较,从而判断该区域属于何种颜色。


下面是显示屏屏幕的分辨率:










OV7725 是一款 VGA(640x480)分辨率的 CMOS 图像传感器,支持彩色图像采集。
OV7725 支持两种常见的接口标准,便于与不同平台或设备集成:


关于单片机对摄像头的代码驱动,这里不做详细讲解。完整的工程可以到文章末尾的资料连接进行下载。
这里需要注意一些关键的地方:
在 OV7725 摄像头模块的驱动中,GPIO 驱动和 DCMI 驱动分别代表了两种不同的硬件接口方式,它们在摄像头与微控制器(MCU)之间的数据传输和信号控制中扮演不同的角色。
GPIO(General Purpose Input/Output)是一种通用的输入输出接口,通常用于处理信号的传输、控制和接收。对于 OV7725 摄像头来说,GPIO 引脚可以用于与摄像头进行基本的控制和数据交互。
在 OV7725 驱动中,GPIO 驱动主要涉及以下几个方面:**控制信号的传输:**复位信号(RESET): 摄像头复位信号通常通过一个 GPIO 引脚来控制。通过将 GPIO 设置为低电平,可以复位摄像头。电源使能(POWER): 某些 MCU 允许通过 GPIO 引脚控制摄像头模块的电源开关。同步信号(VSYNC、HSYNC): 用于标记每一帧图像的开始(VSYNC)和每一行像素的开始(HSYNC)。这些信号通常也通过 GPIO 引脚传输。**像素数据传输:**对于一些简单的配置,OV7725 可以通过并行接口以 8 位数据宽度输出像素数据。这些数据通过 GPIO 引脚进行传输。**时钟信号:**像素时钟(PCLK): 控制像素数据的时序,这个时钟信号也是通过 GPIO 引脚生成并传输的。
性能限制: 使用 GPIO 接口进行像素数据的传输和控制信号的处理,由于 GPIO 的工作频率相对较低,数据传输速率受限,因此对于高分辨率或高帧率的摄像头应用,GPIO 驱动可能不够高效。硬件资源消耗: 每个 GPIO 引脚需要消耗 MCU 的资源,因此如果需要大量的 GPIO 引脚(例如 8 位并行数据线、同步信号等),可能会影响到 MCU 的其他功能和资源。
DCMI 是指 Digital Camera Memory Interface,即数字摄像头内存接口。它是 STM32 等微控制器中常见的一种硬件接口,专门用于连接图像传感器(如 OV7725)和处理器(或其他图像处理单元),用于处理图像数据的传输。
DCMI 驱动在摄像头驱动中的作用包括:
| 特性 | GPIO 驱动 | DCMI 驱动 |
|---|---|---|
| 功能 | 主要用于简单的信号控制和低速数据传输 | 高效的图像数据传输接口,支持 DMA |
| 接口方式 | 通过 GPIO 引脚传输数据和信号 | 专用的数字摄像头接口,支持并行和串行数据 |
| 数据传输速率 | 受限,适用于低分辨率和低帧率的图像 | 高速,适用于高分辨率、高帧率图像 |
| CPU 占用 | 较高,因需要通过软件控制信号和数据流 | 较低,通过 DMA 自动传输数据 |
| 应用场景 | 简单摄像头模块,低速图像采集 | 高分辨率、高帧率的视频采集 |
GPIO 驱动通常用于简单的摄像头控制,适合低分辨率、低帧率的图像采集场景,可以通过基本的控制信号(复位、同步信号)和数据传输来与摄像头进行交互。DCMI 驱动则是一种高效、专业的接口,适用于需要高带宽、高速度数据传输的应用,能自动处理复杂的同步信号和数据流,并结合 DMA 提供高效的图像数据采集。
接下来将详细讲解颜色识别的算法和代码工程。下面就是非常重要的一些概念和原理了!

RGB 阈值法是颜色识别中常见的一种方法。其基本原理是通过设定每个颜色通道(红色 R、绿色 G、蓝色 B)的数值范围来识别图像中的颜色。每个像素的颜色信息可以通过三个通道的数值(通常是 0 到 255 之间的整数)表示。在 RGB 模型中,颜色的表达方式为 (R, G, B),其中每个分量的值控制着对应的颜色成分的强度。
对于颜色识别,我们通过预设的阈值范围来判断图像中的颜色。例如,我们可能会设定如下的阈值来识别红色:
基于这个逻辑,RGB 阈值法能有效地检测图像中符合条件的像素,进而实现颜色的识别。
首先,你需要确保 STM32 和 OV7725 摄像头正确连接,并配置好摄像头接口(通常是通过 DCMI 接口或 GPIO):
使用 STM32 配置 DCMI 驱动程序来捕获图像数据。通过 DCMI 接口,图像数据会通过 DMA 传输到内存。
// 配置 DCMI 和 DMA
void DCMI_DMA_Init(void) {
// 启动 DCMI 和 DMA 配置,配置 DCMI 时钟和像素格式
// 初始化 DMA(例如:使用 DMA2)
// 配置 DMA 传输到内存
// 启动 DCMI 捕获图像
}
例如,如果你想识别红色,可以设置:
通过比较图像中每个像素的 RGB 值与这些阈值进行匹配,就能判断该像素是否属于目标颜色。
// 假设已捕获并存储了图像数据到内存(例如:frame_buffer)
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];
// 提取 RGB 分量(假设图像格式为 RGB565)
uint8_t R = (pixel >> 11) & 0x1F; // 获取红色分量
uint8_t G = (pixel >> 5) & 0x3F; // 获取绿色分量
uint8_t B = pixel & 0x1F; // 获取蓝色分量
// 将 RGB 分量转换为 8 位(如果需要)
R = (R * 255) / 31; // RGB565 转 8-bit
G = (G * 255) / 63;
B = (B * 255) / 31;
// 判断是否属于某个颜色范围(例如识别红色)
if (R > 200 && G < 100 && B < 100) {
// 如果符合红色范围,执行某些操作
// 例如,在此位置标记或执行动作
}
}
}
}
在实际应用中,你可以根据需要调整 RGB 阈值范围。不同的颜色可以使用不同的阈值。以下是识别 红色、绿色 和 蓝色 的示例:**红色:**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]
在对每个像素进行 RGB 处理后,检查该像素是否符合某一颜色的阈值。如果符合,可以标记该像素为目标颜色,也可以根据需要进行其他处理,例如计数、绘制框、发送信号等。
void identify_color(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++) {
// 获取当前像素的 RGB565 格式的颜色值
uint16_t pixel = frame_buffer[y * width + x];
// 提取红色分量,右移 11 位并与 0x1F(11111)进行按位与操作,保留最低 5 位
uint8_t R = (pixel >> 11) & 0x1F;
// 提取绿色分量,右移 5 位并与 0x3F(111111)进行按位与操作,保留最低 6 位
uint8_t G = (pixel >> 5) & 0x3F;
// 提取蓝色分量,直接与 0x1F(11111)进行按位与操作,保留最低 5 位
uint8_t B = pixel & 0x1F;
// 将红色、绿色、蓝色分量的值从 5 位或 6 位范围映射到 8 位(0 到 255)
R = (R * 255) / 31;
G = (G * 255) / 63;
B = (B * 255) / 31;
// 判断是否为红色:红色分量大于 200,绿色和蓝色分量小于 100
if (R > 200 && G < 100 && B < 100) {
// 如果是红色,标记为红色(RGB565 格式中的红色:0xF800)
frame_buffer[y * width + x] = 0xF800; // RGB565 红色
}
// 判断是否为绿色:绿色分量大于 200,红色和蓝色分量小于 100
else if (R < 100 && G > 200 && B < 100) {
// 如果是绿色,标记为绿色(RGB565 格式中的绿色:0x07E0)
frame_buffer[y * width + x] = 0x07E0; // RGB565 绿色
}
// 判断是否为蓝色:蓝色分量大于 200,红色和绿色分量小于 100
else if (R < 100 && G < 100 && B > 200) {
// 如果是蓝色,标记为蓝色(RGB565 格式中的蓝色:0x001F)
frame_buffer[y * width + x] = 0x001F; // RGB565 蓝色
}
}
}
}
使用 HSV(色相、饱和度、明度)空间来进行颜色识别是图像处理中一种常见且有效的方法。与 RGB(红绿蓝)空间不同,HSV 将颜色分解为三部分:色相(H)、饱和度(S)和明度(V),这使得它更接近于人类的感知方式,也更便于从图像中提取特定颜色。尤其是对于一些光照变化较大的情况,HSV 空间的颜色分离表现更为优秀。
RGB 和 HSV 之间的关系及转换

色相(H):代表颜色的种类,取值范围通常为 0 到 360°(即颜色的角度),每个角度对应一种颜色。0° 通常为红色,120° 为绿色,240° 为蓝色,其他颜色则在这三个之间变化。饱和度(S):表示颜色的纯度或强度,取值范围为 0 到 100%。饱和度越高,颜色越纯,越接近原始颜色;饱和度为 0 时,颜色呈灰白色。明度(V):表示颜色的亮度,取值范围为 0 到 100%。明度越高,颜色越明亮;明度为 0 时颜色为黑色。
要在 HSV 空间中进行颜色识别,通常需要将 RGB 颜色空间的像素值转换为 HSV 值。RGB 值的范围通常是 0 到 255,而 HSV 的范围是:**色相 H:0 到 360°**饱和度 S 和明度 V:0 到 100%
下面是 RGB 到 HSV 的转换公式:
1、标准化 RGB 值:将 RGB 的每个分量从 0
255 的范围映射到 01:2、计算最大值和最小值:
计算色差:
3、计算色相 H:
4、计算饱和度 S
5、计算明度 V
基于 HSV 空间,我们可以通过判断色相、饱和度和明度的范围来识别特定的颜色。以下是基于 HSV 空间进行颜色识别的具体步骤:
步骤 1:图像遍历
遍历图像中的每个像素,获取其 RGB 值。步骤 2:RGB 到 HSV 的转换
将每个像素的 RGB 值转换为 HSV 值。使用上述的 RGB 到 HSV 转换公式来实现。步骤 3:定义颜色范围
对于每种你想要识别的颜色(如红色、绿色、蓝色等),定义一个 HSV 范围。例如:红色:H 范围可以是 [0, 10] 或 [350, 360],S 范围可以是 [50, 100],V 范围可以是 [50, 100]。绿色:H 范围可以是 [60, 180],S 范围可以是 [50, 100],V 范围可以是 [50, 100]。蓝色:H 范围可以是 [180, 250],S 范围可以是 [50, 100],V 范围可以是 [50, 100]。步骤 4:进行颜色判断
根据转换得到的 HSV 值,判断是否在目标颜色的范围内。例如,对于红色,如果色相 ( H ) 在 [0, 10] 或 [350, 360] 范围内,饱和度 ( S ) 在 [50, 100] 范围内,明度 ( V ) 在 [50, 100] 范围内,则认为该像素为红色。步骤 5:标记颜色
如果一个像素属于目标颜色(例如红色),可以将其标记或替换为一个特定颜色,或者在后续处理步骤中执行某些操作(如高亮、滤镜等)。
以下是一个简化的基于 HSV 空间进行颜色识别的示例代码。该代码检查图像中的每个像素,识别是否为红色,并将其标记为纯红色(RGB565 格式)。
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;
// 将 RGB 转换为 HSV
float H, S, V;
RGB_to_HSV(R * 255 / 31, G * 255 / 63, B * 255 / 31, &H, &S, &V);
// 判断是否为红色:H 在 [0, 10] 或 [350, 360],S 和 V 都在 [50, 100]
if ((H >= 0 && H <= 10 || H >= 350 && H <= 360) && S >= 50 && V >= 50) {
frame_buffer[y * width + x] = 0xF800; // 标记为红色
}
}
}
}
颜色滤波器的实现原理基于图像处理技术,通过对图像中的像素进行颜色空间的转换与筛选,提取特定颜色的区域或物体。简单来说,颜色滤波器就是通过设定一个颜色范围,过滤掉其他不需要的颜色,保留感兴趣的颜色区域。常用的颜色空间有 RGB 和 HSV,其中 HSV 空间更适合用于颜色滤波,因为它与人眼的颜色感知更接近。
颜色滤波器通常有以下几个步骤:
在图像处理中,最常见的颜色空间是 RGB(红、绿、蓝)和 HSV(色相、饱和度、明度)。在 RGB 空间中,颜色是由三个通道的值(红、绿、蓝)组成的。HSV 空间则通过 色相 (H)、饱和度 (S) 和 明度 (V) 来描述颜色,更符合人类视觉感知。因此,颜色滤波一般使用 HSV 空间,因为它能够更准确地分离色彩(色相)和亮度(明度、饱和度)。
颜色滤波器的关键是设定一个特定的颜色范围,该范围包含了你要识别的颜色。例如,如果你想检测图像中的 红色,你需要定义红色的 色相 范围(例如 H: 0° 到 10°,或 H: 350° 到 360°),并根据 饱和度 (S) 和 明度 (V) 定义允许的阈值。
对于图像中的每个像素,根据其 HSV 值(或者 RGB 值)来判断它是否在定义的颜色范围内。如果在范围内,则保留该像素;如果不在范围内,则将其设为背景色或忽略。
经过颜色滤波后,图像的背景通常会被去除,只留下符合条件的颜色区域。这通常用于图像分割、目标检测、物体跟踪等应用。
一般来说,颜色滤波器的工作流程可以分为以下几个步骤:
HSV 颜色空间中的颜色滤波器通常只需要定义 色相 (H) 范围,结合 饱和度 (S) 和 明度 (V) 范围来实现更加精确的颜色提取。以下是一个基于 HSV 颜色空间的颜色滤波器实现过程:
首先,根据应用需求设定感兴趣的颜色范围。假设我们要提取 红色,可以设置如下的范围:
对于每个像素,通过计算其 HSV 值(如果图像在 RGB 空间中,则先转换到 HSV 空间):
如果一个像素的 HSV 值在指定的范围内,那么它就是我们需要保留的颜色;否则,可以将其设为背景色或其他颜色。

这里介绍 STM32 单片机驱动摄像头对颜色识别的关键代码,整体工程看文章末尾资料连接。注意:这里使用 STM32+OV2640
颜色处理的关键代码:
EasyTracer_color.h
#ifndef EASY_TRACERED_H
#define EASY_TRACERED_H
#define IMG_X 0 //图片 x 坐标
#define IMG_Y 0 //图片 y 坐标
#define IMG_W 240 //图片宽度
#define IMG_H 300 //图片高度
#define ALLOW_FAIL_PER 3 //容错率,每 1<<ALLOW_FAIL_PER 个点允许出现一个错误点,容错率越大越容易识别,但错误率越大
#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;//目标的 x 坐标
unsigned int y;//目标的 y 坐标
unsigned int w;//目标的宽度
unsigned int h;//目标的高度
}RESULT;//识别结果
//将识别条件写入 Condition 指向的结构体中,该函数将返回目标的 x,y 坐标和长宽
//返回 1 识别成功,返回 1 识别失败
int Trace(const TARGET_CONDI *Condition, RESULT *Resu);
#endif
EasyTracer_color.c
#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)))//取最小值
/* ①数据类型备注(STM32F4):
typedef unsigned char uint8_t;
u8
typedef unsigned short int uint16_t;
u16 (int 可省) 为 16 位数据类型,占两个字节,范围为 0~65535
typedef unsigned int uint32_t;
u32
typedef unsigned __INT64 uint64_t
②static 类型函数:static 函数与普通函数的区别:
用 static 修饰的函数,限定在本源码文件中,不能被本源码文件以外的代码文件调用。而普通的函数,默认是 extern 的,即可以被其它代码文件调用该函数。
在函数的返回类型前加上关键字 static,函数就被定义成为静态函数。普通函数的定义和声明默认情况下是 extern 的,但静态函数只是在声明他的文件当中可见,
不能被其他文件所用。因此定义静态函数有以下优点:
<1> 其他文件中可以定义相同名字的函数,不会发生冲突。
<2> 静态函数不能被其他文件所用。
③const 通常修饰常量类型,其变量/对象的值不能被改变。作用有以下几点:
<1> 修饰对象具有不可变性,便于进行类型检查,对象被修改时编译器可查错,增强了程序的健壮性
<2> 节省空间,避免不必要的内存分配,提高效率
④RGB 和 HSL(也叫 HSB/HSV)是两种色彩空间,即:红,绿,蓝(Red,Green,Blue)和色调,饱和度,亮度(Hue,Saturation,Lightness or Brightness or Value),
RGB 适用于机器采样,目前的显示器颜色即由这三种基色构成,而 HSL 更符合人类的直观感觉,
比如人一般表达一个颜色会这样说:有点浓的暗红色,而不会说红色占多少,绿色占多少,蓝色占多少;
<1>亮度仅与图像的最多颜色成分和最少的颜色成分的总量有关。亮度越高,图像越趋于明亮的白色
<2>饱和度与图像的最多颜色成分和最少的颜色成分的差量有关。饱和度越小,图像越趋于灰度图像。饱和度越大,图像越鲜艳,给人的感觉是彩色的
<3>色调决定了人对图像的不同的颜色感受。
*/
//RGB 颜色格式结构体
typedef struct {
unsigned char red; // [0,255]
unsigned char green; // [0,255]
unsigned char blue; // [0,255]
}COLOR_RGB;//RGB 格式颜色
//色相 (H)、饱和度 (S)、明度 (L) 颜色格式结构体
typedef struct {
unsigned char hue; // [0,240] 色调
unsigned char saturation; // [0,240] 饱和度
unsigned char luminance; // [0,240] 亮度
}COLOR_HSL;//HSL 格式颜色
//搜索图像窗口区域,图像 X/Y 起始坐标
typedef struct {
unsigned int X_Start;
unsigned int X_End;
unsigned int Y_Start;
unsigned int Y_End;
}SEARCH_AREA;//区域
//读取 RBG 格式颜色,唯一需要移植的函数
extern unsigned short LCD_ReadPoint(unsigned short x,unsigned short y);//读某点颜色
/***************************************************************************
*函数名称:ReadColor
*函数功能:读 LCD 屏幕某点颜色将之转化为 RGB 格式 并存储在 RGB 颜色格式结构体 中
*入口参数:x,y 要读取颜色的 LCD 屏幕坐标,COLOR_RGB,存放 R、G、B 值的结构体
*返回参数:无
****************************************************************************/
static void ReadColor(unsigned int x,unsigned int y,COLOR_RGB *Rgb) {
unsigned short C16; //即 Color16,R5+G6+B5=16
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);
}
/***************************************************************************
*函数名称:RGBtoHSL
*函数功能:颜色格式转换:RGB 转 HSL
*入口参数:COLOR_RGB:要转换的 RGB 结构体地址 COLOR_HSL:转换完 HSL,存储到的 HSL 结构体地址
*返回参数:无
****************************************************************************/
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;
//提取 R、G、B 值
maxVal = max3v(r, g, b);
minVal = min3v(r, g, b);//提取 RGB 三者最大/最小值
difVal = maxVal-minVal; //计算最大最小值的差值
//计算亮度
l = (maxVal+minVal)*240/255/2;
if(maxVal == minVal)//若 r=g=b
{
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;
s=(difVal)*240/(511 - (maxVal+minVal));
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)));//亮度
}
/***************************************************************************
*函数名称:ColorMatch
*函数功能:颜色匹配
*入口参数:COLOR_HSL:要进行匹配的 HSL TARGET_CONDI:匹配(判定的)目标条件
*返回参数:成功返回 1,失败返回 0
****************************************************************************/
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 )
//hue 为色调,saturation 为饱和度 ,luminance 为亮度
return 1;
else
return 0;
}
/***************************************************************************
*函数名称:SearchCentre
*函数功能:条件、颜色匹配,搜索目标物体的大概坐标
*入口参数:*x、*y 为搜索到目标后存储目标坐标的指针地址,TARGET_CONDI 为要判定的目标条件、SEARCH_AREA 为要进行搜索的区域
*返回参数:成功返回 1,失败返回 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)//i 等于纵轴起始点,i 小于纵轴结束点,i 每次自加目标最小高度的 1/3
{
for(j=Area->X_Start;j<Area->X_End;j+=SpaceX)//j 等于横轴起始点,j 小于横轴结束点,j 每次自加目标最小宽度 1/3
{
FailCount=0; //失败次数初始化
for(k=0;k<SpaceX+SpaceY;k++)//k 小于目标最小宽度与目标最小高度和
{
if(k<SpaceX)//如果 k 小于目标最小宽度
ReadColor(j+k,i+SpaceY/2,&Rgb);//读点 j+k,i+SpaceY/2 的颜色
else//否则
ReadColor(j+SpaceX/2,i+(k-SpaceX),&Rgb);//读点 j+SpaceX/2,i+(k-SpaceX) 的颜色
RGBtoHSL(&Rgb,&Hsl);//读完点后,将 RGB 转 HSL
if(!ColorMatch(&Hsl,Condition))//进行颜色匹配,如果颜色匹配不成功
FailCount++; //失败计数 ++
if(FailCount>((SpaceX+SpaceY)>>ALLOW_FAIL_PER))//如果失败次数大于容错率
break; //结束该长方形区域的搜索循环,移动到下一个区域
}
if(k==SpaceX+SpaceY)//如果某次区域匹配成功,那么 k 必等于 SpaceX+SpaceY,将该区域的中心点赋值给存储坐标的指针地址,并返回成功值 1
{
*x = j+SpaceX/2;
*y = i+SpaceY/2;
return 1;
}
}
}
return 0;
}
/***************************************************************************
*函数名称:Corrode
*函数功能:从腐蚀中心向外腐蚀,得到新的腐蚀中心
*入口参数:oldx、oldy 为上次搜索的目标中心地址,TARGET_CONDI 为要判定的目标条件、RESULT 为要存储的结果值结构体
*返回参数:成功返回 1,失败返回 0
*备注:((Condition->WIDTH_MIN+Condition->WIDTH_MAX)>>2),最大最小和的平均,减小误差除以 4
****************************************************************************/
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--) //左腐蚀,y 不变
{
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; //更新 X 轴最小坐标值
FailCount=0;//清空错误次数
for(i=oldx;i<IMG_X+IMG_W;i++)//右腐蚀,y 不变
{
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;//更新 X 轴最大坐标值
FailCount=0;//清空错误次数
for(i=oldy;i>IMG_Y;i--)//下腐蚀,x 不变
{
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;//更新 Y 轴最小坐标值
FailCount=0;//清空错误次数
for(i=oldy;i<IMG_Y+IMG_H;i++)//上腐蚀,x 不变
{
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;//更新 Y 轴最大坐标值
FailCount=0;//清空错误次数
//更新物体准确质点坐标
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;
}
/***************************************************************************
*函数名称:Trace
*函数功能:用户将识别条件写入 Condition 指向的结构体中,该函数将返回目标的 x,y 坐标和长宽
*入口参数:识别条件:Condition 指向的结构体;识别条件写入 Condition 指向的结构体
*返回参数:返回 1 识别成功,返回 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}; //要进行搜索的图像区域:x 坐标 y 坐标 w 宽度 h 高度
RESULT Result; //RESULT 识别结果
if(flag==0) //如果首次使用或上一次腐蚀失败
{
if(SearchCentre(&x0,&y0,Condition,&Area)) //搜索腐蚀中心并返回给 x0,y0,如果成功搜索到,那么 flag 置 1
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
{
flag=1;//default 0
return 1;
}
else
{
flag=0;//default 0
return 0;
}
}
}
Result.x = x0;//如果 flag!=0,说明上一次有腐蚀中心结果,所以直接使用上一次结果腐蚀即可,而不需要再次遍历图像搜索腐蚀中心
Result.y = y0;//上一次的腐蚀中心赋值给这次的 oldx,oldy
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);
//缩小下次搜索腐蚀中心图像范围
// 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,返回失败值 0
{
flag=0;
return 0;
}
}
rgb565.h
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "usmart.h"
#include "usart2.h"
#include "timer.h"
#include "ov2640.h"
#include "dcmi.h"
void rgb565_test(void);
rgb565.c
#include <rgb565.h>
#include "EasyTracer_color.h"
#include "lcd.h"
const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"}; //7 种特效
/* 4.3 寸,800*480
OV2640_Window_Set 设置图像输出窗口
传感器窗口设置允许用户设置整个传感器区域(1632*1220)的感兴趣部分,也就是在传感器里面开窗,开窗范围从 2*2~1632*1220 都可以设置,不过要求这个窗口必须大于等于随后设置的图像尺寸
OV2640_ImageSize_Set 设置图像尺寸大小,也就是所选格式的输出分辨率
图像尺寸设置,也就是 DSP 输出(最终输出到 LCD 的)图像的最大尺寸,该尺寸要小于等于前面我们传感器窗口设置所设定的窗口尺寸。
OV2640_ImageWin_Set 设置图像开窗大小
图像窗口设置其实和前面的传感器窗口设置类似,只是这个窗口是在我们前面设置的图像尺寸里面,再一次设置窗口大小,该窗口必须小于等于前面设置的图像尺寸。设置后的图像范围,将用于输出到外部。
OV2640_OutSize_Set 设置图像输出大小
图像输出大小设置,控制最终输出到外部的图像尺寸。该设置将图像窗口设置所决定的窗口大小,通过内部 DSP 处理,缩放成我们输出到外部的图像大小。
该设置将会对图像进行缩放处理,如果设置的图像输出大小不等于图像窗口设置图像大小,那么图像就会被缩放处理,只有这两者设置一样大的时候,输出比例才是 1:1 的
lcddev.width=800;
lcddev.height=480;
*/
int x=0,y=0;
TARGET_CONDI Conditionred={215,240,20,240,30,160,15,15,200,200}; //红色 1 API 参数 hsl 的阈值,识别时用的
RESULT Resured;//判定为的目标条件
u8 ov_frame=0;//帧率
void rgb565_test(void) {
OV2640_RGB565_Mode(); //RGB565 模式
My_DCMI_Init(); //DCMI 配置
DCMI_DMA_Init((u32)&LCD->LCD_RAM,1,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA 配置
// OV2640_ImageWin_Set(u16 offx,u16 offy,u16 width,u16 height);
OV2640_OutSize_Set(240,300); //480*600
DCMI_Start(); //启动传输
while(1) {
delay_ms(10);
}
}
//DCMI 中断服务函数
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);//u16 x,u16 y,u16 width,u16 hight,u16 Color
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); //清除中断标志位
}


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online