概述
最近的一个测试项目需要软件实现YUV420sp图像的水平和垂直拼接。程序对时间复杂度和空间复杂度没什么要求,就自己编写代码实现一下,记录一下思路。
YUV420sp
YUV是一种原始图像格式,没有经过编码,比较方便软件处理。本文处理的图像格式为YUV420sp,图像数据中的亮度Y和色度UV分离,并在内存中连续存储。比如一张 4 × 2 图像,以YUV420sp的格式存储就是:Y1Y2Y3Y4Y5Y6Y7Y8U1V1U2V2,
所占的内存空间为:width * height * 3 / 2
了解了YUV420sp图像的存储格式,实现图像的拼接就变得比较简单,无非就是内存操作。
垂直拼接
首先是垂直拼接。根据YUV图像的存储格式,垂直拼接只需要将两幅图像的Y拼接在一起,UV拼接在一起,再把UV拼接在Y的后面即可。用图片描述如下:
上图:uY1 uY2 uY3 uY4 uY5 uY6 uY7 uY8 uU1 uV1 uU2 uV2
下图:dY1 dY2 dY3 dY4 dY5 dY6 dY7 dY8 dU1 dV1 dU2 dV2
拼接:uY1 uY2 uY3 uY4 uY5 uY6 uY7 uY8 dY1 dY2 dY3 dY4 dY5 dY6 dY7 dY8 uU1 uV1 uU2 uV2 dU1 dV1 dU2 dV2
用代码实现就是
int VerticalSplicing(FILE *image1, FILE *image2, FILE *result)
{
unsigned char *imgY1, *imgY2;
// malloc memory
imgY1 = (unsigned char *)malloc(imgWidth * imgHeight * 3 / 2);
if(NULL == imgY1)
{
printf("Malloc memory for %s faild.\n", VNAME(imgY1));
return -1;
}
imgY2 = (unsigned char *)malloc(imgWidth * imgHeight * 3 / 2);
if(NULL == imgY1)
{
printf("Malloc memory for %s faild.\n", VNAME(imgY2));
return -1;
}
// Read data from image
if((imgWidth * imgHeight * 3 / 2) != fread(imgY1, 1, imgWidth * imgHeight * 3 / 2, image1))
{
printf("Read image1 faild!\n");
return -1;
}
if((imgWidth * imgHeight * 3 / 2) != fread(imgY2, 1, imgWidth * imgHeight * 3 / 2, image2))
{
printf("Read image2 faild!\n");
return -1;
}
// write YUV
fwrite(imgY1, 1, imgWidth * imgHeight, result);
fwrite(imgY2, 1, imgWidth * imgHeight, result);
fwrite(imgY1 + imgWidth * imgHeight, 1, imgWidth * imgHeight / 2, result);
fwrite(imgY1 + imgWidth * imgHeight, 1, imgWidth * imgHeight / 2, result);
free(imgY1);
free(imgY2);
return 0;
}
水平拼接
相较于垂直拼接,水平拼接稍微复杂一些,需要对图像的每一行进行处理。需要将两幅图对应行的Y连接在一起组成新的Y行,对应行的UV连接在一起组成新的UV行,再把新的UV连接到Y后面。用图片描述如下:
左图:uY1 uY2 uY3 uY4 uY5 uY6 uY7 uY8 uU1 uV1 uU2 uV2
右图:dY1 dY2 dY3 dY4 dY5 dY6 dY7 dY8 dU1 dV1 dU2 dV2
拼接:uY1 uY2 uY3 uY4 dY1 dY2 dY3 dY4 uY5 uY6 uY7 uY8 dY5 dY6 dY7 dY8 uU1 uV1 dU1 dV1 uU2 uV2 dU2 dV2
用代码实现比纵向拼接也稍微复杂一些
int HorizontalSplicing(FILE *image1, FILE *image2, FILE *result)
{
int i;
unsigned char *imgY1, *imgY2;
unsigned char *temp_y1, *temp_y2, *temp_uv1, *temp_uv2;
unsigned char *temp_yuv, *out_yuv;
//malloc memory
imgY1 = (unsigned char *)malloc(imgWidth * imgHeight * 3 / 2); //image1
if(NULL == imgY1)
{
printf("Malloc memory for %s faild.\n", VNAME(imgY1));
return -1;
}
imgY2 = (unsigned char *)malloc(imgWidth * imgHeight * 3 / 2); //image2
if(NULL == imgY2)
{
printf("Malloc memory for %s faild.\n", VNAME(imgY2));
return -1;
}
out_yuv = (unsigned char *)malloc(imgWidth * imgHeight * 3); // output
if(NULL == out_yuv)
{
printf("Malloc memory for %s faild.\n", VNAME(out_yuv));
return -1;
}
// Read data from image
if((imgWidth * imgHeight * 3 / 2) != fread(imgY1, 1, imgWidth * imgHeight * 3 / 2, image1))
{
printf("Read image1 faild!\n");
return -1;
}
if((imgWidth * imgHeight * 3 / 2) != fread(imgY2, 1, imgWidth * imgHeight * 3 / 2, image2))
{
printf("Read image2 faild!\n");
return -1;
}
temp_y1 = imgY1;
temp_y2 = imgY2;
temp_uv1 = imgY1 + imgWidth * imgHeight; // Y + offset
temp_uv2 = imgY2 + imgWidth * imgHeight;
temp_yuv = out_yuv;
// Copy Y
for(i = 0; i < imgHeight; i++)
{
memcpy(temp_yuv, temp_y1, imgWidth);
temp_y1 += imgWidth;
temp_yuv += imgWidth;
memcpy(temp_yuv, temp_y2, imgWidth);
temp_y2 += imgWidth;
temp_yuv += imgWidth;
}
// Copy UV
for(i = 0; i < imgHeight / 2; i++)
{
memcpy(temp_yuv, temp_uv1, imgWidth);
temp_uv1 += imgWidth;
temp_yuv += imgWidth;
memcpy(temp_yuv, temp_uv2, imgWidth);
temp_uv2 += imgWidth;
temp_yuv += imgWidth;
}
// Write data to file
fwrite(out_yuv, 1, imgWidth * imgHeight * 3, result);
free(imgY1);
free(imgY2);
free(out_yuv);
return 0;
}
结果
使用了两张640 * 360 的YUV图像进行了测试,可以实现垂直拼接和水平拼接,多次拼接还可以实现4窗格。
最新整理的程序已经提交到gitee上,地址为:https://gitee.com/mrs1023/image_process