C++实现JPEG格式图片解析(附代码)

news2024/11/15 22:37:12

在网上看了好多解析JPEG图片的文章,多多少少都有问题,下面是我参考过的文章链接:

首先,解析的步骤

  • 1.读取文件的信息
  • 2.Huffman编码解码
  • 3.直流交流编码解析
    • 然而,读取多少个8×8矩阵才能解析出一个MCU呢?
  • 4.反量化
  • 5.反Zig-Zag变化
  • 6.反DCT变化
  • 8.YCbCr转RGB
  • 效果图

1.读取文件的信息

JPEG格式中信息是以段(数据结构)来存储的。
段的格式如下

名称字节数数据说明
段标识1FF每个新段的开始标识
段类型1类型编码(称作“标记码”)
段长度2包括段内容和段长度本身,不包括段标识和段类型
段内容≤65533字节

其余具体信息请见以下链接,我就不当复读机了。
JPEG标记的说明
格式介绍
值得注意的一点是一个字节的高位在左边,而且直流分量重置标记一共有8个,其他的格式说明在第二个链接中已经足够详细了

这些段中必须要读取的段:SOS, DHT, DQT, SOF, DRI,其他的只是锦上添花
这里面可能会出现多个SOF段,我们需要拿到这几个段中图片高度和宽度的最大值,和YCbCr的水平,垂直采样因子的最大值分别记为Hmax,Vmax,之后会用到

DRI中的开始间隔指的就是直流分量重置间隔,我们记为reset

2.Huffman编码解码

首先Huffman编码分直流表(DC)和交流表(AC),他们一般各自有两张表,具体使用哪张表是通过SOS里面的对应关系来的,一般Y对应第一张表,CbCr对应第二、三张表。

因为规定huffman编码最多16位,所以huffman编码的最大值位65535
以下代码为我的解码方式,直流交流均如此

int curPos = 16, curCode = 0;
for (int i = 0; i < 16; i++) {
    int count = temp[i];//count为二进制位数为i+1的个数
    curCode <<= 1;		//curCode为当前huffman编码数值
    while (count--) {	//一次循环生成一个
        uint16_t code=curCode;
        uint8_t bit=i+1;//比特位有几位 00为2位
        uint8_t weight=temp[curPos];//权重是按照顺序排列的,如比特位为两位的编码有两个,设为00,01,后面权重排列为1,2,则00对应1,01对应2
        pair<uint8_t, uint8_t> t1(bit,weight);
        //<code,<bit,weight>
        pair<uint16_t, pair<uint8_t, uint8_t>> t2(curCode,t1);
        table.insert(t2);
        curCode++;
        curPos++;
    }
}

3.直流交流编码解析

SOS段之后就是真正的图片压缩数据了,可以选择一次性读取到内存中,也可以边读数据边做后面的解析步骤,我是选择了第二种。每读取一个MCU后做一次解析(我使用的是缓存队列)。在图片编码的时候需要划分MCU(最小编码单位),每个MCU由多个8×8矩阵组成,通过编码将二维数组转换为一维的,所以当读取的数据达到了64个,就代表一个8×8的块解析完成,直到读取到0xFFD9结束

然而,读取多少个8×8矩阵才能解析出一个MCU呢?

MCU里面的8×8矩阵个数,如果从编码角度来说的话,8×8矩阵个数是Hmax*Vmax个,但是从解码角度来说,因为此时的YCbCr已经分开成为了三张表,所以8×8矩阵个数应该是三个分量的水平、垂直采样因子的乘积之和(先乘积,再求和)记为SUMmcu,所以读取一次要读取SUMmcu个8×8矩阵(此时这里面有YCbCr三种表,之后通过公式将YCbCr转换为RGB数值)

好了,到这里我们知道了要读取多少个8×8的矩阵 (实际上,因为没有反Zig-Zag编码,此时还是有64个数据的一维数组)
接下来开始解析,解析需要使用上一步解码出来的Huffman编码。
解析方式如下:
1、对于直流(差分编码)按照一个比特位来读取图片压缩数据,若在Huffman表中发现该编码,并且位数相等,则读取该编码所对应的权重,该权重代表接下来读取多少个比特位作为直流分量的值,你以为这就完了?还要加上差分矫正变量 (YCbCr每张表都有一个,所以一共有3个)
2、对于交流(游程编码),其他部分都一样(这个没有差分矫正变量),不同的地方举个例子,设读取的直流分量为0x37,则低4位(这里为7)代表接下来7个比特位是该交流分量的值,而高4位(此处为3)代表此交流分量前有3个0 (这里就不用加上前面的了)。注意直流交流使用的Huffman表不同
3、接下来就是循环读取交流分量了,那么什么时候退出呢?
有两个条件,只要达成一个就可以退出

  1. 读取了63个交流分量
  2. 交流分量的权值为0,此位后面全是0

对于根据权重所读出来的值(不区分直流交流),对于最高位(最左边)若为0则是负数,否则为正数,判断代码如下,curValue为读取的值,curValueLength为读取的值有多少位

curValue = (curValue >= pow(2, curValueLength - 1) ? curValue : curValue - pow(2, curValueLength) + 1);

这里面还有两个坑(若DRI读出来的直流分量重置间隔reset为0,不用管这步)

  1. 假设reset为332(这是我图片的间隔),就是隔了332个MCU(也就是332×SUMmcu个8×8的矩阵),需要将
    所有差分矫正变量全部置为0,并且当这332个MCU读取完后,你要读取两个字节(这两个字节是一个段),这两个字节应该正好是0xFF 0xD0~0xD7,并且D0到D7是按顺序出现的,例如,上一个是0xFFD0那么下一个肯定是0xD1,到D7后下一个是D0,若对不上那就有问题了。还有,这读出来的两个字节不是图片的压缩数据不需要解码
  2. 若读取到了0xFF00则00忽略

到此,我们得到了一个有64个元素的一维数组

4.反量化

我们用之前读出来的量化表(也是64个元素的,你说巧不巧嘿嘿)与上面解码得到的元素对应项相乘,反量化完成!!!

5.反Zig-Zag变化

编码方式如下
编码方式

我使用的模拟法,将一维数组转为8×8矩阵
函数如下,写的不好

double** UnZigZag(int* originArray){
    double** table=new double*[ROW];
    for(int i=0;i<ROW;i++) table[i]=new double[COL];
    int cur=0,x=0,y=0;
    bool flag = true;//true是右上 false是左下
    while (cur < 64) {
        table[y][x] = originArray[cur++];
        if (flag) { x++; y--; }
        else { x--; y++; }
        if (x < 0 || y < 0 || x>7 || y>7) flag = !flag;
        if (x < 0 && y>7) { x = 1; y = 7; }
        if (x < 0) x = 0;
        else if (x > 7) { x = 7; y += 2; }
        if (y < 0) y = 0;
        else if (y > 7) { y = 7; x += 2; }
    }
    return table;

也可以使用另外一种方法,手动记录一个数组,将位置写好,转换只需要4行代码

6.反DCT变化

那个公式太慢了,有这个公式的简化版本,公式可以化为矩阵乘法,只需要一个转换矩阵
矩阵我是用下面的代码计算得到的

double** JPEGData::createDCTAndIDCTArray(int row){
    double** res=new double*[row];
    for(int i=0;i<row;i++) res[i]=new double[row];
    // cout<<endl;
    for(int i=0;i<row;i++){
        for(int j=0;j<row;j++){
            double t=0;
            if(i==0) t=sqrt(1.0/row);
            else t=sqrt(2.0/row);
            res[i][j]=t*cos(M_PI*(j+0.5)*i/row);
            // cout<<res[i][j]<<" ";
        }
        // cout<<endl;
    }
    return res;
}
//设返回的矩阵为A
//DCT原理 Y=A*X*A'(X为正变换输入,Y是输出)
//IDCT原理X=A'*Y*A(Y是逆变换输入,X是输出'是转置)
void JPEGData::IDCT(double** originMatrix){
    vector<vector<double>> temp(ROW,vector<double>(COL,0));
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=DCTAndIDCTArray[k][i]*originMatrix[k][j];
            }
            temp[i][j]=sum;
        }
    }
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=temp[i][k]*DCTAndIDCTArray[k][j];
            }
            originMatrix[i][j]=sum;
        }
    }
}
void JPEGData::DCT(double** originMatrix){
    vector<vector<double>> temp(ROW,vector<double>(COL,0));
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=DCTAndIDCTArray[i][k]*originMatrix[k][j];
            }
            temp[i][j]=sum;
        }
    }
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=temp[i][k]*DCTAndIDCTArray[j][k];
            }
            originMatrix[i][j]=sum;
        }
    }
}

8.YCbCr转RGB

公式如下,这个是真好使
R=128+y+1.402 cr
G=128+y-0.71414
cr-0.34414*cb
B=128+y+1.772 *cb

struct RGB{
	uint8_t red;
	uint8_t green;
	uint8_t blue;
};
RGB** JPEGData::YCbCrToRGB(const int* YUV){
    RGB **res = new RGB *[ROW * max_v_samp_factor];
    int matrixCount = YUV[0] + YUV[1] + YUV[2];
    int crCount = 0, cbCount = 0;
    //1=Y, 2=Cb, 3=Cr
    //式子 scale*x,scale*y
    double cb_h_samp_scale=component[1].h_samp_factor*1.0/max_h_samp_factor,
           cb_v_samp_scale=component[1].v_samp_factor*1.0/max_v_samp_factor,
           cr_h_samp_scale=component[2].h_samp_factor*1.0/max_h_samp_factor,
           cr_v_samp_scale=component[2].v_samp_factor*1.0/max_v_samp_factor;
    for (int i = 0; i < ROW * max_v_samp_factor; i++)
        res[i] = new RGB[COL * max_h_samp_factor];
    //此处直接生成rgb值
    //注意,此处YCbCr的对应关系与采样因子有关
    //这个ycbcr存的是一个MCU,假设YUV为411,那么ycbcr有6个
    //这种方式转换不了YUV为420的,因为数组越界了,不过可以加个判断,我懒得改了
    // cout<<endl;
    for(int j=0;j<ROW * max_v_samp_factor;j++){
        for(int k=0;k<COL * max_h_samp_factor;k++){
            int yPos = (j / ROW) * component[0].h_samp_factor + (k / COL);
            int cbPos = YUV[0] + (int)((k / ROW) * cb_v_samp_scale) + (int)((j / COL) * cb_h_samp_scale);
            int crPos = YUV[0] + YUV[1] + (int)((k / ROW) * cr_v_samp_scale) + (int)((j / COL) * cr_h_samp_scale);
            double y = ycbcr[yPos][j % ROW][k % COL];
            double cb = ycbcr[cbPos][(int)(j * cb_v_samp_scale)][(int)(k * cb_h_samp_scale)];
            double cr = ycbcr[crPos][(int)(j * cr_v_samp_scale)][(int)(k * cr_h_samp_scale)];

            res[j][k].red   =RGBValueLimit(128+y+1.402  *cr);
            res[j][k].green =RGBValueLimit(128+y-0.71414*cr-0.34414*cb);
            res[j][k].blue  =RGBValueLimit(128+y+1.772  *cb);

            
            // 输出当前选择的矩阵
            //cout<<dec<<yPos<<" "<<cbPos<<" "<<crPos<<" ";
            // cout<<hex<<setw(2)<<setfill('0')<<(int)res[j][k].red
            //          <<setw(2)<<setfill('0')<<(int)res[j][k].green
            //          <<setw(2)<<setfill('0')<<(int)res[j][k].blue<<" ";
        }
        // cout<<endl;
    }
    // cout<<endl;
    return res;
}

效果图

这个是JPEG
jpg
这是位图
位图

最最后,如何把图片显示出来呢?,我将信息转换为位图,就能看见了。
下面源码附上
Image.h

#pragma once
#define _USE_MATH_DEFINES
#include <cmath>
#include <fstream>
#include <stdint.h>
#include <utility>
#ifndef _IMAGE_
#define _IMAGE_

#include "Util.h"
#include <string>
#include <vector>
#include <iostream>
using namespace std;

NAME_SPACE_START(myUtil)

#define ROW 8
#define COL 8
#define HUFFMAN_DECODE_DEQUE_CACHE 64//单位:位
// #define _DEBUG_
// #define _DEBUGOUT_
#define FREE_VECTOR_LP(vectorName) \
    for(auto item : vectorName){	\
		for(int i=0;i<ROW;i++)\
			delete [] item[i];\
        delete [] item;	\
    }\
	vectorName.clear();

//释放二维指针
#define FREE_LP_2(lpName,row) \
	for(int i=0;i<row;i++){\
		delete [] lpName[i];\
	}\
	delete [] lpName;
	
//段类型
enum JPEGPType{
    SOF0    = 0xC0,     //帧开始
    SOF1    = 0xC1,     //帧开始
    SOF2    = 0xC2,     //帧开始
    DHT     = 0xC4,     //哈夫曼表
    SOI     = 0xD8,     //文件头
    EOI     = 0xD9,     //文件尾
    SOS     = 0xDA,     //扫描行开始
    DQT     = 0xDB,     //定义量化表
    DRI     = 0xDD,     //定义重新开始间隔
    APP0    = 0xE0,     //定义交换格式和图像识别信息
    APP1    = 0xE1,     //定义交换格式和图像识别信息
    APP2    = 0xE2,     //定义交换格式和图像识别信息
    COM     = 0xFE      //注释
};

//将一维数组变为二维数组
double** UnZigZag(int* originArray);

struct RGB{
	uint8_t red;
	uint8_t green;
	uint8_t blue;
};

//SOS
class JPEGScan{
public:
	//componentId,<DC,AC>
	map<uint8_t,pair<uint8_t,uint8_t>> componentHuffmanMap;
	bool Init(fstream& file,uint16_t len);
};

//APP
class JPEGInfo{
public:
	uint16_t version;

};
//DHT
class JPEGHuffmanCode{
public:
	using iterator = map<uint16_t,pair<uint8_t,uint8_t>>::iterator;
	//<code,<bit,weight>
	map<uint16_t,pair<uint8_t,uint8_t>> table;
	//init huffman table
	bool Init(fstream& file,uint16_t len);
	//find-true not find-false
	bool findKey(const uint16_t& code,const uint8_t& bit,iterator& it);
};
//DQT
//quality table
class JPEGQuality{
public:
	uint8_t precision;
	uint8_t id;
	vector<uint16_t> table;
	bool Init(fstream& file,uint16_t len);
};

//SOF segment
class JPEGComponent{
public:
	//1=Y, 2=Cb, 3=Cr, 4=I, 5=Q
	uint8_t colorId;
	uint8_t h_samp_factor;
	uint8_t v_samp_factor;
	uint8_t qualityId;
	bool Init(fstream& file,uint16_t len);
};

class JPEGData{
	int max_h_samp_factor;//行MCU
	int max_v_samp_factor;//列MCU
	int width;
	int height;
	int precision;
	bool isYUV411=false;
	bool isYUV422=false;
	bool isYUV111=false;
	uint8_t curDRI=0;//当前重置直流分量标识,这里只取个位方便计算
	uint16_t resetInterval=0;//单位是MCU
	int preDCValue[3]={0};  //用于直流差分矫正
	//量化表
	vector<JPEGQuality> quality;
	//huffman码表
	vector<JPEGHuffmanCode> dc_huffman;
	vector<JPEGHuffmanCode> ac_huffman;
	//component每个颜色分量
	vector<JPEGComponent> component;
	JPEGScan scan;
	//vector<int**> deHuffman;
	vector<double**> ycbcr;
	vector<RGB**> rgb;
	double** DCTAndIDCTArray;
	streampos pos;
	bool EOI{false};
public:
	JPEGData():
			max_h_samp_factor(0),
			max_v_samp_factor(0),
			width(0),
			height(0),
			precision(0){
				DCTAndIDCTArray=createDCTAndIDCTArray(ROW);
			}
	~JPEGData(){
		FREE_LP_2(DCTAndIDCTArray,ROW-1)
		// FREE_LP_2(DCTArray,ROW-1)
		// FREE_LP_2(IDCTArray,ROW-1)
		FREE_VECTOR_LP(rgb)
	}
	bool readJPEG(const char* filePath);

	int getWidth() const {return width;}
	int getHeight() const {return height;}
	vector<RGB**> getRGB() const {return rgb;}
	int getMaxHSampFactor() const {return max_h_samp_factor;}
	int getMaxVSampFactor() const {return max_v_samp_factor;}

	double** createDCTAndIDCTArray(int row);
	//double** createIDCTArray(int row);
	void DCT(double** originMatrix);
	void IDCT(double** originMatrix);
protected:
	bool readSOF(fstream& file,uint16_t len);
	bool readData(fstream& file);
	bool huffmanDecode(fstream& file);
	void deQuality(double** originMatrix,int qualityID);
	//隔行正负纠正
	void PAndNCorrect(double** originMatrix);
	RGB** YCbCrToRGB(const int* YUV);
	//标记位检查 是否结束,是否重置直流矫正数值,返回要添加的数值
	string FlagCkeck(fstream& file,int byteInfo);
	uint16_t ReadByte(fstream& file,int len);
	uint16_t findHuffmanCodeByBit(fstream& file,int& length,int& pos,string& deque,int curValue,int& curValLen);
};

NAME_SPACE_END()

#endif //!_IMAGE_

Image.cpp

#include "Image.h"
#include "Util.h"
#include <algorithm>
#include <cmath>
#include <exception>
#include <fstream>
#include <stdint.h>
#include <bitset>
#include <stdlib.h>
#include <utility>
#include <cstring>
#include <vector>
#include <iomanip>

NAME_SPACE_START(myUtil)

int RGBValueLimit(double input){
    if(input<0) return 0;
    else if(input>255) return 255;
    // 四舍五入、取整均可
    // return (int)(input);
    return round(input);
}

void print(double** originMatrix){
    cout<<endl;
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            cout<<originMatrix[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;
}

double** UnZigZag(int* originArray){
    double** table=new double*[ROW];
    for(int i=0;i<ROW;i++) table[i]=new double[COL];
    int cur=0,x=0,y=0;
    bool flag = true;//true是右上 false是左下
    while (cur < 64) {
        table[y][x] = originArray[cur++];
        if (flag) { x++; y--; }
        else { x--; y++; }
        if (x < 0 || y < 0 || x>7 || y>7) flag = !flag;
        if (x < 0 && y>7) { x = 1; y = 7; }
        if (x < 0) x = 0;
        else if (x > 7) { x = 7; y += 2; }
        if (y < 0) y = 0;
        else if (y > 7) { y = 7; x += 2; }
    }
    return table;
}

bool JPEGScan::Init(fstream &file, uint16_t len){
    try {
        uint8_t count=file.get();
        len--;
        while(count--){
            uint8_t componentId=file.get();
            uint8_t table=file.get();
            uint8_t dcId=table>>4;
            uint8_t acId=table&0x0f;
            pair<uint8_t, uint8_t> info1(dcId,acId);
            pair<uint8_t, pair<uint8_t, uint8_t>> info2(componentId,info1);
            componentHuffmanMap.insert(info2);
        }
    } catch (...) {
        return false;
    }
    return true;
}

bool JPEGHuffmanCode::Init(fstream &file, uint16_t len){
    try{
        vector<uint8_t> temp;
        while(len--){
            int info=file.get();
            temp.push_back(info);
        }
        int curPos = 16, curCode = 0;
        for (int i = 0; i < 16; i++) {
            int count = temp[i];
            curCode <<= 1;
            while (count--) {
                uint16_t code=curCode;
                uint8_t bit=i+1;
                uint8_t weight=temp[curPos];
                pair<uint8_t, uint8_t> t1(bit,weight);
                pair<uint16_t, pair<uint8_t, uint8_t>> t2(curCode,t1);
                table.insert(t2);
                curCode++;
                curPos++;
            }
        }

    }
    catch(...){
        return false;
    }
    return true;
}

bool JPEGHuffmanCode::findKey(const uint16_t& code,const uint8_t& bit,iterator& it)
{
    it=table.find(code);
    if(it==table.end()) return true;
    return it->second.first!=bit;
}
bool JPEGQuality::Init(fstream &file, uint16_t len){
    try{
        int info=file.get();
        precision=info>>4;
        id=info&0x0f;
        len--;
        while(len--){
            int t=file.get();
            table.push_back(t);
        }
    }
    catch(...){
        return false;
    }
    return true;
}

bool JPEGComponent::Init(fstream &file, uint16_t len){
    try {
        int info1=file.get();
        int info2=file.get();
        int info3=file.get();
        colorId=info1;
        h_samp_factor=info2>>4;
        v_samp_factor=info2&0x0f;
        qualityId=info3;
    } catch (...) {
        return false;
    }
    return true;
}

bool JPEGData::readJPEG(const char *filePath){
    fstream file(filePath,ios::in|ios::binary);
    if(file.fail()) return false;
    file.seekg(0,ios::end);
    pos = file.tellg();
    file.seekg(2,ios::beg);
    dc_huffman.resize(2);
    ac_huffman.resize(2);
    try {
        //do read data through using other method
        uint16_t pLen=0;
        uint16_t pMarker=0xFF;
        uint16_t pType=0x00;
        while(!file.eof()){
            pMarker=file.get();
            pType=file.get();
            if(pType==EOI) break;

            pLen=file.get();
            pLen=(pLen<<8)+file.get();
            // cout<<hex<<pMarker<<" "<<pType<<" "<<pLen<<endl;
            if(pMarker!=0xFF) throw exception();
            bool flag=true;
            switch (pType) {
                case SOF0:
                case SOF1:
                case SOF2:{
                    flag=readSOF(file, pLen-2);
                    break;
                }
                case DHT:{
                    JPEGHuffmanCode huf;
                    int info=file.get();
                    int tableId=info&0x0f;
                    // cout<<hex<<info<<" ";
                    flag=huf.Init(file, pLen-3);
                    if((info>>4)&1) ac_huffman[tableId]=huf;
                    else dc_huffman[tableId]=huf;
                    break;
                }
                //case SOI:
                //case EOI:
                case SOS:{
                    flag=scan.Init(file, pLen-2);
                    int count=3;
                    // cout<<endl;
                    while(count--) file.get();
                    // cout<<endl;
                    //正式读取数据
                    if(!flag) break;
                    flag=readData(file);
                    break;
                }
                case DQT:{
                    JPEGQuality q;
                    flag=q.Init(file, pLen-2);
                    quality.push_back(q);
                    break;
                }
                case DRI:{
                    resetInterval=ReadByte(file, 2);
                    break;
                }
                case APP0:
                case APP1:
                case APP2:
                case COM:{
                    pLen-=2;
                    while(pLen--){
                        file.get();
                    }
                    break;
                }
                default:
                    pLen-=2;
                    while(pLen--){
                        file.get();
                    }
                    break;
                
            }
            if(!flag) throw exception();
            // cout<<endl;
        }
    } catch (...) {
        file.close();
        return false;
    }
    file.close();
    return true;
}

bool JPEGData::readSOF(fstream& file,uint16_t len){
    try {
        precision=file.get();
        height=max(height,(int)ReadByte(file, 2));
        width=max(width,(int)ReadByte(file, 2));
        int count=ReadByte(file, 1);
        if(count!=3) return false;
        len-=6;
        component.resize(count);
        for(int i=0;i<count;i++){
            JPEGComponent com;
            com.Init(file, len/3);
            max_h_samp_factor=max(max_h_samp_factor,(int)com.h_samp_factor);
            max_v_samp_factor=max(max_v_samp_factor,(int)com.v_samp_factor);
            component[i]=com;
        }
        if((component[0].h_samp_factor*component[0].v_samp_factor)
            /(component[1].h_samp_factor*component[1].v_samp_factor)==4){
            isYUV411=true;
        }
        else if((component[0].h_samp_factor*component[0].v_samp_factor)
            /(component[1].h_samp_factor*component[1].v_samp_factor)==2){
            isYUV422=true;
        }
        else if((component[0].h_samp_factor*component[0].v_samp_factor)
            /(component[1].h_samp_factor*component[1].v_samp_factor)==1){
            isYUV111=true;
        }
    } catch (...) {
        return false;
    }
    return true;
}

bool JPEGData::readData(fstream& file){
    bool flag=true;
    try{
        //使用huffman表来解出RLE编码,接着转回长度为64的矩阵
        flag=huffmanDecode(file);
        if(!flag) return false;
        //反量化,即上面的64矩阵×对应位置的量化表
        //flag=deQuantity();
        //if(!flag) return false;
        //反zig-zag排序
        //flag=deZSort();
        //if(!flag) return false;
        //反离散余弦变换

        //if(!flag) return false;
        //YCbCr转RGB

        //if(!flag) return false;
    }
    catch(...){
        return false;
    }
    return true;
}

bool JPEGData::huffmanDecode(fstream& file){
    try {
        //原图像一个MCU有多少8*8矩阵(此时是YCbCr还没有分开)
        //int MCUBlockCount=max_h_samp_factor*max_v_samp_factor;
        //顺序YCbCr
        int YUV[]={component[0].h_samp_factor*component[0].v_samp_factor,
                    component[1].h_samp_factor*component[1].v_samp_factor,
                    component[2].h_samp_factor*component[2].v_samp_factor};
        int curMCUCount=1;      //当前MCU数量
        int curValueLength=0;   //当前值有多少位
        int curValue=0;         //当前的值

        int curBitDequeLength=8;//当前curBitDeque长度
        int curBitPos=0;        //当前string读取到第几位
        int restart=resetInterval;//直流分量重置
        string curBitDeque="";  //用来存储读出来的2进制数
        //一次循环解析一个MCU
        curBitDeque.append(bitset<8>(file.get()).to_string());
        curBitDequeLength=8;
        // cout<<curBitDeque;
        while(!EOI||(pos-file.tellg())!=2){
            // cout<<endl;
            int count=1;
            for(int i=0;i<3;i++){
                for(int j=0;j<YUV[i];j++){
                    // cout<<count++<<" ";
                    int matrix[64]={0};
                    int valCount=0;
                    uint8_t dcID = scan.componentHuffmanMap[component[i].colorId].first;
                    uint8_t acID = scan.componentHuffmanMap[component[i].colorId].second;
                    int qualityId=component[i].qualityId;
                    if(qualityId>=quality.size()) qualityId=0;
                    // cout<<endl;
                    while(valCount<64){
                        //用curBitDeque和curBit去找权重,curValue作为当前键值
                        JPEGHuffmanCode::iterator it;
                        JPEGHuffmanCode &huffman = valCount==0?dc_huffman[dcID]:ac_huffman[acID];
                        while(curValueLength<=16&&huffman.findKey(curValue,curValueLength,it)){
                            curValue=findHuffmanCodeByBit(file,curBitDequeLength,curBitPos,curBitDeque,curValue,curValueLength);
                        }
                        if(curValueLength>16) 
                            return true;
                        #ifdef _DEBUGOUT_
                        //cout<<dec<<" "<<curBitPos<<" "<<curBitDequeLength<<" ";
                        cout<<"key="<<hex<<curValue<<" len="<<curValueLength<<endl;
                        #endif
                        //已经找到了权重和位宽
                        uint8_t weight,zeroCount=0;
                        if(valCount==0) weight = it->second.second;
                        else { weight = it->second.second & 0x0f; zeroCount = it->second.second >> 4;}
                        curValue=0;//这里变为dc或ac值
                        curValueLength=0;
                        if(valCount!=0&&weight==0&&zeroCount==0) break;//后面全是0
                        // 读取真正的值
                        for(int k=0;k<weight;k++){
                            curValue=findHuffmanCodeByBit(file,curBitDequeLength,curBitPos,curBitDeque,curValue,curValueLength);
                        }
                        curValue = (curValue >= pow(2, curValueLength - 1) ? curValue : curValue - pow(2, curValueLength) + 1);
                        // cout<<curValue<<endl;
                        int writeValue=valCount==0?(preDCValue[i]+=curValue):curValue;
                        valCount+=zeroCount;
                        writeValue*=quality[qualityId].table[valCount];//反量化
                        matrix[valCount]=writeValue;
                        curValue=0;
                        curValueLength=0;
                        valCount++;
                    }
                    double** tempZ = UnZigZag(matrix);//反zig-zag编码
                    //反量化,在反zig-zag编码前后差别,前面:RGB数值与编辑器比偏小,反之偏大,这也与最后取整时的方式有关
                    // deQuality(tempZ,qualityId);
                    // print(tempZ);
                    //隔行正负纠正,有的博客说了,但是没感觉有啥帮助
                    // PAndNCorrect(tempZ);
                    IDCT(tempZ);                    //dct逆变换
                    ycbcr.push_back(tempZ);
                #ifdef _DEBUG_
                    for(int k=0;k<ROW;k++){
                        for(int l=0;l<COL;l++){
                            cout.width(3);
                            cout<<dec<<tempZ[k][j]<<" ";
                        }
                        cout<<endl;
                    }
                    cout<<endl;
                #endif
                }
            }
            // if(count!=6){
            //     cout<<" ";
            // }
            RGB** lpRGB = YCbCrToRGB(YUV);
            FREE_VECTOR_LP(ycbcr)
            rgb.push_back(lpRGB);
            // 直流分量重置间隔不为0的
            if(restart>0){
                resetInterval--;
                if(resetInterval==0){
                    resetInterval=restart;
                    curDRI+=1;
                    curDRI&=0x7;
                    //需要在此处读取两字节信息,看是否为重置标识
                    file.get();
                    if(file.get()==0xD9) EOI=true;
                    curBitPos=curBitDequeLength;
                    preDCValue[0]=0;
                    preDCValue[1]=0;
                    preDCValue[2]=0;
                }
            }
            // cout<<"curMCUCount="<<dec<<curMCUCount++<<" pos="<<pos<<"/"<<file.tellg()<<" "<<file.tellg()*100.0/pos<<"%\n";
            if(pos-file.tellg()==2) break;
        }
        cout<<"\nsuccessfully\n";
    } catch (exception ex) {
        cout<<ex.what();
        return false;
    }
    return true;
}

RGB** JPEGData::YCbCrToRGB(const int* YUV){
    RGB **res = new RGB *[ROW * max_v_samp_factor];
    int matrixCount = YUV[0] + YUV[1] + YUV[2];
    int crCount = 0, cbCount = 0;
    //1=Y, 2=Cb, 3=Cr
    //式子 scale*x,scale*y
    double cb_h_samp_scale=component[1].h_samp_factor*1.0/max_h_samp_factor,
           cb_v_samp_scale=component[1].v_samp_factor*1.0/max_v_samp_factor,
           cr_h_samp_scale=component[2].h_samp_factor*1.0/max_h_samp_factor,
           cr_v_samp_scale=component[2].v_samp_factor*1.0/max_v_samp_factor;
    for (int i = 0; i < ROW * max_v_samp_factor; i++)
        res[i] = new RGB[COL * max_h_samp_factor];
    //此处直接生成rgb值
    //注意,此处YCbCr的对应关系与采样因子有关
    // cout<<endl;
    for(int j=0;j<ROW * max_v_samp_factor;j++){
        for(int k=0;k<COL * max_h_samp_factor;k++){
            int yPos = (j / ROW) * component[0].h_samp_factor + (k / COL);
            int cbPos = YUV[0] + (int)((k / ROW) * cb_v_samp_scale) + (int)((j / COL) * cb_h_samp_scale);
            int crPos = YUV[0] + YUV[1] + (int)((k / ROW) * cr_v_samp_scale) + (int)((j / COL) * cr_h_samp_scale);
            double y = ycbcr[yPos][j % ROW][k % COL];
            double cb = ycbcr[cbPos][(int)(j * cb_v_samp_scale)][(int)(k * cb_h_samp_scale)];
            double cr = ycbcr[crPos][(int)(j * cr_v_samp_scale)][(int)(k * cr_h_samp_scale)];

            res[j][k].red   =RGBValueLimit(128+y+1.402  *cr);
            res[j][k].green =RGBValueLimit(128+y-0.71414*cr-0.34414*cb);
            res[j][k].blue  =RGBValueLimit(128+y+1.772  *cb);

            
            // 输出当前选择的矩阵
            //cout<<dec<<yPos<<" "<<cbPos<<" "<<crPos<<" ";
            // cout<<hex<<setw(2)<<setfill('0')<<(int)res[j][k].red
            //          <<setw(2)<<setfill('0')<<(int)res[j][k].green
            //          <<setw(2)<<setfill('0')<<(int)res[j][k].blue<<" ";
        }
        // cout<<endl;
    }
    // cout<<endl;
    return res;
}

double** JPEGData::createDCTAndIDCTArray(int row){
    double** res=new double*[row];
    for(int i=0;i<row;i++) res[i]=new double[row];
    // cout<<endl;
    for(int i=0;i<row;i++){
        for(int j=0;j<row;j++){
            double t=0;
            if(i==0) t=sqrt(1.0/row);
            else t=sqrt(2.0/row);
            res[i][j]=t*cos(M_PI*(j+0.5)*i/row);
            // cout<<res[i][j]<<" ";
        }
        // cout<<endl;
    }
    return res;
}

void JPEGData::DCT(double** originMatrix){
    //原理 Y=A*X*A'
    vector<vector<double>> temp(ROW,vector<double>(COL,0));
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=DCTAndIDCTArray[i][k]*originMatrix[k][j];
            }
            temp[i][j]=sum;
        }
    }
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=temp[i][k]*DCTAndIDCTArray[j][k];
            }
            originMatrix[i][j]=sum;
        }
    }
}

void JPEGData::IDCT(double** originMatrix){
    //原理X=A'*Y*A
    vector<vector<double>> temp(ROW,vector<double>(COL,0));
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=DCTAndIDCTArray[k][i]*originMatrix[k][j];
            }
            temp[i][j]=sum;
        }
    }
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            double sum=0;
            for(int k=0;k<COL;k++){
                sum+=temp[i][k]*DCTAndIDCTArray[k][j];
            }
            originMatrix[i][j]=sum;
        }
    }
}

void JPEGData::deQuality(double** originMatrix,int qualityID){
    for(int i=0;i<ROW;i++){
        for(int j=0;j<COL;j++){
            originMatrix[i][j]*=quality[qualityID].table[i*ROW+j];
        }
    }
}

void JPEGData::PAndNCorrect(double** originMatrix){
    for(int i=0;i<ROW;i++)
        if(i%2==1)
            for(int j=0;j<COL;j++) 
                originMatrix[i][j]=-originMatrix[i][j];
}

string JPEGData::FlagCkeck(fstream& file,int byteInfo){
    if(byteInfo==0xff){
        uint8_t info=file.get();
        string res=bitset<8>(0xFF).to_string();
        if(info==0xD9) {EOI=true;return "false";}
        else if(info==0x00) return res;
        return res + bitset<8>(info).to_string();
    }
    return bitset<8>(byteInfo).to_string();
}

uint16_t JPEGData::ReadByte(fstream& file,int len){
    uint16_t res=file.get();
    if(len!=1){
        res=(res<<8)+(uint8_t)file.get();
    }
    return res;
}

uint16_t JPEGData::findHuffmanCodeByBit(fstream& file,int& length,int& pos,string& deque,int curValue,int& curValLen){
    if(pos==length&&length>=HUFFMAN_DECODE_DEQUE_CACHE){//达到最大缓存
        deque = deque.substr(pos);
        int info=file.get();
        string res=FlagCkeck(file,info);
        string str=bitset<8>(info).to_string();
        if(res=="false") res=bitset<8>(file.get()).to_string();
        deque.append(res);
        length = deque.length();
        pos = 0;
    }
    else if(length==0 || pos>=length){
        if(length==0){
            deque="";
            pos=0;
        }
        int info=file.get();
        string res=FlagCkeck(file,info);
        string str=bitset<8>(info).to_string();
        if(res=="false") res=bitset<8>(file.get()).to_string();
        deque.append(res);
        length+=8;
    }
    curValue = (curValue << 1) + (uint8_t)(deque.at(pos++) - '0');
    curValLen++;
    return curValue;
}
NAME_SPACE_END()

BmpEncoder.h


#pragma once
#include <stdio.h>
#include <iostream>
#include "Image.h"
using namespace myUtil;


/* Bitmap Header, 54 Bytes  */
static 
unsigned char BmpHeader[54] = 
{
	0x42, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x01, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x60, 0xCD, 0x04, 0x00, 0x23, 0x2E, 0x00, 0x00, 0x23, 0x2E, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};


void SetBitmapInfo(unsigned int size, int height, int width)
{
	for (int i = 0; i < 4; i++)
	{
		// size of image ( header + data )
		BmpHeader[2 + i] = size & 0xff;
		size >>= 8;

		// width of image
		BmpHeader[18 + i] = width & 0xff;
		width >>= 8;

		// height of image
		BmpHeader[22  + i] = height & 0xff;
		height >>= 8;
	}
}


/* BGR format 这是我粘来的改了映射部分代码 */
unsigned char *Encoder(const vector<RGB**>& buf, int height, int width, int mcu_height, int mcu_width, int &size)
{
	uint8_t *bitmap = nullptr;
	int rowSize = (24 * width + 31) / 32 * 4;
	
	// compute the size of total bytes of image
	size = rowSize * height + 54; // data size + header size
	bitmap = new uint8_t [ size ];
	
	// set the header info
	SetBitmapInfo(size, height, width);

	// fill the header area
	for (int i = 0; i < 54; i++)
	{
		bitmap[i] = BmpHeader[i];
	}
	
	// fill the data area
	for (int i = 0; i < height; i++)
	{
		// compute the offset of destination bitmap and source image
		int idx = height - 1 - i;
		int offsetDst = idx * rowSize + 54; // 54 means the header length
		// int offsetSrc = i * width;
		int offsetHeight = (int)floor(i*1.0/mcu_height)*(int)ceil(width*1.0/mcu_width);
		// fill data
		for (int j = 0; j < width * 3; j++)
		{
			int pos=(j/3)/mcu_width+offsetHeight;
			if(pos>=buf.size()) pos=buf.size()-1;
			RGB temp=buf[pos][i%mcu_height][(j/3)%mcu_height];
			if(j%3==0) bitmap[offsetDst + j] = temp.blue;
			else if(j%3==1) bitmap[offsetDst + j] = temp.green;
			else if(j%3==2) bitmap[offsetDst + j] = temp.red;
			// cout<<dec<<pos<<" ";
		}
		// fill 0x0, this part can be ignored
		for (int j = width * 3; j < rowSize; j++)
		{
			bitmap[offsetDst +j] = 0x0;
		}
	}

	return bitmap;
}

/* Save to file */
void Write(const char *fileName, uint8_t *buf, int &size)
{
	FILE *fp = fopen(fileName, "wb+");
	fwrite(buf, 1, size, fp);
	fclose(fp);
}

主程序

#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <locale>
#include <sstream>
#include <stdlib.h>
#include "Image.h"
#include "BmpEncoder.h"
using namespace std;
using namespace myUtil;

// void print(double** input){
// 	cout<<endl;
// 	for(int i=0;i<8;i++){
// 		for(int j=0;j<8;j++){
// 			cout<<input[i][j]<<" ";
// 		}
// 		cout<<endl;
// 	}
// 	cout<<endl;
// }

int main(){
	string str="../img/Image/3.jpg";
	JPEGData data;
	clock_t startTime=clock();
	data.readJPEG(str.c_str());
	int size;
	unsigned char *bitmap = Encoder(data.getRGB(), data.getHeight(), data.getWidth(),
								8*data.getMaxHSampFactor(),
								8*data.getMaxVSampFactor(), size);
	Write("out.bmp", bitmap, size);
	cout<<dec<<clock()-startTime<<"ms"<<endl;
	// DCT正反变换测试
	// JPEGData data;
	// double** arr=new double*[8];
	// for(int i=0;i<8;i++){
	// 	arr[i]=new double[8];
	// 	for(int j=0;j<8;j++){
	// 		arr[i][j]=(int)(rand()%100);
	// 	}
	// }
	// print(arr);
	// data.DCT(arr);
	// print(arr);
	// data.IDCT(arr);
	// print(arr);
	// FREE_LP_2(arr,8)
    return 0;
}

项目环境gcc 7.3.0 工具CMake,源码链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8年测试老鸟总结,接口自动化测试测试用例编写(全覆盖场景)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 自动化测试&#xf…

15-721 Chapter 6 索引

最先是解释了一个古老的&#xff0c;现在没什么人用数据结构----T-tree&#xff0c;因为现代的cpu到cache和到memory差异巨大&#xff0c;同时memory的容量也变大了。 T-tree 两个key标志着范围&#xff0c;决定到哪里找key&#xff0c;然后存的都是指针&#xff0c;指向pare…

CANopen | 对象字典OD 05 - 创建对象字典变量,映射到RPDO

文章目录一、前言二、实验目的三、对象字典OD四、通过RPDO修改变量rx_Value4.1、NMT指令让CANopen从站进入操作状态4.2、RPDO修改变量rx_Value一、前言 该章节的源代码地址&#xff1a;github 以上摘自《CANopen_easy_begin》的第7章。 二、实验目的 CANopen从站有一个变量…

【博学谷学习记录】超强总结,用心分享 | 架构师 MySql扩容学习总结

文章目录1. 停机方案2.停写方案3.日志方案4.双写方案&#xff08;中小型数据&#xff09;5.平滑2N方案&#xff08;大数据量&#xff09;1. 停机方案 发布公告 为了进行数据的重新拆分&#xff0c;在停止服务之前&#xff0c;我们需要提前通知用户&#xff0c;比如&#xff1a…

网络io与select,poll,epoll

一个形象的类比 水龙头等水 水龙头就是内核进程 等水复制到内核区 学生就是进行io的进程或线程 阻塞io 学生在那里 等水来 非阻塞io 学生看数据没准备好,先回寝室,一会儿再过来检查下,看水准备好没 多路复用io 阿姨帮忙看着水龙头,等来水的时候通知学生 前面三个都是同步…

HQChart实战教程60-如何定制十字光标输出内容

HQChart实战教程60-如何定制十字光标输出内容 十字光标效果图步骤:1. 注册事件2. 外部格式化输出内容Y 轴输出说明X轴输出说明HQChart插件源码地址完整的demo源码十字光标 当鼠标或手势在K线上移动的时候, 会出现一个十字线,已经X轴和Y轴对应数值的输出。X轴输出日期+时间 …

2.1.1网络io与io多路复用select/poll/epoll

关于网络io&#xff0c;我们可以通过一个服务端-客户端的示例来了解&#xff1a; 这是一段TCP服务端的代码&#xff1a; #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include &l…

Android系统启动流程--zygote进程的启动流程

在上一篇init进程启动流程中已经提到&#xff0c;在init中会解析一个init.rc文件&#xff0c;解析后会执行其中的命令来启动zygote进程、serviceManager进程等&#xff0c;下面我们来看一下&#xff1a; //文件路径&#xff1a;system/core/init/init.cppstatic void LoadBoot…

电子商务转化率对你来说有多重要?

有许多电子商务企业遇到了瓶颈期&#xff0c;低转化率并不总是表明您的业务出了大问题&#xff0c;但它们确实表明您可以做得更多&#xff0c;赚得更多。在文中&#xff0c;我们将讨论电子商务转化率对你的重要性&#xff0c;以及提高电子商务转化率的最佳久经考验的方法。 一、…

如何选择IT培训机构?

作为学习IT技术的一种方式、平台&#xff0c;培训班存在已久。而作为国内培训机构的老大哥&#xff0c;北大青鸟于1999年成立&#xff0c;是IT职业教育的开创者&#xff0c;专注于软件、网络、营销等各个IT技术领域&#xff0c;为IT行业输送了奖金百万技术人才。24年以来&#…

网络编程学习,项目er图

https://note.youdao.com/s/FEoXGdFe 思路&#xff1a;将每位上的数存放在一个数组里&#xff0c;每次从最高位开始遍历&#xff0c;先找到最大的位数&#xff0c;再根据是否为0&#xff0c;得到要加的数 网络编程是使用Java语言编写网络应用程序的过程。Java提供了一系列API&…

操作系统作业 18-22章

第十八章 1.根据题中所给参数计算线性页表大小和不同情况下的变化 paging-linear-translate.py -P 1k -a 1m -p 512m -v -n 0 paging-linear-translate.py -P 1k -a 2m -p 512m -v -n 0 paging-linear-translate.py -P 1k -a 4m -p 512m -v -n 0 页大小为1kb&#xff0c;地址空…

Git详细教程,彻底理解Git运作机制

Git详细教程前言git常用命令版本管理远程仓库分支管理正文git版本管理版本回退工作区和暂存区工作区版本库&#xff08;Repository&#xff09;撤销修改删除文件git远程仓库github使用添加远程库小结从远程库克隆git分支管理创建和合并分支git merge vs git rebase解决冲突第一…

即视角|元宇宙社交:新瓶旧酒 or 老树新芽?

即视角 Insight 共享即构新洞察&#xff0c;共建行业新动能——ZEGO即构科技基于音视频技术领域的多年深耕&#xff0c;综合面向各行业的服务经验&#xff0c;在【即视角】栏目发布即构对行业的洞察。 近期我们将聚焦于#元宇宙商业化#话题&#xff0c;共分为三篇&#xff1a;…

自从上了数据结构课之后就想自学c++了

所以今天是摆烂的第三天&#xff1a; 就是来总结一下自己刚学c常犯的小错误&#xff08;在注释里&#xff09;和总结吧&#xff1b; 先来看看hello world输出代码&#xff1b; //打了四遍这个代码终于对了TAT //在一整个程序里面如果有多个文件并且不止一个main函数的话&…

聊聊Redis sentinel 机制

Redis 的哨兵机制自动完成了以下三大功能&#xff0c;从而实现了主从库的自动切换&#xff0c;可以降低 Redis 集群的运维开销&#xff1a; 监控主库运行状态&#xff0c;并判断主库是否客观下线&#xff1b;在主库客观下线后&#xff0c;选取新主库&#xff1b;选出新主库后&…

C语言函数大全-- j 开头的函数

C语言函数大全 本篇介绍C语言函数大全– j 开头的函数 1. j0&#xff0c;j0f 1.1 函数说明 函数声明函数功能double j0 (double x);计算 x 的 第一类 0 阶贝塞尔函数&#xff08;double&#xff09;float j0f (float x);计算 x 的 第一类 0 阶贝塞尔函数&#xff08;float&…

AndroidNDK开发——使用Cmake编译生成so文件

文章目录AndroidNDK开发——使用Cmake编译生成so文件1.添加Cmake文件&#xff1a;2.添加Cmake依赖&#xff1a;3.jni文件如下&#xff1a;4.Android.mk文件&#xff1a;5.Application.mk文件6.SerialPort.c文件&#xff1a;7.SerialPort.h文件&#xff1a;8.运行项目&#xff1…

剑指 Offer II 049. 从根节点到叶节点的路径数字之和

中等题题目 &#xff1a; 给定一个二叉树的根节点 root &#xff0c;树中每个节点都存放有一个 0 到 9 之间的数字。 每条从根节点到叶节点的路径都代表一个数字&#xff1a; 例如&#xff0c;从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。计算从根节点到叶节点生…

BloombergGPT: 首个金融垂直领域大语言模型

BloombergGPT: 首个金融垂直领域大语言模型 Bloomberg 刚刚发布了一篇研究论文&#xff0c;详细介绍了他们最新的突破性技术 BloombergGPT。BloombergGPT是一个大型生成式人工智能模型&#xff0c;专门使用大量金融数据进行了训练&#xff0c;以支持金融行业自然语言处理 (NLP…