C++实现JPEG格式图片解析(附代码)
在网上看了好多解析JPEG图片的文章,多多少少都有问题,下面是我参考过的文章链接:
首先,解析的步骤
- 1.读取文件的信息
- 2.Huffman编码解码
- 3.直流交流编码解析
- 然而,读取多少个8×8矩阵才能解析出一个MCU呢?
- 4.反量化
- 5.反Zig-Zag变化
- 6.反DCT变化
- 8.YCbCr转RGB
- 效果图
1.读取文件的信息
JPEG格式中信息是以段(数据结构)来存储的。
段的格式如下
名称 | 字节数 | 数据 | 说明 |
---|---|---|---|
段标识 | 1 | FF | 每个新段的开始标识 |
段类型 | 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对应2pair<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、接下来就是循环读取交流分量了,那么什么时候退出呢?
有两个条件,只要达成一个就可以退出
- 读取了63个交流分量
- 交流分量的权值为0,此位后面全是0
对于根据权重所读出来的值(不区分直流交流),对于最高位(最左边)若为0则是负数,否则为正数,判断代码如下,curValue为读取的值,curValueLength为读取的值有多少位
curValue = (curValue >= pow(2, curValueLength - 1) ? curValue : curValue - pow(2, curValueLength) + 1);
这里面还有两个坑(若DRI读出来的直流分量重置间隔reset为0,不用管这步)
- 假设reset为332(这是我图片的间隔),就是隔了332个MCU(也就是332×SUMmcu个8×8的矩阵),需要将
所有差分矫正变量全部置为0,并且当这332个MCU读取完后,你要读取两个字节(这两个字节是一个段),这两个字节应该正好是0xFF 0xD0~0xD7,并且D0到D7是按顺序出现的,例如,上一个是0xFFD0那么下一个肯定是0xD1,到D7后下一个是D0,若对不上那就有问题了。还有,这读出来的两个字节不是图片的压缩数据不需要解码 - 若读取到了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.71414cr-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*ydouble 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
这是位图
最最后,如何把图片显示出来呢?,我将信息转换为位图,就能看见了。
下面源码附上
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 tablebool Init(fstream& file,uint16_t len);//find-true not find-falsebool 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=Quint8_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;//行MCUint max_v_samp_factor;//列MCUint width;int height;int precision;bool isYUV411=false;bool isYUV422=false;bool isYUV111=false;uint8_t curDRI=0;//当前重置直流分量标识,这里只取个位方便计算uint16_t resetInterval=0;//单位是MCUint 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 methoduint16_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;//顺序YCbCrint 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进制数//一次循环解析一个MCUcurBitDeque.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*ydouble 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*Avector<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 imageBmpHeader[18 + i] = width & 0xff;width >>= 8;// height of imageBmpHeader[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 imagesize = rowSize * height + 54; // data size + header sizebitmap = new uint8_t [ size ];// set the header infoSetBitmapInfo(size, height, width);// fill the header areafor (int i = 0; i < 54; i++){bitmap[i] = BmpHeader[i];}// fill the data areafor (int i = 0; i < height; i++){// compute the offset of destination bitmap and source imageint 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 datafor (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 ignoredfor (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,源码链接
C++实现JPEG格式图片解析(附代码)相关推荐
- Freemarker下载Word文档(文字+图片+表格)Idea示例 (附word展示图片异常解决方案)(附JPEG格式图片通过imageio.read方法读取为null解决方案)
流程:拿到word模板 转为ftl格式并填充占位符参数,调用java代码填充参数即可 (文末附word打开图片显示异常,wps打开却显示正常的解决方案) (文末附Jpeg格式图片获取为null解决方案 ...
- Python实现base64编码文件转化为jpg/png/jpeg/格式图片
Python实现base64编码文件转化为jpg/png/jpeg/格式图片 这个base64文件是图片转化的才行,不是随便找个base64文件都行的 base64图片文件qiaoba.py /9j/ ...
- 查看BMP格式图片的十六进制代码
查看BMP格式图片的十六进制代码 1.下载notepad++: 下载地址 2.在notepad++中安装Hex-Editor插件: 打开notepad++ --> 点击插件按钮 --> 插 ...
- ajax js图片上传到php,Ajax上传并预览图片(附代码)
这次给大家带来Ajax上传并预览图片(附代码),Ajax上传并预览图片的注意事项有哪些,下面就是实战案例,一起来看一下. 1. 直接上最简单的 一种 ajax 异步上传图片,并预览 html: 图片上 ...
- Java【冒泡排序】算法, 大白话式图文解析(附代码)
文章目录 前言 一.排序相关概念 1, 什么是排序 2, 什么是排序的稳定性 3, 七大排序分类 二.冒泡排序 1, 图文解析 2, 代码实现 3, 冒泡排序的优化 三.性能分析 四.七大排序算法总体 ...
- 把多张 PNG 图片拼在一起合成一张 RAW 格式的图片(附代码)
把多张 PNG 图片拼在一起合成一张 RAW 格式的图片设定好输入和输出的路径即可 代码如下 import numpy import os # import argparse from PIL imp ...
- 制作自己的COCO格式数据集,附代码!
最近做了一个细胞检测的练习项目.之前的思路是参考其他大神的代码,后来发现其他人的代码有很多自定义的内容,包括读取的数据格式等等,小白表示看不懂所以改变思路,用最简单的方法--选择mmdetection ...
- Winform中实现对照片添加文字和图片水印(附代码下载)
场景 项目运行效果 注: 博客主页: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 新建 ...
- android word转html标签,如何将Word转换为网页html格式的方法(附代码清理方法)
Word是我们常用的的办公软件,广泛被运用,那么我们怎么把Word转换为网页html格式? 需要软件: word2003 或 wps 个人建议用wps更方便,word生成的图片容易重复 editplu ...
最新文章
- pytorch如何计算导数_Pytorch的自动求导机制与使用方法(一)
- ios pusher使用_如何使用JavaScript和Pusher构建实时图
- mysql之desc 与asc
- cacti监控mysql
- android通讯录加密,Android获取通讯录并上传(包含通讯录加密)
- 用puttygen工具把私钥id_rsa转换成公钥id_rsa.ppk
- HP Proliant DL360 G9使用业务网卡登录ILO管理
- Python-Level2-day11:TCP客户端/服务端传输(循环模型之短连接与长连接形态);TCP粘包问题;与UDP对比;数据传输过程原理;
- Java使用BufferedImage裁剪图片
- 【Unity】在Inspector上显示自定义的位掩码枚举(Flags)
- API等级和Android版本对应关系以及历史
- STM32使用外部中断控制led灯亮灭
- 单片机开发无线控制系列-单片机端代码实现
- 基于超声波的库位重定位算法
- linux开放外部端口访问
- 双剑合璧保障数据库安全
- 【THUWC2019模拟2019.1.18】Counting
- 算法设计与分析--贪心算法
- matlab院校,MATLAB要来了?!
- 3种解决安卓(android)手机锁屏密码忘记的方法!