• YUV422(UYVY)转RGB565源代码及其讲解.md


    前言

    使用zmm220核心板,IFACE102版本的内核等,4300型号的LCD,XC7011_SC1145摄像头,亲测有效。
    本文章使用Markdown写法。

    源码

    需要注意的是,这份代码适用于UYVYUYVYUYVYRGB565,其它类型的转换,看完后面的讲解后自然会举一反三。可以设置一个参数,用于选择数据类型,函数体中用switch case。这里没进行这个处理。

    /*
    * YUV422打包数据,UYVY,转换为RGB565,
    * inBuf -- YUV data
    * outBuf -- RGB565 data
    * imgWidth,imgHeight -- image width and height
    * cvtMethod -- 无效参数
    */
    int convert_uyvy_to_rgb(unsigned char *inBuf, unsigned char *outBuf, int imgWidth, int imgHeight, int cvtMethod)
    {
    	int rows ,cols;	/* 行列标志 */
    	int y, u, v, r, g, b;	/* yuv rgb 相关分量 */
    	unsigned char *YUVdata, *RGBdata;	/* YUV和RGB数据指针 */
    	int Ypos, Upos, Vpos;	/* Y U V在数据缓存中的偏移 */
    	unsigned int i = 0;
    
    	YUVdata = inBuf;
    	RGBdata = outBuf;
    #if 0
    	/*  YUYV */
    	Ypos = 0;
    	Upos = Ypos + 1;
    	Vpos = Upos + 2;
    
    	/* YVYU */
    	Ypos = 0;
    	Vpos = Ypos + 1;
    	Upos = Vpos + 2;
    #endif
    
    #if 1   /* UYVY */
    	Ypos = 1;
    	Upos = Ypos - 1;
    	Vpos = Ypos + 1;
    #endif
    
    	/* 每个像素两个字节 */
    	for(rows = 0; rows < imgHeight; rows++)
    	{
    		for(cols = 0; cols < imgWidth; cols++)
    		{
    			/* 矩阵推到,百度 */
    			y = YUVdata[Ypos];
    			u = YUVdata[Upos] - 128;
    			v = YUVdata[Vpos] - 128;
    
    			r = y + v + ((v * 103) >> 8);
    			g = y - ((u * 88) >> 8) - ((v * 183) >> 8);
    			b = y + u + ((u * 198) >> 8);
    
    			r = r > 255?255:(r < 0?0:r);
    			g = g > 255?255:(g < 0?0:g);
    			b = b > 255?255:(b < 0?0:b);
    
    			/* 从低到高r g b */
    			*(RGBdata ++) = (((g & 0x1c) << 3) | (b >> 3));	/* g低5位,b高5位 */
    			*(RGBdata ++) = ((r & 0xf8) | (g >> 5));	/* r高5位,g高3位 */
    
    			/* 两个字节数据中包含一个Y */
    			Ypos += 2;
    			//Ypos++;
    			i++;
    			/* 每两个Y更新一次UV */
    			if(!(i & 0x01))	
    			{
    				Upos = Ypos - 1;
    				Vpos = Ypos + 1;
    			}
    
    		}
    	}
    
    	return 0;
    }
    

    这里面有几个关键点,一开始写错了,最好的办法是在纸上画出来,第二次重写的时候,在纸上画出来,编译运行一次性通过。光凭想象非常容易出纰漏或者进入思路误区。

    代码分析

    YUV三个分量的关系

    首先你要确定ISP输出给CPU的控制器接口cim的数据排列方式,也就是上面传入的参数inBuf中Y、U、V三分量的存储方式。如:YUYV、YVYU、UYVY、VYUY等。特别要注意当配置寄存器以便输出黑白图的时候,要确定ISP输出的是YUV400,也就是UV也占字节,只不过都是0,如XC7011_SC1145;还是只单独输出Y,如:gc0308.
    这个可以通过工具验证,建议使用海康的YUVplayer,比pYUV好用很多,也准确许多。
    这里分析UYVY的存储方式,见下表:

    YUV三分量各占一个字节,每两个Y共享一对UV,所以每个像素两个字节,在代码中的表现就是,Y递增两次才刷新一次UV,注意这里Y的递增是2,和网上的版本不同
    据此可以很清晰的看出,对于UYVY来说,Y、U、V三个分量的关系如下:

    Y=i和Y=i+2时
    U=i-1;
    V=i+1;
    

    循环遍历

    这个很容易搞错,特别是在做图像的裁剪时,一不注意就会数组越界导致段错误。
    处理YUV422、RGB888等数据有个很实用的规律:对于遍历图像缓存数据的操作,特别是一个像素点对应多个字节的时候,for循环的遍历参数i、j等参考像素点的宽和高递增;而在循环体中涉及到图像缓存数据的操作,一律按照字节数来操作。RGB565数据处理也可以参考这个规律,只不过增加了数据的裁剪增补而已。
    比如分析上面的例子:
    图像的像素点宽高是imgHeight、imgWidth,所以for循环的写法就是:

    for(rows = 0; rows < imgHeight; rows++)
    {
       for(cols = 0; cols < imgWidth; cols++)
       {
       }
    }
    

    循环的参考条件是像素点的宽和高,这样能保证大方向不会出错——遍历每个像素点,不会漏数据(这是对每次递增1来说的,还要看循环体的具体操作)。
    如果每个像素点都要操作,且一个像素点对应两个字节,那么在循环体中你一次就要处理两个字节的数据,这能明白吧?但是针对UYVY这种YUV数据来说,参见上表,一般理解为每4个字节对应两个像素,因为这样理解包含了YUV数据的特性,不会出现Y占一个字节,UV各占半个字节的错觉。所以上面代码的写法思路就是:保证最里层的循环体执行两次——遍历两个像素点——处理4个字节数据——更新两次Y——更新一次UV,这点很关键,否则循环体代码越写越乱,分析如下:

    /* 矩阵推到,百度 */
    			y = YUVdata[Ypos];
    			u = YUVdata[Upos] - 128;
    			v = YUVdata[Vpos] - 128;
    
    			r = y + v + ((v * 103) >> 8);
    			g = y - ((u * 88) >> 8) - ((v * 183) >> 8);
    			b = y + u + ((u * 198) >> 8);
    
    			r = r > 255?255:(r < 0?0:r);
    			g = g > 255?255:(g < 0?0:g);
    			b = b > 255?255:(b < 0?0:b);
    
    			/* 从低到高r g b */
    			*(RGBdata ++) = (((g & 0x1c) << 3) | (b >> 3));	/* g低5位,b高5位 */
    			*(RGBdata ++) = ((r & 0xf8) | (g >> 5));	/* r高5位,g高3位 */
    

    这部分代码是网上提供的YUV和RGB的转换公式,实测证明有效,但是要注意几点

    • 括号不能少

    • 要做数值边界判断和处理

    • RGB的存储方式
      yuv分量转换的来的rgb分量,都是各占一个字节,实际的RGB中,R、B各占5bits,G占6bits,总共是16bits两个字节。在这两个字节中,低字节存放g的低3位(8bits中高6bits中的低3bits)和b的全部(8bits中的高5位),高字节存放r的全部(8bits中的高5位)和g的高3位(8bits中高6bits中的高3bits)

    /* 两个字节数据中包含一个Y */
    			Ypos += 2;
    			//Ypos++;
    			i++;
    			/* 每两个Y更新一次UV */
    			if(!(i & 0x01))	
    			{
    				Upos = Ypos - 1;
    				Vpos = Ypos + 1;
    			}
    

    这个应该很好懂了,j++两次,循环体执行两次,Y更新了两次,UV更新了一次。

    结束语

    明白了这种写法的原理,你就可以举一反三了,比如:每次处理两个像素、数据按照YUYV排列等,找一个适合自己的写法,彻底搞懂就不会再卡壳了。
    (完-共勉)

  • 相关阅读:
    hdu 5936 2016ccpc 杭州
    bzoj 1218: [HNOI2003]激光炸弹
    bzoj 1296: [SCOI2009]粉刷匠
    桃子到底有多少
    计算x的n次方
    计算x的n次方
    菲波拉契数列
    菲波拉契数列
    八皇后(N皇后)问题算法程序(回溯法)
    八皇后(N皇后)问题算法程序(回溯法)
  • 原文地址:https://www.cnblogs.com/fjutacm/p/6432007.html
Copyright © 2020-2023  润新知