Skip to content

Commit c04a8f0

Browse files
committed
報告內容整理
1 parent 9ba7225 commit c04a8f0

1 file changed

Lines changed: 169 additions & 0 deletions

File tree

doc/report.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Fast JPEG Decoder 實作與效能分析報告
2+
3+
**Performance Analysis of JPEG Decoding: C++ (Pybind11) vs. Python (NumPy)**
4+
5+
## 1\. 專案摘要 (Executive Summary)
6+
7+
本專案旨在深入探討 JPEG 壓縮標準的底層實作,並比較不同編程語言與優化策略對解碼效能的影響。我們從零實作了兩套完整的 JPEG Baseline 解碼器:
8+
9+
1. **C++ 版本**:使用 Pybind11 封裝,作為高效能對照組。
10+
2. **Python 版本**:使用 NumPy 向量化運算,作為高階語言實作代表。
11+
12+
**核心成果**
13+
14+
* 成功實作了符合 ITU-T T.81 標準的 Baseline DCT 解碼流程。
15+
* **C++ 版本**展現了卓越的效能,比 NumPy 版本快約 **4.4 倍**
16+
* **準確度驗證**:C++ 版本與標準庫 PIL (Pillow) 的 PSNR 高達 **35.20 dB**,證明解碼邏輯正確。
17+
* **問題修復**:解決了 JPEG 量化表 Zigzag 排列、4:2:0 Upsampling 崩潰等多個關鍵技術難題。
18+
19+
-----
20+
21+
## 2\. 專案動機 (Motivation)
22+
23+
### 2.1 為什麼要「重造輪子」?
24+
25+
雖然市面上已有 `libjpeg-turbo``OpenCV` 等成熟函式庫,但親手實作解碼器是理解視訊壓縮原理的最佳途徑。本專案的學習目標包括:
26+
27+
1. **解構 JPEG 標準**:從位元流 (Bitstream) 解析、霍夫曼解碼 (Huffman Decoding) 到 IDCT 變換,掌握壓縮的核心數學原理。
28+
2. **效能瓶頸分析**:親身體驗 Python 直譯器在處理位元級操作時的效能瓶頸,並驗證 C++ 在系統編程上的優勢。
29+
3. **跨語言整合**:實踐 **Python/C++ 混合編程** (Hybrid Programming),利用 Pybind11 將 C++ 的高效能核心注入 Python 生態系。
30+
31+
-----
32+
33+
## 3\. 系統架構與實作細節 (Implementation)
34+
35+
專案採用三層式架構,將底層運算與上層應用分離。架構圖如下:
36+
37+
```text
38+
┌─────────────────────────────┐
39+
│ 使用者 / Benchmark │
40+
└──────────────┬──────────────┘
41+
│ 呼叫
42+
43+
┌─────────────────────────────┐
44+
│ Python 介面層 │
45+
│ (run_benchmark.py / API) │
46+
└──────────────┬──────────────┘
47+
│ 分流
48+
┌───────┴───────┐
49+
│ │
50+
▼ ▼
51+
┌──────────────┐ ┌──────────────┐
52+
│ C++ 核心 │ │ NumPy 實作 │
53+
│ (Fast Path) │ │ (Reference) │
54+
└──────┬───────┘ └───────┬──────┘
55+
│ │
56+
└───────┬─────────┘
57+
│ 執行解碼流程
58+
59+
┌───────────────────────┐
60+
│ 1. Marker Parsing │
61+
│ 2. Huffman Decoding │
62+
│ 3. Dequantization │
63+
│ 4. Inverse DCT │
64+
│ 5. Chroma Upsampling │
65+
│ 6. YCbCr to RGB │
66+
└───────────────────────┘
67+
```
68+
69+
### 3.1 C++ 核心 (Fast Path)
70+
71+
* **語言標準**:C++17
72+
* **關鍵技術**
73+
* **BitStream 優化**:使用 32-bit 緩衝區與位元位移操作,極大化 Huffman 解碼效率。
74+
* **記憶體管理**:使用 `std::vector` 與指標操作,減少不必要的記憶體拷貝。
75+
* **Pybind11 整合**:實現 `bytes``std::vector<uint8_t>` 的高效轉換,直接回傳 NumPy Array 給 Python 端。
76+
77+
### 3.2 Python NumPy 核心 (Reference Path)
78+
79+
* **設計理念**:利用 NumPy 的矩陣運算能力來加速 IDCT 與顏色轉換。
80+
* **技術挑戰**
81+
* 雖然 IDCT 可以用 `@` 運算子向量化,但 **Huffman 解碼** 具有序列依賴性 (Sequential Dependency),無法向量化,必須在 Python 迴圈中逐位元處理,成為最大效能瓶頸。
82+
83+
-----
84+
85+
## 4\. 關鍵技術難點與解決方案 (Technical Challenges)
86+
87+
在開發過程中,我們遭遇並解決了數個嚴重影響正確性與穩定性的問題:
88+
89+
### 🔥 難點 1: 量化表 (DQT) 的 Zigzag 陷阱
90+
91+
* **問題現象**:NumPy 版本解碼出的圖片嚴重變暗 (Mean \~85 vs 標準值 128),且細節全毀。
92+
* **原因分析**:JPEG 文件中的量化表是以 **Zigzag 順序** 儲存的 1D 陣列。初版代碼直接將其 `reshape(8, 8)`,導致高頻量化係數錯位到低頻位置,破壞了頻域數據。
93+
* **解決方案**:實作 `zigzag_to_2d` 函數,在應用量化表前先將其還原為正確的 8x8 空間排列。
94+
```python
95+
# 修正後的代碼
96+
self.quantization_tables[id] = self.zigzag_to_2d(np.array(values))
97+
```
98+
99+
### 🔥 難點 2: 4:2:0 Upsampling 崩潰
100+
101+
* **問題現象**:解碼非 4:4:4 格式圖片時,程式發生 Segmentation Fault (C++) 或 Index Error (Python)。
102+
* **原因分析**:原始邏輯假設所有 MCU (最小編碼單元) 都是 8x8 像素。但在 YUV 4:2:0 採樣下,一個 MCU 實際上涵蓋 16x16 像素 (4個 Y Block)。
103+
* **解決方案**:重寫 Upsampling 邏輯,正確計算 MCU 索引與 Block 偏移量:
104+
```cpp
105+
int mcu_width = max_h_samp * 8; // 16 for 4:2:0
106+
int mcu_col = col / mcu_width; // 正確計算所在的 MCU
107+
```
108+
109+
### 🔥 難點 3: 像素級誤差 (Pixel Mismatch)
110+
111+
* **問題現象**:即便邏輯正確,自製解碼器與 PIL 的結果仍有細微差異 (PSNR 非無限大)。
112+
* **原因分析**
113+
1. **IDCT 精度**:本專案使用標準浮點數 (`double`) 公式,而 PIL 底層 (libjpeg) 使用優化的整數運算,捨入誤差不可避免。
114+
2. **Upsampling 算法**:本專案使用 **Nearest Neighbor**PIL 可能使用 **Bilinear** 插值,導致色度邊緣數值不同。
115+
* **結論**PSNR \> 30dB 即代表視覺上無失真,目前的誤差在合理範圍內。
116+
117+
-----
118+
119+
## 5\. 實驗結果與效能分析 (Benchmark Results)
120+
121+
### 5.1 測試環境
122+
123+
* **測試對象**`lena.jpg` (512x512, YUV 4:4:4), `images.jpeg` (183x275, YUV 4:2:0)
124+
* **Ground Truth**:PIL (Pillow) 9.x 解碼結果
125+
* **指標**:執行時間 (Time)、峰值訊噪比 (PSNR)
126+
127+
### 5.2 效能數據 (Performance)
128+
129+
| 圖片 | C++ Decoder (ms) | NumPy Decoder (ms) | Speedup |
130+
| :--- | :--- | :--- | :--- |
131+
| **Lena (512x512)** | **67.50 ms** | 295.99 ms | **4.38x** |
132+
| **Images (183x275)** | **7.50 ms** | 33.09 ms | **4.41x** |
133+
| **Sample (64x64)** | **0.56 ms** | 2.05 ms | **3.63x** |
134+
135+
**分析**
136+
137+
* **C++ 穩定領先**:在不同尺寸圖片上,C++ 版本均保持約 **4.4** 的速度優勢。
138+
* **NumPy 的極限**:即使矩陣運算很快,Python `while` 迴圈處理 Huffman 解碼的開銷過大 (佔總時間約 30-40%),這是直譯語言的先天限制。
139+
140+
### 5.3 準確度數據 (Quality - PSNR)
141+
142+
| 解碼器 | vs PIL (Lena) | vs PIL (Images) | 結果判定 |
143+
| :--- | :--- | :--- | :--- |
144+
| **C++ Decoder** | **35.20 dB** | **31.25 dB** | ✅ Pass |
145+
| **NumPy Decoder** | **35.15 dB** | **31.20 dB** | ✅ Pass |
146+
147+
**分析**
148+
149+
* 兩個版本的 PSNR 均超過 30 dB,屬於**高品質還原**
150+
* C++ 與 NumPy 的結果極為接近,證明兩者的演算法邏輯一致且正確。
151+
152+
-----
153+
154+
## 6\. 未來優化方向 (Future Work)
155+
156+
為了進一步挑戰工業級標準 (如 libjpeg-turbo 的 \~5ms),本專案仍有優化空間:
157+
158+
1. **SIMD 指令集優化 (AVX2)**
159+
* 目前 IDCT 採用逐個像素計算 (`double` 運算)。改用 AVX2 指令集一次處理 8float,預期可提升 IDCT 效能 4-8 倍。
160+
2. **整數運算 (Fixed-Point Arithmetic)**
161+
* 將浮點數運算改為整數移位運算 (Integer Shift),減少 CPU 週期消耗。
162+
3. **多執行緒平行化 (Multi-threading)**
163+
* 雖然 Huffman 解碼必須序列執行,但 **IDCT****Color Conversion** 是 Block 獨立的。可使用 OpenMP 平行處理不同 MCU,充分利用多核心 CPU
164+
165+
-----
166+
167+
## 7\. 結論 (Conclusion)
168+
169+
本專案成功驗證了「使用 C++ 優化 Python 關鍵路徑」的有效性。透過 Pybind11,我們將 JPEG 解碼中最耗時的位元流解析與流程控制搬移至 C++ 層,在保持 Python 易用性的同時,獲得了 **4.4** 的效能提升。這不僅是一個圖像解碼器的實作,更是系統效能優化的最佳實踐案例。

0 commit comments

Comments
 (0)