Ubuntu系统版本:Ubuntu 16.04.7 LTS
一、前言
参考博客 海思多媒体(MPP)开发(8)——获取VI中的YUV数据 以及博客 yuv420 转换成 bmp 实现。实际上 SDK 包的 MPP 中也是提供了相关示例源码的,mpp/tools 下就是一些测试用的小程序,包括从各个模块获取 YUV 图像并存储为 YUV 文件的实现,进目录 make 一下就能得到可执行程序:
- 该文件夹下的可执行文件不一定能无条件运行。博主曾将该目录下编译得到的 vi_dump 放到 Hi3531D 的板载 linux 上尝试运行,但是运行失败,原因是错误码 0xA010800E(视频输入缓存为空),而使用时确定是有视频流传输到板子里的。后来检查 vi_dump.c 中的源码发现里面并没有启动 VI 设备直接就在尝试从 VI 通道获取并存储图像帧:
PS:这些工具程序应该是要配合 Linux 的程序前后台执行特性来使用的,在后台挂上测试程序,打通数据流转路径,然后在前台执行工具程序,抓取用于分析和调试的图象数据。
- 实现本文目的的 MPP 核心 API 有四个(前提是 VI 设备已经配置好并且在正常工作),具体说明可以在 HiMPP 开发参考手册中查阅:
- 本文中使用 HI_MPI_VI_GetFrameDepth 接口获取到的图片数据格式为 YUV420SP NV21,也就是 U、V 交替存放,数据结构类似下图的 NV12 结构:
PS:关于 YUV 格式的图像格式还是有点东西的,不清楚的话还是建议先移步博客 YUV图解 (YUV444, YUV422, YUV420, YV12, NV12, NV21) 了解相关信息。
- 实际上咱一开始想做的是把 YUV 封装成 jpg 格式的图片,参考雷神的博客 最简单的基于FFMPEG的图像编码器(YUV编码为JPEG) 写出来却发现调用 avcodec_open2 函数打开编码器一直失败,无法打开。起初以为是 ffmpeg API 版本适配的问题调了大半个礼拜,最后发现是因为自己使用的图片分辨率太大(4k),把图片分辨率改成 1080p 就可以用了 Orz…
- BMP 的封装相对来说会更简单一些,只需要将 YUV 转成 RGB ,添加一下文件头信息后再把所有像素点的像素值塞进文件里就行,当然代价就是单张图片占用的空间更大了,是 YUV 数据所需存储空间大小的两倍。BMP 文件结构的详细信息参考博客 BMP文件转YUV文件_C语言实现 的第三节和第四节;
- 即便退而求其次用 BMP 格式封装图像数据也还是出现了问题。板载 linux 的可用内存太小,而 4k 图像占用空间太大,如果要一次性处理整张图片就会因为占用内存过大(需要同时准备至少两个图像的缓存区)而触发 linux 的自我保护机制,强行中断处理程序以保证维持系统运行所必须的空余内存大小…所以要使程序能在板载 linux 上正常运行就需要调整代码,通过循环读取处理来逐步实现整幅图像的格式转换与 BMP 封装。
2021.6.9 补充说明:后续验证发现给板载 linux 分配的 os 内存太小了,只有 57M,当然不够内存跑完整的图像格式转换,实际上把内存扩大到 512M 应该就没什么大问题了:《海思中内存分配和优化》
二、流程概览
关于启动 VI 设备的部分,博主通过配置让系统在上电以后自动开始显示图像,算是预置实现了,因此在拍照功能的代码中没有加入 VI 设备的配置。但为使处理流程看起来更加完整还是将其加入了流程图中。
三、实现代码
这里公布的是从 VI 通道获取 YUV 数据以后的两步。获取 YUV 数据参考前面说的博客以及 MPP 自带的 tools 源码相当于直接白给,脑子都不用动的那种,因此没有在这一步的实现上浪费篇幅。
自认为代码部分的注释已经非常到位了。VIDEO_FRAME_S 是 Hi_MPP 自带的结构体, pfd 是外部接口函数里创建并打开的 bmp 文件的句柄。经过调整,此份代码可以在 Hi3531D 的板载 linux 上顺利完成 4k 图像数据的格式转换与封装为 BMP(当然速度并不是很快)。
unsigned char header[54] = {
// 位图文件头
0x42, 0x4d, //WORD bfType----------- [0,1] 说明文件类型
0, 0, 0, 0, //DWORD bfSize----------- [2,3,4,5] 说明文件大小
0, 0, //WORD bfReserved1------ [6,7] 保留,设置为 0
0, 0, //WORD bfReserved2------ [8,9] 保留,设置为 0
54, 0, 0, 0, //WORD bfOffBits-------- [10,11,12,13] 说明从位图文件头开始到实际的图像数据之间的字节偏移量
// 位图信息头
40, 0, 0, 0, //DWORD biSize----------- [14,15,16,17] 说明位图信息头需要的字节数
0, 0, 0, 0, //LONG biWidth---------- [18,19,20,21] 以像素为单位说明图像宽度
0, 0, 0, 0, //LONG biHeight--------- [22,23,24,25] 以像素为单位说明图像高度
1, 0, //WORD biplanes--------- [26,27] 说明位面数,必须为 1
24, 0, //WORD biCount---------- [28,29] 说明位数/像素,1、2、4、8、24
0, 0, 0, 0, //DWORD biCompression---- [30,31,32,33] 说明图像是否是压缩及压缩类型 BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS
0, 0, 0, 0, //DWORD biSizeImage------ [34,35,36,37] 以字节为单位说明图像大小,必须是 4 的整数倍
0, 0, 0, 0, //LONG biXPelsPerMeter-- [38,39,40,41] 目标设备的水平分辨率,像素/米
0, 0, 0, 0, //LONG biYPelsPerMeter-- [42,43,44,45] 目标设备的垂直分辨率,像素/米
0, 0, 0, 0, //DWORD biClrUsed-------- [46,47,48,49] 说明图像实际用到的颜色数,如果为 0 则颜色数为 2 的 biBitCount 次方
0, 0, 0, 0 //DWORD biClrImportant--- [50,51,52,53] 说明对图像显示有重要影响的颜色索引的数目,如果为 0 则表示都重要
};
float YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
/* sp420 */
HI_S32 vi_dump_save_one_frame(VIDEO_FRAME_S* pVBuf, FILE* pfd)
{
int i, j, k, l, x;
int width = pVBuf->u32Width;
int height = pVBuf->u32Height;
long len = height * width; // 一整幅幅图像的长度
int two_rows_len = 2 * width; // 两行图像的长度
long int bytePerLine = width * 3; // RGB24 一行的长度
HI_U32 phy_addr, size;
HI_U8* pUserPageAddr[3] = {NULL, NULL, NULL}; // 0:原图像,1:2行 RGB24图像缓存,2:2行 写入bmp文件数据缓存
PIXEL_FORMAT_E enPixelFormat = pVBuf->enPixelFormat;
HI_U8* pVBufVirt_Y = NULL; // YUV420sp Y 分量首地址
HI_U8* pVBufVirt_C = NULL; // YUV420sp VU 分量首地址
HI_U8* pVBufVirt_R = NULL; // RGB24 R 分量首地址
HI_U8* pVBufVirt_G = NULL; // RGB24 G 分量首地址
HI_U8* pVBufVirt_B = NULL; // RGB24 B 分量首地址
HI_U8* data = NULL;
int R[4], G[4], B[4];
int Y[4], U, V;
int y0_Idx, y1_Idx, uIdx, vIdx;
// 图片格式检查
if (width > MAX_FRM_WIDTH)
{
printf("Over max frame width: %d, can't support.\n", MAX_FRM_WIDTH);
return HI_FAILURE;
}
if (PIXEL_FORMAT_YUV_SEMIPLANAR_420 == enPixelFormat)
size = (pVBuf->u32Stride[0]) * height * 3 / 2;
else if (PIXEL_FORMAT_YUV_SEMIPLANAR_422 == enPixelFormat)
size = (pVBuf->u32Stride[0]) * height * 2;
else if (PIXEL_FORMAT_YUV_400 == enPixelFormat)
size = (pVBuf->u32Stride[0]) * height;
else
{
printf("not support pixelformat: %d\n", enPixelFormat);
return HI_FAILURE;
}
// 获取图像的物理地址,并将其映射为虚拟地址
phy_addr = pVBuf->u32PhyAddr[0];
pUserPageAddr[0] = (HI_U8*)HI_MPI_SYS_Mmap(phy_addr, size);
if (NULL == pUserPageAddr[0])
return HI_FAILURE;
printf("stride: %d,%d\n", pVBuf->u32Stride[0], pVBuf->u32Stride[1]);
// 申请图像处理缓存空间
pUserPageAddr[1] = (HI_U8*)malloc(two_rows_len * 3); // 每次循环只处理两行数据
pUserPageAddr[2] = (HI_U8*)malloc(bytePerLine * 2);
if ((NULL == pUserPageAddr[1]) || (NULL == pUserPageAddr[2]))
goto END;
// 获取图像中 Y 分量和 VU 分量的首地址
pVBufVirt_Y = pUserPageAddr[0];
pVBufVirt_C = pVBufVirt_Y + len;
// 写 BMP 文件头
long file_size = (long)(width) * (long)(height) * 3 + 54;
header[2] = (unsigned char)(file_size & 0x000000ff);
header[3] = (file_size >> 8) & 0x000000ff;
header[4] = (file_size >> 16) & 0x000000ff;
header[5] = (file_size >> 24) & 0x000000ff;
header[18] = (width) & 0x000000ff;
header[19] = (width >> 8) & 0x000000ff;
header[20] = (width >> 16) & 0x000000ff;
header[21] = (width >> 24) & 0x000000ff;
header[22] = (height) & 0x000000ff;
header[23] = (height >> 8) & 0x000000ff;
header[24] = (height >> 16) & 0x000000ff;
header[25] = (height >> 24) & 0x000000ff;
fwrite(header, sizeof(unsigned char), 54, pfd);
fflush(pfd);
// 写 BMP 图片数据
pVBufVirt_R = pUserPageAddr[1];
pVBufVirt_G = pVBufVirt_R + two_rows_len;
pVBufVirt_B = pVBufVirt_G + two_rows_len;
data = pUserPageAddr[2];
for (i = height - 2; i >= 0; i = i - 2) // 为便于在循环中增加数据写入,从最后两行开始倒序转换
{
// 两行数据格式转换: NV21 -> RGB24
for (j = 0; j <= width - 2; j = j + 2)
{
y0_Idx = i * width + j;
y1_Idx = (i + 1) * width + j;
// Y[0]、Y[1]、Y[2]、Y[3]分别代表 Y00、Y01、Y10、Y11
Y[0] = pVBufVirt_Y[y0_Idx];
Y[1] = pVBufVirt_Y[y0_Idx + 1];
Y[2] = pVBufVirt_Y[y1_Idx];
Y[3] = pVBufVirt_Y[y1_Idx + 1];
vIdx = (i / 2) * width + j;
uIdx = vIdx + 1;
V = pVBufVirt_C[vIdx];
U = pVBufVirt_C[uIdx];
R[0] = Y[0] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[1] = Y[1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[2] = Y[2] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[3] = Y[3] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
*(pVBufVirt_R + j) = R[0] < 0 ? 0 : (R[0] > 255 ? 255 : R[0]);
*(pVBufVirt_G + j) = G[0] < 0 ? 0 : (G[0] > 255 ? 255 : G[0]);
*(pVBufVirt_B + j) = B[0] < 0 ? 0 : (B[0] > 255 ? 255 : B[0]);
*(pVBufVirt_R + j + 1) = R[1] < 0 ? 0 : (R[1] > 255 ? 255 : R[1]);
*(pVBufVirt_G + j + 1) = G[1] < 0 ? 0 : (G[1] > 255 ? 255 : G[1]);
*(pVBufVirt_B + j + 1) = B[1] < 0 ? 0 : (B[1] > 255 ? 255 : B[1]);
*(pVBufVirt_R + width + j) = R[2] < 0 ? 0 : (R[2] > 255 ? 255 : R[2]);
*(pVBufVirt_G + width + j) = G[2] < 0 ? 0 : (G[2] > 255 ? 255 : G[2]);
*(pVBufVirt_B + width + j) = B[2] < 0 ? 0 : (B[2] > 255 ? 255 : B[2]);
*(pVBufVirt_R + width + j + 1) = R[3] < 0 ? 0 : (R[3] > 255 ? 255 : R[3]);
*(pVBufVirt_G + width + j + 1) = G[3] < 0 ? 0 : (G[3] > 255 ? 255 : G[3]);
*(pVBufVirt_B + width + j + 1) = B[3] < 0 ? 0 : (B[3] > 255 ? 255 : B[3]);
}
// 两行数据写入 BMP 文件: 以 B、G、R 顺序存储,且 BMP 图像的高度与 RGB24 相反
for(x = 0, k = 0, l = 0; x < width; x++)
{
// B
data[k++] = pVBufVirt_B[width + x];
data[bytePerLine + l++] = pVBufVirt_B[x];
// G
data[k++] = pVBufVirt_G[width + x];
data[bytePerLine + l++] = pVBufVirt_G[x];
// R
data[k++] = pVBufVirt_R[width + x];
data[bytePerLine + l++] = pVBufVirt_R[x];
}
fwrite(data, sizeof(unsigned char), two_rows_len * 3, pfd);
}
fflush(pfd);
printf("done %d!\n", pVBuf->u32TimeRef);
END:
if(pUserPageAddr[2] != NULL)
{
data = NULL;
free(pUserPageAddr[2]);
pUserPageAddr[2] = NULL;
}
if(pUserPageAddr[1] != NULL)
{
pVBufVirt_R = NULL;
pVBufVirt_G = NULL;
pVBufVirt_B = NULL;
free(pUserPageAddr[1]);
pUserPageAddr[1] = NULL;
}
// 解除映射
pVBufVirt_Y = NULL;
pVBufVirt_C = NULL;
HI_MPI_SYS_Munmap(pUserPageAddr[0], size);
pUserPageAddr[0] = NULL;
return HI_SUCCESS;
}
PS:从整份代码中应该可以看出对指针的处理格外重视,因为咱在调试期间被指针引起的内存泄露问题(Segment fault 的原因之一)折腾了很久,都快出现 PTSD 了QAQ…
四、附录
以备不时之需,这里附上直接将整张图进行数据格式转换的函数以及 Win10 上的完整测试代码。
4.1 YUV420p I420/YV12 转 RGB
这里的是 I420 转 RGB 的代码,但是 I420 和 YV12 的区别只在于 U 分量和 V 分量的存放位置是相反的,因此如果需要 YV12 转 RGB,仅需将代码中 U 分量和 V 分量的首地址互换一下即可。
typedef unsigned char byte;
double YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
void ConvertYUV2RGB(unsigned char *yuvFrame, unsigned char *rgbFrame, int width, int height)
{
int uIndex = width * height;
int vIndex = uIndex + ((width * height) >> 2);
int gIndex = width * height;
int bIndex = gIndex * 2;
int temp, x, y;
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
// R分量
temp = (int)(yuvFrame[y * width + x] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[0][2]);
rgbFrame[y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
// G分量
temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1][2]);
rgbFrame[gIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
// B分量
temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[2][1]);
rgbFrame[bIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
}
}
}
4.2 YUV420sp NV21/NV12 转 RGB
这里的是 NV21 转 RGB 的代码,NV12 和 NV21 的区别同第一小节 I420 和 YV12 二者的区别,需要 NV12 转 RGB 只要将 U 分量和 V 分量的索引赋值互换即可。
double YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
void ConvertYUV2RGB(unsigned char *yuvFrame, unsigned char *rgbFrame, int width, int height)
{
const long len = height * width;
// Y与UV数据地址
unsigned char *yData = yuvFrame;
unsigned char *vuData = yData + len;
// R、G、B数据地址
unsigned char *rData = rgbFrame;
unsigned char *gData = rData + len;
unsigned char *bData = gData + len;
int R[4], G[4], B[4];
int Y[4], U, V;
int y0_Idx, y1_Idx, uIdx, vIdx;
for (int i = 0; i < height; i = i + 2)
{
for (int j = 0; j < width; j = j + 2)
{
y0_Idx = i * width + j;
y1_Idx = (i + 1) * width + j;
// Y[0]、Y[1]、Y[2]、Y[3]分别代表 Y00、Y01、Y10、Y11
Y[0] = yData[y0_Idx];
Y[1] = yData[y0_Idx + 1];
Y[2] = yData[y1_Idx];
Y[3] = yData[y1_Idx + 1];
vIdx = (i / 2) * width + j;
uIdx = vIdx + 1;
V = vuData[vIdx];
U = vuData[uIdx];
R[0] = Y[0] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[1] = Y[1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[2] = Y[2] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[3] = Y[3] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
*(rData + y0_Idx) = R[0] < 0 ? 0 : (R[0] > 255 ? 255 : R[0]);
*(gData + y0_Idx) = G[0] < 0 ? 0 : (G[0] > 255 ? 255 : G[0]);
*(bData + y0_Idx) = B[0] < 0 ? 0 : (B[0] > 255 ? 255 : B[0]);
*(rData + y0_Idx + 1) = R[1] < 0 ? 0 : (R[1] > 255 ? 255 : R[1]);
*(gData + y0_Idx + 1) = G[1] < 0 ? 0 : (G[1] > 255 ? 255 : G[1]);
*(bData + y0_Idx + 1) = B[1] < 0 ? 0 : (B[1] > 255 ? 255 : B[1]);
*(rData + y1_Idx) = R[2] < 0 ? 0 : (R[2] > 255 ? 255 : R[2]);
*(gData + y1_Idx) = G[2] < 0 ? 0 : (G[2] > 255 ? 255 : G[2]);
*(bData + y1_Idx) = B[2] < 0 ? 0 : (B[2] > 255 ? 255 : B[2]);
*(rData + y1_Idx + 1) = R[3] < 0 ? 0 : (R[3] > 255 ? 255 : R[3]);
*(gData + y1_Idx + 1) = G[3] < 0 ? 0 : (G[3] > 255 ? 255 : G[3]);
*(bData + y1_Idx + 1) = B[3] < 0 ? 0 : (B[3] > 255 ? 255 : B[3]);
}
}
}
4.3 PC端测试完整代码
留一份 PC 端的终版作为备份。
#include <stdio.h>
#include <stdlib.h>
double YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
int main()
{
char InputFileName[] = "./test.yuv";
char OutputFileName[] = "./test.bmp";
int i, j, k, l, x;
int width = 3840, height = 2160; // 图像分辨率
const long len = height * width; // 一整幅幅图像的长度
const int two_rows_len = 2 * width; // 两行图像的长度
long int bytePerLine = width * 3; // RGB24
unsigned char *data;
unsigned char *image;
unsigned char *image_bmp;
unsigned char *Yaddr; // YUV420sp Y 分量首地址
unsigned char *VUaddr; // YUV420sp VU 分量首地址
unsigned char *Raddr; // RGB24 R 分量首地址
unsigned char *Gaddr; // RGB24 G 分量首地址
unsigned char *Baddr; // RGB24 B 分量首地址
// 申请空间
image = (unsigned char *)malloc(len * 3 / 2);
image_bmp = (unsigned char *)malloc(two_rows_len * 3); // 每次循环只处理两行数据
data = (unsigned char*)malloc(bytePerLine * 2);
if ((NULL == image) || (image_bmp == NULL) || (data == NULL))
{
printf("faied to malloc the image\n");
return -1;
}
/******** 读取yuv 文件 ***********/
FILE *fp_r;
fp_r = fopen(InputFileName, "rb"); // 打开yuv 文件
if (NULL == fp_r)
{
printf("failed to open the fp_r\n");
return -1;
}
fread(image, sizeof(unsigned char), width * height * 3 / 2, fp_r);
fclose(fp_r);
Yaddr = image;
VUaddr = image + len;
/******** 写 bmp 图片 ***********/
unsigned char header[54] = {
// 位图文件头
0x42, 0x4d, //WORD bfType----------- [0,1] 说明文件类型
0, 0, 0, 0, //DWORD bfSize----------- [2,3,4,5] 说明文件大小
0, 0, //WORD bfReserved1------ [6,7] 保留,设置为 0
0, 0, //WORD bfReserved2------ [8,9] 保留,设置为 0
54, 0, 0, 0, //WORD bfOffBits-------- [10,11,12,13] 说明从位图文件头开始到实际的图像数据之间的字节偏移量
// 位图信息头
40, 0, 0, 0, //DWORD biSize----------- [14,15,16,17] 说明位图信息头需要的字节数
0, 0, 0, 0, //LONG biWidth---------- [18,19,20,21] 以像素为单位说明图像宽度
0, 0, 0, 0, //LONG biHeight--------- [22,23,24,25] 以像素为单位说明图像高度
1, 0, //WORD biplanes--------- [26,27] 说明位面数,必须为 1
24, 0, //WORD biCount---------- [28,29] 说明位数/像素,1、2、4、8、24
0, 0, 0, 0, //DWORD biCompression---- [30,31,32,33] 说明图像是否是压缩及压缩类型 BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS
0, 0, 0, 0, //DWORD biSizeImage------ [34,35,36,37] 以字节为单位说明图像大小,必须是 4 的整数倍
0, 0, 0, 0, //LONG biXPelsPerMeter-- [38,39,40,41] 目标设备的水平分辨率,像素/米
0, 0, 0, 0, //LONG biYPelsPerMeter-- [42,43,44,45] 目标设备的垂直分辨率,像素/米
0, 0, 0, 0, //DWORD biClrUsed-------- [46,47,48,49] 说明图像实际用到的颜色数,如果为 0 则颜色数为 2 的 biBitCount 次方
0, 0, 0, 0 //DWORD biClrImportant--- [50,51,52,53] 说明对图像显示有重要影响的颜色索引的数目,如果为 0 则表示都重要
};
long file_size = (long)width * (long)height * 3 + 54;
header[2] = (unsigned char)(file_size & 0x000000ff);
header[3] = (file_size >> 8) & 0x000000ff;
header[4] = (file_size >> 16) & 0x000000ff;
header[5] = (file_size >> 24) & 0x000000ff;
header[18] = width & 0x000000ff;
header[19] = (width >> 8) & 0x000000ff;
header[20] = (width >> 16) & 0x000000ff;
header[21] = (width >> 24) & 0x000000ff;
header[22] = height & 0x000000ff;
header[23] = (height >> 8) & 0x000000ff;
header[24] = (height >> 16) & 0x000000ff;
header[25] = (height >> 24) & 0x000000ff;
FILE *fp_w;
fp_w = fopen(OutputFileName, "wb");
if (fp_w == NULL)
{
printf("failed to open the fp_w\n");
return -1;
}
fwrite(header, sizeof(unsigned char), 54, fp_w);
Raddr = image_bmp;
Gaddr = Raddr + two_rows_len;
Baddr = Gaddr + two_rows_len;
int R[4], G[4], B[4];
int Y[4], U, V;
int y0_Idx, y1_Idx, uIdx, vIdx;
for (i = height - 2; i >= 0; i = i - 2) // 为便于在循环中增加数据写入,从最后两行开始倒序转换
{
// 两行数据格式转换: NV21 -> RGB24
for (j = 0; j < width; j = j + 2)
{
y0_Idx = i * width + j;
y1_Idx = (i + 1) * width + j;
// Y[0]、Y[1]、Y[2]、Y[3]分别代表 Y00、Y01、Y10、Y11
Y[0] = Yaddr[y0_Idx];
Y[1] = Yaddr[y0_Idx + 1];
Y[2] = Yaddr[y1_Idx];
Y[3] = Yaddr[y1_Idx + 1];
vIdx = (i / 2) * width + j;
uIdx = vIdx + 1;
V = VUaddr[vIdx];
U = VUaddr[uIdx];
R[0] = Y[0] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[1] = Y[1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[2] = Y[2] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
R[3] = Y[3] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
G[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
B[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];
*(Raddr + j) = R[0] < 0 ? 0 : (R[0] > 255 ? 255 : R[0]);
*(Gaddr + j) = G[0] < 0 ? 0 : (G[0] > 255 ? 255 : G[0]);
*(Baddr + j) = B[0] < 0 ? 0 : (B[0] > 255 ? 255 : B[0]);
*(Raddr + j + 1) = R[1] < 0 ? 0 : (R[1] > 255 ? 255 : R[1]);
*(Gaddr + j + 1) = G[1] < 0 ? 0 : (G[1] > 255 ? 255 : G[1]);
*(Baddr + j + 1) = B[1] < 0 ? 0 : (B[1] > 255 ? 255 : B[1]);
*(Raddr + width + j) = R[2] < 0 ? 0 : (R[2] > 255 ? 255 : R[2]);
*(Gaddr + width + j) = G[2] < 0 ? 0 : (G[2] > 255 ? 255 : G[2]);
*(Baddr + width + j) = B[2] < 0 ? 0 : (B[2] > 255 ? 255 : B[2]);
*(Raddr + width + j + 1) = R[3] < 0 ? 0 : (R[3] > 255 ? 255 : R[3]);
*(Gaddr + width + j + 1) = G[3] < 0 ? 0 : (G[3] > 255 ? 255 : G[3]);
*(Baddr + width + j + 1) = B[3] < 0 ? 0 : (B[3] > 255 ? 255 : B[3]);
}
// 两行数据写入 BMP 文件: 以 B、G、R 顺序存储,且 BMP 图像的高度与 RGB24 相反
for(x = 0, k = 0, l = 0; x < width; x++)
{
// B
data[k++] = Baddr[width + x];
data[bytePerLine + l++] = Baddr[x];
// G
data[k++] = Gaddr[width + x];
data[bytePerLine + l++] = Gaddr[x];
// R
data[k++] = Raddr[width + x];
data[bytePerLine + l++] = Raddr[x];
}
fwrite(data, sizeof(unsigned char), two_rows_len * 3, fp_w);
}
fclose(fp_w);
free(data);
free(image_bmp);
free(image);
printf("done.\n");
return 0;
}