准备 NV12 图像
在 github 搜索关键字 “YUVViewer", 找到样例文件:
https://github.com/LiuYinChina/YUVViewer/blob/master/Output/720X576-NV12.yuv
它是二进制文件,没有文件头信息,只有像素内容, 排布方式: 先 Y 平面,再 U V 交错的平面:
Y Y Y Y .... Y Y
Y Y Y Y .... Y Y
...
U V U V ... U V
读取 NV12 图像
以二进制文件形式读取 .yuv 文件。
基于之前实现的 pgm 图像读写功能,将 Y 平面内容写入 .pgm 文件:
void test_nv12_to_rgb()
{
const char* image_path = "/Users/zz/work/YUVViewer/Output/720X576-NV12.yuv";
int width = 720;
int height = 576;
FILE* fin = fopen(image_path, "rb");
const int buf_size = width * height * 3 / 2;
uint8_t* buffer = (uint8_t*) malloc(buf_size);
fread(buffer, buf_size, 1, fin);
savePGM("test.pgm", width, height, buffer);
free(buffer);
fclose(fin);
}
3. 遍历 UV 平面,并转换得到 RGB
对于 UV 平面, 可以看做2通道,宽度、高度都是Y平面的一半:
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
U V U V U V U V
遍历 UV 平面,每个 UV 元素, 对应到4个Y: 2行Y,2列Y:
Y Y
Y Y
U V
每一组 YUV 可以算出一个 RGB, 一共有4组:
y00:
Y _
_ _
U V
y01
_ Y
_ _
U V
y10:
_ _
Y _
U V
y11:
_ _
_ Y
U V
对应的 C++ 代码如下,实现整张图的 NV12 到 RGB 的转换:
void test_nv12_to_rgb()
{
const char* image_path = "/Users/zz/work/YUVViewer/Output/720X576-NV12.yuv";
int width = 720;
int height = 576;
// const char* image_path = "/Users/zz/data/0_45_1280x720.NV12";
// int width = 1280;
// int height = 720;
FILE* fin = fopen(image_path, "rb");
const int buf_size = width * height * 3 / 2;
uint8_t* buffer = (uint8_t*) malloc(buf_size);
fread(buffer, buf_size, 1, fin);
NCV_Image rgbImg;
rgbImg.format = NCV_PIXFMT_RGB;
rgbImg.height = height;
rgbImg.width = width;
rgbImg.pitch[0] = width * 3;
rgbImg.plane[0] = (uint8_t*) malloc(height * width * 3);
savePGM("test.pgm", width, height, buffer);
YuvToRgb_Converter_v2 converter;
uint8_t* y_plane = buffer;
uint8_t* uv_plane = buffer + height * width;
uint8_t* rgb = rgbImg.plane[0];
for (int i = 0; i < height/2; i++)
{
for (int j = 0; j < width/2; j++)
{
uint8_t v = uv_plane[i * width + 2 * j];
uint8_t u = uv_plane[i * width + 2 * j + 1];
int si = i * 2;
int sj = j * 2;
uint8_t y, r, g, b;
/// y00
y = y_plane[si * width + sj];
// B = 1.164(Y - 16) + 2.018(U - 128)
// G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
// R = 1.164(Y - 16) + 1.596(V - 128)
r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );
// R = Y + 1.403V'
// G = Y - 0.344U' - 0.714V'
// B = Y + 1.770U'
// uint8_t r = NCV_CLAMP( (1.164 * (y - 16)) + (2.018 * (v - 128)), 0, 255);
// uint8_t g = NCV_CLAMP( (1.164 * (y - 16)) - (0.813 * (u - 128)) - (0.391 * (v - 128)), 0, 255);
// uint8_t b = NCV_CLAMP( (1.164 * (y - 16)) + (1.596 * (u - 128)), 0, 255);
// uint8_t r = NCV_CLAMP( y + (1.370705 * (v-128)), 0, 255);
// uint8_t g = NCV_CLAMP( y - (0.698001 * (v-128)) - (0.337633 * (u-128)), 0, 255);
// uint8_t b = NCV_CLAMP( y + (1.732446 * (u-128)), 0, 255);
// uint8_t r = y + 1.400*(v-128);
// uint8_t g = y - 0.343*(u-128) - 0.711*(v-128);
// uint8_t b = y + 1.765*(u-128);
// uint8_t r = converter.get_r(y, u, v);
// uint8_t g = converter.get_g(y, u, v);
// uint8_t b = converter.get_b(y, u, v);
rgb[si * width * 3 + sj * 3 + 0] = r;
rgb[si * width * 3 + sj * 3 + 1] = g;
rgb[si * width * 3 + sj * 3 + 2] = b;
/// y01
y = y_plane[(si + 1) * width + sj];
r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );
rgb[(si + 1) * width * 3 + sj * 3 + 0] = r;
rgb[(si + 1) * width * 3 + sj * 3 + 1] = g;
rgb[(si + 1) * width * 3 + sj * 3 + 2] = b;
/// y10
y = y_plane[si * width + sj + 1];
r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );
rgb[si * width * 3 + (sj + 1) * 3 + 0] = r;
rgb[si * width * 3 + (sj + 1) * 3 + 1] = g;
rgb[si * width * 3 + (sj + 1) * 3 + 2] = b;
/// y11
y = y_plane[(si + 1) * width + sj + 1];
r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );
rgb[(si + 1) * width * 3 + (sj + 1) * 3 + 0] = r;
rgb[(si + 1) * width * 3 + (sj + 1) * 3 + 1] = g;
rgb[(si + 1) * width * 3 + (sj + 1) * 3 + 2] = b;
}
}
savePPM("test.ppm", width, height, rgb);
free(buffer);
free(rgb);
fclose(fin);
}
4. 整理和总结
通过搜索github,得到了用于测试的 NV12 图像。
基于对 NV12 图像格式的理解,用C语言读取了 NV12 图像内容。
遍历 NV12 的 UV 平面,并得到对应的 Y 平面的像素点, 从而得到了一组 RGB 像素值; 由于每个 UV 是被 2x2 的 4个Y共享的,因此, 先获取剩余的3个 Y, 然后算出三组 RGB 像素值。 这样一来就写入了 dst rgb 图像的 2x2 像素区域,进而完成了整个 NV12 到 RGB 的图像格式转换。
基于先前的 pgm 和 ppm 图像格式保存函数, 得到了图像文件, 肉眼观察验证了正确性。