跳到主要内容STM32 单片机驱动 OV7725/OV2640 摄像头颜色识别检测 | 极客日志CAI算法
STM32 单片机驱动 OV7725/OV2640 摄像头颜色识别检测
介绍基于 STM32 单片机配合 OV7725/OV2640 摄像头模块实现颜色识别的技术方案。内容涵盖图像采集基本概念、传感器特性、GPIO 与 DCMI 驱动方式对比,以及 RGB 阈值法和 HSV 空间转换两种核心识别算法原理与代码实现。通过定义颜色范围并进行像素遍历判断,最终在 LCD 上标记目标颜色区域。
人间过客43 浏览 STM32 单片机驱动 OV7725/OV2640 摄像头颜色识别检测
本文介绍如何使用 STM32 单片机对彩色摄像头(OV7725/OV2640)采集的图像数据进行分析处理,实现颜色的识别和检测。

一、什么是颜色识别
颜色识别(Color Recognition)是一种通过分析图像中像素的颜色信息来识别或区分特定颜色的技术。它通常用于图像处理和计算机视觉领域,通过将图像的每个像素或区域的颜色信息与预设的颜色标准进行比较,从而判断该区域属于何种颜色。
1、图像采集识别的一些基本概念
1. 像素(Pixel)
- 定义:像素是图像中最小的单元,它代表了图像的基本组成部分。每个像素包含图像的颜色信息,通常是 RGB(红、绿、蓝)或灰度值(在黑白图像中)。
- 说明:图像是由多个像素按照一定的规律(如矩阵)排列而成的。每个像素的值通常表示颜色的强度,或者在黑白图像中表示亮度。图像的分辨率通常以像素的宽度和高度表示,例如 1920x1080 的图像,意味着图像有 1920 个水平像素和 1080 个垂直像素。

2. 分辨率(Resolution)
- 定义:分辨率通常表示图像的细节水平,通常是指图像中水平方向和垂直方向上的像素数。分辨率越高,图像包含的细节和信息就越多。
- 说明:分辨率通常以宽度和高度的像素数表示。例如,"高清"(HD)分辨率一般是 1280x720,而'全高清'(FHD)分辨率是 1920x1080。高分辨率的图像具有更多的像素,能提供更清晰的细节。

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

3. 帧率(Frame Rate)
- 定义:帧率是指每秒钟显示的图像帧数,通常以'FPS'(Frames Per Second,帧每秒)来表示。帧率是视频采集和图像处理中的一个重要参数,影响图像和视频的流畅性。
- 说明:在动态视频处理中,帧率越高,视频画面显示的流畅度就越好。常见的视频帧率有:
- 24 FPS:电影制作的标准帧率。
- 30 FPS:常见于电视广播和网络视频。
60 FPS:常用于游戏视频或高速动作场景中,能提供更加流畅的视觉效果。4. 颜色深度(Color Depth)
- 定义:颜色深度是指每个像素所能表示的颜色的位数,通常以'位数'(bits)表示。颜色深度越大,每个像素能够显示的颜色数量就越多。
- 说明:
- 8 位颜色:每个像素可以表示 256 种颜色(常见于灰度图像)。
- 24 位颜色(真彩色):每个像素由红、绿、蓝三种颜色的 8 位组成,每种颜色有 256 个不同的强度,表示约 1677 万种颜色(即 256 × 256 × 256)。
- 32 位颜色:通常是 24 位颜色加上额外的 8 位用于透明度(alpha 通道)
5. 图像处理(Image Processing)
- 定义:图像处理是对图像进行算法处理的过程,用于增强、分析、分割或转换图像数据。这些技术通常应用于图像采集后,通过计算机算法分析图像的内容或提取有用信息。
- 说明:图像处理包括但不限于以下内容:
- 图像增强:如去噪、对比度调整、锐化等。
- 边缘检测:检测图像中的边缘,常见算法包括 Sobel、Canny 等。
- 图像分割:将图像分割成多个区域,以便进一步处理和分析
6. 图像采集设备
- 定义:图像采集设备用于捕捉图像或视频数据。常见的设备包括相机、摄像头、扫描仪等。
- 说明:
- 相机(Camera):静态图像采集设备,拍摄单张图片。
- 摄像头(Camera Module/Video Camera):动态图像采集设备,通常用于视频录制,获取连续帧图像数据。
- 深度相机(Depth Camera):能获取图像的深度信息,通常用于 3D 重建、物体识别和运动跟踪。
7. 亮度(Luminance)与色度(Chrominance)
- 亮度(Luminance):亮度表示图像的明暗程度。在彩色图像中,亮度通常由灰度值或某种加权的红、绿、蓝分量计算得出。亮度越高,图像看起来越亮。
- 色度(Chrominance):色度描述图像的颜色信息,包含色调(H)和饱和度(S)。在某些图像格式中,亮度和色度被分开存储,这有助于更高效的压缩和处理。
8. 图像编码与压缩(Image Encoding and Compression)
- 定义:图像压缩是减少图像文件大小的过程,通常用于存储和传输图像。压缩算法通过去除冗余信息,减少图像数据量。
- 说明:
- 无损压缩(Lossless Compression):压缩后可以恢复原始图像(如 PNG、GIF 格式)。
- 有损压缩(Lossy Compression):压缩后会丢失部分图像数据,但能大幅减小文件大小(如 JPEG 格式)。
9. 图像识别(Image Recognition)
- 定义:图像识别是利用计算机视觉技术从图像中提取并识别出特定目标或特征的过程。它通常包括分类、目标检测、特征提取等步骤。
- 说明:图像识别技术广泛应用于人脸识别、物体检测、文本识别(OCR)等领域。常见的技术包括:
- 卷积神经网络(CNN):一种深度学习模型,广泛应用于图像分类和目标识别。
- 特征点匹配:通过计算图像的关键特征点,识别图像内容。
10. 图像采集与处理中的延迟(Latency)
- 定义:延迟是指图像采集到图像处理结果输出之间的时间差。延迟通常是衡量图像采集和处理系统性能的关键指标之一。
- 说明:延迟在实时应用中非常重要,例如自动驾驶、视频监控、实时视频流处理等。高帧率和低延迟是保证实时性和系统响应能力的关键因素。
二、OV7725 简介
OV7725 是一款 VGA(640x480)分辨率的 CMOS 图像传感器,支持彩色图像采集。
1. 基本特性
- 分辨率:OV7725 支持 VGA 分辨率(640x480 像素),能够提供清晰的图像,适合低分辨率的视觉应用,如视频监控、物体检测等。
- 图像传感器类型:该传感器使用 CMOS(互补金属氧化物半导体)技术,CMOS 图像传感器通常比 CCD 更低功耗,成本也较低。
- 像素尺寸:OV7725 采用 3.6 µm 像素尺寸,可以在低光照条件下提供较好的图像质量。
- 图像格式:OV7725 支持 YUV422 和 RGB565 等常见的输出格式,能够方便地与不同的显示设备或图像处理系统连接。
2. 主要特点
- 自动曝光控制(AEC):该功能允许 OV7725 根据环境光照变化自动调整曝光时间,保证图像的清晰度和细节。
- 自动白平衡(AWB):OV7725 可以自动调节图像的色彩,以适应不同的光源条件,从而提供自然的颜色表现。
- 自动亮度控制(AGC):自动调整图像的亮度,确保图像在不同光照条件下的可见性。
- 动态范围控制(DRC):提高图像的细节表现,尤其是在高对比度的场景中,避免过度曝光或过暗的区域。
- 噪声抑制:OV7725 具备一定的图像噪声抑制能力,在低光环境下能有效减少图像噪点,提供更清晰的图像。
3. 输出接口
OV7725 支持两种常见的接口标准,便于与不同平台或设备集成:
- Parallel 接口:OV7725 提供 8 位并行输出,通过数据总线直接传输图像数据。并行接口通常在速度上有较好的表现,适用于对带宽要求较高的应用。
- I2C 接口:用于配置传感器的控制和调节参数。I2C 是一种标准的串行通信协议,通过少量引脚进行数据交互,简化了与微控制器或其他外部设备的连接。
三、单片机如何驱动 OV7725
关于单片机对摄像头的代码驱动,这里不做详细讲解。完整的工程可以到文章末尾的资料连接进行下载。
在 OV7725 摄像头模块的驱动中,GPIO 驱动和 DCMI 驱动分别代表了两种不同的硬件接口方式,它们在摄像头与微控制器(MCU)之间的数据传输和信号控制中扮演不同的角色。
1、GPIO 驱动
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 引脚生成并传输的。
2、GPIO 驱动的局限性:
**性能限制:**使用 GPIO 接口进行像素数据的传输和控制信号的处理,由于 GPIO 的工作频率相对较低,数据传输速率受限,因此对于高分辨率或高帧率的摄像头应用,GPIO 驱动可能不够高效。**硬件资源消耗:**每个 GPIO 引脚需要消耗 MCU 的资源,因此如果需要大量的 GPIO 引脚(例如 8 位并行数据线、同步信号等),可能会影响到 MCU 的其他功能和资源。
3、DCMI 驱动
DCMI 是指 Digital Camera Memory Interface,即数字摄像头内存接口。它是 STM32 等微控制器中常见的一种硬件接口,专门用于连接图像传感器(如 OV7725)和处理器(或其他图像处理单元),用于处理图像数据的传输。
- 图像数据传输:
- DCMI 提供了直接与摄像头模块(如 OV7725)进行图像数据传输的能力。它通过高效的硬件接口,能够在不占用 CPU 资源的情况下,快速地接收摄像头传输的图像数据。DCMI 通常用于并行数据传输方式,可以接收来自摄像头的像素数据,并通过 DMA(直接内存访问)将数据传输到内存中。
- 时序控制:
- DCMI 还支持同步信号(VSYNC、HSYNC)和像素时钟(PCLK)的处理。它能自动处理这些信号,以确保图像数据的正确接收。
- 支持多种摄像头格式:
- DCMI 支持多种图像格式(如 RGB、YCbCr 等),并可以配置为不同的数据宽度,以便与摄像头传输的数据格式匹配。
- DMA 加速:
- DCMI 通常与 DMA 一起使用,这样可以将数据直接从 DCMI 传输到内存,减少 CPU 的负担,提高图像处理的效率。
DCMI 驱动的优势:
- **高效的图像数据传输:**DCMI 专为高速图像数据传输设计,能够处理更高分辨率和帧率的图像数据,适用于复杂和要求高性能的摄像头应用。
- **自动化:**DCMI 可以自动管理时序和数据流,减少了软件层面的干预,提升了稳定性和效率。
- **较低的 CPU 占用:**使用 DMA 进行数据传输时,CPU 占用较低,允许系统进行其他任务。
4、GPIO 驱动与 DCMI 驱动的对比
| 特性 | GPIO 驱动 | DCMI 驱动 |
|---|
| 功能 | 主要用于简单的信号控制和低速数据传输 | 高效的图像数据传输接口,支持 DMA |
| 接口方式 | 通过 GPIO 引脚传输数据和信号 | 专用的数字摄像头接口,支持并行和串行数据 |
| 数据传输速率 | 受限,适用于低分辨率和低帧率的图像 | 高速,适用于高分辨率、高帧率图像 |
| CPU 占用 | 较高,因需要通过软件控制信号和数据流 | 较低,通过 DMA 自动传输数据 |
| 应用场景 | 简单摄像头模块,低速图像采集 | 高分辨率、高帧率的视频采集 |
GPIO 驱动通常用于简单的摄像头控制,适合低分辨率、低帧率的图像采集场景,可以通过基本的控制信号(复位、同步信号)和数据传输来与摄像头进行交互。DCMI 驱动则是一种高效、专业的接口,适用于需要高带宽、高速度数据传输的应用,能自动处理复杂的同步信号和数据流,并结合 DMA 提供高效的图像数据采集。
四、颜色识别的步骤和原理
接下来将详细讲解颜色识别的算法和代码工程。下面就是非常重要的一些概念和原理了!
1、RGB 阈值法
1. RGB 阈值法原理
RGB 阈值法是颜色识别中常见的一种方法。其基本原理是通过设定每个颜色通道(红色 R、绿色 G、蓝色 B)的数值范围来识别图像中的颜色。每个像素的颜色信息可以通过三个通道的数值(通常是 0 到 255 之间的整数)表示。在 RGB 模型中,颜色的表达方式为 (R, G, B),其中每个分量的值控制着对应的颜色成分的强度。
对于颜色识别,我们通过预设的阈值范围来判断图像中的颜色。例如,我们可能会设定如下的阈值来识别红色:
- 红色分量(R)大于一定的阈值;
- 绿色分量(G)小于一定的阈值;
- 蓝色分量(B)小于一定的阈值。
基于这个逻辑,RGB 阈值法能有效地检测图像中符合条件的像素,进而实现颜色的识别。
2. 硬件连接和配置
首先,你需要确保 STM32 和 OV7725 摄像头正确连接,并配置好摄像头接口(通常是通过 DCMI 接口或 GPIO):
- OV7725 与 STM32 连接: OV7725 通常通过并行数据接口与 STM32 单片机连接,使用 DCMI(Digital Camera Interface)进行高速图像数据传输。你需要配置好 DCMI 时钟、数据线、同步信号(VSYNC、HSYNC)等。
- 摄像头初始化: 在 STM32 中配置 OV7725 的寄存器,确保它以正确的分辨率(如 640x480)和格式(如 RGB565)工作。
3. 捕获图像数据
使用 STM32 配置 DCMI 驱动程序来捕获图像数据。通过 DCMI 接口,图像数据会通过 DMA 传输到内存。
void DCMI_DMA_Init(void) {
}
4. RGB 阈值法的实现步骤
- **红色通道(R)**的阈值范围:[200, 255]
- **绿色通道(G)**的阈值范围:[0, 100]
- **蓝色通道(B)**的阈值范围:[0, 100]
通过比较图像中每个像素的 RGB 值与这些阈值进行匹配,就能判断该像素是否属于目标颜色。
1. 处理图像数据
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) {
}
}
}
}
2. 阈值法实现
在实际应用中,你可以根据需要调整 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++) {
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) {
frame_buffer[y * width + x] = 0xF800;
}
else if (R < 100 && G > 200 && B < 100) {
frame_buffer[y * width + x] = 0x07E0;
}
else if (R < 100 && G < 100 && B > 200) {
frame_buffer[y * width + x] = 0x001F;
}
}
}
}
2、HSV 空间转换
使用 HSV(色相、饱和度、明度)空间来进行颜色识别是图像处理中一种常见且有效的方法。与 RGB(红绿蓝)空间不同,HSV 将颜色分解为三部分:色相(H)、饱和度(S)和明度(V),这使得它更接近于人类的感知方式,也更便于从图像中提取特定颜色。尤其是对于一些光照变化较大的情况,HSV 空间的颜色分离表现更为优秀。
1. HSV 空间简介
色相(H):代表颜色的种类,取值范围通常为 0 到 360°(即颜色的角度),每个角度对应一种颜色。0° 通常为红色,120° 为绿色,240° 为蓝色,其他颜色则在这三个之间变化。饱和度(S):表示颜色的纯度或强度,取值范围为 0 到 100%。饱和度越高,颜色越纯,越接近原始颜色;饱和度为 0 时,颜色呈灰白色。明度(V):表示颜色的亮度,取值范围为 0 到 100%。明度越高,颜色越明亮;明度为 0 时颜色为黑色。
2. RGB 到 HSV 转换公式
要在 HSV 空间中进行颜色识别,通常需要将 RGB 颜色空间的像素值转换为 HSV 值。RGB 值的范围通常是 0 到 255,而 HSV 的范围是:**色相 H:0 到 360°**饱和度 S 和明度 V:0 到 100%
下面是 RGB 到 HSV 的转换公式:
1、标准化 RGB 值:将 RGB 的每个分量从 0255 的范围映射到 01:
2、计算最大值和最小值:
计算色差:
3、计算色相 H:
4、计算饱和度 S
5、计算明度 V
3. HSV 颜色识别的实现步骤
基于 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;
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 空间更适合用于颜色滤波,因为它与人眼的颜色感知更接近。
1. 颜色滤波器的基本原理
1.1 颜色空间选择
在图像处理中,最常见的颜色空间是 RGB(红、绿、蓝)和 HSV(色相、饱和度、明度)。在 RGB 空间中,颜色是由三个通道的值(红、绿、蓝)组成的。HSV 空间则通过 色相 (H)、饱和度 (S) 和 明度 (V) 来描述颜色,更符合人类视觉感知。因此,颜色滤波一般使用 HSV 空间,因为它能够更准确地分离色彩(色相)和亮度(明度、饱和度)。
1.2 颜色范围定义
颜色滤波器的关键是设定一个特定的颜色范围,该范围包含了你要识别的颜色。例如,如果你想检测图像中的 红色,你需要定义红色的 色相 范围(例如 H: 0° 到 10°,或 H: 350° 到 360°),并根据 饱和度 (S) 和 明度 (V) 定义允许的阈值。
1.3 像素的颜色检测
对于图像中的每个像素,根据其 HSV 值(或者 RGB 值)来判断它是否在定义的颜色范围内。如果在范围内,则保留该像素;如果不在范围内,则将其设为背景色或忽略。
1.4 过滤结果
经过颜色滤波后,图像的背景通常会被去除,只留下符合条件的颜色区域。这通常用于图像分割、目标检测、物体跟踪等应用。
2. 颜色滤波器的工作流程
一般来说,颜色滤波器的工作流程可以分为以下几个步骤:
- 读取图像:加载图像数据到内存。
- 转换颜色空间:将图像从 RGB 转换为 HSV(如果需要)。
- 设定颜色范围:根据应用的需求设定一个颜色范围(H、S、V 范围)。
- 像素检测:遍历图像中的每个像素,检测其颜色是否在设定的范围内。
- 输出结果:将符合条件的像素保留下来,不符合条件的像素设为其他颜色或透明,从而得到一个新的图像。
3. HSV 颜色空间中的颜色滤波器实现
HSV 颜色空间中的颜色滤波器通常只需要定义 色相 (H) 范围,结合 饱和度 (S) 和 明度 (V) 范围来实现更加精确的颜色提取。以下是一个基于 HSV 颜色空间的颜色滤波器实现过程:
3.1 定义颜色范围
首先,根据应用需求设定感兴趣的颜色范围。假设我们要提取 红色,可以设置如下的范围:
- 红色的色相 (H) 通常在 0° 到 10° 或 350° 到 360° 之间。
- 饱和度 (S) 和 明度 (V) 根据实际情况设定,通常会在 50 到 100% 之间。
3.2 颜色检测
对于每个像素,通过计算其 HSV 值(如果图像在 RGB 空间中,则先转换到 HSV 空间):
- 检查该像素的 色相 (H) 是否在指定的范围内。
- 检查该像素的 饱和度 (S) 和 明度 (V) 是否符合要求。
3.3 像素替换
如果一个像素的 HSV 值在指定的范围内,那么它就是我们需要保留的颜色;否则,可以将其设为背景色或其他颜色。
五、代码工程开源
这里介绍 STM32 单片机驱动摄像头对颜色识别的关键代码,整体工程看文章末尾资料连接。注意:这里使用 STM32+OV2640
#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=(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)));
}
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;
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;
}
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 "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);
#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
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online