经典算法,yuv与rgb互转,查表法,让你的软件飞起来

2024-06-02 22:18

本文主要是介绍经典算法,yuv与rgb互转,查表法,让你的软件飞起来,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

代码的运算速度取决于以下几个方面

1、 算法本身的复杂度,比如MPEG比JPEG复杂,JPEG比BMP图片的编码复杂。

2、 CPU自身的速度和设计架构

3、 CPU的总线带宽

4、 您自己代码的写法

将RGB格式的彩色图像先转换成YUV图像。

图像转换的公式如下:

Y = 0.299 * R + 0.587 * G + 0.114 * B;

图像尺寸640*480*24bit,RGB图像已经按照RGBRGB顺序排列的格式,放在内存里面了。

以下是输入和输出的定义:

#define XSIZE 640

#define YSIZE 480

#define IMGSIZE XSIZE * YSIZE

typedef struct RGB

{

unsigned char R;

unsigned char G;

unsigned char B;

}RGB;

struct RGB in[IMGSIZE]; //需要计算的原始数据

unsigned char out[IMGSIZE]; //计算后的结果

 

一、浮点运算

优化原则:图像是一个2D数组,我用一个一维数组来存储。编译器处理一维数组的效率要高过二维数组。

 

先写一个代码:

Y = 0.299 * R + 0.587 * G + 0.114 * B;

void calc_lum()

{

int i;

for(i = 0; i < IMGSIZE; i++)

{

double r,g,b,y;

unsigned char yy;

r = in[i].r;

g = in[i].g;

b = in[i].b;

y = 0.299 * r + 0.587 * g + 0.114 * b;

yy = y;

out[i] = yy;

}

}

这大概是能想得出来的最简单的写法了,实在看不出有什么毛病,好了,编译一下跑一跑吧。

第一次试跑

这个代码分别用vc6.0和gcc编译,生成2个版本,分别在pc上和我的embedded system上面跑。

速度多少?

在PC上,由于存在硬件浮点处理器,CPU频率也够高,计算速度为20秒。

我的embedded system,没有以上2个优势,浮点操作被编译器分解成了整数运算,运算速度为120秒左右。

 

二、整形运算 

上面这个代码还没有跑,我已经知道会很慢了,因为这其中有大量的浮点运算。只要能不用浮点运算,一定能快很多。

 

Y = 0.299 * R + 0.587 * G + 0.114 * B;

这个公式怎么能用定点的整数运算替代呢?

0.299 * R可以如何化简?

Y = 0.299 * R + 0.587 * G + 0.114 * B;

Y = D + E + F;

D = 0.299 * R;

E = 0.587 * G;

F = 0.114 * B;

我们就先简化算式D吧!

RGB的取值范围都是0~255,都是整数,只是这个系数比较麻烦,不过这个系数可以表示为:0.299 = 299 / 1000;

所以 D = ( R * 299) / 1000;

Y = (R * 299 + G * 587 + B * 114) / 1000;

 

这一下,能快多少呢?

Embedded system上的速度为45秒;

PC上的速度为2秒;

0.299 * R可以如何化简

Y = 0.299 * R + 0.587 * G + 0.114 * B;

Y = (R * 299 + G * 587 + B * 114) / 1000;

这个式子好像还有点复杂,可以再砍掉一个除法运算。

前面的算式D可以这样写:

0.299=299/1000=1224/4096

所以 D = (R * 1224) / 4096

Y=(R*1224)/4096+(G*2404)/4096+(B*467)/4096

再简化为:

Y=(R*1224+G*2404+B*467)/4096

这里的/4096除法,因为它是2的N次方,所以可以用移位操作替代,往右移位12bit就是把某个数除以4096了。

 

void calc_lum()

{

int i;

for(i = 0; i < IMGSIZE; i++)

{

int r,g,b,y;

r = 1224 * in[i].r;

g = 2404 * in[i].g;

b = 467 * in[i].b;

y = r + g + b;

y = y >> 12; //这里去掉了除法运算

out[i] = y;

}

}

这个代码编译后,又快了20%。

虽然快了不少,还是太慢了一些,20秒处理一幅图像,地球人都不能接受。

 


三、查表,速度提高为2秒

仔细端详一下这个式子!

Y = 0.299 * R + 0.587 * G + 0.114 * B;

Y=D+E+F;

D=0.299*R;

E=0.587*G;

F=0.114*B;

 

RGB的取值有文章可做,RGB的取值永远都大于等于0,小于等于255,我们能不能将D,E,F都预先计算好呢?然后用查表算法计算呢?

我们使用3个数组分别存放DEF的256种可能的取值,然后。。。

 


查表数组初始化

int D[256],F[256],E[256];

void table_init()

{

int i;

for(i=0;i<256;i++)

{

D[i]=i*1224;

D[i]=D[i]>>12;

E[i]=i*2404;

E[i]=E[i]>>12;

F[i]=i*467;

F[i]=F[i]>>12;

}

}

void calc_lum()

{

int i;

for(i = 0; i < IMGSIZE; i++)

{

int r,g,b,y;

r = D[in[i].r];//查表

g = E[in[i].g];

b = F[in[i].b];

y = r + g + b;

out[i] = y;

}

}

这一次的成绩把我吓出一身冷汗,执行时间居然从30秒一下提高到了2秒!在PC上测试这段代码,眼皮还没眨一下,代码就执行完了。一下提高15倍,爽不爽?


四、查表法+2ALU   速度提高为1秒

继续优化
很多embedded system的32bit CPU,都至少有2个ALU,能不能让2个ALU都跑起来?

 

void calc_lum()

{

int i;

for(i = 0; i < IMGSIZE; i += 2) //一次并行处理2个数据

{

int r,g,b,y,r1,g1,b1,y1;

r = D[in[i].r];//查表 //这里给第一个ALU执行

g = E[in[i].g];

b = F[in[i].b];

y = r + g + b;

out[i] = y;

r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行

g1 = E[in[i + 1].g];

b1 = F[in[i + 1].b];

y = r1 + g1 + b1;

out[i + 1] = y;

}

}

2个ALU处理的数据不能有数据依赖,也就是说:某个ALU的输入条件不能是别的ALU的输出,这样才可以并行。

这次成绩是1秒。

 


五、 int表改为unsigned short 表,并将函数声明为 inline   速度提高为0.5秒

查看这个代码

int D[256],F[256],E[256]; //查表数组

void table_init()

{

int i;

for(i=0;i<256;i++)

{

D[i]=i*1224;

D[i]=D[i]>>12;

E[i]=i*2404;

E[i]=E[i]>>12;

F[i]=i*467;

F[i]=F[i]>>12;

}

}

到这里,似乎已经足够快了,但是我们反复实验,发现,还有办法再快!

可以将int D[256],F[256],E[256]; //查表数组

更改为

unsigned short D[256],F[256],E[256]; //查表数组

 

这是因为编译器处理int类型和处理unsigned short类型的效率不一样。

再改动

inline void calc_lum()

{

int i;

for(i = 0; i < IMGSIZE; i += 2) //一次并行处理2个数据

{

int r,g,b,y,r1,g1,b1,y1;

r = D[in[i].r];//查表 //这里给第一个ALU执行

g = E[in[i].g];

b = F[in[i].b];

y = r + g + b;

out[i] = y;

r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行

g1 = E[in[i + 1].g];

b1 = F[in[i + 1].b];

y = r1 + g1 + b1;

out[i + 1] = y;

}

}

将函数声明为inline,这样编译器就会将其嵌入到母函数中,可以减少CPU调用子函数所产生的开销。

这次速度:0.5秒。

 











其实,我们还可以飞出地球的!

如果加上以下措施,应该还可以更快:

1、 把查表的数据放置在CPU的高速数据CACHE里面;

2、 把函数calc_lum()用汇编语言来写

 

其实,CPU的潜力是很大的

1、 不要抱怨你的CPU,记住一句话:“只要功率足够,砖头都能飞!”

2、 同样的需求,写法不一样,速度可以从120秒变化为0.5秒,说明CPU的潜能是很大的!看你如何去挖掘。

3、 我想:要是Microsoft的工程师都像我这样优化代码,我大概就可以用489跑windows XP了!

 

以上就是对《让你的软件飞起来》的摘录,下面,我将按照这位牛人的介绍,对RGB到YCbCr的转换算法做以总结。

 

Y = 0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V = 0.615R - 0.515G - 0.100B

 

 

#deinfe SIZE 256

#define XSIZE 640

#define YSIZE 480

#define IMGSIZE XSIZE * YSIZE

typedef struct RGB

{

unsigned char r;

unsigned char g;

unsigned char b;

}RGB;

struct RGB in[IMGSIZE]; //需要计算的原始数据

unsigned char out[IMGSIZE * 3]; //计算后的结果

 

unsigned short Y_R[SIZE],Y_G[SIZE],Y_B[SIZE],U_R[SIZE],U_G[SIZE],U_B[SIZE],V_R[SIZE],V_G[SIZE],V_B[SIZE]; //查表数组

void table_init()

{

int i;

for(i = 0; i < SIZE; i++)

{

Y_R[i] = (i * 1224) >> 12; //Y对应的查表数组

Y_G[i] = (i * 2404) >> 12;

Y_B[i] = (i * 467) >> 12;

U_R[i] = (i * 602) >> 12; //U对应的查表数组

U_G[i] = (i * 1183) >> 12;

U_B[i] = (i * 1785) >> 12;

V_R[i] = (i * 2519) >> 12; //V对应的查表数组

V_G[i] = (i * 2109) >> 12;

V_B[i] = (i * 409) >> 12;

}

}

 

inline void calc_lum()

{

int i;

for(i = 0; i < IMGSIZE; i += 2) //一次并行处理2个数据

{

out[i] = Y_R[in[i].r] + Y_G[in[i].g] + Y_B[in[i].b]; //Y

out[i + IMGSIZE] = U_B[in[i].b] - U_R[in[i].r] - U_G[in[i].g]; //U

out[i + 2 * IMGSIZE] = V_R[in[i].r] - V_G[in[i].g] - V_B[in[i].b]; //V

 

out[i + 1] = Y_R[in[i + 1].r] + Y_G[in[i + 1].g] + Y_B[in[i + 1].b]; //Y

out[i + 1 + IMGSIZE] = U_B[in[i + 1].b] - U_R[in[i + 1].r] - U_G[in[i + 1].g]; //U

out[i + 1 + 2 * IMGSIZE] = V_R[in[i + 1].r] - V_G[in[i + 1].g] - V_B[in[i + 1].b]; //V

}

}

这篇关于经典算法,yuv与rgb互转,查表法,让你的软件飞起来的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1025242

相关文章

代码随想录算法训练营:12/60

非科班学习算法day12 | LeetCode150:逆波兰表达式 ,Leetcode239: 滑动窗口最大值  目录 介绍 一、基础概念补充: 1.c++字符串转为数字 1. std::stoi, std::stol, std::stoll, std::stoul, std::stoull(最常用) 2. std::stringstream 3. std::atoi, std

人工智能机器学习算法总结神经网络算法(前向及反向传播)

1.定义,意义和优缺点 定义: 神经网络算法是一种模仿人类大脑神经元之间连接方式的机器学习算法。通过多层神经元的组合和激活函数的非线性转换,神经网络能够学习数据的特征和模式,实现对复杂数据的建模和预测。(我们可以借助人类的神经元模型来更好的帮助我们理解该算法的本质,不过这里需要说明的是,虽然名字是神经网络,并且结构等等也是借鉴了神经网络,但其原型以及算法本质上还和生物层面的神经网络运行原理存在

一道经典Python程序样例带你飞速掌握Python的字典和列表

Python中的列表(list)和字典(dict)是两种常用的数据结构,它们在数据组织和存储方面有很大的不同。 列表(List) 列表是Python中的一种有序集合,可以随时添加和删除其中的元素。列表中的元素可以是任何数据类型,包括数字、字符串、其他列表等。列表使用方括号[]表示,元素之间用逗号,分隔。 定义和使用 # 定义一个列表 fruits = ['apple', 'banana

电子盖章怎么做_电子盖章软件

使用e-章宝(易友EU3000智能盖章软件)进行电子盖章的步骤如下: 一、准备阶段 软件获取: 访问e-章宝(易友EU3000智能盖章软件)的官方网站或相关渠道,下载并安装软件。账户注册与登录: 首次使用需注册账户,并根据指引完成注册流程。注册完成后,使用用户名和密码登录软件。 二、电子盖章操作 文档导入: 在e-章宝软件中,点击“添加”按钮,导入待盖章的PDF文件。支持批量导入多个文件,

前端 CSS 经典:文字描边

前言:文字描边有两种实现方式 1. text-shadow 设置 8 个方向的文字阴影,缺点是只有八个方向,文字转角处可能有锯齿状。不支持文字透明,设置 color: transparent,文字会成描边颜色。 <!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta http-equiv="X-UA-Comp

大林 PID 算法

Dahlin PID算法是一种用于控制和调节系统的比例积分延迟算法。以下是一个简单的C语言实现示例: #include <stdio.h>// DALIN PID 结构体定义typedef struct {float SetPoint; // 设定点float Proportion; // 比例float Integral; // 积分float Derivative; // 微分flo

小红书商家电话采集软件使用指南

使用小红书商家电话采集软件可以提高商家电话的采集效率,以下是使用指南及附带代码。 步骤一:安装Python和相关库 首先,确保你的电脑已经安装了Python运行环境(建议安装Python3版本)。安装完成后,同样需要安装一些相关的库,如requests、beautifulsoup4等。在命令行窗口中输入以下命令进行安装: pip install requestspip install bea

LeetCode 算法:二叉树的中序遍历 c++

原题链接🔗:二叉树的中序遍历 难度:简单⭐️ 题目 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 示例 1: 输入:root = [1,null,2,3] 输出:[1,3,2] 示例 2: 输入:root = [] 输出:[] 示例 3: 输入:root = [1] 输出:[1] 提示: 树中节点数目在范围 [0, 100] 内 -100 <= Node.

【Java算法】滑动窗口 下

​ ​    🔥个人主页: 中草药 🔥专栏:【算法工作坊】算法实战揭秘 🦌一.水果成篮 题目链接:904.水果成篮 ​ 算法原理 算法原理是使用“滑动窗口”(Sliding Window)策略,结合哈希表(Map)来高效地统计窗口内不同水果的种类数量。以下是详细分析: 初始化:创建一个空的哈希表 map 用来存储每种水果的数量,初始化左右指针 left

ROS2从入门到精通4-4:局部控制插件开发案例(以PID算法为例)

目录 0 专栏介绍1 控制插件编写模板1.1 构造控制插件类1.2 注册并导出插件1.3 编译与使用插件 2 基于PID的路径跟踪原理3 控制插件开发案例(PID算法)常见问题 0 专栏介绍 本专栏旨在通过对ROS2的系统学习,掌握ROS2底层基本分布式原理,并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。 🚀详情:《ROS2从入门到精通》 1 控制插