文章目录

  • PNG文件转YUV
    • PNG文件格式
    • YUV文件格式
      • 什么是YUV格式文件?
      • YUV采样
        • YUV 4:2:0
      • RGB转YUV
    • 实验过程
      • 获取PNG文件信息
      • 获取IHDR信息
      • 获取PLTE调色板信息
      • 获取IDAT信息
      • 对IDAT数据块进行解压缩
      • 将解压缩的IDAT数据进行转换
      • 多个YUV合成

PNG文件转YUV

前言:本次实验选用了PNGYUV,做的过程十分煎熬…主要原因是PNGIDAT数据块的解码部分没有自己手写轮子,直接调用的Pythonzlib库,所以每个像素解压缩出来的数据分布完全没有概念,而且第一次选的图片还是带α\alphaα通道的…所以这一次的实验代码并不具备很好的鲁棒性,仅供本实验使用,引用从慎!

PNG文件格式

这一部分的介绍可以看我之前写的PNG格式文件分析

YUV文件格式

什么是YUV格式文件?

YUV是指亮度参量和色度参量分开表示的像素格式,其中Y表示亮度信号(Luma),也就是灰度值,UV表示的是色度信号。通常来说,人们一般指的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有两种存储方式:YUV420spYUV420p
下面是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

由此我们就可以实现RGBYUV的转换。

实验过程

获取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数据块进行解压缩

PNGIDAT数据块实际是经过压缩的,这里由于并不清楚压缩算法,所以直接采用Pythonzlib库进行解压缩。
编写的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格式并实现播放相关推荐

  1. MP4文件转YUV格式

    mp4文件变为YUV文件 用ffmpeg一行代码解决. 例如:诺爹的一个MP4视频,现把它变为一个YUV序列. 打开命令提示符cmd,输入 ffmpeg -i soccer.mp4 soccer_32 ...

  2. ubuntu 下播放 yuv 格式的文件预览Raw格式图片

    1.ubuntu 下播放 yuv 格式的文件 1)使用ffplay sudo apt-get install ffmpeg 查看图片 ffplay -f rawvideo -video_size 64 ...

  3. vs2010MFC D3D播放YUV格式视频详细制作全过程

    1.环境配置 1.1 Microsoft Visual Studio 2010安装 先下载Visual Studio 2010,然后双击setup.exe安装,安装时有一步选择vc++安装就可以了,其 ...

  4. vs2010MFC D3D播放YUV格式视频详细制作全过程

    目录(?)[+] 1.环境配置 1.1 Microsoft Visual Studio 2010安装 先下载Visual Studio 2010,然后双击setup.exe安装,安装时有一步选择vc+ ...

  5. FFmpeg4入门07:解码视频并保存为YUV格式文件

    上一篇我们解码并保存了其中的几帧确保解码过程和结果是对的.本篇我们将解码整个视频并保存为标准的YUV格式(YUV格式具体信息详见YUV格式介绍),我们就选YUV420P(I420)作为输出格式. 保存 ...

  6. 【工具】音乐播放相关工具,音乐文件格式转换工具,MP3文件转换成arduino可以直接播放的wav格式,MP3转WAV工具...

    微信关注 "DLGG创客DIY" 设为"星标",重磅干货,第一时间送达. 最近玩播放音乐又用到了audio.online-convert这个音乐转换工具,好用, ...

  7. 如何快速无损地把flv格式文件转换为mp4格式(可在iPhone上播放)

    众所周知,mp4格式文件是现在非常主流且高质的视频格式.flv格式相对于mp4格式的视频来说,适用范围比较窄,现在很多播放器也都不支持播放flv格式的视频.解决办法也简单,用MP4/RM转换专家把fl ...

  8. java 播放器开发 dat_dat格式用什么播放器 JAVA中文件的读写 I/O 输入输出流(3)

    OutputStreamWriter 和InputStreamReader都是包裹流 实例:如何将键盘输入的字符组成字符串直接赋给String对象. readLine()与回车符的问题: Print流 ...

  9. html5播放qsv文件吗,qsv格式文件怎么播放?xp系统播放qsv格式文件的方法

    qsv是一个视频格式文件?是爱奇艺的视频文件,只能在爱奇艺的播放器播放上.一些用户xp系统没有安装爱奇艺播放器,是不是就不能播放qsv格式文件?能不能在其他的视频播放器上播放?当然可以的,大家只要转换 ...

  10. 加mp4文件后js失效_video不能播放mp4的问题(一)

    最近项目中遇到了video标签无法播放mp4的问题,表现如下: IE可正常播放 safari需要点击两次可播放 chrome内核系列都不能播放 原因排除 首先,排除掉代码错误:替换其它可播放的mp4文 ...

最新文章

  1. 深度学习11个实用技巧
  2. 封闭、缺少代码审查,硕士刚毕业的“老”工程师揭露机器学习残酷现状!
  3. 喝凉水都长胖?吸收比别人好?肠道菌群真是个任性的小妖精
  4. 【Linux】一步一步学Linux——telinit命令(144)
  5. LiveVideoStack线上分享第三季(一):低延迟线上K歌玩法的思考与实践
  6. 我如何使用回归分析通过Scikit-Learn和Statsmodels分析预期寿命
  7. carbon安装win7 thinkpad x1_联想ThinkPad X1 Carbon 2018笔记本win10怎么改win7
  8. zedboard板子上呼吸灯的实现(第一版)
  9. 程序员面试难题,在你结婚的时候领导要求你30分钟归队,你会如何
  10. 关于instr和like的比较
  11. 目标跟踪 MOSSE(Visual Object Tracking using Adaptive Correlation Filters)
  12. 35、html制作QQ彩贝热销时装页面(注意:需要素材)
  13. ios14描述文件无法与服务器连接,iOS14屏蔽更新描述文件已损坏,无法安装的解决办法...
  14. WCDMA物理层--信道整体结构流程
  15. 有趣的23000词根
  16. java日志篇(2)-JUL(java.util.logging)
  17. DHTMLET-Cascading Style Sheet 2.0 中文手册
  18. 用Python实现字典树(Trie)与双数组字典树(DATrie)
  19. 荣耀8一下显示无服务器,买到荣耀手机后,不打开这七个功能你就亏了!
  20. Java面试宝典(2018版)

热门文章

  1. java 数组排序 Arrays.sort()用法
  2. 机器学习基石——作业2解答
  3. 北京市内可以攀登的八座山
  4. 23种设计模式设计原则
  5. 远程桌面的端口3389及关闭此端口
  6. 三对角、五对角追赶法求解线性方程组
  7. [随笔所想] 沉痛悼念开发技术专家毛星云老师
  8. 微信小程序——video视频播放
  9. 异步电动机的matlab建模与仿真,异步电动机的matlab建模与仿真
  10. pscc2019滤镜抽出_「PS-CC2019新版教程」魔棒工具,让你一秒钟完成抠图-基础篇