前言

使用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排列等,找一个适合自己的写法,彻底搞懂就不会再卡壳了。

(完-共勉)

YUV422(UYVY)转RGB565源代码及其讲解.md的更多相关文章

  1. 格式转换至yuv422转 yuv420

    //pYUV为422,yuv为420 /*ok! * brief:pyuv is yuv422sp srcIn, and yuv is yuv420p desOut  */ int YUV422To4 ...

  2. Excel催化剂开源第1波-自定义函数的源代码全公开

    Excel催化剂插件从2018年1月1日开始运营,到今天刚好一周年,在过去一年时间里,感谢社区里的许多友人们的关心和鼓励,得以坚持下来,并收获一定的用户量和粉丝数和少量的经济收入回报和个人知名度的提升 ...

  3. 唐巧的iOS技术博客选摘

    1. 那些被遗漏的objective-c保留字:http://blog.devtang.com/blog/2013/04/29/the-missing-objc-keywords/   2. 使用cr ...

  4. 从零开始写一个武侠冒险游戏-8-用GPU提升性能(3)

    从零开始写一个武侠冒险游戏-8-用GPU提升性能(3) ----解决因绘制雷达图导致的帧速下降问题 作者:FreeBlues 修订记录 2016.06.23 初稿完成. 2016.08.07 增加对 ...

  5. 从零开始写一个武侠冒险游戏-7-用GPU提升性能(2)

    从零开始写一个武侠冒险游戏-7-用GPU提升性能(2) ----把地图处理放在GPU上 作者:FreeBlues 修订记录 2016.06.21 初稿完成. 2016.08.06 增加对 XCode ...

  6. 从零开始写一个武侠冒险游戏-6-用GPU提升性能(1)

    从零开始写一个武侠冒险游戏-6-用GPU提升性能(1) ----把帧动画的实现放在GPU上 作者:FreeBlues 修订记录 2016.06.19 初稿完成. 2016.08.05 增加对 XCod ...

  7. Golang源码探索(三) GC的实现原理

    Golang从1.5开始引入了三色GC, 经过多次改进, 当前的1.9版本的GC停顿时间已经可以做到极短. 停顿时间的减少意味着"最大响应时间"的缩短, 这也让go更适合编写网络服 ...

  8. Golang源码探索(三) GC的实现原理(转)

    Golang从1.5开始引入了三色GC, 经过多次改进, 当前的1.9版本的GC停顿时间已经可以做到极短.停顿时间的减少意味着"最大响应时间"的缩短, 这也让go更适合编写网络服务 ...

  9. 从零开始写一个武侠冒险游戏-0-开发框架Codea简介

    从零开始写一个武侠冒险游戏-0-开发框架Codea简介 作者:FreeBlues 修订记录 2016.06.21 初稿完成. 2016.08.03 增加对 XCode 项目文件的说明. 概述 本游戏全 ...

随机推荐

  1. BZOJ5294 BJOI2018二进制(线段树)

    二进制数能被3整除相当于奇数.偶数位上1的个数模3同余.那么如果有偶数个1,一定存在重排方案使其合法:否则则要求至少有两个0且至少有3个1,这样可以给奇数位单独安排3个1. 考虑线段树维护区间内的一堆 ...

  2. 让你的wordpress在新窗口打开链接

    在使用wordpress过程中笔者发现还有一些不太完善的地方,没有充分考虑到用户体验.所以,在使用wordpress建博之初,我们有必要对wordpress进行一次小改造,让wordpress更个性. ...

  3. 再谈MySql索引

    一.索引简介 MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度. 索引分单列索引(主键索引.唯一索引.普通索引)和组合索引.单列索引,即一个索引只包含单个列 ...

  4. Git 常用操作(二)

    第一次传数据:echo "# miya" >> README.mdgit initgit add README.mdgit commit -m "first ...

  5. mysql:InnoDB行/表级锁实现/事务

    转载:http://book.51cto.com/art/200803/68127.htm 20.3.4 InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL ...

  6. go语言操作mongodb

    Install the MongoDB Go Driver The MongoDB Go Driver is made up of several packages. If you are just ...

  7. 《剑指offer》— JavaScript(24)二叉树中和为某一值的路径

    二叉树中和为某一值的路径 题目描述 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径. 思路 前序遍历二叉树, ...

  8. Java入门:构造方法

    什么是构造方法 类体中有两大形式的成员,其中一个是成员方法(另一个就是成员变量啦~).成员方法又分两种,一种是普通成员方法,另一种是构造方法(有的资料中也称之为构造函数). 所谓构造方法,就是这个类在 ...

  9. ajax的坑

    $('#mkcode').on('click',function(){ $.ajax({ type : 'POST', url : '__URL__/mkcode', data : {}, dataT ...

  10. python文件加入python环境变量

    在python中,把一个python文件加入到python环境变量中,以方便其他python文件调用. 方式一: import sys sys.path.append(r'E:\syz\ly-code ...