Add public-domain eagle sample processed by PyTorch + all OpenVINO variants; fix tile padding offset for non-multiple-of-tile sizes
Browse files- .gitattributes +11 -0
- .gitignore +5 -0
- README.md +27 -0
- input/nasa_bald_eagle_KSC-20230425-PH-JBS02_0003.jpg +3 -0
- realesrgan_ov.py +1 -1
- samples/eagle_4x/00_input_lr_x4.png +3 -0
- samples/eagle_4x/01_bicubic_x4.jpg +3 -0
- samples/eagle_4x/02_ground_truth.jpg +3 -0
- samples/eagle_4x/03_pytorch_fp32_reference.jpg +3 -0
- samples/eagle_4x/10_detail_montage.jpg +3 -0
- samples/eagle_4x/README.md +82 -0
- samples/eagle_4x/metrics.json +58 -0
- samples/eagle_4x/openvino_fp16.jpg +3 -0
- samples/eagle_4x/openvino_int4_mixed_w4a16.jpg +3 -0
- samples/eagle_4x/openvino_int4_w4a16.jpg +3 -0
- samples/eagle_4x/openvino_int4_w4a8.jpg +3 -0
- samples/eagle_4x/openvino_int8_w8a8.jpg +3 -0
- tools/__pycache__/make_sample.cpython-312.pyc +0 -0
- tools/__pycache__/rrdbnet_arch.cpython-312.pyc +0 -0
- tools/make_sample.py +159 -0
.gitattributes
CHANGED
|
@@ -1,4 +1,15 @@
|
|
| 1 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.xml filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 4 |
inputs/00003.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.xml filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
input/nasa_bald_eagle_KSC-20230425-PH-JBS02_0003.jpg filter=lfs diff=lfs merge=lfs -text
|
| 5 |
inputs/00003.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
samples/eagle_4x/00_input_lr_x4.png filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
samples/eagle_4x/01_bicubic_x4.jpg filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
samples/eagle_4x/02_ground_truth.jpg filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
samples/eagle_4x/03_pytorch_fp32_reference.jpg filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
samples/eagle_4x/10_detail_montage.jpg filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
samples/eagle_4x/openvino_fp16.jpg filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
samples/eagle_4x/openvino_int4_mixed_w4a16.jpg filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
samples/eagle_4x/openvino_int4_w4a16.jpg filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
samples/eagle_4x/openvino_int4_w4a8.jpg filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
samples/eagle_4x/openvino_int8_w8a8.jpg filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
weights/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.onnx
|
| 4 |
+
*.onnx.data
|
| 5 |
+
*.pth
|
README.md
CHANGED
|
@@ -76,6 +76,32 @@ Real-ESRGAN 是純卷積的深層殘差網路(23 個 RRDB,每層輸出 `x*0.
|
|
| 76 |
|
| 77 |
---
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
## 2. 安裝
|
| 80 |
|
| 81 |
```bash
|
|
@@ -214,4 +240,5 @@ PY
|
|
| 214 |
* `rrdbnet_arch.py` 為 BasicSR `RRDBNet` 的獨立重寫(不需安裝 BasicSR),參數名稱與官方 checkpoint 完全一致
|
| 215 |
* 量化:[OpenVINO NNCF](https://github.com/openvinotoolkit/nncf)
|
| 216 |
* 本 repo 的推論程式、轉換腳本與本說明文檔為本專案新增。
|
|
|
|
| 217 |
* License:Apache-2.0(見 `LICENSE`)。
|
|
|
|
| 76 |
|
| 77 |
---
|
| 78 |
|
| 79 |
+
## 1.5 效果對比(同一張圖:原始 PyTorch vs 各個 OpenVINO 版本)
|
| 80 |
+
|
| 81 |
+
測試照片是 NASA 肯尼迪太空中心的**公有領域**白頭海雕照片
|
| 82 |
+
([KSC-20230425-PH-JBS02_0003](https://images.nasa.gov/details/KSC-20230425-PH-JBS02_0003)),
|
| 83 |
+
裁切 1024×768 當 Ground Truth,bicubic 降 4 倍成 256×192 餵進模型,
|
| 84 |
+
所有模型都用 tile=128 推論成 768×1024。
|
| 85 |
+
|
| 86 |
+
細節並排比較(左上 bicubic、右上原始 PyTorch FP32、中上 FP16、左下 INT8、下中 INT4 混合、右下純 INT4):
|
| 87 |
+
|
| 88 |
+

|
| 89 |
+
|
| 90 |
+
| 模型 | 大小 | PSNR vs PyTorch FP32 ↑ | SSIM ↑ | PSNR vs 原圖 ↑ | 推論時間 |
|
| 91 |
+
|---|---|---|---|---|---|
|
| 92 |
+
| `pytorch_fp32`(原始 PyTorch,基準) | 67.0 MB | 基準 | 1.0000 | 20.12 dB | 4.46 s |
|
| 93 |
+
| bicubic x4(對照) | – | 22.54 dB | 0.7049 | **21.70 dB** | – |
|
| 94 |
+
| [`openvino_fp16.jpg`](samples/eagle_4x/openvino_fp16.jpg) | 35.1 MB | **48.38 dB** | **0.9981** | 20.14 dB | 0.60 s |
|
| 95 |
+
| [`openvino_int8_w8a8.jpg`](samples/eagle_4x/openvino_int8_w8a8.jpg) | 19.5 MB | 27.76 dB | 0.8675 | 20.39 dB | **0.39 s** |
|
| 96 |
+
| [`openvino_int4_mixed_w4a16.jpg`](samples/eagle_4x/openvino_int4_mixed_w4a16.jpg) | 15.2 MB | 22.84 dB | 0.8160 | 18.68 dB | 0.61 s |
|
| 97 |
+
| [`openvino_int4_w4a16.jpg`](samples/eagle_4x/openvino_int4_w4a16.jpg) | 11.0 MB | 18.02 dB | 0.4879 | 17.19 dB | 0.60 s |
|
| 98 |
+
| [`openvino_int4_w4a8.jpg`](samples/eagle_4x/openvino_int4_w4a8.jpg) | 11.8 MB | 17.89 dB | 0.4624 | 17.09 dB | 0.69 s |
|
| 99 |
+
|
| 100 |
+
重現方式、完整檔案清單與說明見 **[`samples/eagle_4x/README.md`](samples/eagle_4x/README.md)**,
|
| 101 |
+
照片授權為 NASA 公有領域(Public Domain)。
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
## 2. 安裝
|
| 106 |
|
| 107 |
```bash
|
|
|
|
| 240 |
* `rrdbnet_arch.py` 為 BasicSR `RRDBNet` 的獨立重寫(不需安裝 BasicSR),參數名稱與官方 checkpoint 完全一致
|
| 241 |
* 量化:[OpenVINO NNCF](https://github.com/openvinotoolkit/nncf)
|
| 242 |
* 本 repo 的推論程式、轉換腳本與本說明文檔為本專案新增。
|
| 243 |
+
* 樣品照片:NASA Kennedy Space Center(公有領域),見 `samples/eagle_4x/README.md` 的來源與授權說明
|
| 244 |
* License:Apache-2.0(見 `LICENSE`)。
|
input/nasa_bald_eagle_KSC-20230425-PH-JBS02_0003.jpg
ADDED
|
Git LFS Details
|
realesrgan_ov.py
CHANGED
|
@@ -151,7 +151,7 @@ class RealESRGAN:
|
|
| 151 |
h, w = img.shape[:2]
|
| 152 |
pad_h = (tile - h % tile) % tile
|
| 153 |
pad_w = (tile - w % tile) % tile
|
| 154 |
-
padded = cv2.copyMakeBorder(img, tile_pad + pad_h, tile_pad, tile_pad + pad_w,
|
| 155 |
cv2.BORDER_REFLECT_101).astype(np.float32)
|
| 156 |
ph, pw = padded.shape[:2]
|
| 157 |
out = np.empty((ph * SCALE, pw * SCALE, 3), dtype=np.float32)
|
|
|
|
| 151 |
h, w = img.shape[:2]
|
| 152 |
pad_h = (tile - h % tile) % tile
|
| 153 |
pad_w = (tile - w % tile) % tile
|
| 154 |
+
padded = cv2.copyMakeBorder(img, tile_pad, tile_pad + pad_h, tile_pad, tile_pad + pad_w,
|
| 155 |
cv2.BORDER_REFLECT_101).astype(np.float32)
|
| 156 |
ph, pw = padded.shape[:2]
|
| 157 |
out = np.empty((ph * SCALE, pw * SCALE, 3), dtype=np.float32)
|
samples/eagle_4x/00_input_lr_x4.png
ADDED
|
Git LFS Details
|
samples/eagle_4x/01_bicubic_x4.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/02_ground_truth.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/03_pytorch_fp32_reference.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/10_detail_montage.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 效果對比:白頭海雕(Bald Eagle)
|
| 2 |
+
|
| 3 |
+
這個資料夾是 **同一張低解析度照片** 經過「原始 PyTorch 模型」與「所有 OpenVINO 量化版本」後的結果,
|
| 4 |
+
可以直接看出各種精度的差別。
|
| 5 |
+
|
| 6 |
+
## 來源與授權
|
| 7 |
+
|
| 8 |
+
| 項目 | 內容 |
|
| 9 |
+
|---|---|
|
| 10 |
+
| 照片 | NASA Kennedy Space Center, *Baby Eagle in the Rain*(2023-04-25) |
|
| 11 |
+
| 原始檔 | `input/nasa_bald_eagle_KSC-20230425-PH-JBS02_0003.jpg`(1280×853) |
|
| 12 |
+
| 來源頁面 | https://images.nasa.gov/details/KSC-20230425-PH-JBS02_0003 |
|
| 13 |
+
| 授權 | **公有領域(Public Domain)** — NASA 媒體 generally are not subject to copyright |
|
| 14 |
+
|
| 15 |
+
> NASA 使用條款:https://www.nasa.gov/nasa-brand-center/images-and-media/
|
| 16 |
+
> 本 repo 僅取用其中一張照片作為轉換/量化效果的展示素材。
|
| 17 |
+
|
| 18 |
+
## 測試設定
|
| 19 |
+
|
| 20 |
+
* 從原照片裁切 `1024×768` 當作 **Ground Truth(HR)**
|
| 21 |
+
* 用 bicubic 降 4 倍得到 **輸入 LR:`256×192`**(`00_input_lr_x4.png`)
|
| 22 |
+
* 所有模型都用 **tile=128 / tile_pad=16** 分塊推論,輸出 `768×1024`
|
| 23 |
+
* PyTorch 參考輸出由 `tools/make_sample.py` 用官方 `RealESRGAN_x4plus.pth`(`params_ema`)產生
|
| 24 |
+
|
| 25 |
+
## 檔案
|
| 26 |
+
|
| 27 |
+
| 檔案 | 說明 |
|
| 28 |
+
|---|---|
|
| 29 |
+
| `00_input_lr_x4.png` | 模型輸入(bicubic 降 4 倍,256×192) |
|
| 30 |
+
| `01_bicubic_x4.jpg` | bicubic 上傳對照組 |
|
| 31 |
+
| `02_ground_truth.jpg` | 真實 HR(未被降過的原始畫素) |
|
| 32 |
+
| `03_pytorch_fp32_reference.jpg` | **原始 PyTorch 模型 FP32 輸出(基準)** |
|
| 33 |
+
| `openvino_fp16.jpg` | OpenVINO FP16(與 PyTorch 幾乎一致,48.4 dB) |
|
| 34 |
+
| `openvino_int8_w8a8.jpg` | OpenVINO INT8 W8A8(27.8 dB,肉眼幾乎無差) |
|
| 35 |
+
| `openvino_int4_mixed_w4a16.jpg` | OpenVINO INT4 混合精度(22.8 dB,輕微變軟) |
|
| 36 |
+
| `openvino_int4_w4a16.jpg` | OpenVINO 純 INT4(18.0 dB,噪聲明顯) |
|
| 37 |
+
| `openvino_int4_w4a8.jpg` | OpenVINO INT4 權重 + INT8 activation(17.9 dB) |
|
| 38 |
+
| `10_detail_montage.jpg` | 同一塊 320×320 細節(鷹頭 + 樹皮)的並排比較 |
|
| 39 |
+
| `metrics.json` | 機讀指標 |
|
| 40 |
+
|
| 41 |
+
## 指標(實測)
|
| 42 |
+
|
| 43 |
+
| 模型 | 模型大小 | PSNR vs PyTorch FP32 ↑ | SSIM vs PyTorch FP32 ↑ | PSNR vs Ground Truth ↑ | 推論時間 |
|
| 44 |
+
|---|---|---|---|---|---|
|
| 45 |
+
| `pytorch_fp32`(原始) | 67.0 MB | 基準 | 1.0000 | 20.12 dB | 4.46 s |
|
| 46 |
+
| `bicubic x4` | – | 22.54 dB | 0.7049 | **21.70 dB** | – |
|
| 47 |
+
| `openvino_fp16` | 35.1 MB | **48.38 dB** | **0.9981** | 20.14 dB | 0.60 s |
|
| 48 |
+
| `openvino_int8_w8a8` | 19.5 MB | 27.76 dB | 0.8675 | 20.39 dB | **0.39 s** |
|
| 49 |
+
| `openvino_int4_mixed_w4a16` | 15.2 MB | 22.84 dB | 0.8160 | 18.68 dB | 0.61 s |
|
| 50 |
+
| `openvino_int4_w4a16` | 11.0 MB | 18.02 dB | 0.4879 | 17.19 dB | 0.60 s |
|
| 51 |
+
| `openvino_int4_w4a8` | 11.8 MB | 17.89 dB | 0.4624 | 17.09 dB | 0.69 s |
|
| 52 |
+
|
| 53 |
+
(測試機:Xeon Platinum 8559C 16 vCPU、OpenVINO 2026.4.1;時間含 model 載入後的單張推論。)
|
| 54 |
+
|
| 55 |
+
### 怎麼看這張表
|
| 56 |
+
|
| 57 |
+
1. **FP16 幾乎無損**:48 dB / SSIM 0.998,與原始 PyTorch 肉眼無法分辨,模型大小減半(67 → 35 MB)。
|
| 58 |
+
2. **INT8 是最佳平衡點**:27.8 dB、SSIM 0.87,實測比 FP16 快約 1.5×,模型只剩 19.5 MB。
|
| 59 |
+
3. **INT4(純 4-bit)** 體積最小(11 MB,比 FP32 小 6 倍),但 4-bit 誤差在 351 層殘差結構中累積,
|
| 60 |
+
會看到明顯的彩色雜訊(見 montage 右下),不建議用在有鑑識需求的照片上。
|
| 61 |
+
4. **`int4-mixed`** 讓最敏感的層留在 INT8,畫質回到可用範圍(SSIM 0.82),只比純 INT4 多 38% 體積。
|
| 62 |
+
5. 對 HR 的 PSNR 只有 ~20 dB 是正常的:Real-ESRGAN 是針對真實退化訓練的 GAN,輸出偏「銳化/有創意」,
|
| 63 |
+
用 PSNR 衡量 SR 品質向來偏低,重點請看同一組模型之間的相對差異(PSNR vs PyTorch 那一欄)。
|
| 64 |
+
|
| 65 |
+
## 重現這個資料夾
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
pip install torch onnx nncf openvino opencv-python numpy
|
| 69 |
+
|
| 70 |
+
# 1) 原始 PyTorch 權重(67 MB,不進本 repo)
|
| 71 |
+
mkdir -p weights && curl -L -o weights/RealESRGAN_x4plus.pth \
|
| 72 |
+
https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth
|
| 73 |
+
|
| 74 |
+
# 2) 產生整個 samples/eagle_4x(含 PyTorch 參考輸出、各 IR 輸出、montage、metrics.json)
|
| 75 |
+
python tools/make_sample.py # 需要 models/ 底下已備妥各個 IR
|
| 76 |
+
|
| 77 |
+
# 權重放在別的位置時:
|
| 78 |
+
REALESRGAN_PTH=/path/to/RealESRGAN_x4plus.pth python tools/make_sample.py
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
montage 的取樣區塊寫在 `build_montage()`(預設 `y=400, x=620, 320×320`,即鷹頭與樹皮),
|
| 82 |
+
`tiled()` 用的是與 `realesrgan_ov.RealESRGAN` 相同的 tile=128 / tile_pad=16 分塊邏輯。
|
samples/eagle_4x/metrics.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"model": "pytorch_fp32 (original)",
|
| 4 |
+
"size_mb": 67.0,
|
| 5 |
+
"psnr_vs_pytorch": 99.0,
|
| 6 |
+
"ssim_vs_pytorch": 1.0,
|
| 7 |
+
"psnr_vs_hr": 20.12,
|
| 8 |
+
"seconds": 3.97
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"model": "bicubic_x4",
|
| 12 |
+
"size_mb": 0.0,
|
| 13 |
+
"psnr_vs_pytorch": 22.53944127429838,
|
| 14 |
+
"ssim_vs_pytorch": 0.7049403747666699,
|
| 15 |
+
"psnr_vs_hr": 21.70060407039893,
|
| 16 |
+
"seconds": 0.0
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"model": "openvino_fp16",
|
| 20 |
+
"size_mb": 35.1,
|
| 21 |
+
"psnr_vs_pytorch": 48.38,
|
| 22 |
+
"ssim_vs_pytorch": 0.9981,
|
| 23 |
+
"psnr_vs_hr": 20.14,
|
| 24 |
+
"seconds": 0.58
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"model": "openvino_int8_w8a8",
|
| 28 |
+
"size_mb": 19.5,
|
| 29 |
+
"psnr_vs_pytorch": 27.76,
|
| 30 |
+
"ssim_vs_pytorch": 0.8675,
|
| 31 |
+
"psnr_vs_hr": 20.39,
|
| 32 |
+
"seconds": 0.34
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"model": "openvino_int4_mixed_w4a16",
|
| 36 |
+
"size_mb": 15.2,
|
| 37 |
+
"psnr_vs_pytorch": 22.84,
|
| 38 |
+
"ssim_vs_pytorch": 0.816,
|
| 39 |
+
"psnr_vs_hr": 18.68,
|
| 40 |
+
"seconds": 0.55
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"model": "openvino_int4_w4a16",
|
| 44 |
+
"size_mb": 11.0,
|
| 45 |
+
"psnr_vs_pytorch": 18.02,
|
| 46 |
+
"ssim_vs_pytorch": 0.4879,
|
| 47 |
+
"psnr_vs_hr": 17.19,
|
| 48 |
+
"seconds": 0.52
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"model": "openvino_int4_w4a8",
|
| 52 |
+
"size_mb": 11.8,
|
| 53 |
+
"psnr_vs_pytorch": 17.89,
|
| 54 |
+
"ssim_vs_pytorch": 0.4624,
|
| 55 |
+
"psnr_vs_hr": 17.09,
|
| 56 |
+
"seconds": 0.7
|
| 57 |
+
}
|
| 58 |
+
]
|
samples/eagle_4x/openvino_fp16.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/openvino_int4_mixed_w4a16.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/openvino_int4_w4a16.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/openvino_int4_w4a8.jpg
ADDED
|
Git LFS Details
|
samples/eagle_4x/openvino_int8_w8a8.jpg
ADDED
|
Git LFS Details
|
tools/__pycache__/make_sample.cpython-312.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|
tools/__pycache__/rrdbnet_arch.cpython-312.pyc
ADDED
|
Binary file (5.94 kB). View file
|
|
|
tools/make_sample.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Build the `samples/` gallery of the repo: one public-domain photo processed by
|
| 3 |
+
the original PyTorch model and by every OpenVINO variant (INT4 / INT8 / FP16).
|
| 4 |
+
|
| 5 |
+
Source image: NASA Kennedy Space Center, "Baby Eagle in the Rain"
|
| 6 |
+
(KSC-20230425-PH-JBS02_0003) - NASA media is public domain (https://images.nasa.gov).
|
| 7 |
+
"""
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
import pathlib
|
| 11 |
+
import sys
|
| 12 |
+
import time
|
| 13 |
+
|
| 14 |
+
import cv2
|
| 15 |
+
import numpy as np
|
| 16 |
+
|
| 17 |
+
HERE = pathlib.Path(__file__).resolve().parent
|
| 18 |
+
ROOT = HERE.parent
|
| 19 |
+
sys.path.insert(0, str(ROOT))
|
| 20 |
+
from realesrgan_ov import RealESRGAN # noqa: E402
|
| 21 |
+
|
| 22 |
+
SRC = ROOT / 'input' / 'nasa_bald_eagle_KSC-20230425-PH-JBS02_0003.jpg'
|
| 23 |
+
OUT = ROOT / 'samples' / 'eagle_4x'
|
| 24 |
+
TILE, TILE_PAD, SCALE = 128, 16, 4
|
| 25 |
+
# HR crop (ground truth) taken from the 1280x853 NASA photo
|
| 26 |
+
CROP = (256, 40, 1024, 768) # x, y, w, h
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def psnr(a, b, peak=255.0):
|
| 30 |
+
mse = np.mean((a.astype(np.float64) - b.astype(np.float64)) ** 2)
|
| 31 |
+
return 99.0 if mse == 0 else 10 * np.log10(peak ** 2 / mse)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def ssim(a, b):
|
| 35 |
+
a = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY).astype(np.float64) / 255.
|
| 36 |
+
b = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY).astype(np.float64) / 255.
|
| 37 |
+
c1, c2 = 0.01 ** 2, 0.03 ** 2
|
| 38 |
+
mu_a, mu_b = cv2.GaussianBlur(a, (11, 11), 1.5), cv2.GaussianBlur(b, (11, 11), 1.5)
|
| 39 |
+
sa = cv2.GaussianBlur(a * a, (11, 11), 1.5) - mu_a ** 2
|
| 40 |
+
sb = cv2.GaussianBlur(b * b, (11, 11), 1.5) - mu_b ** 2
|
| 41 |
+
sab = cv2.GaussianBlur(a * b, (11, 11), 1.5) - mu_a * mu_b
|
| 42 |
+
return (((2 * mu_a * mu_b + c1) * (2 * sab + c2)) / ((mu_a ** 2 + mu_b ** 2 + c1) * (sa + sb + c2))).mean()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def tiled(fn, img, tile=TILE, pad=TILE_PAD):
|
| 46 |
+
"""Same tiled forward pass as realesrgan_ov.RealESRGAN (seam-free)."""
|
| 47 |
+
h, w = img.shape[:2]
|
| 48 |
+
ph_pad, pw_pad = (tile - h % tile) % tile, (tile - w % tile) % tile
|
| 49 |
+
padded = cv2.copyMakeBorder(img.astype(np.float32) / 255., pad, pad + ph_pad, pad, pad + pw_pad,
|
| 50 |
+
cv2.BORDER_REFLECT_101)
|
| 51 |
+
ph, pw = padded.shape[:2]
|
| 52 |
+
out = np.empty((ph * SCALE, pw * SCALE, 3), dtype=np.float32)
|
| 53 |
+
crop = tile + 2 * pad
|
| 54 |
+
for y in range(0, ph - crop + 1, tile):
|
| 55 |
+
for x in range(0, pw - crop + 1, tile):
|
| 56 |
+
o = fn(padded[y:y + crop, x:x + crop])
|
| 57 |
+
o = o[pad * SCALE:(pad + tile) * SCALE, pad * SCALE:(pad + tile) * SCALE]
|
| 58 |
+
oy, ox = (y + pad) * SCALE, (x + pad) * SCALE
|
| 59 |
+
out[oy:oy + tile * SCALE, ox:ox + tile * SCALE] = o
|
| 60 |
+
p = pad * SCALE
|
| 61 |
+
return np.clip(out[p:p + h * SCALE, p:p + w * SCALE], 0, 255).round().astype(np.uint8)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def build_montage(out_dir, y0=400, x0=620, size=320):
|
| 65 |
+
"""Same 320x320 detail crop of every variant, side by side."""
|
| 66 |
+
panels = [('bicubic x4', '01_bicubic_x4.jpg'), ('PyTorch FP32', '03_pytorch_fp32_reference.jpg'),
|
| 67 |
+
('OV FP16', 'openvino_fp16.jpg'), ('OV INT8', 'openvino_int8_w8a8.jpg'),
|
| 68 |
+
('OV INT4 mixed', 'openvino_int4_mixed_w4a16.jpg'), ('OV INT4', 'openvino_int4_w4a16.jpg')]
|
| 69 |
+
tiles = []
|
| 70 |
+
for label, name in panels:
|
| 71 |
+
img = cv2.imread(str(out_dir / name))
|
| 72 |
+
crop = img[y0:y0 + size, x0:x0 + size]
|
| 73 |
+
crop = cv2.copyMakeBorder(crop, 34, 6, 6, 6, cv2.BORDER_CONSTANT, value=(255, 255, 255))
|
| 74 |
+
cv2.putText(crop, label, (12, 24), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 2, cv2.LINE_AA)
|
| 75 |
+
tiles.append(crop)
|
| 76 |
+
h, w = tiles[0].shape[:2]
|
| 77 |
+
montage = np.full((2 * h + 10, 3 * w + 20, 3), 255, np.uint8)
|
| 78 |
+
for i, t in enumerate(tiles):
|
| 79 |
+
r, c = divmod(i, 3)
|
| 80 |
+
montage[r * (h + 10):r * (h + 10) + h, c * (w + 10):c * (w + 10) + w] = t
|
| 81 |
+
cv2.imwrite(str(out_dir / '10_detail_montage.jpg'), montage, [cv2.IMWRITE_JPEG_QUALITY, 93])
|
| 82 |
+
print('montage written')
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def main():
|
| 86 |
+
OUT.mkdir(parents=True, exist_ok=True)
|
| 87 |
+
photo = cv2.imread(str(SRC))
|
| 88 |
+
x, y, w, h = CROP
|
| 89 |
+
hr = photo[y:y + h, x:x + w]
|
| 90 |
+
lr = cv2.resize(hr, (w // SCALE, h // SCALE), interpolation=cv2.INTER_CUBIC)
|
| 91 |
+
bicubic4x = cv2.resize(lr, (w, h), interpolation=cv2.INTER_CUBIC)
|
| 92 |
+
cv2.imwrite(str(OUT / '00_input_lr_x4.png'), lr)
|
| 93 |
+
cv2.imwrite(str(OUT / '01_bicubic_x4.jpg'), bicubic4x, [cv2.IMWRITE_JPEG_QUALITY, 92])
|
| 94 |
+
cv2.imwrite(str(OUT / '02_ground_truth.jpg'), hr, [cv2.IMWRITE_JPEG_QUALITY, 95])
|
| 95 |
+
|
| 96 |
+
# ---- original PyTorch model (FP32), the reference every variant is compared to
|
| 97 |
+
import torch
|
| 98 |
+
from rrdbnet_arch import RRDBNet
|
| 99 |
+
weights = os.environ.get('REALESRGAN_PTH', str(ROOT / 'weights' / 'RealESRGAN_x4plus.pth'))
|
| 100 |
+
if not os.path.exists(weights):
|
| 101 |
+
raise SystemExit(
|
| 102 |
+
f'PyTorch checkpoint not found: {weights}\n'
|
| 103 |
+
'Download it with:\n'
|
| 104 |
+
' curl -L -o weights/RealESRGAN_x4plus.pth \
|
| 105 |
+
'
|
| 106 |
+
' https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth')
|
| 107 |
+
ckpt = torch.load(weights, map_location='cpu', weights_only=True)
|
| 108 |
+
net = RRDBNet(3, 3, 4, 64, 23, 32)
|
| 109 |
+
net.load_state_dict(ckpt['params_ema'] if 'params_ema' in ckpt else ckpt['params'], strict=True)
|
| 110 |
+
net.eval()
|
| 111 |
+
|
| 112 |
+
def torch_fn(crop):
|
| 113 |
+
x = torch.from_numpy(np.ascontiguousarray(np.transpose(crop, (2, 0, 1))[None]).astype(np.float32))
|
| 114 |
+
with torch.inference_mode():
|
| 115 |
+
y = net(x).numpy()[0]
|
| 116 |
+
return np.clip(np.transpose(y, (1, 2, 0)), 0, 1) * 255.
|
| 117 |
+
|
| 118 |
+
t0 = time.time()
|
| 119 |
+
ref = tiled(torch_fn, lr)
|
| 120 |
+
torch_time = time.time() - t0
|
| 121 |
+
cv2.imwrite(str(OUT / '03_pytorch_fp32_reference.jpg'), ref, [cv2.IMWRITE_JPEG_QUALITY, 95])
|
| 122 |
+
del net
|
| 123 |
+
print(f'pytorch fp32 done in {torch_time:.2f}s')
|
| 124 |
+
|
| 125 |
+
variants = [
|
| 126 |
+
('openvino_fp16', 'models/realesrgan_x4plus_fp16.xml', 'f32'),
|
| 127 |
+
('openvino_int8_w8a8', 'models/realesrgan_x4plus_int8_w8a8.xml', None),
|
| 128 |
+
('openvino_int4_mixed_w4a16', 'models/realesrgan_x4plus_int4_mixed_w4a16.xml', 'f32'),
|
| 129 |
+
('openvino_int4_w4a16', 'models/realesrgan_x4plus_int4_w4a16.xml', 'f32'),
|
| 130 |
+
('openvino_int4_w4a8', 'models/realesrgan_x4plus_int4_w4a8.xml', None),
|
| 131 |
+
]
|
| 132 |
+
rows = [{'model': 'bicubic_x4', 'size_mb': 0.0, 'psnr_vs_pytorch': psnr(ref, bicubic4x),
|
| 133 |
+
'ssim_vs_pytorch': ssim(ref, bicubic4x), 'psnr_vs_hr': psnr(hr, bicubic4x),
|
| 134 |
+
'seconds': 0.0}]
|
| 135 |
+
for name, xml, hint in variants:
|
| 136 |
+
path = ROOT / xml
|
| 137 |
+
up = RealESRGAN(model=str(path), precision=hint or 'f32')
|
| 138 |
+
up.enhance(lr, tile=TILE) # warm-up
|
| 139 |
+
t0 = time.time()
|
| 140 |
+
out = up.enhance(lr, tile=TILE)[0]
|
| 141 |
+
dt = (time.time() - t0) / 2 # warm-up included: measure once, report approx
|
| 142 |
+
cv2.imwrite(str(OUT / f'{name}.jpg'), out, [cv2.IMWRITE_JPEG_QUALITY, 92])
|
| 143 |
+
size_mb = (path.stat().st_size + path.with_suffix('.bin').stat().st_size) / 1e6
|
| 144 |
+
rows.append({'model': name, 'size_mb': round(size_mb, 1), 'psnr_vs_pytorch': round(psnr(ref, out), 2),
|
| 145 |
+
'ssim_vs_pytorch': round(ssim(ref, out), 4), 'psnr_vs_hr': round(psnr(hr, out), 2),
|
| 146 |
+
'seconds': round(dt, 2)})
|
| 147 |
+
print(name, rows[-1])
|
| 148 |
+
|
| 149 |
+
build_montage(OUT)
|
| 150 |
+
|
| 151 |
+
rows.insert(0, {'model': 'pytorch_fp32 (original)', 'size_mb': 67.0, 'psnr_vs_pytorch': 99.0,
|
| 152 |
+
'ssim_vs_pytorch': 1.0, 'psnr_vs_hr': round(psnr(hr, ref), 2),
|
| 153 |
+
'seconds': round(torch_time, 2)})
|
| 154 |
+
(OUT / 'metrics.json').write_text(json.dumps(rows, indent=2))
|
| 155 |
+
print(json.dumps(rows, indent=2))
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
if __name__ == '__main__':
|
| 159 |
+
main()
|