第45章 DCMI—OV2640摄像头—零死角玩转STM32-F429系列

 

第45章     DCMI—OV2640摄像头

全套200集视频教程和1000PDF教程请到秉火论坛下载:www.firebbs.cn

野火视频教程优酷观看网址:http://i.youku.com/firege

 

 

 

本章参考资料:《STM32F4xx参考手册》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。

关于开发板配套的OV2640摄像头参数可查阅《ov2640datasheet》配套资料获知。

STM32F4芯片具有浮点运算单元,适合对图像信息使用DSP进行基本的图像处理,其处理速度比传统的816位机快得多,而且它还具有与摄像头通讯的专用DCMI接口,所以使用它驱动摄像头采集图像信息并进行基本的加工处理非常适合。本章讲解如何使用STM32驱动OV2640型号的摄像头。

45.1 摄像头简介

在各类信息中,图像含有最丰富的信息,作为机器视觉领域的核心部件,摄像头被广泛地应用在安防、探险以及车牌检测等场合。摄像头按输出信号的类型来看可以分为数字摄像头和模拟摄像头,按照摄像头图像传感器材料构成来看可以分为CCDCMOS。现在智能手机的摄像头绝大部分都是CMOS类型的数字摄像头。

45.1.1 数字摄像头跟模拟摄像头区别

    输出信号类型

数字摄像头输出信号为数字信号,模拟摄像头输出信号为标准的模拟信号。

    接口类型

数字摄像头有USB接口(比如常见的PC端免驱摄像头)IEE1394火线接口(由苹果公司领导的开发联盟开发的一种高速度传送接口,数据传输率高达800Mbps)、千兆网接口(网络摄像头)。模拟摄像头多采用AV视频端子(信号线+地线)或S-VIDEO(即莲花头--SUPER VIDEO,是一种五芯的接口,由两路视频亮度信号、两路视频色度信号和一路公共屏蔽地线共五条芯线组成)。

    分辨率

模拟摄像头的感光器件,其像素指标一般维持在752(H)*582(V)左右的水平,像素数一般情况下维持在41万左右。现在的数字摄像头分辨率一般从数十万到数千万。但这并不能说明数字摄像头的成像分辨率就比模拟摄像头的高,原因在于模拟摄像头输出的是模拟视频信号,一般直接输入至电视或监视器,其感光器件的分辨率与电视信号的扫描数呈一定的换算关系,图像的显示介质已经确定,因此模拟摄像头的感光器件分辨率不是不能做高,而是依据于实际情况没必要做这么高。

45.1.2 CCD与CMOS的区别

摄像头的图像传感器CCDCMOS传感器主要区别如下:

    成像材料

CCDCMOS的名称跟它们成像使用的材料有关,CCD"电荷耦合器件"(Charge Coupled Device)的简称,而CMOS"互补金属氧化物半导体"(Complementary Metal Oxide Semiconductor)的简称。

    功耗

由于CCD的像素由MOS电容构成,读取电荷信号时需使用电压相当大(至少12V)的二相或三相或四相时序脉冲信号,才能有效地传输电荷。因此CCD的取像系统除了要有多个电源外,其外设电路也会消耗相当大的功率。有的CCD取像系统需消耗2~5W的功率。而CMOS光电传感器件只需使用一个单电源5V3V,耗电量非常小,仅为CCD1/8~1/10,有的CMOS取像系统只消耗20~50mW的功率。

    成像质量

CCD传感器件制作技术起步早,技术成熟,采用PN结或二氧化硅(sio2)隔离层隔离噪声,所以噪声低,成像质量好。与CCD相比,CMOS的主要缺点是噪声高及灵敏度低,不过现在随着CMOS电路消噪技术的不断发展,为生产高密度优质的CMOS传感器件提供了良好的条件,现在的CMOS传感器已经占领了大部分的市场,主流的单反相机、智能手机都已普遍采用CMOS传感器。

45.2 OV2640摄像头

本章主要讲解实验板配套的摄像头,它的实物见图 451,该摄像头主要由镜头、图像传感器、板载电路及下方的信号引脚组成。

451 实验板配套的OV2640摄像头

镜头部件包含一个镜头座和一个可旋转调节距离的凸透镜,通过旋转可以调节焦距,正常使用时,镜头座覆盖在电路板上遮光,光线只能经过镜头传输到正中央的图像传感器,它采集光线信号,然后把采集得的数据通过下方的信号引脚输出数据到外部器件。

45.2.1 OV2640传感器简介

图像传感器是摄像头的核心部件,上述摄像头中的图像传感器是一款型号为OV2640CMOS类型数字图像传感器。该传感器支持输出最大为200万像素的图像 (1600x1200分辨率),支持使用VGA时序输出图像数据,输出图像的数据格式支持YUV(422/420)YCbCr422RGB565以及JPEG格式,若直接输出JPEG格式的图像时可大大减少数据量,方便网络传输。它还可以对采集得的图像进行补偿,支持伽玛曲线、白平衡、饱和度、色度等基础处理。根据不同的分辨率配置,传感器输出图像数据的帧率从15-60帧可调,工作时功率在125mW-140mW之间。

 

45.2.2 OV2640引脚及功能框图

OV2640传感器采用BGA封装,它的前端是采光窗口,引脚都在背面引出,引脚的分布见图 452

452 OV2640传感器引脚分布图

图中的非彩色部分是电源相关的引脚,彩色部分是主要的信号引脚,介绍如下表 451

451 OV7670管脚

管脚名称

管脚类型

管脚描述

SIO_C

输入

SCCB总线的时钟线,可类比I2CSCL

SIO_D

I/O

SCCB总线的数据线,可类比I2CSDA

RESETB

输入

系统复位管脚,低电平有效

PWDN

输入

掉电/省电模式,高电平有效

HREF

输出

行同步信号

VSYNC

输出

帧同步信号

PCLK

输出

像素同步时钟输出信号

XCLK

输入

外部时钟输入端口,可接外部晶振

Y0Y9

输出

像素数据输出端口

下面我们配合图 453中的OV2640功能框图讲解这些信号引脚。

453 OV2640功能框图

(1)    控制寄存器

标号处的是OV2640的控制寄存器,它根据这些寄存器配置的参数来运行,而这些参数是由外部控制器通过SIO_CSIO_D引脚写入的,SIO_CSIO_D使用的通讯协议跟I2C十分类似,在STM32中我们完全可以直接用I2C硬件外设来控制。

(2)    通信、控制信号及时钟

标号‚处包含了OV2640的通信、控制信号及外部时钟,其中PCLKHREFVSYNC分别是像素同步时钟、行同步信号以及帧同步信号,这与液晶屏控制中的信号是很类似的。RESETB引脚为低电平时,用于复位整个传感器芯片,PWDN用于控制芯片进入低功耗模式。注意最后的一个XCLK引脚,它跟PCLK是完全不同的,XCLK是用于驱动整个传感器芯片的时钟信号,是外部输入到OV2640的信号;而PCLKOV2640输出数据时的同步信号,它是由OV2640输出的信号。XCLK可以外接晶振或由外部控制器提供,若要类比XCLK之于OV2640就相当于HSE时钟输入引脚与STM32芯片的关系,PCLK引脚可类比STM32I2C外设的SCL引脚。

(3)    感光矩阵

标号ƒ处的是感光矩阵,光信号在这里转化成电信号,经过各种处理,这些信号存储成由一个个像素点表示的数字图像。

(4)    数据输出信号

标号„处包含了DSP处理单元,它会根据控制寄存器的配置做一些基本的图像处理运算。这部分还包含了图像格式转换单元及压缩单元,转换出的数据最终通过Y0-Y9引脚输出,一般来说我们使用8根据数据线来传输,这时仅使用Y2-Y9引脚,OV2640与外部器件的连接方式见图 454

454 8位数据线接法

45.2.3 SCCB时序

外部控制器对OV2640寄存器的配置参数是通过SCCB总线传输过去的,而SCCB总线跟I2C十分类似,所以在STM32驱动中我们直接使用片上I2C外设与它通讯。SCCB与标准的I2C协议的区别是它每次传输只能写入或读取一个字节的数据,而I2C协议是支持突发读写的,即在一次传输中可以写入多个字节的数据(EEPROM中的页写入时序即突发写)。关于SCCB协议的完整内容可查看配套资料里的《SCCB协议》文档,下面我们简单介绍下。

SCCB的起始、停止信号及数据有效性

SCCB的起始信号、停止信号及数据有效性与I2C完全一样,见图 455及图 456

    起始信号:在SIO_C为高电平时,SIO_D出现一个下降沿,则SCCB开始传输。

    停止信号:在SIO_C为高电平时,SIO_D出现一个上升沿,则SCCB停止传输。

    数据有效性:除了开始和停止状态,在数据传输过程中,当SIO_C为高电平时,必须保证SIO_D上的数据稳定,也就是说,SIO_D上的电平变换只能发生在SIO_C为低电平的时候,SIO_D的信号在SIO_C为高电平时被采集。

455 SCCB停止信号

456 SCCB的数据有效性

SCCB数据读写过程

SCCB协议中定义的读写操作与I2C也是一样的,只是换了一种说法。它定义了两种写操作,即三步写操作和两步写操作。三步写操作可向从设备的一个目的寄存器中写入数据,见图 457。在三步写操作中,第一阶段发送从设备的ID地址+W标志(等于I2C的设备地址:7位设备地址+读写方向标志),第二阶段发送从设备目标寄存器的8位地址,第三阶段发送要写入寄存器的8位数据。图中的"X"数据位可写入10,对通讯无影响。

457 SCCB的三步写操作

而两步写操作没有第三阶段,即只向从器件传输了设备ID+W标志和目的寄存器的地址,见图 458。两步写操作是用来配合后面的读寄存器数据操作的,它与读操作一起使用,实现I2C的复合过程。

458 SCCB的两步写操作

两步读操作,它用于读取从设备目的寄存器中的数据,见图 459。在第一阶段中发送从设备的设备ID+R标志(设备地址+读方向标志)和自由位,在第二阶段中读取寄存器中的8位数据和写NA (非应答信号)。由于两步读操作没有确定目的寄存器的地址,所以在读操作前,必需有一个两步写操作,以提供读操作中的寄存器地址。

459 SCCB的两步读操作

可以看到,以上介绍的SCCB特性都与I2C无区别,而I2CSCCB还多出了突发读写的功能,所以SCCB可以看作是I2C的子集,我们完全可以使用STM32I2C外设来与OV2640进行SCCB通讯。

45.2.4 OV2640的寄存器

控制OV2640涉及到它很多的寄存器,可直接查询《ov2640datasheet》了解,通过这些寄存器的配置,可以控制它输出图像的分辨率大小、图像格式及图像方向等。要注意的是OV2640有两组寄存器,这两组寄存器有部分地址重合,通过设置地址为0xFFRA_DLMT寄存器可以切换寄存器组,当RA_DLMT寄存器为0时,通过SCCB发送的寄存器地址在DSP相关的寄存器组寻址,见图 4510RA_DLMT寄存器为1时,在Sensor相关的寄存器组寻址,图 4510

4510 0xFF=0时的DSP相关寄存器说明(部分)

4511 0xFF=1时的Sensor相关寄存器说明(部分)

官方还提供了一个《OV2640_Camera_app》的文档,它针对不同的配置需求,提供了配置范例,见图 4512。其中write_SCCB是一个利用SCCB向寄存器写入数据的函数,第一个参数为要写入的寄存器的地址,第二个参数为要写入的内容。

4512 调节帧率的寄存器配置范例

45.2.5 像素数据输出时序

主控器控制OV2640时采用SCCB协议读写其寄存器,而它输出图像时则使用VGA时序(还可用SVGAUXGA,这些时序都差不多),这跟控制液晶屏输入图像时很类似。OV2640输出图像时,一帧帧地输出,在帧内的数据一般从左到右,从上到下,一个像素一个像素地输出(也可通过寄存器修改方向

<think>好的,我现在需要解决用户的问题,即如何用C语言将320x240的摄像头数据灰度化,并保存到128x128的gray128数组中。首先,我需要理解用户提供的硬件相关部分:使用(uint32_t)&DCMI->DR读取数据,以及通过volatile uint8_t *ra访问该寄存器的字节。 首先,DCMI(数字摄像头接口)的DR寄存器通常存储摄像头传输的像素数据。假设这里每个DR寄存器读取的是一个像素的原始数据,可能是RGB格式,比如RGB565或RGB888。用户需要将其灰度化,所以需要将RGB转换为灰度值。 接下来,原始数据分辨率是320x240,而目标数组是128x128,所以可能需要进行下采样。下采样的方法有多种,比如取中心区域或者均匀采样。这里可能需要计算如何从320x240的图像中提取128x128的区域,或者进行缩放。 然后,灰度化的公式通常是Gray = 0.299*R + 0.587*G + 0.114*B。需要根据摄像头输出的像素格式来解析R、G、B分量。例如,如果是RGB565(16位),则高5位是R,中间6位是G,低5位是B。如果是32位的,可能是ARGB8888,需要提取R、G、B三个8位分量。 用户提到用(uint32_t)&DCMI->DR读取,可能每个DR寄存器是32位,存储两个16位的RGB565像素(因为32位可以存两个16位数据)。或者可能是每个32位存储一个像素的ARGB8888数据。需要明确像素格式,但用户没有说明,可能需要假设常见的格式,比如RGB565(16位)或者RGB888(24位,但可能用32位寄存器存储)。 假设这里DCMI->DR是32位寄存器,每次读取包含两个16位的RGB565像素(例如,摄像头是16位模式,每个像素16位,32位寄存器可以存两个像素)。或者可能每个32位是一个像素的ARGB8888,其中A是透明度,可能为0。 另外,用户用volatile uint8_t *ra = (uint8_t *)&DCMI->DR; 来访问字节,可能意味着需要按字节读取寄存器的内容。例如,32位的DR寄存器可以按4个字节访问,每个字节对应不同的颜色分量。 现在,步骤可能是: 1. 读取DCMI->DR的32位数据,解析其中的RGB分量。 2. 将每个RGB像素转换为灰度值。 3. 将320x240的图像下采样到128x128,存储到gray128数组。 关于下采样,320x240到128x128的缩放比例约为2.5:1(320/128=2.5,240/128=1.875)。可能需要双线性插值或简单的像素抽取。但用户可能需要简单的方法,比如每隔一定像素取一个,或者取中心区域。例如,从( (320-128)/2, (240-128)/2 )开始的128x128区域,但320-128=192,192/2=96,所以x从96到96+127=223;240-128=112,112/2=56,y从56到56+127=183。这样截取中心区域。 但用户的问题可能更关注灰度转换的过程,下采样可能需要根据实际情况处理。 现在,编写代码的大致步骤: - 定义gray128为static uint8_t gray128[128][128]; - 循环遍历128x128的每个位置(i,j)。 - 对于每个(i,j),计算对应的原始图像中的坐标(x,y),比如x = i * (320/128) 或者更精确的浮点计算,然后取整。 - 读取DCMI->DR中的对应像素数据,解析RGB分量,转换为灰度。 - 存储灰度值到gray128[i][j]。 但需要注意,摄像头数据的读取可能是连续的,可能需要从FIFO或缓冲区中读取所有320x240的像素,然后处理。假设用户已经将320x240的数据读入一个缓冲区,比如uint32_t buffer[320*240/2](如果每个32位存两个16位像素),或者其他结构。但用户的问题中可能直接从DCMI->DR读取,可能需要循环读取每个像素。 另外,DCMI->DR可能是一个数据寄存器,每次读取会获取下一个像素,所以需要循环读取足够的次数来获取320x240的所有像素。 可能的代码结构: // 假设DCMI已经配置好,开始传输数据 static uint8_t gray128[128][128]; // 定义原始图像的分辨率 #define WIDTH 320 #define HEIGHT 240 // 目标分辨率 #define TARGET_WIDTH 128 #define TARGET_HEIGHT 128 // 读取原始图像数据到临时缓冲区(可能需要,因为直接读取DCMI->DR可能会覆盖) // 假设每个DCMI->DR是32位,存储两个16位的RGB565像素(共320*240=76800像素,需要38400个32位读取) uint16_t raw_pixels[WIDTH * HEIGHT]; // 存储所有原始像素,每个16位RGB565 for (int i = 0; i < WIDTH * HEIGHT / 2; i++) { uint32_t dr_value = DCMI->DR; // 读取32位数据 raw_pixels[2*i] = (dr_value >> 16) & 0xFFFF; // 高16位是第一个像素 raw_pixels[2*i + 1] = dr_value & 0xFFFF; // 低16位是第二个像素 } // 或者如果是每个32位存一个像素的ARGB8888,则: // uint32_t raw_pixels[WIDTH * HEIGHT]; // for (int i = 0; i < WIDTH * HEIGHT; i++) { // raw_pixels[i] = DCMI->DR; // } // 然后进行灰度转换和下采样 // 计算下采样的坐标映射 for (int i = 0; i < TARGET_HEIGHT; i++) { for (int j = 0; j < TARGET_WIDTH; j++) { // 计算原始图像中的坐标(这里使用双线性插值或最近邻) float x = (float)j * (WIDTH - 1) / (TARGET_WIDTH - 1); float y = (float)i * (HEIGHT - 1) / (TARGET_HEIGHT - 1); int x0 = (int)x; int y0 = (int)y; int x1 = x0 + 1 < WIDTH ? x0 + 1 : x0; int y1 = y0 + 1 < HEIGHT ? y0 + 1 : y0; // 获取四个邻近像素的灰度值(假设已转换为灰度) // 这里可能需要先将原始像素转换为灰度,存储到一个灰度缓冲区 // 或者直接转换每个原始像素并存储,然后下采样 // 假设先转换所有原始像素到灰度 // 例如,对于RGB565像素: // 原始像素是16位,格式为R5 G6 B5 // 提取R, G, B分量 uint16_t pixel = raw_pixels[y0 * WIDTH + x0]; uint8_t r = (pixel >> 11) & 0x1F; // 5位,0-31 uint8_t g = (pixel >> 5) & 0x3F; // 6位,0-63 uint8_t b = pixel & 0x1F; // 5位,0-31 // 转换为8位分量(扩展) r = (r * 255) / 31; // 扩展到0-255 g = (g * 255) / 63; b = (b * 255) / 31; // 计算灰度 uint8_t gray = 0.299 * r + 0.587 * g + 0.114 * b; // 这里简化为最近邻采样,直接取对应位置的像素 // 更简单的下采样:原始图像中心区域128x128 int src_x = 96 + j; // 320-128=192, 192/2=96,所以x从96到223(96+127) int src_y = 56 + i; // 240-128=112, 112/2=56,y从56到183(56+127) if (src_x >= 0 && src_x < WIDTH && src_y >=0 && src_y < HEIGHT) { uint16_t pixel = raw_pixels[src_y * WIDTH + src_x]; // 转换为灰度 uint8_t r = (pixel >> 11) & 0x1F; uint8_t g = (pixel >> 5) & 0x3F; uint8_t b = pixel & 0x1F; r = (r * 255 + 15) / 31; // 四舍五入 g = (g * 255 + 31) / 63; b = (b * 255 + 15) / 31; gray128[i][j] = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); } else { gray128[i][j] = 0; // 超出范围设为0 } } } 但需要注意,DCMI->DR可能是一个只能读取一次的寄存器,每次读取会获取下一个像素,因此可能需要正确的读取顺序,确保获取所有320x240像素。此外,volatile的使用是为了防止编译器优化,确保每次读取都是从硬件寄存器获取最新数据。 另外,用户可能没有存储原始数据的缓冲区,而是直接处理每个读取的像素,同时进行灰度转换和下采样。这可能更高效,但需要注意下采样的坐标计算。 可能的优化是直接处理每个像素,当读取到对应下采样位置的像素时,进行转换并存储。但需要跟踪当前读取的像素坐标,判断是否属于目标区域。 例如: static uint8_t gray128[128][128] = {0}; uint32_t dr_value; volatile uint8_t *ra = (uint8_t *)&DCMI->DR; // 原始图像的坐标 int src_x = 0; int src_y = 0; // 目标区域的起始坐标(中心区域) int start_x = (320 - 128) / 2; int start_y = (240 - 128) / 2; int end_x = start_x + 127; int end_y = start_y + 127; for (src_y = 0; src_y < 240; src_y++) { for (src_x = 0; src_x < 320; src_x++) { // 读取当前像素的32位数据(假设每个像素占32位,或者需要处理两个像素) // 这里可能需要根据实际情况调整,比如每个32位存两个16位像素,所以每读取一次处理两个像素 dr_value = *(uint32_t *)ra; // 等同于DCMI->DR的读取 // 假设每个32位存两个16位RGB565像素,高16位和低16位 uint16_t pixel1 = (dr_value >> 16) & 0xFFFF; uint16_t pixel2 = dr_value & 0xFFFF; // 处理第一个像素(对应src_x) if (src_x < 320) { if (src_y >= start_y && src_y <= end_y && src_x >= start_x && src_x <= end_x) { int target_x = src_x - start_x; int target_y = src_y - start_y; // 转换pixel1为灰度 uint8_t r = (pixel1 >> 11) & 0x1F; uint8_t g = (pixel1 >> 5) & 0x3F; uint8_t b = pixel1 & 0x1F; r = (r * 255 + 15) / 31; // 转换为8位 g = (g * 255 + 31) / 63; b = (b * 255 + 15) / 31; gray128[target_y][target_x] = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); } src_x++; // 注意这里可能需要调整循环变量,因为处理了两个像素 } // 处理第二个像素(src_x+1) if (src_x < 320) { if (src_y >= start_y && src_y <= end_y && src_x >= start_x && src_x <= end_x) { int target_x = src_x - start_x; int target_y = src_y - start_y; // 转换pixel2为灰度 uint8_t r = (pixel2 >> 11) & 0x1F; uint8_t g = (pixel2 >> 5) & 0x3F; uint8_t b = pixel2 & 0x1F; r = (r * 255 + 15) / 31; g = (g * 255 + 31) / 63; b = (b * 255 + 15) / 31; gray128[target_y][target_x] = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); } src_x++; } } } 但这里的循环可能需要更精确的控制,因为每次读取DR会得到两个像素,所以src_x需要每次增加2,或者在循环内处理。 另外,可能DCMI->DR的每个32位数据对应一个像素的ARGB8888格式,即每个字节是A、R、G、B。例如,ra[0]是B,ra[1]是G,ra[2]是R,ra[3]是A(假设小端模式)。这时候解析方式不同: uint32_t dr_value = DCMI->DR; uint8_t b = (dr_value >> 0) & 0xFF; // 小端模式下,第一个字节是最低有效位 uint8_t g = (dr_value >> 8) & 0xFF; uint8_t r = (dr_value >> 16) & 0xFF; uint8_t a = (dr_value >> 24) & 0xFF; 然后灰度计算为0.299*r + 0.587*g + 0.114*b。 需要明确摄像头输出的像素格式,但用户没有说明,所以代码中需要假设一种常见格式,比如RGB565或ARGB8888,并在注释中说明。 综上,最终的代码需要: 1. 读取DCMI->DR的32位数据,解析像素的RGB分量。 2. 将每个像素转换为灰度值。 3. 将320x240的图像下采样到128x128,存储到gray128数组。 可能的代码示例(假设RGB565,每个32位存两个像素): #include <stdint.h> // 假设DCMI寄存器定义 typedef struct { volatile uint32_t DR; // 数据寄存器 } DCMI_TypeDef; #define DCMI ((DCMI_TypeDef *)0x40050000) // 示例地址 static uint8_t gray128[128][128]; void convert_to_gray(void) { const int src_width = 320; const int src_height = 240; const int target_width = 128; const int target_height = 128; const int start_x = (src_width - target_width) / 2; // 96 const int start_y = (src_height - target_height) / 2; // 56 for (int y = 0; y < src_height; y++) { for (int x = 0; x < src_width; x += 2) { // 每次处理两个像素(32位) uint32_t dr_value = DCMI->DR; // 读取32位数据 uint16_t pixel1 = (dr_value >> 16) & 0xFFFF; // 高16位像素 uint16_t pixel2 = dr_value & 0xFFFF; // 低16位像素 // 处理第一个像素(x坐标) if (y >= start_y && y < start_y + target_height && x >= start_x && x < start_x + target_width) { int ty = y - start_y; int tx = x - start_x; if (tx < target_width) { // 提取RGB565分量 uint8_t r = (pixel1 >> 11) & 0x1F; // 5位 uint8_t g = (pixel1 >> 5) & 0x3F; // 6位 uint8_t b = pixel1 & 0x1F; // 5位 // 转换为8位 r = (r * 255 + 15) / 31; // 四舍五入 g = (g * 255 + 31) / 63; b = (b * 255 + 15) / 31; // 灰度计算 gray128[ty][tx] = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); } } // 处理第二个像素(x+1坐标) int x2 = x + 1; if (y >= start_y && y < start_y + target_height && x2 >= start_x && x2 < start_x + target_width) { int ty = y - start_y; int tx = x2 - start_x; if (tx < target_width) { uint8_t r = (pixel2 >> 11) & 0x1F; uint8_t g = (pixel2 >> 5) & 0x3F; uint8_t b = pixel2 & 0x1F; r = (r * 255 + 15) / 31; g = (g * 255 + 31) / 63; b = (b * 255 + 15) / 31; gray128[ty][tx] = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); } } } } } 注意:此代码假设DCMI->DR每次读取返回两个连续的16位RGB565像素,且摄像头输出顺序是按行优先。实际中需要根据摄像头DCMI的配置调整像素解析方式和读取顺序。此外,下采样采用了中心区域截取的方法,可能需要根据具体需求调整。 现在,需要生成相关问题。用户的问题是关于C语言实现摄像头数据灰度化并存储到128x128数组,相关问题可能涉及像素格式、下采样方法、寄存器读取方式、灰度公式选择、缓冲区处理等。 相关问题可能包括: 1. 如果摄像头输出的是RGB888格式(24位),代码需要做哪些调整? 2. 除了中心区域截取,还有哪些常用的下采样方法? 3. 如何确保读取DCMI->DR时不会丢失摄像头数据? 4. 灰度化公式中的系数(0.299、0.587、0.114)是否可以调整?有什么影响? 5. 如果目标数组gray128的大小与原始图像的比例不是整数,如何处理边界像素? </think> 要将320x240的摄像头数据灰度化并存储到128x128的`gray128`数组中,需结合硬件寄存器读取、像素格式解析、灰度转换及下采样处理。以下是C语言实现示例(假设摄像头输出RGB565格式,DCMI->DR为32位寄存器,每次读取包含两个16位像素): ```c #include <stdint.h> // 假设DCMI寄存器结构体(需根据实际硬件定义) typedef struct { volatile uint32_t DR; // 摄像头数据寄存器 } DCMI_TypeDef; #define DCMI ((DCMI_TypeDef *)0x40050000) // 示例寄存器地址 static uint8_t gray128[128][128]; // 目标灰度数组 void camera_data_to_gray(void) { const int src_width = 320; // 原始图像宽度 const int src_height = 240; // 原始图像高度 const int target_width = 128; // 目标宽度 const int target_height = 128;// 目标高度 // 计算原始图像中心区域的起始坐标(用于下采样) const int start_x = (src_width - target_width) / 2; // 96 const int start_y = (src_height - target_height) / 2; // 56 for (int y = 0; y < src_height; y++) { for (int x = 0; x < src_width; x += 2) { // 每次读取2个16位像素(32位DR) uint32_t dr_value = DCMI->DR; // 读取32位数据寄存器 // 解析高16位和低16位的RGB565像素 uint16_t pixel1 = (dr_value >> 16) & 0xFFFF; // 第1个像素(x坐标) uint16_t pixel2 = dr_value & 0xFFFF; // 第2个像素(x+1坐标) // 处理第1个像素(坐标x) if (y >= start_y && y < start_y + target_height && x >= start_x) { int ty = y - start_y; // 目标y坐标 int tx = x - start_x; // 目标x坐标 if (tx < target_width) { // 确保在目标范围内 // 从RGB565提取R(5位)、G(6位)、B(5位)分量 uint8_t r = (pixel1 >> 11) & 0x1F; uint8_t g = (pixel1 >> 5) & 0x3F; uint8_t b = pixel1 & 0x1F; // 转换为8位(0-255) r = (r * 255 + 15) / 31; // 5位扩展到8位(四舍五入) g = (g * 255 + 31) / 63; // 6位扩展到8位 b = (b * 255 + 15) / 31; // 灰度转换(标准公式:Gray=0.299R+0.587G+0.114B) gray128[ty][tx] = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); } } // 处理第2个像素(坐标x+1) int x2 = x + 1; if (y >= start_y && y < start_y + target_height && x2 < start_x + target_width) { int ty = y - start_y; int tx = x2 - start_x; if (tx < target_width) { uint8_t r = (pixel2 >> 11) & 0x1F; uint8_t g = (pixel2 >> 5) & 0x3F; uint8_t b = pixel2 & 0x1F; r = (r * 255 + 15) / 31; g = (g * 255 + 31) / 63; b = (b * 255 + 15) / 31; gray128[ty][tx] = (uint8_t)(0.299f * r + 0.587f * g + 0.114f * b); } } } } } ``` ### 代码说明: 1. **寄存器读取**:通过`DCMI->DR`读取32位数据,假设每个32位包含两个16位的RGB565像素(高16位和低16位)。 2. **像素解析**:从RGB565(16位)中提取R(5位)、G(6位)、B(5位)分量,并扩展为8位(0-255)。 3. **灰度转换**:使用标准灰度公式`Gray=0.299R+0.587G+0.114B`计算灰度值。 4. **下采样**:通过截取原始图像的中心区域(128x128)实现下采样,避免复杂插值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值