目录

引言

知识直通车:

YUV2RGB原语

YUV2RGB NEON加速


引言

opencv4.x版本开始对YUV2RGB做了neon加速,这篇文章对转换源码进行了详细分析,想要了解实现细节的同学可以做个了解,也比较简单。

知识直通车:

对YUV结构不了解的看这篇:https://blog.csdn.net/xjhhjx/article/details/80291465

对YUV2RGB不了解的看这篇:https://blog.csdn.net/xiaoyafang123/article/details/82153279

YUV2RGB原语

/***********************************************************************
入参:unsigned char* dst_data:目标图像指针 size_t dst_step:目标图像每行间隔数据的大小=通道数x宽度int dst_width:目标图像宽度int dst_height:目标图像高度size_t src_step:源图像每行间隔数据的大小=通道数x宽度 const unsigned char* y1:源图像y数据指针const unsigned char* uv:源图像uv数据指针
*************************************************************************/
inline void cvtYUV420sp2RGB(unsigned char* dst_data, size_t dst_step, int dst_width, int dst_height, size_t src_step, const unsigned char* y1, const unsigned char* uv)
{for (int j = 0; j < dst_height; j += 2, y1 += (src_step << 1), uv += src_step) {unsigned char* row1 = dst_data + dst_step * j;         //目标图像当前第一行数据指针unsigned char* row2 = dst_data + dst_step * (j + 1);   //目标图像当前第二行数据指针const unsigned char* y2 = y1 + src_step;               //源图像当前第二行数据指针int i = 0;//每次求得目标图像的4个像素值(两行,每行两个,每个像素储存rgb三个值,row+6)for (; i < dst_width; i += 2, row1 += 6, row2 += 6){//uIdx决定uv的存储顺序,按照YUV格式决定,NV12为UV的存储顺序,NV21为VU的存储顺序unsigned char u = uv[i + 0 + uIdx]; unsigned char v = uv[i + 1 - uIdx];unsigned char vy01 = y1[i];unsigned char vy11 = y1[i + 1];unsigned char vy02 = y2[i];unsigned char vy12 = y2[i + 1];//uv+y转换rgb主函数cvtYuv42xxp2RGB8<bIdx, dcn, true>(u, v, vy01, vy11, vy02, vy12, row1, row2);}}
}

上面为YUV2RGB 的主函数,思路很简单啊:

上下行分别2个y共用一个UV,那么计算的时候直接通过原图像的第一行y1及第二行y2的指针再加上uv,

即可求得目标图像的4个像素的rgb值,分别对于代码的row1及row2(rgb值按通道排列即hwc格式因此for循环每次+6,6=2x3)详细的解释可参考注释。

template<int bIdx, int dcn, bool is420>
static inline void cvtYuv42xxp2RGB8(const unsigned char u, const unsigned char v,const unsigned char vy01, const unsigned char vy11, const unsigned char vy02, const unsigned char vy12,unsigned char* row1, unsigned char* row2)
{int ruv, guv, buv;//计算rgb中与uv相关的分量ruv、guv、buvuvToRGBuv(u, v, ruv, guv, buv);unsigned char r00, g00, b00, a00;unsigned char r01, g01, b01, a01;//结合y以及uv相关的分量ruv、guv、buv计算最终的rgb分量的值yRGBuvToRGBA(vy01, ruv, guv, buv, r00, g00, b00, a00);yRGBuvToRGBA(vy11, ruv, guv, buv, r01, g01, b01, a01);//bIdx为0则为bgr格式,bIdx为2则为rgb格式row1[2 - bIdx] = r00;row1[1] = g00;row1[bIdx] = b00;if (dcn == 4)row1[3] = a00;//如果转换为rgba格式,a通道赋值为0xffrow1[dcn + 2 - bIdx] = r01;row1[dcn + 1] = g01;row1[dcn + 0 + bIdx] = b01;if (dcn == 4)row1[7] = a01;//如果源图片为420采样模式,代表4个y共用uv,因此需要计算第二行的像素值,//若为422或者444采样格式则不需要计算,具体可以参考上面给的直通车链接if (is420){unsigned char r10, g10, b10, a10;unsigned char r11, g11, b11, a11;yRGBuvToRGBA(vy02, ruv, guv, buv, r10, g10, b10, a10);yRGBuvToRGBA(vy12, ruv, guv, buv, r11, g11, b11, a11);row2[2 - bIdx] = r10;row2[1] = g10;row2[bIdx] = b10;if (dcn == 4)row2[3] = a10;row2[dcn + 2 - bIdx] = r11;row2[dcn + 1] = g11;row2[dcn + 0 + bIdx] = b11;if (dcn == 4)row2[7] = a11;}
}

本段代码实现了uv+y转rgb的功能,相关注释已经很清楚了,内部主要包含的两个函数:uvToRGBuv、yRGBuvToRGBA。uvToRGBuv的功能主要是将uv值转换为最终rgb公式中与uv相关的分量;yRGBuvToRGBA的功能是将uvToRGBuv求得的ruv/guv/buv分量结合y得到最终的rgb分量的值。下面分别介绍这两个函数:

//R = 1.164(Y - 16) + 1.596(V - 128)
//G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
//B = 1.164(Y - 16)                  + 2.018(U - 128)//定点处理,将各个系数乘以2^20,加1<<19四舍五入
//R = (1220542(Y - 16) + 1673527(V - 128)                  + (1 << 19)) >> 20
//G = (1220542(Y - 16) - 852492(V - 128) - 409993(U - 128) + (1 << 19)) >> 20
//B = (1220542(Y - 16)                  + 2116026(U - 128) + (1 << 19)) >> 20static inline void uvToRGBuv(const unsigned char u, const unsigned char v, int& ruv, int& guv, int& buv)
{int uu, vv;uu = int(u) - 128;vv = int(v) - 128;//const int ITUR_BT_601_CY = 1220542;//const int ITUR_BT_601_CUB = 2116026;//const int ITUR_BT_601_CUG = -409993;//const int ITUR_BT_601_CVG = -852492;//const int ITUR_BT_601_CVR = 1673527;//const int ITUR_BT_601_SHIFT = 20;//计算rgb中uv分量ruv = (1 << (ITUR_BT_601_SHIFT - 1)) + ITUR_BT_601_CVR * vv;guv = (1 << (ITUR_BT_601_SHIFT - 1)) + ITUR_BT_601_CVG * vv + ITUR_BT_601_CUG * uu;buv = (1 << (ITUR_BT_601_SHIFT - 1)) + ITUR_BT_601_CUB * uu;
}
static inline void yRGBuvToRGBA(const unsigned char vy, const int ruv, const int guv, const int buv,unsigned char& r, unsigned char& g, unsigned char& b, unsigned char& a)
{int yy = int(vy);//y-16之后要做饱和处理int y = maxValue(0, yy - 16) * ITUR_BT_601_CY; r = saturate_cast<unsigned char>((y + ruv) >> ITUR_BT_601_SHIFT);//除以2^20,还原g = saturate_cast<unsigned char>((y + guv) >> ITUR_BT_601_SHIFT);b = saturate_cast<unsigned char>((y + buv) >> ITUR_BT_601_SHIFT);a = (unsigned char)(0xff);
}

上面两段代码意思很简单了,就是利用y+uv根据转换矩阵计算rgb分量。

需要注意两点:1、对浮点运算做了定点,乘以2^20转换为int,最后将结果再除以2^20

2、y-16之后要做饱和处理,不然最后转换出来的图像灰度小的地方就是亮点

YUV2RGB NEON加速

uint8x16_t a = vdupq_n_u8((unsigned char)(0xFF));
for (; i <= dst_width - (u8_nlanes << 1); i += (u8_nlanes << 1), row1 += (u8_nlanes*dcn << 1), row2 += (u8_nlanes*dcn << 1))
{uint8x16_t u, v;v_load_deinterleave(uv + i, u, v);//分别加载16个u及16个v,uv分别放于两个neon寄存器if (uIdx) swap(u, v);//参考原语逻辑uint8x16_t vy[4];v_load_deinterleave(y1 + i, vy[0], vy[1]);//分别加载16个u及16个v,uv分别放于两个neon寄存器v_load_deinterleave(y2 + i, vy[2], vy[3]);int32x4_t ruv[4], guv[4], buv[4]; uvToRGBuv(u, v, ruv, guv, buv); //每对uv计算得到一组ruv、guv、buv;16对uv产生16组数据uint8x16_t r[4], g[4], b[4];for (int k = 0; k < 4; k++){//同样利用ruv、guv、buv,计算最终的rgb值yRGBuvToRGBA(vy[k], ruv, guv, buv, r[k], g[k], b[k]);}if (bIdx){for (int k = 0; k < 4; k++)swap(r[k], b[k]);}// [r0...], [r1...] => [r0, r1, r0, r1...], [r0, r1, r0, r1...]uint8x16_t r0_0, r0_1, r1_0, r1_1;v_zip(r[0], r[1], r0_0, r0_1);v_zip(r[2], r[3], r1_0, r1_1);uint8x16_t g0_0, g0_1, g1_0, g1_1;v_zip(g[0], g[1], g0_0, g0_1);v_zip(g[2], g[3], g1_0, g1_1);uint8x16_t b0_0, b0_1, b1_0, b1_1;v_zip(b[0], b[1], b0_0, b0_1);v_zip(b[2], b[3], b1_0, b1_1);if (dcn == 4){v_store_interleave(row1 + 0 * u8_nlanes, b0_0, g0_0, r0_0, a);v_store_interleave(row1 + 4 * u8_nlanes, b0_1, g0_1, r0_1, a);v_store_interleave(row2 + 0 * u8_nlanes, b1_0, g1_0, r1_0, a);v_store_interleave(row2 + 4 * u8_nlanes, b1_1, g1_1, r1_1, a);}else //dcn == 3{v_store_interleave(row1 + 0 * u8_nlanes, b0_0, g0_0, r0_0);v_store_interleave(row1 + 3 * u8_nlanes, b0_1, g0_1, r0_1);v_store_interleave(row2 + 0 * u8_nlanes, b1_0, g1_0, r1_0);v_store_interleave(row2 + 3 * u8_nlanes, b1_1, g1_1, r1_1);}
}

neon加速主要是利用单指令执行可并行执行的部分,在YUV2RGB转换中,像素与像素之间的计算都是无关的,因此多个像素的计算完全可以并行执行,主要考虑最大化的利用neon寄存器即可。

从上面代码可以看出,每次计算目标图像的64个像素,分为两行,每行32个像素。由于4个y共用uv,因此每次计算需要16组uv值。

理解了原语的代码逻辑再理解neon加速的版本就很容易了,逻辑都是一样的。这里主要说明一下neon指令涉及到的计算,不理解的地方上面的代码也有相应的注释,应该很容易理解。

static inline void uvToRGBuv(const uint8x16_t& u, const uint8x16_t& v, int32x4_t(&ruv)[4], int32x4_t(&guv)[4], int32x4_t(&buv)[4])
{uint8x16_t v128 = vdupq_n_u8((unsigned char)(128));int8x16_t su = vreinterpretq_s8_u8(vsubq_u8(u, v128));int8x16_t sv = vreinterpretq_s8_u8(vsubq_u8(v, v128));//将16对uv进行位扩展,u、v分别扩展到4个128bit neon寄存器,每个寄存器4个32bit数据int16x8_t uu0, uu1, vv0, vv1;v_expand_i8_16(su, uu0, uu1);v_expand_i8_16(sv, vv0, vv1);int32x4_t uu[4], vv[4];v_expand16_32(uu0, uu[0], uu[1]); v_expand16_32(uu1, uu[2], uu[3]);v_expand16_32(vv0, vv[0], vv[1]); v_expand16_32(vv1, vv[2], vv[3]);//相应系数乘以2^20,每个数据占用32bitint32x4_t vshift = vdupq_n_s32(1 << (ITUR_BT_601_SHIFT - 1));int32x4_t vr = vdupq_n_s32(ITUR_BT_601_CVR);int32x4_t vg = vdupq_n_s32(ITUR_BT_601_CVG);int32x4_t ug = vdupq_n_s32(ITUR_BT_601_CUG);int32x4_t ub = vdupq_n_s32(ITUR_BT_601_CUB);//计算rgb中与uv相关的分量,共16组ruv、guv、buvfor (int k = 0; k < 4; k++){ruv[k] = vaddq_s32(vshift, vr * vv[k]);guv[k] = vaddq_s32(vshift, vaddq_s32(vg * vv[k], ug * uu[k]));buv[k] = vaddq_s32(vshift, ub * uu[k]);}
}

static inline void yRGBuvToRGBA(const uint8x16_t& vy,const int32x4_t(&ruv)[4],const int32x4_t(&guv)[4],const int32x4_t(&buv)[4],uint8x16_t& rr, uint8x16_t& gg, uint8x16_t& bb)
{uint8x16_t v16 = vdupq_n_u8(16);uint8x16_t posY = vqsubq_u8(vy, v16); //饱和相减指令,<0的值等于0//y值扩展到32bit,与ruv、guv、buv相对应uint16x8_t yy0, yy1;v_expand_u8_16(posY, yy0, yy1);int32x4_t yy[4];v_expand16_32(vreinterpretq_s16_u16(yy0), yy[0], yy[1]);v_expand16_32(vreinterpretq_s16_u16(yy1), yy[2], yy[3]);int32x4_t vcy = vdupq_n_s32(ITUR_BT_601_CY);int32x4_t y[4], r[4], g[4], b[4];for (int k = 0; k < 4; k++){y[k] = yy[k] * vcy;r[k] = vshrq_n_s32(vaddq_s32(y[k], ruv[k]), ITUR_BT_601_SHIFT);g[k] = vshrq_n_s32(vaddq_s32(y[k], guv[k]), ITUR_BT_601_SHIFT);b[k] = vshrq_n_s32(vaddq_s32(y[k], buv[k]), ITUR_BT_601_SHIFT);}//将r[0]-r[4]转化为uint8合并到一个neon寄存器中,gb同理int16x8_t r0, r1, g0, g1, b0, b1;r0 = v_pack(r[0], r[1]);r1 = v_pack(r[2], r[3]);g0 = v_pack(g[0], g[1]);g1 = v_pack(g[2], g[3]);b0 = v_pack(b[0], b[1]);b1 = v_pack(b[2], b[3]);rr = v_pack_u(r0, r1);gg = v_pack_u(g0, g1);bb = v_pack_u(b0, b1);
}

熟悉neon指令的你对上面代码很容易理解了,没啥好说的。主要思想就是最大化利用neon寄存器实现并行操作,每次读取64个y值,16对uv值,一次计算两行分别32个像素值

YUV2RGB源码详解(参考Opencv4.1)相关推荐

  1. Resize源码详解(参考Opencv4.1)

    inline uint16x8_t v_pack(const uint32x4_t& a, const uint32x4_t& b) { uint16x4_t a1 = vqmovn_ ...

  2. Go 语言 bytes.Buffer 源码详解之1

    转载地址:Go 语言 bytes.Buffer 源码详解之1 - lifelmy的博客 前言 前面一篇文章 Go语言 strings.Reader 源码详解,我们对 strings 包中的 Reade ...

  3. AidLux“换脸”案例源码详解 (Python)

    "换脸"案例源码详解 (Python) faceswap_gui.py用于换脸,可与facemovie_gui.py身体互换源码(上一篇文章)对照观看 打开faceswap_gui ...

  4. Vue-Watcher观察者源码详解

    源码调试地址 https://github.com/KingComedy/vue-debugger 什么是Watcher Watcher是Vue中的观察者类,主要任务是:观察Vue组件中的属性,当属性 ...

  5. 【5G/4G】加/解密+完整性保护/校验算法源码详解

    文章目录 加/解密+完整性保护/校验算法源码详解 一.加解密算法 二.完整性保护/校验算法 本人就职于国际知名终端厂商,负责modem芯片研发. 在5G早期负责终端数据业务层.核心网相关的开发工作,目 ...

  6. OpenstackSDK 源码详解

    OpenstackSDK 源码详解 openstacksdk是基于当前最新版openstacksdk-0.17.2版本,可从 GitHub:OpenstackSDK 获取到最新的源码.openstac ...

  7. java源码详解——String类

    java源码详解--String类目录: Java String 类 下面开始介绍主要方法: Java charAt() 方法 Java compareTo() 方法 int compareTo(St ...

  8. Rocksdb Compaction源码详解(二):Compaction 完整实现过程 概览

    文章目录 1. 摘要 2. Compaction 概述 3. 实现 3.1 Prepare keys 过程 3.1.1 compaction触发的条件 3.1.2 compaction 的文件筛选过程 ...

  9. 源码详解Android 9.0(P) 系统启动流程之SystemServer

    源码详解Android 9.0(P) 系统启动流程目录: 源码详解Android 9.0(P)系统启动流程之init进程(第一阶段) 源码详解Android 9.0(P)系统启动流程之init进程(第 ...

最新文章

  1. python3.7源码分析-字典
  2. 复现经典:《统计学习方法》第1章 统计学习方法概论
  3. ai怎么调界面大小_科研论文作图系列-从PPT到AI (一)
  4. C语言中的数组的使用——混乱的内存管理
  5. DELPHI TreeView 文件目录树和 设置节点图标 完整
  6. SystemTimer,TimerTaskList等源码分析
  7. python 摄像头录制帧率_实践:用python实现把视频以帧数输出成连续的多图片
  8. 员工培训管理系统设计与实现
  9. STM32CubeMx开发之路—13使用SPI读写W25Q64
  10. FTP服务器搭建详细步骤
  11. 学习日记day25 平面设计 综合例子
  12. 74cms前台getshell漏洞
  13. python numpy 中linspace函数
  14. 新华三:照耀城市的数字演进之路
  15. InstallShield可靠的 Windows 安装程序
  16. iPhone与win10传输大文件,使用局域网
  17. Verilog实现减法器
  18. 脱单有望女程序员越来越多了
  19. 用连接去创造,研发不再成为老大难
  20. 测试用例(功能用例)——完整demo(一千多条测试用例)

热门文章

  1. 蓝牙配对模式 java_BLE(低功耗蓝牙)配对和绑定
  2. java jmi的基本思想_jmi: JMI 是 JNI 的 C++11/14封装,目的是为了简化JNI使用
  3. bzoj 1031 [JSOI2007]字符加密Cipher 后缀数组
  4. 使用C#获取IP地址方法
  5. Ubuntu 14.04.5 imx6 开发环境搭建
  6. 航旅事业群面试(li)
  7. iscroll5 上拉,下拉 加载数据
  8. 【JavaScript】理解与使用Javascript中的回调函数
  9. 人家可是见过大世面的
  10. 使用BurpSuite抓取HTTPS网站