您当前的位置: 首页 >  stm32

正点原子

暂无认证

  • 9浏览

    0关注

    382博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【正点原子STM32连载】第五十章 照相机实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

正点原子 发布时间:2022-10-14 17:26:03 ,浏览量:9

1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-336836-1-1.html 4)对正点原子STM32感兴趣的同学可以加群讨论:879133275

第五十章 照相机实验

上一章,我们学习了如何使用STM32H750自带的硬件JPEG编解码器,实现对JPG/JPEG图片的硬解码,从而大大提高解码速度。本章我们将学习BMP&JPEG编码,结合前面的摄像头实验,实现一个简单的照相机。 本章分为如下几个小节: 50.1 BMP&JPEG编码简介 50.2 硬件设计 50.3 程序设计 50.4 下载验证

50.1 BMP&JPEG编码简介 我们要实现支持BMP图片格式的照片和JPEG图片格式的照片的照相机功能,这里简单介绍一下这两种图片格式的编码。这里我们使用ATK-OV5640-AF摄像头,来实现拍照。关于OV5640的相关知识点,请参考第四十三章。 50.1.1 BMP编码简介 前面的章节中,我们学习了各种图片格式的解码。本章,我们介绍最简单的图片编码方法:BMP图片编码。通过前面的了解,我们知道BMP文件是由文件头、位图信息头、颜色信息和图形数据等四部分组成。我们先来了解下这几个部分。 1、BMP文件头(14字节):BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。

/* BMP头文件 */
typedef __packed struct
{
    uint16_t  bfType ;      	/* 文件标志.只对'BM',用来识别BMP位图类型 */
    uint32_t  bfSize ;      	/* 文件大小,占四个字节 */
    uint16_t  bfReserved1 ; 	/* 保留 */
    uint16_t  bfReserved2 ; 	/* 保留 */
    uint32_t  bfOffBits ;   	/* 从文件开始到位图数据(bitmap data)开始之间的的偏移量 */
}BITMAPFILEHEADER ;

2、位图信息头(40字节):BMP位图信息头数据用于说明位图的尺寸等信息。 /* BMP信息头 */

typedef __packed struct
{
    uint32_t biSize ;        	/* 说明BITMAPINFOHEADER结构所需要的字数。 */
    long  biWidth ;           /* 说明图象的宽度,以象素为单位 */
    long  biHeight ;          /* 说明图象的高度,以象素为单位 */
    uint16_t  biPlanes ;     	/* 为目标设备说明位面数,其值将总是被设为1 */
    uint16_t  biBitCount ;   /* 说明比特数/象素,其值为1、4、8、16、24、或32 */
    uint32_t biCompression ;/* 说明图象数据压缩的类型。其值可以是下述值之一
                                 * BI_RGB      :没有压缩
                                 * BI_RLE8     :每个象素8比特的RLE压缩编码,压缩格式由
2字节组成(重复象素计数和颜色索引) 
                                 * BI_RLE4     :每个象素4比特的RLE压缩编码,压缩格式由
2字节组成 
                                 * BI_BITFIELDS:每个象素的比特由指定的掩码决定 
                                 */
    uint32_t biSizeImage ;/*说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0*/
    long  biXPelsPerMeter ; /* 说明水平分辨率,用象素/米表示 */
    long  biYPelsPerMeter ; /* 说明垂直分辨率,用象素/米表示 */
uint32_t biClrUsed ;    /* 说明位图实际使用的彩色表中的颜色索引数 */
/* 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要 */
    uint32_t biClrImportant ;   
}BITMAPINFOHEADER ;

3、颜色表:颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。 /* 彩色表 */

typedef __packed struct 
{
    uint8_t rgbBlue ;           	/* 指定蓝色强度 */
    uint8_t rgbGreen ;          	/* 指定绿色强度 */
    uint8_t rgbRed ;            	/* 指定红色强度 */
    uint8_t rgbReserved ;      	/* 保留,设置为0 */
}RGBQUAD ;

颜色表中RGBQUAD结构数据的个数由biBitCount来确定:当biBitCount=1、4、8时,分别有2、16、256个表项;当biBitCount大于8时,没有颜色表项。 BMP文件头、位图信息头和颜色表组成位图信息(我们将BMP文件头也加进来,方便处理),BITMAPINFO结构定义如下: /* 位图信息头 */

typedef __packed struct
{ 
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bmiHeader;  
    uint32_t RGB_MASK[3];       /* 调色板用于存放RGB掩码 */
    //RGBQUAD bmiColors[256];
}BITMAPINFO; 

4、位图数据:位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数: 当biBitCount=1时,8个像素占1个字节; 当biBitCount=4时,2个像素占1个字节; 当biBitCount=8时,1个像素占1个字节; 当biBitCount=16时,1个像素占2个字节; 当biBitCount=24时,1个像素占3个字节; 当biBitCount=32时,1个像素占4个字节; biBitCount=1 表示位图最多有两种颜色,缺省情况下是黑色和白色,你也可以自己定义这两种颜色。图像信息头装调色板中将有两个调色板项,称为索引0和索引1。图象数据阵列中的每一位表示一个像素。如果一个位是0,显示时就使用索引0的RGB值,如果位是1,则使用索引1的RGB值。 biBitCount=16 表示位图最多有65536种颜色。每个像素用16位(2个字节)表示。这种格式叫作高彩色,或叫增强型16位色,或64K色。它的情况比较复杂,当biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。这种格式也被称作555 16位位图。如果biCompression成员的值是BI_BITFIELDS,那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。分别用于描述红、绿、蓝分量在16位中所占的位置。在Windows 95(或98)中,系统可接受两种格式的位域:555和565,在555格式下,红、绿、蓝的掩码分别是:0x7C00、0x03E0、0x001F,而在565格式下,它们则分别为:0xF800、0x07E0、0x001F。你在读取一个像素之后,可以分别用掩码“与”上像素值,从而提取出想要的颜色分量(当然还要再经过适当的左右移操作)。在NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。 biBitCount=32 表示位图最多有4294967296(2的32次方)种颜色。这种位图的结构与16位位图结构非常类似,当biCompression成员的值是BI_RGB时,它也没有调色板,32位中有24位用于存放RGB值,顺序是:最高位—保留,红8位、绿8位、蓝8位。这种格式也被成为888 32位图。如果 biCompression成员的值是BI_BITFIELDS时,原来调色板的位置将被三个DWORD变量占据,成为红、绿、蓝掩码,分别用于描述红、绿、蓝分量在32位中所占的位置。在Windows 95(or 98)中,系统只接受888格式,也就是说三个掩码的值将只能是:0xFF0000、0xFF00、0xFF。而NT系统,只要注意使掩码之间不产生重叠就行。(注:这种图像格式比较规整,因为它是DWORD对齐的,所以在内存中进行图像处理时可进行汇编级的代码优化(简单)。 通过以上了解,我们对BMP有了一个比较深入的了解,本章,我们采用16位BMP编码(因为我们的LCD就是16位色的,而且16位BMP编码比24位BMP编码更省空间),故我们需要设置biBitCount的值为16,这样得到新的位图信息(BITMAPINFO)结构体: /* 位图信息头 */

typedef __packed struct
{ 
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bmiHeader;  
    uint32_t RGB_MASK[3];       /* 调色板用于存放RGB掩码 */
}BITMAPINFO; 

其实就是颜色表由3个RGB掩码代替。最后,我们来看看将LCD的显存保存为BMP格式的图片文件的步骤: 1)创建BMP位图信息,并初始化各个相关信息 这里,我们要设置BMP图片的分辨率为LCD分辨率、BMP图片的大小(整个BMP文件大小)、BMP的像素位数(16位)和掩码等信息。 2)创建新BMP文件,写入BMP位图信息 我们要保存BMP,当然要存放在某个地方(文件),所以需要先创建文件,同时先保存BMP位图信息,之后才开始BMP数据的写入。 3)保存位图数据。 这里就比较简单了,只需要从LCD的GRAM里面读取各点的颜色值,依次写入第二步创建的BMP文件即可。注意:保存顺序(即读GRAM顺序)是从左到右,从下到上。 4)关闭文件。 使用FATFS,在文件创建之后,必须调用f_close,文件才会真正体现在文件系统里面,否则是不会写入的!这个要特别注意,写完之后,一定要调用f_close。 BMP编码就介绍到这里。 50.1.2 JPEG编码简介 JPEG(Joint Photographic Experts Group)是一个由ISO和IEC两个组织机构联合组成的一个专家组,负责制定静态的数字图像数据压缩编码标准,这个专家组开发的算法称为JPEG算法,并且成为国际上通用的标准,因此又称为JPEG标准。JPEG是一个适用范围很广的静态图像数据压缩标准,既可用于灰度图像又可用于彩色图像。 JPEG专家组开发了两种基本的压缩算法,一种是采用以离散余弦变换(Discrete Cosine Transform,DCT)为基础的有损压缩算法,另一种是采用以预测技术为基础的无损压缩算法。使用有损压缩算法时,在压缩比为25:1的情况下,压缩后还原得到的图像与原始图像相比较,非图像专家难于找出它们之间的区别,因此得到了广泛的应用。 JPEG压缩是有损压缩,它利用了人的视角系统的特性,使用量化和无损压缩编码相结合来去掉视角的冗余信息和数据本身的冗余信息。 JPEG压缩编码分为三个步骤: 1)使用正向离散余弦变换(Forward Discrete Cosine Transform,FDCT)把空间域表示的图变换成频率域表示的图。 2)使用加权函数对DCT系数进行量化,这个加权函数对于人的视觉系统是最佳的。 3)使用霍夫曼可变字长编码器对量化系数进行编码。 这里我们不详细介绍JPEG压缩的过程了,大家可以自行查找相关资料。我们本实验要实现的JPEG拍照,并不需要自己压缩图像,因为我们使用的ALIENTEK OV5640摄像头模块,直接就可以输出压缩后的JPEG数据,我们完全不需要理会压缩过程,所以本实验我们实现JPEG拍照的关键,在于准确接收OV5640摄像头模块发送过来的编码数据,然后将这些数据保存为.jpg文件,就可以实现JPEG拍照了。 在第四十三章的摄像头实验中,我们定义了一个很大的数组jpeg_data_buf(480KB字节)来存储JPEG图像数据。而在本实验中,我们可以使用内存管理来申请内存,无需定义这么大的数组,使用上更加灵活。DCMI接口使用DMA直接传输JPEG数据,DMA接收到的JPEG数据放到内部SRAM。所以,我们本章将使用DMA的双缓冲机制来读取,DMA双缓冲读取JPEG数据框图如图50.1.2.1所示: 在这里插入图片描述

图50.1.2.1 DMA双缓冲读取JPEG数据原理框图 DMA接收来自OV5640的JPEG数据流,首先使用M0AR(内存1)来存储,当M0AR满了以后,自动切换到M1AR(内存2),同时程序读取M0AR(内存1)的数据到内部SRAM;当M1AR满了以后,又切回M0AR,同时程序读取M1AR(内存2)的数据到内部SRAM;依次循环(此时的数据处理,是通过DMA传输完成中断实现的,在中断里面处理),直到帧中断,结束一帧数据的采集,读取剩余数据到内部SRAM,完成一次JPEG数据的采集。 这里,M0AR,M1AR所指向的内存,必须是内部内存,不过由于采用了双缓冲机制,我们就不必定义一个很大的数组,一次性接收所有JPEG数据了,而是可以分批次接收,数组可以定义的比较小。 最后,将存储在内部SRAM的jpeg数据,保存为.jpg/.jpeg存放在SD卡,就完成了一次JPEG拍照。 50.2 硬件设计

  1. 例程功能 1、首先是检测字库,然后检测SD卡根目录是否存在PHOTO文件夹,如果不存在则创建,如果创建失败,则报错(提示拍照功能不可用)。在找到SD卡的PHOTO文件夹后,开始初始化OV5640,如果初始化成功,则提示信息:KEY0:拍照(bmp格式),KEY1:拍照(jpg格式),WK_UP选择:1:1显示,即不缩放,图片不变形,但是显示区域小(液晶分辨率大小),或者缩放显示,即将1280*800的图像压缩到液晶分辨率尺寸显示,图片变形,但是显示了整个图片内容。可以通过串口1,借助USMART设置/读取OV5640的寄存器,方便大家调试。 2、LED0闪烁,提示程序运行。LED1用于指示帧中断。
  2. 硬件资源 1)RGB灯 RED :LED0 - PB4 GREEN :LED1 - PE6 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)独立按键 :KEY0 - PA1、KEY1 - PA15、WK_UP - PA0 5)SD卡,通过SDMMC1(SDMMC_D0D4(PC8PC11), SDMMC_SCK(PC12),SDMMC_CMD(PD2))连接 6)norflash(QSPI FLASH芯片,连接在QSPI上) 7)硬件JPEG解码内核(STM32H750自带) 8)定时器6(用于打印摄像头帧率等信息) 9)ALIENTEK OV5640摄像头模块,连接关系为: OV5640模块 ----------- STM32开发板 OV_D0~D7 ------------ PC6/PC7/PC8/PC9/PC11/PD3/PB8/PB9 OV_SCL ------------ PB10 OV_SDA ------------ PB11 OV_VSYNC ------------ PB7 OV_HREF ------------ PA4 OV_RESET ------------ PA7 OV_PCLK ------------ PA6 OV_PWDN ------------ PC4 50.3 程序设计 50.3.1 程序流程图 在这里插入图片描述

图50.3.1.1 照相机实验程序流程图 50.3.2 程序解析

  1. PICTURE驱动代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PICTURE驱动源码包括两个文件:bmp.c和bmp.h。 bmp.h头文件在50.1.1小节基本讲过,具体请看源码。下面来看到bmp.c文件里面的bmp编码函数:bmp_encode,该函数代码如下:
/**
 * @brief   BMP编码函数
 *   @note  将当前LCD屏幕的指定区域截图,存为16位格式的BMP文件 RGB565格式.
 *           保存为rgb565则需要掩码,需要利用原来的调色板位置增加掩码.这里我们已经增加了掩码.
 *           保存为rgb555格式则需要颜色转换,耗时间比较久,所以保存为565是最快速的办法.
 *
 * @param       filename    : 包含存储路径的文件名(.bmp)
 * @param       x, y         : 起始坐标
 * @param       width,height: 区域大小
 * @param       acolor      : 附加的alphablend的颜色(这个仅对32位色bmp有效!!!)
 * @param       mode        : 保存模式
 *   @arg                     0, 仅仅创建新文件的方式编码;
 *   @arg                     1, 如果之前存在文件,则覆盖之前的文件.如果没有,则创建新的文件; 
 * @retval      操作结果
 *   @arg       0   , 成功
 *   @arg       其他, 错误码
 */
uint8_t bmp_encode(uint8_t *filename, uint16_t x, uint16_t y, uint16_t width, 
uint16_t height, uint8_t mode)
{
    FIL *f_bmp;
    uint32_t bw = 0;
    uint16_t bmpheadsize;  	/* bmp头大小 */
    BITMAPINFO hbmp;        	/* bmp头 */
    uint8_t res = 0;
    uint16_t tx, ty;        	/* 图像尺寸 */
    uint16_t *databuf;     	/* 数据缓存区地址 */
    uint16_t pixcnt;       	/* 像素计数器 */
    uint16_t bi4width;    	/* 水平像素字节数 */

    if (width == 0 || height == 0)return PIC_WINDOW_ERR;         	/* 区域错误 */
    if ((x + width - 1) > lcddev.width)return PIC_WINDOW_ERR;   	/* 区域错误 */
    if ((y + height - 1) > lcddev.height)return PIC_WINDOW_ERR;	/* 区域错误 */

#if BMP_USE_MALLOC == 1     /* 使用malloc */
    
/* 开辟至少bi4width大小的字节的内存区域 ,对240宽的屏,480个字节就够了.
最大支持1024宽度的bmp编码 */
databuf = (uint16_t *)piclib_mem_malloc(2048);

    if (databuf == NULL)return PIC_MEM_ERR;     		/* 内存申请失败. */
    f_bmp = (FIL *)piclib_mem_malloc(sizeof(FIL)); 	/* 开辟FIL字节的内存区域 */
    if (f_bmp == NULL)      /* 内存申请失败 */
    {
        piclib_mem_free(databuf);
        return PIC_MEM_ERR;
    }
#else
    databuf = (uint16_t *)bmpreadbuf;
    f_bmp = &f_bfile;
#endif
    bmpheadsize = sizeof(hbmp);         				/* 得到bmp文件头的大小 */
    my_mem_set((uint8_t *)&hbmp, 0, sizeof(hbmp)); 	/* 置零空申请到的内存 */
    hbmp.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);   /* 信息头大小 */
    hbmp.bmiHeader.biWidth = width;     	/* bmp的宽度 */
    hbmp.bmiHeader.biHeight = height;  	/* bmp的高度 */
    hbmp.bmiHeader.biPlanes = 1;        	/* 恒为1 */
    hbmp.bmiHeader.biBitCount = 16;    	/* bmp为16位色bmp */
    hbmp.bmiHeader.biCompression = BI_BITFIELDS; /* 每个象素的比特由指定的掩码决定 */
    hbmp.bmiHeader.biSizeImage = hbmp.bmiHeader.biHeight * hbmp.bmiHeader.biWidth * hbmp.bmiHeader.biBitCount / 8;/* bmp数据区大小 */
hbmp.bmfHeader.bfType = ((uint16_t)'M'             
关注
打赏
1665308814
查看更多评论
0.0452s