PNG文件转YUV格式并实现播放
文章目录
- PNG文件转YUV
- PNG文件格式
- YUV文件格式
- 什么是YUV格式文件?
- YUV采样
- YUV 4:2:0
- RGB转YUV
- 实验过程
- 获取PNG文件信息
- 获取IHDR信息
- 获取PLTE调色板信息
- 获取IDAT信息
- 对IDAT数据块进行解压缩
- 将解压缩的IDAT数据进行转换
- 多个YUV合成
PNG文件转YUV
前言:本次实验选用了PNG
转YUV
,做的过程十分煎熬…主要原因是PNG
的IDAT
数据块的解码部分没有自己手写轮子,直接调用的Python
的zlib
库,所以每个像素解压缩出来的数据分布完全没有概念,而且第一次选的图片还是带α\alphaα通道的…所以这一次的实验代码并不具备很好的鲁棒性,仅供本实验使用,引用从慎!
PNG文件格式
这一部分的介绍可以看我之前写的PNG格式文件分析
YUV文件格式
什么是YUV格式文件?
YUV
是指亮度参量和色度参量分开表示的像素格式,其中Y
表示亮度信号(Luma
),也就是灰度值,U
和V
表示的是色度信号。通常来说,人们一般指的YUV
格式既是YCbCr
格式,YCbCr
格式有很多中采样格式,如4:4:4,4:2:2,4:2:0和4:1:1等。
YUV采样
色度通道的采样率可以低于亮度通道,而不会显著降低感知质量。
4:4:4 表示完全取样。
4:2:2 表示2:1的水平取样,垂直完全采样。
4:2:0 表示2:1的水平取样,垂直2:1采样。
4:1:1 表示4:1的水平取样,垂直完全采样。
本实验中所采用的格式为4:2:0采样,下面重点介绍该采样格式。
YUV 4:2:0
每四个Y
信号共用一对UV
信号。
在存储该采样格式的YUV
文件时,首先存储每一个像素的Y
值,然后存储UV
值,在这里,UV
有两种存储方式:YUV420sp
和YUV420p
。
下面是YUV420sp
的存储格式:
这是YUV420p
的存储格式:
可以看到,YUV420sp
中的UV
分量是交织存储的,本次实验中我们采用YUV420sp
的存储方式。
RGB转YUV
我们一般得到的都是图像的原始RGB
信息,那么如何将RGB
信号转换成YUV
信号?
我们这里直接给出数字RGB
转换成YUV
的公式:(BT.601标准)
Y=16+0.257∗R+0.504∗G+0.098∗BY = 16 + 0.257 * R + 0.504 * G+ 0.098 * BY=16+0.257∗R+0.504∗G+0.098∗B
Cb=128−0.148∗R−0.291∗G+0.439∗BCb = 128 - 0.148 * R - 0.291 * G+ 0.439 * BCb=128−0.148∗R−0.291∗G+0.439∗B
Cr=128+0.439∗R−0.368∗G−0.071∗BCr = 128 + 0.439 * R - 0.368 * G - 0.071 * BCr=128+0.439∗R−0.368∗G−0.071∗B
由此我们就可以实现RGB
到YUV
的转换。
实验过程
获取PNG文件信息
我们可以通过运行以下代码获取文件的基本数据块信息:
#include <iostream>
#include <fstream>
#include <cstdio>
#include <vector>
#include <map>
#include <set>
#define uchar unsigned charusing namespace std;const string path = "test.png";
map<string, vector<int> > mp;
set <string> ancillary_chunk;struct FileHeader
{uchar head[8];void GetHead(ifstream &in) {in.read((char *)head, 8);}void Print() {for (auto i : head) cout << (int) i << ' ';cout << endl;}
}file_header;struct Chunks
{unsigned int length = 0;uchar type[4];string c_type = "";uchar *data;uchar CRC[4];void GetChunk(ifstream & in) {uchar * buffer = new uchar[4];in.read((char *)buffer, 4);for (int i = 0;i < 4;i ++) length = (length << 8) + buffer[i];in.read((char *)type, 4);if (length) {data = new uchar[length];in.read((char*)data, length);}in.read((char *)CRC, 4);for (auto i : type) c_type += (int)i;return ;}
}chunk;int main()
{ifstream in(path, ios :: binary);file_header.GetHead(in);int pos = 0;while (true) {Chunks tmp;tmp.GetChunk(in);mp[tmp.c_type].push_back(pos);pos ++;if (tmp.c_type == "IEND") break;if (tmp.c_type != "IDAT" && tmp.c_type != "IHDR" && tmp.c_type != "PLTE") ancillary_chunk.insert(tmp.c_type);delete(tmp.data);}cout << "All Chunks" << endl;for (auto &i : mp) {cout << "Chunk Type :" << i.first << endl;}cout << "Ancillary Chunks" << endl;for (auto &i : ancillary_chunk) cout << i << endl;
}
我们选取其中的一个PNG
文件来展示结果:
我们可以得到该文件的数据块组成。
获取IHDR信息
我们通过添加以下代码来实现:
struct iHDR
{unsigned int width, height;unsigned char bit_depth, color_type, compression_method;unsigned char filter_method, interlace_method;void GetIHDR(unsigned char* buffer){for( int i = 0; i < 4; ++ i ) { width = (width << 8) + buffer[i]; }for( int i = 0; i < 4; ++ i ) { height = (height << 8) + buffer[i + 4]; }int pos = 8;for(auto i : {&bit_depth, &color_type, &compression_method, &filter_method, &interlace_method})*i = buffer[pos ++];}void Print(){cout << width << " " << height << endl;for( auto i : {bit_depth, color_type, compression_method, filter_method, interlace_method})cout << (int)i << " ";cout << endl;}
}ihdr;
在主程序中,添加以下代码:
//获取IHDR信息
ihdr.GetIHDR(v[position].data);
ihdr.Print();
还是选取一张图片我们来看一下结果:
根据PNG
的格式定义,我们可以得到以下信息:
名称 | 取值 |
---|---|
width | 80 |
height | 80 |
Bit depth | 8 |
ColorType | 3,带有调色板 |
Compression method | 0 ,deflate压缩算法 |
Filter method | 0,即滤波方法 0 |
Interlace method | 0,非隔行扫描 |
获取PLTE调色板信息
添加如下代码:
struct pLTE
{int r, g, b;static pLTE * GetPLET(uchar* inBuffer) {int plte_size = 1 << ihdr.bit_depth;pLTE* plte = new pLTE[plte_size];int pos = 0;for (int i = 0;i < plte_size;i ++) {for (auto j : {&plte[i].r, &plte[i].g, &plte[i].b}) {*j = inBuffer[pos ++];}}return plte;}void Print() {for (auto i : {r, g, b}) cout << i << ' ';cout << endl;return ;}
}*plte;
即可获取调色板信息
获取IDAT信息
在代码中添加如下定义:
void TranslateIDAT(ofstream& out, uchar* buffer, int bufferLength)
{out.write((char*)buffer, bufferLength);return ;
}void OutputIDAT()
{ifstream in(out_path, ios :: binary);if (in.is_open()) return ;ofstream out(out_path, ios :: binary);for (auto &i : mp["IDAT"]) TranslateIDAT(out, v[i].data, v[i].length);return ;
}
通过调用OutputIDAT
即可获取。
对IDAT数据块进行解压缩
PNG
的IDAT
数据块实际是经过压缩的,这里由于并不清楚压缩算法,所以直接采用Python
的zlib
库进行解压缩。
编写的Python
部分代码为:
import zlib
list_dec = []
f = open('C:\\Users\\sdlwq\\Desktop\\Github\\Data-Compression\\Work\\PNG2YUV\\out.idat', 'rb')
data = f.read()
new_data = zlib.decompress(data)
fp = open('in.idat','wb')
fp.write(new_data)
fp.close()
print('done')
即可完成解码工作。
将解压缩的IDAT数据进行转换
添加如下代码:
void png2yuv(string out_yuv_path, uchar* buffer, uint buffer_length)
{unsigned char* y, * u, * v;int width = ihdr.width, height = ihdr.height;int y_size = width * height;int uv_size = y_size / 4;cout << y_size << ' ' << uv_size << endl;y = new unsigned char[y_size];u = new unsigned char[uv_size];v = new unsigned char[uv_size];int uv_pos = 0, y_pos = 0;for( int i = 0; i < buffer_length; ++ i ){if((i + 1) % (width + 1) == 0) continue;int r, g, b; auto plte_tmp = plte[buffer[i]];r = plte_tmp.r, g = plte_tmp.g, b = plte_tmp.b; //先从调色板转换成为真彩int h = i / (width + 1 ), w = i % (width + 1);//转换成为4:2:0的YUV文件y[y_pos ++] = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;if((h & 1) || (w & 1)) continue;u[uv_pos] = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;v[uv_pos ++] = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;}cout << y_pos << ' ' << uv_pos << endl;ofstream out(out_yuv_path, ios::binary);out.write((char*)y, y_size); out.write((char*)u, uv_size); out.write((char*)v, uv_size);out.close();for(auto i : {&y, &u, &v}) delete[] *i;cout << "ok" << endl;return ;
}
在主函数中,添加如下语句:
// 解压缩IDAT后进行转换in.open(in_path, ios :: binary);cout << "Ok" << endl;in.seekg(0, ios :: end);int length = in.tellg();cout << length << endl;in.seekg(0, ios :: beg);uchar* data_buffer = new uchar[length];in.read((char*)data_buffer, length);png2yuv(out_yuv, data_buffer, length);return 0;
最后的完整代码如下所示:
#include <iostream>
#include <fstream>
#include <cstdio>
#include <vector>
#include <map>
#include <set>
#define uchar unsigned char
#define uint unsigned intusing namespace std;struct FileHeader
{uchar head[8];void GetHead(ifstream &in) {in.read((char *)head, 8);}void Print() {for (auto i : head) cout << (int) i << ' ';cout << endl;}
}file_header;struct Chunks
{unsigned int length = 0;char type[4];string c_type = "";unsigned char *data;unsigned char CRC[4];void GetChunk( ifstream &in ){unsigned char* buffer = new unsigned char[4];in.read((char*)buffer,4);for( int i = 0; i < 4; ++ i ) { length = (length << 8) + buffer[i]; }in.read(type,4);if( length != 0 ) {data = new unsigned char[length];in.read((char*)data, length);}in.read((char*)CRC,4);for( auto i : type ) c_type += (int)i;return;}
};const string path = "Panda80.png";
const string out_path = "out.idat";
const string in_path = "in.idat";
const string out_yuv = "out4.yuv";map<string, vector<int> > mp;
set <string> ancillary_chunk;
vector<Chunks> v;struct iHDR
{unsigned int width, height;unsigned char bit_depth, color_type, compression_method;unsigned char filter_method, interlace_method;void GetIHDR(unsigned char* buffer){for( int i = 0; i < 4; ++ i ) { width = (width << 8) + buffer[i]; }for( int i = 0; i < 4; ++ i ) { height = (height << 8) + buffer[i + 4]; }int pos = 8;for(auto i : {&bit_depth, &color_type, &compression_method, &filter_method, &interlace_method})*i = buffer[pos ++];}void Print(){cout << width << " " << height << endl;for( auto i : {bit_depth, color_type, compression_method, filter_method, interlace_method})cout << (int)i << " ";cout << endl;}
}ihdr;void TranslateIDAT(ofstream& out, uchar* buffer, int bufferLength)
{out.write((char*)buffer, bufferLength);return ;
}void OutputIDAT()
{ifstream in(out_path, ios :: binary);if (in.is_open()) return ;ofstream out(out_path, ios :: binary);for (auto &i : mp["IDAT"]) TranslateIDAT(out, v[i].data, v[i].length);return ;
}struct pLTE
{int r, g, b;static pLTE * GetPLET(uchar* inBuffer) {int plte_size = 1 << ihdr.bit_depth;pLTE* plte = new pLTE[plte_size];int pos = 0;for (int i = 0;i < plte_size;i ++) {for (auto j : {&plte[i].r, &plte[i].g, &plte[i].b}) {*j = inBuffer[pos ++];}}return plte;}void Print() {for (auto i : {r, g, b}) cout << i << ' ';cout << endl;return ;}
}*plte;void png2yuv(string out_yuv_path, uchar* buffer, uint buffer_length)
{unsigned char* y, * u, * v;int width = ihdr.width, height = ihdr.height;int y_size = width * height;int uv_size = y_size / 4;cout << y_size << ' ' << uv_size << endl;y = new unsigned char[y_size];u = new unsigned char[uv_size];v = new unsigned char[uv_size];int uv_pos = 0, y_pos = 0;for( int i = 0; i < buffer_length; ++ i ){if((i + 1) % (width + 1) == 0) continue;int r, g, b; auto plte_tmp = plte[buffer[i]];r = plte_tmp.r, g = plte_tmp.g, b = plte_tmp.b;int h = i / (width + 1 ), w = i % (width + 1);y[y_pos ++] = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;if((h & 1) || (w & 1)) continue;u[uv_pos] = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;v[uv_pos ++] = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;}cout << y_pos << ' ' << uv_pos << endl;ofstream out(out_yuv_path, ios::binary);out.write((char*)y, y_size); out.write((char*)u, uv_size); out.write((char*)v, uv_size);out.close();for(auto i : {&y, &u, &v}) delete[] *i;cout << "ok" << endl;return ;
}int main()
{ifstream in(path, ios :: binary);file_header.GetHead(in);int pos = -1;while (true) {Chunks tmp;tmp.GetChunk(in);v.push_back(tmp); pos ++;mp[tmp.c_type].push_back(pos);if (tmp.c_type == "IEND") break;if (tmp.c_type != "IDAT" && tmp.c_type != "IHDR" && tmp.c_type != "PLTE") ancillary_chunk.insert(tmp.c_type);}in.close();cout << "All Chunks" << endl;for (auto &i : mp) {cout << "Chunk Type :" << i.first << " Chunk Position : ";auto &v = i.second;for (auto &j : v) cout << j << ' ';cout << endl;}cout << "Ancillary Chunks" << endl;for (auto &i : ancillary_chunk) cout << i << endl;int position = mp["IHDR"][0];//获取IHDR信息ihdr.GetIHDR(v[position].data);ihdr.Print();//获取IDAT信息OutputIDAT();position = mp["PLTE"][0];plte = pLTE::GetPLET(v[position].data);cout << "OK" << endl;// 解压缩IDAT后进行转换in.open(in_path, ios :: binary);cout << "Ok" << endl;in.seekg(0, ios :: end);int length = in.tellg();cout << length << endl;in.seekg(0, ios :: beg);uchar* data_buffer = new uchar[length];in.read((char*)data_buffer, length);png2yuv(out_yuv, data_buffer, length);return 0;
}
import zlib
list_dec = []
f = open('C:\\Users\\sdlwq\\Desktop\\Github\\Data-Compression\\Work\\PNG2YUV\\out.idat', 'rb')
data = f.read()
new_data = zlib.decompress(data)
fp = open('in.idat','wb')
fp.write(new_data)
fp.close()
print('done')
原始PNG
图像:
转换出的YUV
图像:
效果还是比较好的。
多个YUV合成
我们把四幅PNG
图片转换成YUV
格式,然后每一个YUV
文件重复50次,合成一个可播放的200帧YUV
视频。
编写的代码如下所示:
#include <iostream>
#include <fstream>
#include <cstdio>
#include <vector>
#include <map>
#include <set>
#define uchar unsigned char
#define uint unsigned intusing namespace std;const string path1 = "out1.yuv";
const string path2 = "out2.yuv";
const string path3 = "out3.yuv";
const string path4 = "out4.yuv";
const string out_yuv_path = "ans.yuv";int main()
{uchar* outputYuv = NULL;ifstream in1(path1, ios :: binary);ifstream in2(path2, ios :: binary);ifstream in3(path3, ios :: binary);ifstream in4(path4, ios :: binary);int length = 0, len1 = 0, len2 = 0, len3 = 0, len4 = 0;in1.seekg(0, ios :: end);len1 += in1.tellg();length += len1;in1.seekg(0, ios :: beg);in2.seekg(0, ios :: end);len2 += in2.tellg();length += len2;in2.seekg(0, ios :: beg);in3.seekg(0, ios :: end);len3 += in3.tellg();length += len3;in3.seekg(0, ios :: beg);in4.seekg(0, ios :: end);len4 += in4.tellg();length += len4;in4.seekg(0, ios :: beg);outputYuv = new uchar[length * 50];uchar* data_buffer = new uchar[len1];in1.read((char*)data_buffer, len1);for (int i = 0;i < 50;i ++) {for (int j = 0;j < len1;j ++) {outputYuv[j + i * len1] = data_buffer[j];}}in2.read((char*)data_buffer, len2);for (int i = 0;i < 50;i ++) {for (int j = 0;j < len2;j ++) {outputYuv[j + (i + 50) * len2] = data_buffer[j];}}in3.read((char*)data_buffer, len3);for (int i = 0;i < 50;i ++) {for (int j = 0;j < len4;j ++) {outputYuv[j + (i + 100) * len3] = data_buffer[j];}}in4.read((char*)data_buffer, len4);for (int i = 0;i < 50;i ++) {for (int j = 0;j < len3;j ++) {outputYuv[j + (i + 150) * len4] = data_buffer[j];}}ofstream out(out_yuv_path, ios :: binary);out.write((char*)outputYuv, 50 * length);delete [] data_buffer;delete [] outputYuv; return 0;
}
最后合成的视频如下:
PS:选的图片太小了,没法再加入姓名了QAQ
csdn
不吃YUV
的文件,于是用ffmpeg
转成了gif
(感谢sdx老师)
ffmpeg
的指令如下:
ffmpeg -video_size 80x80 -i ans.yuv -y ans.gif
PNG文件转YUV格式并实现播放相关推荐
- MP4文件转YUV格式
mp4文件变为YUV文件 用ffmpeg一行代码解决. 例如:诺爹的一个MP4视频,现把它变为一个YUV序列. 打开命令提示符cmd,输入 ffmpeg -i soccer.mp4 soccer_32 ...
- ubuntu 下播放 yuv 格式的文件预览Raw格式图片
1.ubuntu 下播放 yuv 格式的文件 1)使用ffplay sudo apt-get install ffmpeg 查看图片 ffplay -f rawvideo -video_size 64 ...
- vs2010MFC D3D播放YUV格式视频详细制作全过程
1.环境配置 1.1 Microsoft Visual Studio 2010安装 先下载Visual Studio 2010,然后双击setup.exe安装,安装时有一步选择vc++安装就可以了,其 ...
- vs2010MFC D3D播放YUV格式视频详细制作全过程
目录(?)[+] 1.环境配置 1.1 Microsoft Visual Studio 2010安装 先下载Visual Studio 2010,然后双击setup.exe安装,安装时有一步选择vc+ ...
- FFmpeg4入门07:解码视频并保存为YUV格式文件
上一篇我们解码并保存了其中的几帧确保解码过程和结果是对的.本篇我们将解码整个视频并保存为标准的YUV格式(YUV格式具体信息详见YUV格式介绍),我们就选YUV420P(I420)作为输出格式. 保存 ...
- 【工具】音乐播放相关工具,音乐文件格式转换工具,MP3文件转换成arduino可以直接播放的wav格式,MP3转WAV工具...
微信关注 "DLGG创客DIY" 设为"星标",重磅干货,第一时间送达. 最近玩播放音乐又用到了audio.online-convert这个音乐转换工具,好用, ...
- 如何快速无损地把flv格式文件转换为mp4格式(可在iPhone上播放)
众所周知,mp4格式文件是现在非常主流且高质的视频格式.flv格式相对于mp4格式的视频来说,适用范围比较窄,现在很多播放器也都不支持播放flv格式的视频.解决办法也简单,用MP4/RM转换专家把fl ...
- java 播放器开发 dat_dat格式用什么播放器 JAVA中文件的读写 I/O 输入输出流(3)
OutputStreamWriter 和InputStreamReader都是包裹流 实例:如何将键盘输入的字符组成字符串直接赋给String对象. readLine()与回车符的问题: Print流 ...
- html5播放qsv文件吗,qsv格式文件怎么播放?xp系统播放qsv格式文件的方法
qsv是一个视频格式文件?是爱奇艺的视频文件,只能在爱奇艺的播放器播放上.一些用户xp系统没有安装爱奇艺播放器,是不是就不能播放qsv格式文件?能不能在其他的视频播放器上播放?当然可以的,大家只要转换 ...
- 加mp4文件后js失效_video不能播放mp4的问题(一)
最近项目中遇到了video标签无法播放mp4的问题,表现如下: IE可正常播放 safari需要点击两次可播放 chrome内核系列都不能播放 原因排除 首先,排除掉代码错误:替换其它可播放的mp4文 ...
最新文章
- 深度学习11个实用技巧
- 封闭、缺少代码审查,硕士刚毕业的“老”工程师揭露机器学习残酷现状!
- 喝凉水都长胖?吸收比别人好?肠道菌群真是个任性的小妖精
- 【Linux】一步一步学Linux——telinit命令(144)
- LiveVideoStack线上分享第三季(一):低延迟线上K歌玩法的思考与实践
- 我如何使用回归分析通过Scikit-Learn和Statsmodels分析预期寿命
- carbon安装win7 thinkpad x1_联想ThinkPad X1 Carbon 2018笔记本win10怎么改win7
- zedboard板子上呼吸灯的实现(第一版)
- 程序员面试难题,在你结婚的时候领导要求你30分钟归队,你会如何
- 关于instr和like的比较
- 目标跟踪 MOSSE(Visual Object Tracking using Adaptive Correlation Filters)
- 35、html制作QQ彩贝热销时装页面(注意:需要素材)
- ios14描述文件无法与服务器连接,iOS14屏蔽更新描述文件已损坏,无法安装的解决办法...
- WCDMA物理层--信道整体结构流程
- 有趣的23000词根
- java日志篇(2)-JUL(java.util.logging)
- DHTMLET-Cascading Style Sheet 2.0 中文手册
- 用Python实现字典树(Trie)与双数组字典树(DATrie)
- 荣耀8一下显示无服务器,买到荣耀手机后,不打开这七个功能你就亏了!
- Java面试宝典(2018版)