NV21格式图像旋转
刚刚接触Android开发,开始学习一些图像像素格式,需要完成NV21的图像进行旋转,这里我们将从原理开始分析,分享一下我对NV21图像旋转的理解与实现。
基础知识
在开始旋转NV21图像时,需要对图像的RGB格式与YUV格式有一定的了解,没有概念的同学可以看看这儿:
常用视频像素格式NV12、NV2、I420、、Yv12、YUYV
NV21属于YUV420,其采样方式如下:
上图为一个4*4的图像,即宽度width=4,高度height=4;
于是,其图像数据特点是四个Y共用一个组UV,且共用方式是Y1、Y2、Y5、Y6公用(UV)1,而Y3、Y4、Y7、Y8共用(UV)2,这是由它的采样方式得到的<共用方式很重要>。
需要记住的是:在内存中,YUV的存储一般为,Y是连续存储,而UV打包一起存储,即在内存中其存储方式如下:当用一个一维数组data来保存该数据时,其在内存中的分布则是:
data= {Y1,Y2,Y3,Y4,Y5,Y6,Y7,Y8,Y9,Y10,Y11,Y12,Y13,Y14,Y15,Y16,V1,U1,V2,U2,V3,U3,V4,U4}
这么看好像不是很好懂,咱么换种方式:
{
Y1, Y2, Y3, Y4,
Y5, Y6, Y7, Y8,
Y9, Y10, Y11, Y12,
Y13, Y14, Y15, Y16,
V1, U1, V2, U2,
V3, U3, V4, U4
}
同样还是一维数组,只是这次根据图像的宽度和高度把一维数组的堆放方式换一下,可以看到Y1~Y16即为16个像素的Y分量的值(记住:4*4的图像),而Y1、Y2、Y3、Y4共用V1、U1。
逆时针旋转90°
为了对上午进行逆时针旋转,首先将旋转后图像数据在内存中的分布画出来,一开始我画出的图像如下:
{
Y4, Y8, Y12, Y16, U2, U4
Y3, Y7, Y11, Y15, V2, V4
Y2, Y6, Y10, Y14, U1, U3
Y1, Y5, Y9, Y13, V1, V3
}
<错误方式...>
当时我怎么也没有想明白,这样旋转后,即使Y1、Y2、Y5、Y6依然可以共用V1、U1,但它在内存中还如何存储?因为这个数据在内存中使用一个一维数组连续存储,这样的话岂不是Y分量的存储断开了?…
之后,从采样方式出发想了想之后,或许应该是把Y分量和(UV)分量分开旋转,如下:
{
Y4, Y8, Y12, Y16,
Y3, Y7, Y11, Y15,
Y2, Y6, Y10, Y14,
Y1, Y5, Y9, Y13,
V2, U2, V4, U4,
V1, U1, V3, U3
}
< V 和对应的 U 应该为一个整体共同旋转>
于是,Y4、Y8、Y3、Y7仍然共用V2、U2;Y2、Y6、Y1、Y5仍然共用V1、U1;
下面给出一段简单的测试代码:
定义类
class YUVImage{
public:
YUVImage();
int rotateTest(const int* dataIn, int width, int height, int degree, int* dataOut);
int showIamge(const int* data, int width, int height);
};
成员方法实现
int YUVImage::rotateTest(const int* dataIn, int width, int height, int degree, int* dataOut){
int k = 0;
int height2 = (int)(1.5 * height);
//旋转Y部分
for (int i = width - 1; i >= 0; --i){
for (int j = 0; j< height; ++j){
*(dataOut + k) = *(dataIn + j*width + i);
++k;
}
}
//旋转UV部分
for (int i = width - 1; i>0; --i){
for (int j = height; j< height2; ++j){
*(dataOut + k) = *(dataIn + j*width + i - 1);
++k;
*(dataOut + k) = *(dataIn + j*width + i);
++k;
}
--i;
}
return 0;
}
//显示图像方法
int YUVImage::showIamge(const int* data, int width, int height){
int height2 = int(1.5 * height);
for (int i = 0; i< height2; ++i){
for (int j = 0; j<width; ++j){
cout << *(data + i * width + j) << " ";
}
cout << endl;
}
cout << endl;
return 0;
测试
int _tmain(int argc, _TCHAR* argv[])
{
int old[48] = { 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 38, 41, 42, 43, 44, 45, 46, 47, 48, 81, 82, 83, 84, 85, 86, 87, 88, 91, 92, 93, 94, 95, 96, 97, 98 };
//NV21图像宽度width=8,高度height=4
int* pOld = old;
int width = 8;
int height = 4;
int height2 = (int)(1.5*height);
//NV21 image rotate test
int temp1[48] = { 0 };
int* pRot = temp1;
YUVImage nv21;
nv21.rotateTest(pOld, width, height, 90, pRot);
cout << "Original NV21 data:" << endl;
nv21.showIamge(pOld, width, height);
cout << "Rotate NV21 data:" << endl;
nv21.showIamge(pRot, width, height);
}
下面是测试结果:
原始数据
旋转后的数据
之后,做了一些NV21图像平移,缩放,画矩形的操作,并输出执行时间消耗,可以在这里下载:
https://download.csdn.net/download/cgwang_1580/10392863
最后,给出几个比较有用的工具,用于查看YUV图像:
https://download.csdn.net/download/ming825569719/10016190
http://www.sunrayimage.com/(YUV tools 没法查看NV21数据)