影象的基本有失真壓縮和無失真壓縮及解壓
關鍵詞:5-5-5,5-6-5,遊長編碼優化,影象壓縮、解壓
背景
有損量化這裡介紹從8-8-8到5-5-5和5-6-5的量化壓縮原理及其程式設計實現。無失真壓縮這裡基於遊長編碼演算法(利用畫素的重複)首先提出一種簡單改進演算法,即在影象的各通道上進行遊長編碼,利用各通道畫素值得重複性分別進行壓縮,一定程度上提高了壓縮性,因為兩個相鄰畫素雖然不同,但他們的某個通道可能會相同,這種方法簡單高效,但適應性差,主要利用了影象的空間冗餘性;之後,提出壓縮前的分塊處理,為了減少影象各區域之間的巨大差異造成的重複性被分割削弱,先從二維上將影象分塊,再對分塊進行空間冗餘壓縮,也就是更加充分地利用影象的空間冗餘性。
有損量化5-5-5和5-6-5
壓縮物件使影象的RGB通道值,每個值都是0~255之間的數字,分別使用8位儲存,因此原始影象每個畫素要使用3*8=24位,即‘8-8-8’。這裡要將其量化壓縮,使用16位來儲存24位的資訊,因此要損失部分精度,壓縮率固定為1.50。
5-5-5指的是隻使用低15位,剩下的一位棄用,這樣每個通道一致的都壓縮為5位;
5-6-5則是充分使用了16位,其中G通道佔6位,另外兩通道各佔5位。
演算法原理很簡單:
壓縮時5-5-5是將每個通道的二進位制值都右移3位(除以8),保留剩下的5位,然後依次放入16位數的低15位;解壓時分別將各通道的5位二進位制數取出並左移3位,低位補0還原成8位,因此低三位的資料丟失掉了。
5-6-6和5-5-5同理,只是G通道的二進位制數右移2兩位(除以4),將剩下的6位和其他兩通道的10位一同放入16位二進位制數中。解壓時同樣是低位補0還原為8位。
演算法程式碼:
程式背景說明:width
和height
指的是匯入的圖片的尺寸(畫素個數),Input
是儲存三個通道的畫素值的陣列,這裡windows工程儲存的三通道順序為B,G,R,不是R,G,B。
5-5-5:
unsigned char *CAppQuantize::Quantize555(int &qDataSize) {
int i, j ;
unsigned int r, g, b ;
unsigned short rgb16 ;
qDataSize = width * height * 2 ;
unsigned char *quantizedImageData = new unsigned char[width * height * 2] ;
for(j = 0; j < height; j++) {
for(i = 0; i < width; i++) {
b = pInput[(i + j * width) * 3 + 0] ; // Blue Color Component
g = pInput[(i + j * width) * 3 + 1] ; // Red Color Component
r = pInput[(i + j * width) * 3 + 2] ; // Green COlor Component
rgb16 = ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3) ;
quantizedImageData[(i + j * width) * 2 + 0] = rgb16 & 0xFF ;
quantizedImageData[(i + j * width) * 2 + 1] = (rgb16 >> 8) & 0xFF ;
}
}
return quantizedImageData ;
}
void CAppQuantize::Dequantize555(unsigned char *quantizedImageData, unsigned char *unquantizedImageData) {
int i, j ;
unsigned int r, g, b ;
unsigned short rgb16 ;
for(j = 0; j < height; j++) {
for(i = 0; i < width; i++) {
rgb16 = quantizedImageData[(i + j * width) * 2 + 0] | (((unsigned short) quantizedImageData[(i + j * width) * 2 + 1]) << 8) ;
b = rgb16 & 0x1F;
g = (rgb16 >> 5) & 0x1F ;
r = (rgb16 >> 10) & 0x1F ;
unquantizedImageData[(i + j * width) * 3 + 0] = (b << 3) ;
unquantizedImageData[(i + j * width) * 3 + 1] = (g << 3) ;
unquantizedImageData[(i + j * width) * 3 + 2] = (r << 3) ;
}
}
}
5-6-5:
unsigned char *CAppQuantize::Quantize565(int &qDataSize) {
int i, j;
unsigned int r, g, b;
unsigned short rgb16;
qDataSize = width * height * 2 ;
unsigned char *quantizedImageData = new unsigned char[width * height * 2] ;
for (j = 0; j < height; j++) {
for (i = 0; i < width; i++) {
b = pInput[(i + j * width) * 3 + 0]; // Blue Color Component
g = pInput[(i + j * width) * 3 + 1]; // Green Color Component
r = pInput[(i + j * width) * 3 + 2]; // Red Color Component
rgb16 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); // r分量和b分量右移3位,g分量右移2位
quantizedImageData[(i + j * width) * 2 + 0] = rgb16 & 0xFF; // 高8位
quantizedImageData[(i + j * width) * 2 + 1] = (rgb16 >> 8) & 0xFF;// 低8位
}
}
return quantizedImageData ;
}
void CAppQuantize::Dequantize565(unsigned char *quantizedImageData, unsigned char *unquantizedImageData) {
int i, j;
unsigned int r, g, b;
unsigned short rgb16;
for (j = 0; j < height; j++) {
for (i = 0; i < width; i++) {
rgb16 = quantizedImageData[(i + j * width) * 2 + 0] | (((unsigned short)quantizedImageData[(i + j * width) * 2 + 1]) << 8);
b = rgb16 & 0x1F; // 保留高5位
g = (rgb16 >> 5) & 0x3F;// 右移5位後保留高6位
r = (rgb16 >> 11) & 0x1F;// 右移11位後保留高5位
unquantizedImageData[(i + j * width) * 3 + 0] = (b << 3); // 左移3位,高位補0
unquantizedImageData[(i + j * width) * 3 + 1] = (g << 2); // 左移2位,高位補0
unquantizedImageData[(i + j * width) * 3 + 2] = (r << 3); // 左移3位,高位補0
}
}
}
通道遊長編碼無失真壓縮
壓縮過程:
壓縮後的資料形式是:兩個無符號8位二進位制數為一組,第一個儲存重複的個數,第二個儲存通道值。
分B,G,R三個通道依次進行,對於每個通道從第一個值開始,計算後面相同的值的個數,碰到新的不同值或者重複個數超出了8位數的表示上限,則將之前的重複值和通道值儲存到一組壓縮後的資料中,並開始下一組同樣的計算壓縮,直到所有資料全部壓縮完。
解壓過程:
解壓也是分三個通道依次解壓,由於三個通道的壓縮資料都放在了同一個陣列,因此先要找到G通道和R通道的開始位置offset_g和offset_r,尋找方法是迴圈同時累加計算前面通道各畫素的重複個數,每當重複個數達到圖片畫素個數,下一個即時另一個通道的開始了。之後開始解壓,每次從各通道取一個值組成一個畫素,直到各通道同時取完,解壓後的資料就是壓縮前的原資料了,實現了影象的無失真壓縮。
演算法程式碼:
無失真壓縮:
unsigned char *CAppCompress::Compress(int &cDataSize) {
unsigned char *compressedData ;
cDataSize = width * height * 3 ;
// 儲存壓縮後的資料,最差的情況尺寸也不會到大於cDataSize * 2
compressedData = new unsigned char[cDataSize * 2];
// 實際壓縮字元長度
int compressedSize = 0;
// 採用分通道遊離的方法,按照每個通道相鄰畫素的重複性進行壓縮
// 1.b通道
unsigned short curB = pInput[0];// 第一個畫素的b
unsigned short repeat = 1;// 重複次數
for (int i = 1; i < cDataSize / 3; i++)
{
unsigned short nextB = pInput[i * 3 + 0];// 下一個畫素的b
if (nextB == curB && repeat < 127)
{
++repeat;
// 如果是最後一個則儲存
if (i == cDataSize / 3 - 1)
{
// 儲存最後一個b值組
compressedData[compressedSize] = repeat;
compressedData[compressedSize + 1] = curB;
// 增加編碼資料長度
compressedSize += 2;
}
}
else
{
// 儲存上一個b值組
compressedData[compressedSize] = repeat;
compressedData[compressedSize + 1] = curB;
// 增加編碼資料長度
compressedSize += 2;
// 換下一種b值
curB = nextB;
repeat = 1;
// 如果是最後一個
if (i == cDataSize / 3 - 1)
{
// 儲存最後一個b值
compressedData[compressedSize] = 1;
compressedData[compressedSize + 1] = curB;
// 增加編碼資料長度
compressedSize += 2;
}
}
}
// 2.g通道
unsigned short curG = pInput[1];// 第一個畫素的g
repeat = 1;// 重複次數
for (int i = 1; i < cDataSize / 3; i++)
{
unsigned short nextG = pInput[i * 3 + 1];// 下一個畫素的g
if (nextG == curG && repeat <= 127)
{
++repeat;
// 如果是最後一個則儲存
if (i == cDataSize / 3 - 1)
{
// 儲存最後一個g值組
compressedData[compressedSize] = repeat;
compressedData[compressedSize + 1] = curG;
// 增加編碼資料長度
compressedSize += 2;
}
}
else
{
// 儲存上一個g值組
compressedData[compressedSize] = repeat;
compressedData[compressedSize + 1] = curG;
// 增加編碼資料長度
compressedSize += 2;
// 換下一種g值
curG = nextG;
repeat = 1;
// 如果是最後一個
if (i == cDataSize / 3 - 1)
{
// 儲存最後一個g值
compressedData[compressedSize] = 1;
compressedData[compressedSize + 1] = curB;
// 增加編碼資料長度
compressedSize += 2;
}
}
}
// 3.r通道
unsigned short curR = pInput[2];// 第一個畫素的r
repeat = 1;// 重複次數
for (int i = 1; i < cDataSize / 3; i++)
{
unsigned short nextR = pInput[i * 3 + 2];// 下一個畫素的r
if (nextR == curR && repeat <= 127)
{
++repeat;
// 如果是最後一個則儲存
if (i == cDataSize / 3 - 1)
{
// 儲存最後一個g值組
compressedData[compressedSize] = repeat;
compressedData[compressedSize + 1] = curR;
// 增加編碼資料長度
compressedSize += 2;
}
}
else
{
// 儲存上一個g值組
compressedData[compressedSize] = repeat;
compressedData[compressedSize + 1] = curR;
// 增加編碼資料長度
compressedSize += 2;
// 換下一種r值
curR = nextR;
repeat = 1;
// 如果是最後一個
if (i == cDataSize / 3 - 1)
{
// 儲存最後一個r值
compressedData[compressedSize] = 1;
compressedData[compressedSize + 1] = curR;
// 增加編碼資料長度
compressedSize += 2;
}
}
}
// 取出壓縮後的純資料
cDataSize = compressedSize;
unsigned char *finalData = new unsigned char[cDataSize];
for (int i = 0; i < cDataSize; i++)
{
unsigned char temp = compressedData[i];
finalData[i] = temp;
}
delete compressedData;
compressedData = finalData;
return compressedData;
}
無損解壓縮:
void CAppCompress::Decompress(unsigned char *compressedData, int cDataSize, unsigned char *uncompressedData) {
// 尋找g通道和r通道在壓縮資料陣列中的偏移座標
int offset_r = 0, offset_g = 0;
int pixelCount = 0;
for (int i = 0; i < cDataSize;)
{
int curRpeat = compressedData[i];
pixelCount += curRpeat;
i += 2;
if (pixelCount == width*height)
{
offset_g = i;// g通道的開始座標
}
if (pixelCount == width*height * 2)
{
offset_r = i;// r通道的開始座標
}
}
unsigned int b, g, r;
int repeat;
// 1.還原b通道
for (int i = 0, j = 0; i < width*height, j < offset_g; j += 2)
{
// 恢復一組重複的b值
repeat = compressedData[j];
for (int p = 0; p < repeat; p++)
{
int d = compressedData[j + 1];
uncompressedData[i * 3 + p*3 + 0] = compressedData[j + 1];
}
i += repeat;
}
// 2.還原g通道
for (int i = 0, j = offset_g; i < width*height, j < offset_r; j += 2)
{
repeat = compressedData[j];
for (int p = 0; p < repeat; p++)
{
int d = compressedData[j + 1];
uncompressedData[i * 3 + p * 3 + 1] = compressedData[j + 1];
}
i += repeat;
}
// 3.還原r通道
for (int i = 0, j = offset_r; i < width*height, j < cDataSize; j += 2)
{
repeat = compressedData[j];
for (int p = 0; p < repeat; p++)
{
int d = compressedData[j + 1];
uncompressedData[i * 3 + p * 3 + 2] = compressedData[j + 1];
}
i += repeat;
}
}
效果分析:
最好情況: 演算法基於通道畫素重複,最好的情況自然是純色推影象。演算法對於顏色比較單調的影象壓縮效果較好;
最差情況: 最差情況是三個通道相鄰的兩個畫素的值都不同,這時候壓縮後的資料剛好是原資料的兩倍大小,每一個畫素各通道值都額外用了一個8位儲存重複個數,且重複個數都是1。
壓縮到六十四分之一:
壓縮到三分之一:
壓縮失敗:
空間分割優化
演算法步驟:首先先後對影象進行橫向、縱向或者橫向、縱向掃描,掃描時對每一行或者每一列計算平均值,當平均值和上一行或者列差值大於閾值時,設定當前行列為一個邊界。例如:如果先橫向分割,後縱向分割,那麼橫向分割後將影象分成了幾個子影象,之後再對每一個子影象進行同樣的縱向分割,即可將影象分成內部類似的子影象區域。之後再對子影象進行空間冗餘性壓縮。影象分割效果大致如下:
示例程式碼:
type cpp
量化壓縮與無失真壓縮組合
直接使用該演算法對影象壓縮,面對色彩變化豐富的影象總是壓縮失敗的,但如果先對影象進行有損量化,再對量化後的影象進行無失真壓縮往往可以取得不錯的效果。量化實際上是為無失真壓縮提高了容錯性,本來兩個通道值相差可能很小,如果能包容這微小的差異那麼將大大提高壓縮率。下圖中列印的三個壓縮率依次是:直接壓縮的壓縮率、有損量化的壓縮率、對量化後的影象再進行無失真壓縮的壓縮率。
進一步的先進壓縮演算法
實際中的影象往往是顏色豐富錯綜複雜的,僅僅利用空間冗餘來進行壓縮適應性太低,利用重複性進行遊長編碼壓縮往往不但壓縮失敗,甚至會使壓縮後的影象體積更大,最差的情況如上所說會是原來的兩倍。因此為了研究適應性更好的演算法就要從更多維度去利用影象本身的重複性(影象的重複性再多維度上是很大的)。
從另一種程度上影象資訊是一種訊號資訊,影象資料的內在聯絡不僅僅是相鄰畫素之間的相似性而已,影象可以向聲音訊號一樣常使用波訊號去模擬預測,挖掘影象整體的資訊後可以利用已有資訊在壓縮過程中對未壓縮資料進行預測,利用影象的多維度重複性進行進一步的壓縮。
自適應預測熵編碼
。。。
基於分片的無失真壓縮方法
。。。