本篇文章分享关于如何使用STM32单片机对彩色摄像头(OV7725/OV2604)采集的图像数据进行分析处理,最后实现颜色的识别和检测。
目录
一、什么是颜色识别
1、图像采集识别的一些基本概念
1. 像素(Pixel)
2. 分辨率(Resolution)
3. 帧率(Frame Rate)
4. 颜色深度(Color Depth)
5. 图像处理(Image Processing)
6. 图像采集设备
7. 亮度(Luminance)与色度(Chrominance)
8. 图像编码与压缩(Image Encoding and Compression)
9. 图像识别(Image Recognition)
10. 图像采集与处理中的延迟(Latency)
二、OV7725简介
1. 基本特性
2. 主要特点
3. 输出接口
三、单片机如何驱动OV7725
1、GPIO驱动
2、GPIO 驱动的局限性:
3、DCMI驱动
DCMI 驱动的优势:
4、 GPIO 驱动与 DCMI 驱动的对比
四、颜色识别的步骤和原理
1、RGB阈值法
1. RGB阈值法原理
2. 硬件连接和配置
3. 捕获图像数据
4.RGB 阈值法的实现步骤
1. 处理图像数据
2.阈值法实现
2、HSV空间转换
1. HSV空间简介
2. RGB 到 HSV 转换公式
3、 HSV颜色识别的实现步骤
步骤 1:图像遍历
步骤 2:RGB 到 HSV 的转换
步骤 3:定义颜色范围
步骤 4:进行颜色判断
步骤 5:标记颜色
3、颜色滤波器
1. 颜色滤波器的基本原理
1.1 颜色空间选择
1.2 颜色范围定义
1.3 像素的颜色检测
1.4 过滤结果
2. 颜色滤波器的工作流程
3. HSV 颜色空间中的颜色滤波器实现
3.1 定义颜色范围
3.2 颜色检测
3.3 像素替换
五、代码工程开源
一、什么是颜色识别
颜色识别(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 驱动在摄像头驱动中的作用包括:
-
图像数据传输:
- 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 传输到内存。
// 配置 DCMI 和 DMA
void DCMI_DMA_Init(void) {
// 启动 DCMI 和 DMA 配置,配置 DCMI 时钟和像素格式
// 初始化 DMA(例如:使用 DMA2)
// 配置 DMA 传输到内存
// 启动 DCMI 捕获图像
}
4.RGB 阈值法的实现步骤
例如,如果你想识别红色,可以设置:
- **红色通道(R)**的阈值范围:[200, 255]
- **绿色通道(G)**的阈值范围:[0, 100]
- **蓝色通道(B)**的阈值范围:[0, 100]
通过比较图像中每个像素的 RGB 值与这些阈值进行匹配,就能判断该像素是否属于目标颜色。
1. 处理图像数据
// 假设已捕获并存储了图像数据到内存(例如: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) {
// 如果符合红色范围,执行某些操作
// 例如,在此位置标记或执行动作
}
}
}
}
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++) {
// 获取当前像素的 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 蓝色
}
}
}
}
2、HSV空间转换
使用 HSV(色相、饱和度、明度)空间 来进行颜色识别是图像处理中一种常见且有效的方法。与 RGB(红绿蓝)空间不同,HSV 将颜色分解为三部分:色相(H)、饱和度(S)和明度(V),这使得它更接近于人类的感知方式,也更便于从图像中提取特定颜色。尤其是对于一些光照变化较大的情况,HSV 空间的颜色分离表现更为优秀。
RGB和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 的每个分量从 0~255 的范围映射到 0~1:
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;
// 将 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; // 标记为红色
}
}
}
}
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
颜色处理的关键代码:
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或Brightness或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); //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); //清除中断标志位
}
创作不易,领取的时候可别忘了点赞哈!愿我们一起进步,逐梦不止!
通过百度网盘分享的文件:STM32F407+OV2640摄像头追踪特定颜色物体的小Demo.zip
链接:https://pan.baidu.com/s/1cX6XFCR_lY5W80nNneCSUg?pwd=zxf1
提取码:zxf1
--来自百度网盘超级会员V5的分享通过百度网盘分享的文件:形状识别+颜色识别-STM32103.zip
链接:https://pan.baidu.com/s/1qiLDR1usoyeKuycI03DNzg?pwd=zxf1
提取码:zxf1
--来自百度网盘超级会员V5的分享