在YUV你究竟了解多少和YUV格式大全两篇文章中,我们已经对YUV图像格式的数据排列方式,格式类型等有了个大概的认识,这篇文章中我们会给大家介绍下YUV图像的处理,下面还是以较常见的 NV21格式为例。
YUV 图像的定义、加载、保存及内存释放。
// 定义结构体
typedef struct
{
int width; // 图片宽
int height; // 图片高
unsigned char *yPlane; // Y 平面指针
unsigned char *uvPlane; // UV 平面指针
} YUVImage;
// 加载YUV图像
void LoadYUVImage(const char *filePath, YUVImage *yUV)
{
FILE *fpData = fopen(filePath, "rb+");
if (fpData)
{
fseek(fpData, 0, SEEK_END);
int len = ftell(fpData);
yUV->yPlane = malloc(len);
fseek(fpData, 0, SEEK_SET);
fread(yUV->yPlane, 1, len, fpData);
fclose(fpData);
fpData = NULL;
}
yUV->uvPlane = yUV->yPlane + yUV->width * yUV->height;
}
// 保存YUV图像
void SaveYUVImage(const char *filePath, YUVImage *yUV)
{
FILE *fp = fopen(filePath, "wb+");
if (fp)
{
fwrite(yUV->yPlane, yUV->width * yUV->height, 1, fp);
fwrite(yUV->uvPlane, yUV->width * (yUV->height >> 1), 1, fp);
}
}
void ReleaseYUVImage(YUVImage *yUV)
{
if (yUV->yPlane)
{
free(yUV->yPlane);
yUV->yPlane = NULL;
yUV->uvPlane = NULL;
}
}
NV21 图像旋转
以顺时针旋转 90 度为例,Y 和 UV 两个平面分别从平面左下角进行旋转拷贝,需要注意的是一对 UV 分量作为一个整体进行拷贝,如下所示:
Y00 Y01 Y02 Y03 Y30 Y20 Y10 Y00
Y10 Y11 Y12 Y13 旋转90度 Y31 Y21 Y11 Y01
Y20 Y21 Y22 Y23 Y32 Y22 Y12 Y02
Y30 Y31 Y32 Y33 Y33 Y23 Y13 Y03
旋转90度
V00 U00 V01 U01 V10 U10 V00 U00
V10 U10 V11 U11 V11 U11 V01 U01
代码如下:
void RotateYUV(YUVImage *pOriImg, YUVImage *pDstImg, int angle)
{
int yIndex = 0;
int uvIndex = 0;
switch (angle)
{
case 90://Y 和 UV 两个平面分别从平面左下角进行纵向拷贝,UV一起转
{
// y plane
for (int i = 0; i < pOriImg->width; i++) {
for (int j = 0; j < pOriImg->height; j++) {
*(pDstImg->yPlane + yIndex) = *(pOriImg->yPlane + (pOriImg->height - j - 1) * pOriImg->width + i);
yIndex++;
}
}
//uv plane
for (int i = 0; i < pOriImg->width; i += 2) {
for (int j = 0; j < pOriImg->height / 2; j++) {
*(pDstImg->uvPlane + uvIndex) = *(pOriImg->uvPlane + (pOriImg->height / 2 - j - 1) * pOriImg->width + i);
*(pDstImg->uvPlane + uvIndex + 1) = *(pOriImg->uvPlane + (pOriImg->height / 2 - j - 1) * pOriImg->width + i + 1);
uvIndex += 2;
}
}
}
break;
case 180: // 顺时针旋转 180 度时从平面右下角进行横向拷贝,UV一起转
{
// y plane
for (int i = 0; i < pOriImg->height; i++) {
for (int j = 0; j < pOriImg->width; j++) {
*(pDstImg->yPlane + yIndex) = *(pOriImg->yPlane + (pOriImg->height - 1 - i) * pOriImg->width + pOriImg->width - 1 - j);
yIndex++;
}
}
//uv plane
for (int i = 0; i < pOriImg->height / 2; i++) {
for (int j = 0; j < pOriImg->width; j += 2) {
*(pDstImg->uvPlane + uvIndex) = *(pOriImg->uvPlane + (pOriImg->height / 2 - 1 - i) * pOriImg->width + pOriImg->width - 2 - j);
*(pDstImg->uvPlane + uvIndex + 1) = *(pOriImg->uvPlane + (pOriImg->height / 2 - 1 - i) * pOriImg->width + pOriImg->width - 1 - j);
uvIndex += 2;
}
}
}
break;
case 270:// 顺时针旋转 270 度时从平面右上角进行纵向拷贝
{
// y plane
for (int i = 0; i < pOriImg->width; i++) {
for (int j = 0; j < pOriImg->height; j++) {
*(pDstImg->yPlane + yIndex) = *(pOriImg->yPlane + j * pOriImg->width + (pOriImg->width - i - 1));
yIndex++;
}
}
//uv plane
for (int i = 0; i < pOriImg->width; i += 2) {
for (int j = 0; j < pOriImg->height / 2; j++) {
*(pDstImg->uvPlane + uvIndex + 1) = *(pOriImg->uvPlane + j * pOriImg->width + (pOriImg->width - i - 1));
*(pDstImg->uvPlane + uvIndex) = *(pOriImg->uvPlane + j * pOriImg->width + (pOriImg->width - i - 2));
uvIndex += 2;
}
}
}
break;
default:
break;
}
}
NV21 图片缩放
将 2x2 的 NV21 图缩放成 4x4 的 NV21 ,原数据横向每个像素的 Y 分量向右拷贝 1(放大倍数-1)次,纵向每列元素以列为单位向下拷贝 1(放大倍数-1)次.
Y00 Y01 Y00 Y00 Y01 Y01
Y10 Y11 缩放4*4 Y00 Y00 Y01 Y01
Y10 Y10 Y11 Y11
Y10 Y10 Y11 Y11
V00 U00 V00 U00 V00 U00
V00 U00 V00 U00
将 4x4 的 NV21 图缩放成 2x2 的 NV21 ,实际上就是进行采样。
Y00 Y01 Y02 Y03
Y10 Y11 Y12 Y13 Y00 Y02
Y20 Y21 Y22 Y23 Y20 Y22
Y30 Y31 Y32 Y33
缩放为2*2
V00 U00 V01 U01 V00 U00
V10 U10 V11 U11
代码如下:
void ResizeYUV(YUVImage *pOriImg, YUVImage *pDstImg)
{
if (pOriImg->width > pDstImg->width)
{
//缩小
int x_scale = pOriImg->width / pDstImg->width;
int y_scale = pOriImg->height / pDstImg->height;
for (size_t i = 0; i < pDstImg->height; i++)
{
for (size_t j = 0; j < pDstImg->width; j++)
{
*(pDstImg->yPlane + i*pDstImg->width + j) = *(pOriImg->yPlane + i * y_scale *pOriImg->width + j * x_scale);
}
}
for (size_t i = 0; i < pDstImg->height / 2; i++)
{
for (size_t j = 0; j < pDstImg->width; j += 2)
{
*(pDstImg->uvPlane + i*pDstImg->width + j) = *(pOriImg->uvPlane + i * y_scale *pOriImg->width + j * x_scale);
*(pDstImg->uvPlane + i*pDstImg->width + j + 1) = *(pOriImg->uvPlane + i * y_scale *pOriImg->width + j * x_scale + 1);
}
}
}
else
{
// 放大
int x_scale = pDstImg->width / pOriImg->width;
int y_scale = pDstImg->height / pOriImg->height;
for (size_t i = 0; i < pOriImg->height; i++)
{
for (size_t j = 0; j < pOriImg->width; j++)
{
int yValue = *(pOriImg->yPlane + i *pOriImg->width + j);
for (size_t k = 0; k < x_scale; k++)
{
*(pDstImg->yPlane + i * y_scale * pDstImg->width + j * x_scale + k) = yValue;
}
}
unsigned char *pOriRow = pDstImg->yPlane + i * y_scale * pDstImg->width;
unsigned char *pDstRow = NULL;
for (size_t l = 1; l < y_scale; l++)
{
pDstRow = (pDstImg->yPlane + (i * y_scale + l)* pDstImg->width);
memcpy(pDstRow, pOriRow, pDstImg->width * sizeof(unsigned char ));
}
}
for (size_t i = 0; i < pOriImg->height / 2; i++)
{
for (size_t j = 0; j < pOriImg->width; j += 2)
{
int vValue = *(pOriImg->uvPlane + i *pOriImg->width + j);
int uValue = *(pOriImg->uvPlane + i *pOriImg->width + j + 1);
for (size_t k = 0; k < x_scale * 2; k += 2)
{
*(pDstImg->uvPlane + i * y_scale * pDstImg->width + j * x_scale + k) = vValue;
*(pDstImg->uvPlane + i * y_scale * pDstImg->width + j * x_scale + k + 1) = uValue;
}
}
unsigned char *pOriRow = pDstImg->uvPlane + i * y_scale * pDstImg->width;
unsigned char *pDstRow = NULL;
for (size_t l = 1; l < y_scale; l++)
{
pDstRow = (pDstImg->uvPlane + (i * y_scale + l)* pDstImg->width);
memcpy(pDstRow, pOriRow, pDstImg->width * sizeof(unsigned char ));
}
}
}
}
NV21 图像裁剪
Y00 Y01 Y02 Y03
Y10 Y11 Y12 Y13 Y22 Y23
Y20 Y21 Y22 Y23 Y32 Y33
Y30 Y31 Y32 Y33
按照横纵坐标(2,2)
V00 U00 V01 U01
V10 U10 V11 U11 V11 U11
代码如示:
void CropYUV(YUVImage *pOriImg, int x_offSet, int y_offSet, YUVImage *pDstImg)
{
// 确保裁剪区域不存在内存越界
int cropWidth = pOriImg->width - x_offSet;
cropWidth = cropWidth > pDstImg->width ? pDstImg->width : cropWidth;
int cropHeight = pOriImg->height - y_offSet;
cropHeight = cropHeight > pDstImg->height ? pDstImg->height : cropHeight;
unsigned char *pOriCursor = NULL;
unsigned char *pDstCursor = NULL;
//crop yPlane
for (size_t i = 0; i < cropHeight; i++)
{
pOriCursor = pOriImg->yPlane + (y_offSet + i) * pOriImg->width + x_offSet;
pDstCursor = pDstImg->yPlane + i * pDstImg->width;
memcpy(pDstCursor, pOriCursor, sizeof(unsigned char ) * cropWidth);
}
//crop uvPlane
for (size_t i = 0; i < cropHeight / 2; i++)
{
pOriCursor = pOriImg->uvPlane + (y_offSet / 2 + i) * pOriImg->width + x_offSet;
pDstCursor = pDstImg->uvPlane + i * pDstImg->width;
memcpy(pDstCursor, pOriCursor, sizeof(unsigned char ) * cropWidth);
}
}
微信公众号首发,欢迎关注微信公众号:江湖修行,感谢共同进步的你。