取樣後,需要對8*8block進行DCT(離散餘弦變換)。為什麼要進行DCT?第一點是餘弦變化後的圖片能量主要集中在低頻,我們只需要儲存低頻資料,預設高頻0。第二點是,DCT後的圖片很適合哈夫曼壓縮,對於原圖而言,區域相連的pixle數值差不多,哈夫曼壓縮效果差。全部程式碼在 https://github.com/Cheemion/JPEG_COMPRESS。
圖片引用自"Compressed Image File Formats JPEG, PNG, GIF, XBM, BMP - John Miano"[1]
1.離散餘弦變換直覺上的認識
假設我們有一個2*2的圖片如下,最亮代表1, 最暗代表-1。
我們普通的認識是,這是一個由4個4維向量組成的一個圖片(如下)。可以看成是一個2*2的平面向量,也可以看成4維向量(1,0,0,0)(0,1,0,0)(0,0,1,0)(0,0,0,1)。每個向量兩兩相交,並且都是單位向量。這4個基向量可以得到任何的一張2*2的圖片。
(1,0,0,-1) = 1 *(1, 0, 0, 0) + 0 *(0,1, 0, 0) + 0 *(0, 0, 1, 0) + (-1) * (0, 0, 0, 1);
離散餘弦變化,其實只是把上面的4個基向量進行了變化。如下
基向量變成了(0.5,0.5,0.5, 0.5)(0.5, -0.5, 0.5, -0.5) (0.5, 0.5, -0.5, -0.5) (0.5, -0,5, -0.5, 0.5),每個向量兩兩相交,並且都是單位向量。這4個基向量可以得到任何的一張2*2的圖片。
所以DCT其實只是換了一個基向量而已。
左邊第一個圖為低頻,因為整個圖的顏色沒有變化,可以認為變化頻率為0
最右邊的圖為高頻,上下和左右的顏色都變化了一次。
(1,0,0,-1) = 0 *(0.5, 0.5, 0.5, 0.5) + 1 *(0.5,-0.5, 0.5, -0.5) + 1 *(0.5, 0.5, -0.5, -0.5) + 0 * (0.5, -0.5, -0.5, 0.5);
藍色的係數只要通過原圖和新的基向量點成就可以得到,比如向量(0.5, 0.5, 0.5, 0.5)前面的係數為 1 * 0.5 + 0 * 0.5 + 0 * 0.5 + (-1) * 0.5 = 0;
2.離散餘弦變換公式上的認識
DCT離散餘弦正變換
令G(i, j) = cos((i + 0.5) * pi * u /N) * cos((j + 0.5) * pi * u / N), u 和 v是constant
隨著u和v從0到7(因為我們的block是8*8) 不斷的變化我們可以畫出如下圖
畫圖程式碼matlab如下
function [ out ] = g( u,v ) p = zeros(8,8); for i = 0:7 for j = 0:7 p(i + 1,j + 1) = cos((i + 0.5) * u * pi / 8) * cos((j + 0.5) * v * pi / 8); end end out = p; end
clc; clear; close all; figure; maximum = 0; minimum = 100; for u = 0:7 for v = 0:7 pp = g(u,v) subplot(8, 8, u * 8 + v + 1); imshow((pp + 1) ./ 2); end end
乘以係數c(u)c(v)是使我們的G(i, j)變成單位向量。公式剩餘部分就是點乘的過程。
IDCT逆變換
3.程式碼
void DCT(Block& block) { Block temp; std::memcpy(&temp, &block, sizeof(Block)); //copy from original //8 rows //DCT行變化 for(uint i = 0; i < 8; i++) { double* f = &temp[i * 8]; //one dimension Array , and will perform DCT on it for(uint k = 0; k < 8; k++) { double sum = 0.0; for(uint n = 0; n < 8; n++){ sum = sum + f[n] * std::cos(((n + 0.5) * M_PI * k / 8)); } sum = (k == 0) ? (sum * std::sqrt(1.0 / 8)) : (sum * std::sqrt(2.0 / 8)); block[i * 8 + k] = sum; } } std::memcpy(&temp, &block, sizeof(Block)); //copy from //DCT列變化 for(uint i = 0; i < 8; i++) { double* f = &temp[i]; //one dimension Array with 8 steps increment , and will perform DCT on it for(uint k = 0; k < 8; k++) { double sum = 0.0; for(uint n = 0; n < 8; n++){ sum = sum + f[n * 8] * std::cos(((n + 0.5) * M_PI * k / 8)); } sum = (k == 0) ? (sum * std::sqrt(1.0 / 8)) : (sum * std::sqrt(2.0 / 8)); block[i + k * 8] = sum; } } }
以上全部的程式碼在https://github.com/Cheemion/JPEG_COMPRESS/tree/main/Day3
完結
Thanks for reading.
wish you have a good day.
>>>> JPG學習筆記4(附完整程式碼)