自制电子相机(3)-软件设计

记录该项目的软件设计过程

使用的库

根据前面所分析的需求,我使用了一下软件库:

  • FreeRTOS:实时操作系统,用以更好的管理程序
  • FatFS:文件系统,用以更好的管理图片、视频
  • LibJPEG:JPEG转换库,用以将其他图片文件格式转换为JPEG,方便和上位机的交互
  • st系统编程库:用以控制stm32芯片及其外设电路

不同模式所不同的软硬件

由于我们的相机使用了两种模式,所以我们需要对于两种模式都进行设计。
我们主要分为两种模式:

  1. 拍摄模式
  2. 查看模式

各个图片格式的特点

由于 OV7670 芯片的图像数据格式是 RGB565,为了在显示器的看上去的图片更加真实,我们需要将 RGB565 格式的数据转换为 RGB888 格式的数据。

RGB888

RGB888 是指每个颜色通道(红、绿、蓝)都有8位表示,总共占用24位(3个字节)。这种格式可以表示约1677万种颜色(2^24),足以满足人眼对色彩的分辨需求。因此,RGB888格式常用于高质量图像的存储和显示,如桌面电脑显示器、高清电视等。

RGB565

RGB565 是一种16位颜色格式,其中红色和蓝色各有5位表示,绿色有6位表示。这种格式可以表示约65536种颜色(2^16),相比于RGB888,颜色精度较低,但占用的空间较小,适用于资源受限的环境。

拍摄模式

其中拍摄模式又分为两种:

  • 照片拍摄
  • 视频拍摄

照片拍摄模式

首先我们需要实时获取摄像头拍摄到的图像数据。主要就是将 OV7670 芯片的图像数据实时传输到 IlI9342 控制的显示器上。

使用的外设和硬件都在上面图中标注出来了,之间将 DCMI 中的数据以 RGB656 格式传输给 FSMC ,通过这个外设传输到 ILI9342 显示器上

下面这个图才表示真正在拍摄照片时候的数据传输路径:

注意使用这个模式的时候,应该需要停止 OV7670 的图像传输,放置 GRAM 中的数据出现混乱

视频模式

这个模式允许用户进行拍摄视频。

这个模式的数据传输过程如上图所示,我们将视频模式的数据传输分成了两个部分

  • STEP1:这个步骤主要是讲数据从 OV7670 芯片传输到 DCMI 外设,然后再传输到 FSMC 外设。
  • STEP2:这个步骤主要是讲数据从 FSMC 外设传输到 SD 卡,然后再传输到 SD 卡的存储器中。

首先存入 FSMC 外设的数据格式是 RGB656 ,所以需要在传输前将其转换为 RGB8888 格式。在 FSMC 传出的格式是 RGB888 ,通过格式转换库 libjpeg 将 RGB888 格式又转成 Jpeg 格式,然后再通过外设 SPI 存到 SD 卡中。同时,由于我们使用的视频格式是 .avi 的格式,而这个格式的特点就是多个 Jpeg 图片组成的(可以看作是一种动态的 Jpeg 格式,所以我们只需要将每一帧的 Jpeg 图片存到 SD 卡中即可)

所以可以知道,数据进行的三次转换:RGB656 -> RGB888 -> Jpeg

为什么需要将这个数据变来变去?
这主要是对于嵌入式系统的资源限制所造成的。

总的硬件分配方案如下:

查看模式

这个模式就是用来查看照片和视频的。在此模式之下,直接从 SD 卡中读取 Jpeg 图片或者 AVI 视频,然后显示到显示器上。

数据转换的方法

由于我们知道,RGB565格式中,红色和蓝色各有5位表示,绿色有6位表示;而对于 RGB888 来说三个颜色都是使用 8 位来表示。

所以,我们使用下面三个函数来进行 565 到 888 的转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdint.h>

typedef union {
struct {
uint16_t red : 5;
uint16_t green : 6;
uint16_t blue : 5;
} fields;
uint16_t value;
} rgb565_t;

typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
} rgb888_t;

rgb888_t convert_rgb565_to_rgb888(uint16_t rgb565) {
rgb565_t src;
rgb888_t dest;

// 将16位的RGB565值分解成各个颜色分量
src.value = rgb565;

// 扩展红色分量至8位
dest.red = (src.fields.red << 3) | (src.fields.red >> 2);
// 扩展绿色分量至8位
dest.green = (src.fields.green << 2) | (src.fields.green >> 4);
// 扩展蓝色分量至8位
dest.blue = (src.fields.blue << 3) | (src.fields.blue >> 2);

return dest;
}

假如有数:11111(565) -> 11111000(<<3) | 0011111(>>2) => 11111000(888)

所以,我们可以将 565 格式的图像数据转换为 888 格式的图像数据。

同时如果不对于时间要求比较高的话:

其实这个方法的原理和上面哪个差不多,只不过这个没有使用 >> 和 <<

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
typedef union
{
uint32_t RGB888;
struct
{
uint32_t dummy0:3;
uint32_t RGB_B :5;
uint32_t dummy1:2;
uint32_t RGB_G :6;
uint32_t dummy2:3;
uint32_t RGB_R :5;
uint32_t dummy3:8;
}Work;
}RGB888_struct;
typedef union
{
uint16_t RGB565;
struct
{
uint16_t RGB_B :5;
uint16_t RGB_G :6;
uint16_t RGB_R :5;
}Work;
}RGB565_struct;

//输入一个RGB888的32位数据地址,返回转换后的RGB565
static uint16_t RGB888_To_RGB565(const RGB888_struct *RGB888)
{
RGB565_struct RGB565 = { 0 };

RGB565.Work.RGB_R = RGB888->Work.RGB_R;
RGB565.Work.RGB_G = RGB888->Work.RGB_G;
RGB565.Work.RGB_B = RGB888->Work.RGB_B;
return RGB565.RGB565;
}
//输入一个RG565的16位数据地址,返回转换后的RGB888
static uint32_t RGB565_To_RGB888(const RGB565_struct *RGB565)
{
RGB888_struct RGB888 = { 0 };

RGB888.Work.RGB_R = RGB565->Work.RGB_R;
RGB888.Work.RGB_G = RGB565->Work.RGB_G;
RGB888.Work.RGB_B = RGB565->Work.RGB_B;
return RGB888.RGB888;
}

自制电子相机(3)-软件设计
https://ysc2.github.io/ysc2.github.io/2024/02/28/自制电子相机(3)-软件设计/
作者
Ysc
发布于
2024年2月28日
许可协议