• (49)STM32——照相机实验


    目录

    学习目标

    图片格式

    BMP

    组成

    编码 

    步骤

    JPG

    编码

    拍照步骤

    配置

    代码

    总结 


    学习目标

            本节我们学习的是照相机实验,主要的功能就是将照片拍下,然后把数据解码,最后将图片数据保存到SD卡里,在运用上节课的图片显示实验来显示。

    图片格式

    BMP

    • 全称BitMap,是Windows中的标准图像文件格式,后缀名为:“.bmp”。
    • 采用位映射存储方式,除图像深度可选外,不做任何压缩。
    • 图像深度可选:1、4、8、16、24、32bit。
    • BMP文件存储数据时,图像的扫描方式是按照从左到右、从上到小的顺序。
    • 优点:但是没有任何失真,图片保存完好。
    • 缺点:图片占用空间大。

    组成

    1. 位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
    2. 位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
    3. 调色板,这个部分可选,有些位图需要调色板,有些位图不需要调色板(比如:24位的BMP);
    4. 位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。

    BMP文件头 

    BMP文件头(14字节):BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。BMP文件头结构体定义如下:

    1. //BMP文件头
    2. typedef __packed struct
    3. {
    4. u16 bfType; //文件标志,只对‘B’‘M’,用来识别BMP位图类型
    5. u32 bfSize; //文件大小,占四个字节
    6. u16 bfReserved1;//保留
    7. u16 bfReserved2;//保留
    8. u32 bfOffBits; //从文件开始到位图数据(bitmap data)开始之间的偏移量
    9. }BITMAPFILEHADER;

     位图信息头 

            位图信息头(40字节):BMP位图信息头数据用于说明位图的尺寸等信息。 BMP位图信息头结构体定义如下:

            设置biCompression的值时一般不会设置为BI_RLE84和BI_RLE8,经常设置为BI_BITFIELDS如果是16位图时会直接设置为BI_BITFIELDS。biSizeImage是根据biWidth、 biHeight、biBitCount计算出一个字节数来设置。剩下的几个一般设置为0。

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

    颜色表 

            颜色表(调色板):颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色,如下所示:

    1. typedef __packed struct
    2. {
    3. u8 rgbBlue ; //指定蓝色强度
    4. u8 rgbGreen ; //指定绿色强度
    5. u8 rgbRed ; //指定红色强度
    6. u8 rgbReserved ; //保留,设置为0
    7. }RGBQUAD ;

            RGBQUAD结构数据的个数由biBitCount来确定:当biBitCount=1、4、8时,分别有2、16、256个表项;当biBitCount大于8时,没有颜色表项。  

            BMP文件头、位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:

    1. typedef __packed struct
    2. {
    3. BITMAPFILEHADER bmfHeader;
    4. BITMAPINFOHEADER bmiHeader;
    5. RGBQUAD bmiColors[1];
    6. }BITMAPINFO;

    位图数据 

            位图数据:记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图一个像素值所占字节数:

    当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=16,即高彩色(65K色)。当biCompression=BI_RGB(0),则采用RGB555格式,最高位恒为0;当biCompression= BI_BITFIELDS(3),则在原来调色板位置用3个DWORD类型的掩码替换,分别代表红、绿、蓝三色的掩码,一般是: 0X7C00(高5位)、0X03E0(中6位)、0X001F(低5位)。

    编码 

            我们采用16位BMP编码(因为LCD就是16位色的,而且16位BMP编码比24位BMP编码更省空间),故我们需要设置biBitCount的值为16,这样得到新的位图信息(BITMAPINFO)结构体。

    1. typedef __packed struct
    2.     BITMAPFILEHEADER bmfHeader;
    3.     BITMAPINFOHEADER bmiHeader;   
    4.     u32 RGB_MASK[3];        //调色板用于存放RGB掩码
    5. }BITMAPINFO;   

    RGB_MASK[3],即颜色掩码,分别代表红、绿、蓝三色的掩码,分别是: 0X7C00、0X03E0、0X001F。

    步骤

    1. 创建BMP位图信息(上面的结构体),并初始化各个相关信息。首先,我们要设置BMP图片的分辨率为LCD分辨率、BMP图片的大小(整个BMP文件大小)、BMP的像素位数(16位)和掩码等信息。
    2. 创建新BMP文件,写入BMP位图信息。我们要保存BMP,当然要存放在某个地方(文件)(SD卡或U盘),所以需要先创建文件,同时先保存BMP位图信息,之后才开始BMP数据的写入。
    3. 保存位图数据。这里就比较简单了,只需要从LCD的GRAM里面读取各点的颜色值,依次写入第二步创建的BMP文件即可。注意:保存顺序(即读GRAM顺序)是从左到右,从下到上
    4. 关闭文件。使用FATFS,在文件创建之后,必须调用f_close,文件才会真正体现在文件系统里面,否则是不会写入的!这个要特别注意,写完之后,一定要调用f_close

    JPG

    • JPG是Joint  Photographic Experts Group(联合图像专家小组)的缩写,是第一个国际图像压缩标准。后缀名为:“.JPEG”
    • JPEG图像压缩算法能够在提供良好的压缩性能的同时,具有比较好的重建质量,被广泛应用于图像处理领域。
    • 采用有损压缩格式,能够将图像压缩在很小的存储空间。压缩技术先进,允许用不同的压缩比例对文件进行压缩,支持多种压缩级别。压缩比越大品质越低。
    • 在图像质量和存储空间之间选择一个平衡点

    编码

    1. 使用正向离散余弦变换(Forward Discrete Cosine Transform,FDCT)把空间域表示的图变换成频率域表示的图。        
    2. 使用加权函数对DCT系数进行量化,这个加权函数对于人的视觉系统是最佳的。        
    3. 使用霍夫曼可变字长编码器对量化系数进行编码。

    拍照步骤

    1. 初始化STM32F4的DCMI接口和OV2640模块。首先,我们要初始化STM32的DCMI接口(包括开启DMA和相关中断)和相关IO,然后配置好OV2640输出JPEG数据流。
    2. 读取OV2640模块的数据。在DCMI接口的驱动下,有序读取OV2640输出的JPEG数据流,我们采用DMA双缓冲来接收JPEG数据流,并将这些数据及时搬运到外部SRAM(不能直接将OV2640的数据输出到外部SRAM因为外部SRAM速度跟不上,通过DMA的传输完成中断来处理)。
    3. 保存JPEG数据。在采集完一帧JPEG数据后,利用fatfs,创建一个.jpg文件,然后将存储在外部SRAM的数组(以0XFF,0XD8开头)存储在这个文件里面,最后调用f_close关闭文件,即可实现JPEG拍照保存。

    配置

    本例程使用DMA的双缓冲机制来读取,DMA双缓冲读取JPEG数据框图如下图:

    • DMA接收来自OV2640的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拍照。

    代码

    给出main函数的部分代码。

    1. while(1)
    2. {
    3. key=KEY_Scan(0);//不支持连按
    4. if(key)
    5. {
    6. DCMI_Stop(); //停止显示
    7. if(key==WKUP_PRES)
    8. {
    9. scale=!scale;
    10. if(scale==0)
    11. {
    12. if(lcddev.id == 0X5510)
    13. {
    14. SCCB_WR_Reg(0xd3,0x02);
    15. }
    16. OV2640_ImageWin_Set((1600-lcddev.width)/2,(1200-lcddev.height)/2,lcddev.width,lcddev.height);//1:1真实尺寸
    17. OV2640_OutSize_Set(lcddev.width,lcddev.height);
    18. sprintf((char*)msgbuf,"Full Size 1:1");
    19. }else
    20. {
    21. OV2640_ImageWin_Set(0,0,1600,1200); //全尺寸缩放
    22. OV2640_OutSize_Set(lcddev.width,lcddev.height);
    23. sprintf((char*)msgbuf,"Scale");
    24. }
    25. LCD_ShowString(30,50,210,16,16,msgbuf);//显示提示内容
    26. delay_ms(800);
    27. }else if(sd_ok)//SD卡正常才可以拍照
    28. {
    29. sw_sdcard_mode(); //切换为SD卡模式
    30. if(key==KEY0_PRES) //BMP拍照
    31. {
    32. camera_new_pathname(pname,0);//得到文件名
    33. res=bmp_encode(pname,0,0,lcddev.width,lcddev.height,0);
    34. }else if(key==KEY1_PRES)//JPG拍照
    35. {
    36. camera_new_pathname(pname,1);//得到文件名
    37. res=ov2640_jpg_photo(pname);
    38. if(scale==0)
    39. {
    40. OV2640_ImageWin_Set((1600-lcddev.width)/2,(1200-lcddev.height)/2,lcddev.width,lcddev.height);//1:1真实尺寸
    41. OV2640_OutSize_Set(lcddev.width,lcddev.height);
    42. }else
    43. {
    44. OV2640_ImageWin_Set(0,0,1600,1200); //全尺寸缩放
    45. }
    46. OV2640_OutSize_Set(lcddev.width,lcddev.height);
    47. }
    48. sw_ov2640_mode(); //切换为OV2640模式
    49. if(res)//拍照有误
    50. {
    51. Show_Str(30,130,240,16,"写入文件错误!",16,0);
    52. }else
    53. {
    54. Show_Str(30,130,240,16,"拍照成功!",16,0);
    55. Show_Str(30,150,240,16,"保存为:",16,0);
    56. Show_Str(30+42,150,240,16,pname,16,0);
    57. BEEP=1; //蜂鸣器短叫,提示拍照完成
    58. delay_ms(100);
    59. }
    60. }else //提示SD卡错误
    61. {
    62. Show_Str(30,130,240,16,"SD卡错误!",16,0);
    63. Show_Str(30,150,240,16,"拍照功能不可用!",16,0);
    64. }
    65. BEEP=0; //关闭蜂鸣器
    66. if(key!=WKUP_PRES)delay_ms(1800);//非尺寸切换,等待1.8秒钟
    67. DCMI_Start(); //停止显示
    68. }
    69. if (hsync_int) //刚刚产生帧中断,可以延时
    70. {
    71. delay_ms(10);
    72. hsync_int = 0;
    73. }
    74. i++;
    75. if(i==20)//DS0闪烁.
    76. {
    77. i=0;
    78. LED0=!LED0;
    79. }

    总结 

            本实验还是挺有趣的,和前面结合在一起,但没能完全掌握,还得慢慢理解。 

  • 相关阅读:
    Navicat 连接Docker的MySQL报错2003,10060 “Unknown error“
    线上线程池配置错误导致服务故障
    数据库系统原理【练习题】——第一章:概述
    mysql主从复制与读写分离
    搭建hbase集群环境
    js逆向之加密参数还原与模拟
    C语言——三种方式实现学生信息管理
    【Linux开发基础知识】Makefile语法
    百度地图——轨迹上传
    【opencv-c++】保存图片文件
  • 原文地址:https://blog.csdn.net/weixin_66578482/article/details/127708385