HelloSun commited on
Commit
3f5e1ac
·
verified ·
1 Parent(s): 5a1f4db

Add public-domain eagle sample processed by PyTorch + all OpenVINO variants; fix tile padding offset for non-multiple-of-tile sizes

Browse files
.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
+ ![detail montage](samples/eagle_4x/10_detail_montage.jpg)
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

  • SHA256: 9221f63bb6e552bdb3fdb8c5f58e494ac622568c5ae83cba6676ff3d89c53fa7
  • Pointer size: 131 Bytes
  • Size of remote file: 271 kB
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, tile_pad,
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

  • SHA256: 55e6c84c5d081df586d087ffc1dc6e5b33fbb583223988e53862a8ba61298dc1
  • Pointer size: 131 Bytes
  • Size of remote file: 122 kB
samples/eagle_4x/01_bicubic_x4.jpg ADDED

Git LFS Details

  • SHA256: 44941d735a74a848f64eb7728e4122bc6e58a777f3ac9056fde6f7cdcf736de0
  • Pointer size: 131 Bytes
  • Size of remote file: 213 kB
samples/eagle_4x/02_ground_truth.jpg ADDED

Git LFS Details

  • SHA256: 55b8da1db95a415577fa8ff7025655618c56eff2492a2d14ca5594a199a75096
  • Pointer size: 131 Bytes
  • Size of remote file: 366 kB
samples/eagle_4x/03_pytorch_fp32_reference.jpg ADDED

Git LFS Details

  • SHA256: 297c42fb8a6d1f10f354fc73928f2d958bac1cd0936ac8b113fc7a6e5778ee9e
  • Pointer size: 131 Bytes
  • Size of remote file: 391 kB
samples/eagle_4x/10_detail_montage.jpg ADDED

Git LFS Details

  • SHA256: 4dd065de88eee2afa13b30e5eee42df19a6caa6f7444cb371fff78f2175284bb
  • Pointer size: 131 Bytes
  • Size of remote file: 275 kB
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

  • SHA256: b28805300360b5a32bbcb13fe720df21e17fb96984cb119487bac10ad6d9de34
  • Pointer size: 131 Bytes
  • Size of remote file: 304 kB
samples/eagle_4x/openvino_int4_mixed_w4a16.jpg ADDED

Git LFS Details

  • SHA256: e2ef0b284c7954821b36f47b4be63e72f7dcb90cbc53b90c263b7d3a678da2d9
  • Pointer size: 131 Bytes
  • Size of remote file: 335 kB
samples/eagle_4x/openvino_int4_w4a16.jpg ADDED

Git LFS Details

  • SHA256: dfe1b18b2e3990f00ba5058cb2b6967e6c81d6607ec23e2aa04aeacaf21881a7
  • Pointer size: 131 Bytes
  • Size of remote file: 338 kB
samples/eagle_4x/openvino_int4_w4a8.jpg ADDED

Git LFS Details

  • SHA256: 523ee1b4f2b7aab29b9c06daae79076e2b810a1012e5f297bd894ebc405f96dd
  • Pointer size: 131 Bytes
  • Size of remote file: 355 kB
samples/eagle_4x/openvino_int8_w8a8.jpg ADDED

Git LFS Details

  • SHA256: af6e1ab6142699c7b3100c07e94cae5cfe96c4a2b67bb1c7fff07e19ba157d82
  • Pointer size: 131 Bytes
  • Size of remote file: 297 kB
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()