離散餘弦變換 (DCT) 是 JPEG、MPEG、H.264 等影像/視訊壓縮核心。直接依定義計算二維 DCT 複雜度 O(N^4),對 256×256 圖像需數十分鐘不可接受。本文實作實數域快速 DCT/IDCT (複雜度 O(N^2 log N)),採用查找表消除重複三角函數計算、位反序重排、多執行緒非阻塞架構,256×256 灰階圖 DCT/IDCT 僅耗 94ms (vs 普通 575 秒),效能提升 **6000 倍以上**。
參考《DCT 快速新演算法及濾波器結構研究與子波變換域影像去噪研究》華南理工大學博士論文。核心思想:
cos((2k+1)π/4N) 僅依賴 N,預先計算存入查找表 C[]。
double* C = nullptr;
bool bHasInit = false;
void initDCTParam(int deg) { // deg = log2(N)
if (bHasInit) return;
int N = 1 << deg;
if (C) delete[] C;
C = new double[N];
int half = N >> 1;
for (int i = 0; i < half; ++i)
C[N - i - 1] = 2*i + 1; // 基礎奇數序列
for (int g = 0; g < deg - 1; ++g) {
int endStart = 1 << (deg - 1 - g);
int len = endStart >> 1;
int factor = 1 << (g + 1);
for (int j = 0; j < len; ++j)
C[endStart - j - 1] = factor * C[N - j - 1];
}
for (int i = 1; i < N; ++i) // C[0] 未使用
C[i] = 2.0 * cos(C[i] * M_PI / (N << 1));
bHasInit = true;
}
void dct_forward(double* f, int deg) {
int N = 1 << deg;
for (int d = 0; d < deg; ++d) {
int wings = 1 << d;
int wingLen = N >> d;
int halfWing = wingLen >> 1;
for (int w = 0; w < wings; ++w) {
for (int hw = 0; hw < halfWing; ++hw) {
double t1 = f[w*wingLen + hw];
double t2 = f[(w+1)*wingLen - 1 - hw];
if (w & 1) swap(t1, t2); // 奇數翼交換
f[w*wingLen + hw] = t1 + t2;
f[(w+1)*wingLen - 1 - hw] = (t1 - t2) * C[wingLen - 1 - hw];
}
}
}
}
void dct_backward(double* f, int deg) {
int N = 1 << deg;
for (int d = deg - 1; d >= 0; --d) {
int wings = 1 << d;
int wingLen = 1 << (deg - d);
int halfWing = wingLen >> 1;
for (int w = 0; w < wings; ++w) {
for (int hw = 0; hw < halfWing; ++hw) {
if (hw == 0) {
f[halfWing + w*wingLen] *= 0.5;
} else {
int r1 = bitrev(hw, deg - d - 1);
int r2 = bitrev(hw - 1, deg - d - 1);
f[halfWing + w*wingLen + r1] -= f[halfWing + w*wingLen + r2];
}
}
}
}
}
int bitrev(int x, int bits) {
int rev = 0;
for (int i = 0; i < bits; ++i) {
rev = (rev << 1) | (x & 1);
x >>= 1;
}
return rev;
}
void fbitrev(double* f, int deg) {
int N = 1 << deg;
double* tmp = new double[N];
for (int i = 0; i < N; ++i) tmp[bitrev(i, deg)] = f[i];
memcpy(f, tmp, N * sizeof(double));
delete[] tmp;
}
void fdct_1D(double* f, int deg) {
initDCTParam(deg);
dct_forward(f, deg);
dct_backward(f, deg);
fbitrev(f, deg);
f[0] *= M_SQRT1_2; // sqrt(1/2)
int N = 1 << deg;
double scale = sqrt(2.0 / N);
for (int i = 0; i < N; ++i) f[i] *= scale;
}
double* temp_2D = nullptr;
void init2D_Param(int rows, int cols) {
if (temp_2D) delete[] temp_2D;
temp_2D = new double[max(rows, cols)];
}
void fdct_2D(double* f, int degRow, int degCol) {
int rows = 1 << degRow, cols = 1 << degCol;
init2D_Param(rows, cols);
double scale = 2.0 / sqrt(rows * cols);
// 1. 對每列做 1D DCT
for (int r = 0; r < rows; ++r)
fdct_1D_no_param(f + r*cols, degCol);
// 2. 對每行做 1D DCT (需轉置暫存)
for (int c = 0; c < cols; ++c) {
for (int r = 0; r < rows; ++r)
temp_2D[r] = f[r*cols + c];
fdct_1D_no_param(temp_2D, degRow);
for (int r = 0; r < rows; ++r)
f[r*cols + c] = temp_2D[r] * scale;
}
bHasInit = false; // 允許下次不同尺寸重新初始化
}
查找表係數改為倒數:C[i] = 1.0 / (2.0 * cos(...))。前向追底改為疊加差分,後向回根改為蝶形平均。完整 fidct_1D、fidct_2D 程式碼見附件。
避免 UI 凍結,將 DCT/IDCT 放入背景執行緒:
struct RUNINFO {
int row, col; // log2 尺寸
double* data; // 輸入輸出緩衝 (呼叫端配置)
bool finished;
DWORD startTick, endTick;
};
UINT ThreadProc_DCT(LPVOID p) {
RUNINFO* info = (RUNINFO*)p;
info->startTick = GetTickCount();
fdct_2D(info->data, GetTwoIndex(info->row), GetTwoIndex(info->col));
info->endTick = GetTickCount();
info->finished = true;
return 0;
}
// UI 執行緒輪詢 info->finished 或用 PostMessage 通知完成
| 演算法 | DCT 正向 | IDCT 逆向 |
|---|---|---|
| 快速 DCT (本文) | 94 ms | 94 ms |
| 普通定義法 | 575,172 ms (~9.6 min) | — |
圖 1-3 為程式介面截圖:普通 DCT 耗時、快速 DCT 耗時、快速 IDCT 耗時。
實數域快速 DCT/IDCT 結合查找表與位反序,將 O(N^4) 降為 O(N^2 log N),配合多執行緒非阻塞架構,滿足即時影像處理效能需求。完整原始碼含一維/二維正逆變換、查找表管理、多執行緒包裝、MFC 測試專案 (載入 BMP、顯示 DCT 係數圖、IDCT 還原驗證),請下載附件。