PNG文件转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的存储格式:
YUV420sp的存储
这是YUV420p的存储格式:
在这里插入图片描述
可以看到,YUV420sp中的UV分量是交织存储的,本次实验中我们采用YUV420sp的存储方式。

RGB转YUV

我们一般得到的都是图像的原始RGB信息,那么如何将RGB信号转换成YUV信号?
我们这里直接给出数字RGB转换成YUV的公式:(BT.601标准)
Y = 16 + 0.257 ∗ R + 0.504 ∗ G + 0.098 ∗ B Y = 16 + 0.257 * R + 0.504 * G+ 0.098 * B Y=16+0.257R+0.504G+0.098B
C b = 128 − 0.148 ∗ R − 0.291 ∗ G + 0.439 ∗ B Cb = 128 - 0.148 * R - 0.291 * G+ 0.439 * B Cb=1280.148R0.291G+0.439B
C r = 128 + 0.439 ∗ R − 0.368 ∗ G − 0.071 ∗ B Cr = 128 + 0.439 * R - 0.368 * G - 0.071 * B Cr=128+0.439R0.368G0.071B

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

实验过程

获取PNG文件信息

我们可以通过运行以下代码获取文件的基本数据块信息:

#include <iostream>
#include <fstream>
#include <cstdio>
#include <vector>
#include <map>
#include <set>
#define uchar unsigned char

using 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的格式定义,我们可以得到以下信息:

名称取值
width80
height80
Bit depth8
ColorType3,带有调色板
Compression method0 ,deflate压缩算法
Filter method0,即滤波方法 0
Interlace method0,非隔行扫描

获取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 int

using 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 int

using 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
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: YUV格式的图像通常是用于视频处理的,其中Y表示亮度分量,U和V表示颜色分量。这种图像格式在计算机视觉、数字信号处理、视频压缩等领域中广泛使用。下面介绍几种常见的打开YUV格式图像的方法。 1. 使用视频播放器:常见的视频播放器如Potplayer、VLC等都支持打开YUV格式的图像文件,只需将文件拖动到播放器窗口即可打开。 2. 使用Matlab:Matlab是一种常见的科学计算软件,也可以用于图像处理。在Matlab中打开YUV格式图像需要使用`yuvread()`函数,该函数可以读取和显示YUV格式文件。 3. 使用Python:Python是一种常见的编程语言,也可以用于图像处理。使用Python打开YUV格式图像需要使用相应的库,如OpenCV或PyAV等,同时需要编写相应的代码实现图像的读取和显示。 无论是使用视频播放器、Matlab还是Python,打开YUV格式图像都需要注意一些细节。例如,需要确定图像的尺寸和颜色空间,不同的YUV格式可能会有不同的解码方式,需要根据具体的需要进行相应的设置和调整。 ### 回答2: YUV是一种用于存储图像或视频的格式,在计算机图形和多媒体处理中应用广泛。下面是一个简单的步骤,告诉您如何打开YUV格式的图像。 首先,您需要一个图像查看器或多媒体播放器,它支持YUV格式。一些常见的软件包括VLC媒体播放器、PotPlayer、Photoshop和FFmpeg工具等。 其次,确保您已经将需要打开的YUV文件保存在计算机的某个位置。通常,YUV图像文件的扩展名为.yuv。 接下来,打开您选择的图像查看器或多媒体播放器。对于VLC媒体播放器,您只需点击"媒体"菜单中的"打开文件"选项,然后选择要打开的YUV文件。 对于PotPlayer,您可以使用类似的方法。单击"文件"菜单,然后选择"打开"选项,找到YUV文件并点击"打开"。 如果您想使用Photoshop打开YUV文件,可以选择文件菜单中的"打开"选项,并选择YUV文件。在打开对话框中,您还可以选择将其打开为灰度图像、RGB图像或其他特定颜色空间的图像。 另外,您还可以使用FFmpeg工具在命令行中打开YUV文件。您可以编写一个简单的命令,指定输入文件和所需的输出格式。例如,"ffmpeg -s 1920x1080 -i input.yuv output.png"可以将YUV文件换为PNG图像。 最后,一旦您成功打开了YUV格式的图像文件,您可以使用相应的软件进行浏览、编辑、处理或换等操作,以满足您的需要。 总而言之,打开YUV格式图像需要一个支持该格式的软件,并根据软件的指导打开文件。 ### 回答3: YUV格式是一种用于存储彩色图像的文件格式,常用于视频压缩和流媒体传输。要打开YUV格式的图像,可以按照以下步骤进行。 首先,需要一个支持YUV格式的图像编辑软件或播放器。常见的软件包括Adobe Premiere、VirtualDub和VLC媒体播放器等。在电脑上安装这些软件后,可以直接打开YUV文件。 另外,还可以通过使用YUV换工具将YUV文件换为其他常见的图像格式,例如JPEG或PNG。这些换工具可以在互联网上找到,并且有多个免费和付费的选项可供选择。 打开YUV格式图像时,可以选择加载整个图像或者仅选择加载其中的部分区域。然后,可以使用软件提供的功能来编辑图像,如调整亮度和对比度、改变颜色饱和度、添加滤镜效果等。 需要注意的是,YUV格式图像通常由分为亮度(Y)和色度(UV)两个分量组成,因此在查看或编辑YUV图像时,确保正确理解和处理这些分量是非常重要的。 总结起来,要打开YUV格式图像,可以通过使用支持YUV格式的软件或换工具,或者将文件换为其他常见的图像格式实现。在打开和处理YUV图像时,需要注意其分量的特性,以便正确对图像进行查看和编辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CUCKyrie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值