first commit
Browse files- .gitattributes +4 -3
- DnCNN/model_convert/DnCNN.json +58 -0
- DnCNN/model_convert/axmodel/dncnn_color_blind_416x416_sim.axmodel +3 -0
- DnCNN/pic/3096.png +3 -0
- DnCNN/python/axmodel_infer.py +150 -0
- DnCNN/python/onnx_infer.py +165 -0
- DnCNN/res/axmodel_res.png +3 -0
- FFDNet/model_convert/FFDNet.json +58 -0
- FFDNet/model_convert/axmodel/ffdnet_color_fixed_sigma10_640x640_sim.axmodel +3 -0
- FFDNet/pic/3096.png +3 -0
- FFDNet/python/axmodel_infer.py +161 -0
- FFDNet/python/onnx_infer.py +177 -0
- FFDNet/res/axmodel_res.png +3 -0
- NAFNet/demo/noisy.png +3 -0
- NAFNet/model_convert/NAFNet.json +58 -0
- NAFNet/model_convert/axmodel/NAFNet_1_3_256_256.axmodel +3 -0
- NAFNet/python/axmodel_infer.py +69 -0
- NAFNet/python/onnx_infer.py +70 -0
- NAFNet/res/axmodel_compare.png +3 -0
- README.md +107 -0
- config.json +0 -0
- fastDVDnet/model_quant/fastDVDnet.json +58 -0
- fastDVDnet/python/axmodel_video_infer.py +166 -0
- fastDVDnet/python/onnx_video_infer.py +172 -0
- fastDVDnet/res/people-sunset_axmodel_side_by_side.mp4 +3 -0
- fastDVDnet/video/people-sunset.mp4 +3 -0
.gitattributes
CHANGED
|
@@ -33,8 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
-
*.gif filter=lfs diff=lfs merge=lfs -text
|
| 37 |
-
*.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 38 |
-
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
*.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 40 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
| 36 |
*.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.PNG filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
*.axmodel filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
DnCNN/model_convert/DnCNN.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"input": "./dncnn_color_blind_416x416_sim.onnx",
|
| 3 |
+
"output_dir": "./DnCNN",
|
| 4 |
+
"output_name": "dncnn_color_blind_416x416_sim.axmodel",
|
| 5 |
+
"model_type": "ONNX",
|
| 6 |
+
"target_hardware": "AX650",
|
| 7 |
+
"npu_mode": "NPU3",
|
| 8 |
+
"quant": {
|
| 9 |
+
"input_configs": [
|
| 10 |
+
{
|
| 11 |
+
"tensor_name": "input",
|
| 12 |
+
"calibration_dataset": "./BSD.tar.gz",
|
| 13 |
+
"calibration_size": 32,
|
| 14 |
+
"calibration_mean": [0.0, 0.0, 0.0],
|
| 15 |
+
"calibration_std": [255.0, 255.0, 255.0]
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"calibration_method": "MinMax",
|
| 19 |
+
"precision_analysis": true,
|
| 20 |
+
"transformer_opt_level":1,
|
| 21 |
+
// "enable_smooth_quant": true,
|
| 22 |
+
"precision_analysis_method": "EndToEnd",
|
| 23 |
+
"precision_analysis_mode": "NPUBackend",
|
| 24 |
+
"device": "cuda:1",
|
| 25 |
+
// "layer_configs": [
|
| 26 |
+
// {
|
| 27 |
+
// "start_tensor_names": ["DEFAULT"],
|
| 28 |
+
// "end_tensor_names": ["DEFAULT"],
|
| 29 |
+
// "data_type": "U16"
|
| 30 |
+
// }
|
| 31 |
+
// ]
|
| 32 |
+
},
|
| 33 |
+
"input_processors": [
|
| 34 |
+
{
|
| 35 |
+
"tensor_name": "input",
|
| 36 |
+
"tensor_format": "RGB",
|
| 37 |
+
"tensor_layout": "NCHW",
|
| 38 |
+
"src_format": "BGR",
|
| 39 |
+
"src_dtype": "U8",
|
| 40 |
+
"src_layout": "NCHW",
|
| 41 |
+
"csc_mode": "NoCSC"
|
| 42 |
+
}
|
| 43 |
+
// {
|
| 44 |
+
// "tensor_name": "input",
|
| 45 |
+
// "tensor_format": "RGB",
|
| 46 |
+
// "tensor_layout": "NCHW",
|
| 47 |
+
// "src_format": "YUV420SP",
|
| 48 |
+
// "src_layout": "NHWC",
|
| 49 |
+
// "src_dtype": "U8",
|
| 50 |
+
// "csc_mode": "FullRange",
|
| 51 |
+
// "csc_mat": [1.164, 0, 1.596, -222.912, 1.164, -0.392, -0.813, 135.616, 1.164, 2.017, 0, -276.8]
|
| 52 |
+
// }
|
| 53 |
+
],
|
| 54 |
+
"compiler": {
|
| 55 |
+
"check": 2
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
DnCNN/model_convert/axmodel/dncnn_color_blind_416x416_sim.axmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:028bfaddf44376cac8fdcc458bb6cfa85b0345d0a26eb5cfd124ca613949fe7f
|
| 3 |
+
size 818237
|
DnCNN/pic/3096.png
ADDED
|
Git LFS Details
|
DnCNN/python/axmodel_infer.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import os
|
| 3 |
+
import cv2
|
| 4 |
+
import numpy as np
|
| 5 |
+
import axengine as axe
|
| 6 |
+
|
| 7 |
+
# ============================================================
|
| 8 |
+
# 默认参数(可按需修改)
|
| 9 |
+
# ============================================================
|
| 10 |
+
AXMODEL_PATH = "dncnn_color_blind_416x416_sim.axmodel"
|
| 11 |
+
OUTPUT_DIR = "./"
|
| 12 |
+
# AX 量化模型输入格式: "float32" 或 "uint8",取决于编译时的量化配置
|
| 13 |
+
INPUT_DTYPE = "uint8" # 量化模型通常为 uint8;float 模型设为 "float32"
|
| 14 |
+
NOISE_SIGMA = 25 # 对原图施加的 AWGN 噪声强度(uint8 尺度,设为 0 则不加噪)
|
| 15 |
+
|
| 16 |
+
# ============================================================
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def load_image(img_path):
|
| 20 |
+
"""读取图像,返回 BGR uint8 原始图"""
|
| 21 |
+
img_bgr = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
|
| 22 |
+
if img_bgr is None:
|
| 23 |
+
raise FileNotFoundError(f"无法读取图像: {img_path}")
|
| 24 |
+
if img_bgr.ndim == 2:
|
| 25 |
+
img_bgr = cv2.cvtColor(img_bgr, cv2.COLOR_GRAY2BGR)
|
| 26 |
+
return img_bgr
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def add_awgn(img_bgr, sigma):
|
| 30 |
+
"""对 BGR uint8 图像施加 AWGN,返回加噪后的 BGR uint8 图像"""
|
| 31 |
+
noise = np.random.randn(*img_bgr.shape).astype(np.float32) * sigma
|
| 32 |
+
noisy = img_bgr.astype(np.float32) + noise
|
| 33 |
+
noisy = np.clip(noisy, 0, 255).astype(np.uint8)
|
| 34 |
+
return noisy
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def preprocess(img_bgr, model_h, model_w):
|
| 38 |
+
"""
|
| 39 |
+
预处理: BGR->RGB, resize to model size, 按 INPUT_DTYPE 做归一化或保持 uint8
|
| 40 |
+
返回: 模型输入张量, 原始尺寸 (h, w)
|
| 41 |
+
"""
|
| 42 |
+
orig_h, orig_w = img_bgr.shape[:2]
|
| 43 |
+
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 44 |
+
img_resized = cv2.resize(img_rgb, (model_w, model_h), interpolation=cv2.INTER_LINEAR)
|
| 45 |
+
|
| 46 |
+
if INPUT_DTYPE == "float32":
|
| 47 |
+
img_float = img_resized.astype(np.float32) / 255.0
|
| 48 |
+
tensor = np.transpose(img_float, (2, 0, 1))[np.newaxis, ...].astype(np.float32)
|
| 49 |
+
else:
|
| 50 |
+
tensor = np.transpose(img_resized, (2, 0, 1))[np.newaxis, ...].astype(np.uint8)
|
| 51 |
+
return tensor, (orig_h, orig_w)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def postprocess(tensor, orig_h, orig_w):
|
| 55 |
+
"""
|
| 56 |
+
后处理: squeeze batch, CHW->HWC, clip, uint8, resize 回原始尺寸
|
| 57 |
+
返回: BGR uint8 图像
|
| 58 |
+
"""
|
| 59 |
+
if tensor.dtype == np.uint8:
|
| 60 |
+
arr = np.squeeze(tensor, axis=0)
|
| 61 |
+
arr = np.transpose(arr, (1, 2, 0))
|
| 62 |
+
arr_uint = arr
|
| 63 |
+
else:
|
| 64 |
+
arr = np.clip(np.squeeze(tensor, axis=0), 0.0, 1.0)
|
| 65 |
+
arr = np.transpose(arr, (1, 2, 0))
|
| 66 |
+
arr_uint = (arr * 255.0).round().astype(np.uint8)
|
| 67 |
+
arr_uint = cv2.resize(arr_uint, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 68 |
+
result_bgr = cv2.cvtColor(arr_uint, cv2.COLOR_RGB2BGR)
|
| 69 |
+
return result_bgr
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def make_concat(original_bgr, noisy_bgr, result_bgr,
|
| 73 |
+
label_left="Original", label_mid="Noisy", label_right="DnCNN AXModel"):
|
| 74 |
+
"""左中右拼接原图、加噪图与结果图,并添加顶部标注"""
|
| 75 |
+
imgs = [original_bgr, noisy_bgr, result_bgr]
|
| 76 |
+
labels = [label_left, label_mid, label_right]
|
| 77 |
+
hs = [im.shape[0] for im in imgs]
|
| 78 |
+
h = max(hs)
|
| 79 |
+
|
| 80 |
+
resized = []
|
| 81 |
+
for im in imgs:
|
| 82 |
+
hh, ww = im.shape[:2]
|
| 83 |
+
if hh != h:
|
| 84 |
+
im = cv2.resize(im, (int(ww * h / hh), h), interpolation=cv2.INTER_LINEAR)
|
| 85 |
+
resized.append(im)
|
| 86 |
+
|
| 87 |
+
ws = [im.shape[1] for im in resized]
|
| 88 |
+
total_w = sum(ws)
|
| 89 |
+
label_h = max(30, h // 25)
|
| 90 |
+
canvas = np.full((h + label_h, total_w, 3), 255, dtype=np.uint8)
|
| 91 |
+
|
| 92 |
+
x = 0
|
| 93 |
+
for idx, im in enumerate(resized):
|
| 94 |
+
canvas[label_h:, x:x + ws[idx]] = im
|
| 95 |
+
x += ws[idx]
|
| 96 |
+
|
| 97 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 98 |
+
font_scale = label_h / 30.0
|
| 99 |
+
thickness = max(1, int(font_scale))
|
| 100 |
+
color = (0, 0, 0)
|
| 101 |
+
|
| 102 |
+
x = 0
|
| 103 |
+
for idx, (label, w) in enumerate(zip(labels, ws)):
|
| 104 |
+
(tw, th), _ = cv2.getTextSize(label, font, font_scale, thickness)
|
| 105 |
+
cv2.putText(canvas, label, (x + w // 2 - tw // 2, label_h - (label_h - th) // 2),
|
| 106 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 107 |
+
x += w
|
| 108 |
+
|
| 109 |
+
return canvas
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def main():
|
| 113 |
+
img_path = './3096.png'
|
| 114 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 115 |
+
|
| 116 |
+
# ---- 加载 AX 模型 ----
|
| 117 |
+
session = axe.InferenceSession(AXMODEL_PATH, providers=["AxEngineExecutionProvider"])
|
| 118 |
+
input_name = session.get_inputs()[0].name
|
| 119 |
+
_, _, model_h, model_w = session.get_inputs()[0].shape
|
| 120 |
+
|
| 121 |
+
# ---- 读取原图 ----
|
| 122 |
+
img_bgr = load_image(img_path)
|
| 123 |
+
|
| 124 |
+
# ---- 加噪 ----
|
| 125 |
+
if NOISE_SIGMA > 0:
|
| 126 |
+
noisy_bgr = add_awgn(img_bgr, NOISE_SIGMA)
|
| 127 |
+
else:
|
| 128 |
+
noisy_bgr = img_bgr
|
| 129 |
+
|
| 130 |
+
# ---- 预处理(对加噪图) ----
|
| 131 |
+
tensor, (orig_h, orig_w) = preprocess(noisy_bgr, model_h, model_w)
|
| 132 |
+
|
| 133 |
+
# ---- AX 推理 ----
|
| 134 |
+
result = session.run(None, {input_name: tensor})[0]
|
| 135 |
+
|
| 136 |
+
# ---- 后处理 & 还原原始尺寸 ----
|
| 137 |
+
result_bgr = postprocess(result, orig_h, orig_w)
|
| 138 |
+
|
| 139 |
+
# ---- 三图拼接输出 ----
|
| 140 |
+
label_mid = f"Noisy (sigma={NOISE_SIGMA})" if NOISE_SIGMA > 0 else "Input"
|
| 141 |
+
concat = make_concat(img_bgr, noisy_bgr, result_bgr, label_mid=label_mid)
|
| 142 |
+
|
| 143 |
+
name = os.path.splitext(os.path.basename(img_path))[0]
|
| 144 |
+
out_path = os.path.join(OUTPUT_DIR, "axmodel_res.png")
|
| 145 |
+
cv2.imwrite(out_path, concat)
|
| 146 |
+
print(f"输出已保存: {out_path}")
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
if __name__ == "__main__":
|
| 150 |
+
main()
|
DnCNN/python/onnx_infer.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
DnCNN ONNX 单图推理脚本
|
| 4 |
+
- 读取原图 → 加 AWGN 噪声 → 预处理 → ONNX 推理 → 后处理 → 还原原图大小
|
| 5 |
+
- 输出: 原图 + 加噪图 + 结果图 的三图左右拼接(带标注)
|
| 6 |
+
- 无 torch 依赖,仅需 cv2 / numpy / onnxruntime
|
| 7 |
+
- 所有参数已写为 default 常量,可直接运行
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
|
| 13 |
+
import cv2
|
| 14 |
+
import numpy as np
|
| 15 |
+
import onnxruntime as ort
|
| 16 |
+
|
| 17 |
+
# ============================================================
|
| 18 |
+
# 默认参数(可按需修改)
|
| 19 |
+
# ============================================================
|
| 20 |
+
ONNX_PATH = os.path.join(os.path.dirname(__file__), "..", "..", "model_zoo", "dncnn_color_blind_640x640_sim.onnx")
|
| 21 |
+
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "results", "dncnn_onnx_infer")
|
| 22 |
+
NOISE_SIGMA = 25 # 对原图施加的 AWGN 噪声强度(uint8 尺度,设为 0 则不加噪)
|
| 23 |
+
PROVIDERS = ["CPUExecutionProvider"] # ONNX Runtime 执行后端
|
| 24 |
+
|
| 25 |
+
# ============================================================
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def load_image(img_path):
|
| 29 |
+
"""读取图像,返回 BGR uint8 原始图"""
|
| 30 |
+
img_bgr = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
|
| 31 |
+
if img_bgr is None:
|
| 32 |
+
raise FileNotFoundError(f"无法读取图像: {img_path}")
|
| 33 |
+
if img_bgr.ndim == 2:
|
| 34 |
+
img_bgr = cv2.cvtColor(img_bgr, cv2.COLOR_GRAY2BGR)
|
| 35 |
+
return img_bgr
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def add_awgn(img_bgr, sigma):
|
| 39 |
+
"""对 BGR uint8 图像施加 AWGN,返回加噪后的 BGR uint8 图像"""
|
| 40 |
+
noise = np.random.randn(*img_bgr.shape).astype(np.float32) * sigma
|
| 41 |
+
noisy = img_bgr.astype(np.float32) + noise
|
| 42 |
+
noisy = np.clip(noisy, 0, 255).astype(np.uint8)
|
| 43 |
+
return noisy
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def preprocess(img_bgr, model_h, model_w):
|
| 47 |
+
"""
|
| 48 |
+
预处理: BGR->RGB, resize to model size, normalize [0,1], HWC->CHW, add batch
|
| 49 |
+
返回: (1, 3, H, W) float32 张量, 原始尺寸 (h, w)
|
| 50 |
+
"""
|
| 51 |
+
orig_h, orig_w = img_bgr.shape[:2]
|
| 52 |
+
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 53 |
+
img_resized = cv2.resize(img_rgb, (model_w, model_h), interpolation=cv2.INTER_LINEAR)
|
| 54 |
+
img_float = img_resized.astype(np.float32) / 255.0
|
| 55 |
+
tensor = np.transpose(img_float, (2, 0, 1))[np.newaxis, ...].astype(np.float32)
|
| 56 |
+
return tensor, (orig_h, orig_w)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def postprocess(tensor, orig_h, orig_w):
|
| 60 |
+
"""
|
| 61 |
+
后处理: squeeze batch, CHW->HWC, clip [0,1], uint8, resize 回原始尺寸
|
| 62 |
+
返回: BGR uint8 图像
|
| 63 |
+
"""
|
| 64 |
+
arr = np.clip(np.squeeze(tensor, axis=0), 0.0, 1.0) # (3, H, W)
|
| 65 |
+
arr = np.transpose(arr, (1, 2, 0)) # (H, W, 3)
|
| 66 |
+
arr_uint = (arr * 255.0).round().astype(np.uint8)
|
| 67 |
+
arr_uint = cv2.resize(arr_uint, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 68 |
+
result_bgr = cv2.cvtColor(arr_uint, cv2.COLOR_RGB2BGR)
|
| 69 |
+
return result_bgr
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def make_concat(original_bgr, noisy_bgr, result_bgr,
|
| 73 |
+
label_left="Original", label_mid="Noisy", label_right="DnCNN ONNX"):
|
| 74 |
+
"""左中右拼接原图、加噪图与结果图,并添加顶部标注"""
|
| 75 |
+
imgs = [original_bgr, noisy_bgr, result_bgr]
|
| 76 |
+
labels = [label_left, label_mid, label_right]
|
| 77 |
+
hs = [im.shape[0] for im in imgs]
|
| 78 |
+
h = max(hs)
|
| 79 |
+
|
| 80 |
+
resized = []
|
| 81 |
+
for im in imgs:
|
| 82 |
+
hh, ww = im.shape[:2]
|
| 83 |
+
if hh != h:
|
| 84 |
+
im = cv2.resize(im, (int(ww * h / hh), h), interpolation=cv2.INTER_LINEAR)
|
| 85 |
+
resized.append(im)
|
| 86 |
+
|
| 87 |
+
ws = [im.shape[1] for im in resized]
|
| 88 |
+
total_w = sum(ws)
|
| 89 |
+
label_h = max(30, h // 25)
|
| 90 |
+
canvas = np.full((h + label_h, total_w, 3), 255, dtype=np.uint8)
|
| 91 |
+
|
| 92 |
+
x = 0
|
| 93 |
+
for idx, im in enumerate(resized):
|
| 94 |
+
canvas[label_h:, x:x + ws[idx]] = im
|
| 95 |
+
x += ws[idx]
|
| 96 |
+
|
| 97 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 98 |
+
font_scale = label_h / 30.0
|
| 99 |
+
thickness = max(1, int(font_scale))
|
| 100 |
+
color = (0, 0, 0)
|
| 101 |
+
|
| 102 |
+
x = 0
|
| 103 |
+
for idx, (label, w) in enumerate(zip(labels, ws)):
|
| 104 |
+
(tw, th), _ = cv2.getTextSize(label, font, font_scale, thickness)
|
| 105 |
+
cv2.putText(canvas, label, (x + w // 2 - tw // 2, label_h - (label_h - th) // 2),
|
| 106 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 107 |
+
x += w
|
| 108 |
+
|
| 109 |
+
return canvas
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def main():
|
| 113 |
+
if len(sys.argv) < 2:
|
| 114 |
+
print(f"用法: python {os.path.basename(__file__)} <图像路径>")
|
| 115 |
+
print(f"默认 ONNX 模型: {ONNX_PATH}")
|
| 116 |
+
print(f"噪声 sigma: {NOISE_SIGMA}")
|
| 117 |
+
print(f"输出目录: {OUTPUT_DIR}")
|
| 118 |
+
sys.exit(1)
|
| 119 |
+
|
| 120 |
+
img_path = sys.argv[1]
|
| 121 |
+
if not os.path.isfile(img_path):
|
| 122 |
+
print(f"错误: 图像不存在: {img_path}")
|
| 123 |
+
sys.exit(1)
|
| 124 |
+
if not os.path.isfile(ONNX_PATH):
|
| 125 |
+
print(f"错误: ONNX 模型不存在: {ONNX_PATH}")
|
| 126 |
+
print(f"请修改脚本顶部 ONNX_PATH 常量,或先导出 ONNX 模型。")
|
| 127 |
+
sys.exit(1)
|
| 128 |
+
|
| 129 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 130 |
+
|
| 131 |
+
# ---- 加载 ONNX 模型 ----
|
| 132 |
+
session = ort.InferenceSession(ONNX_PATH, providers=PROVIDERS)
|
| 133 |
+
input_name = session.get_inputs()[0].name
|
| 134 |
+
_, _, model_h, model_w = session.get_inputs()[0].shape
|
| 135 |
+
|
| 136 |
+
# ---- 读取原图 ----
|
| 137 |
+
img_bgr = load_image(img_path)
|
| 138 |
+
|
| 139 |
+
# ---- 加噪 ----
|
| 140 |
+
if NOISE_SIGMA > 0:
|
| 141 |
+
noisy_bgr = add_awgn(img_bgr, NOISE_SIGMA)
|
| 142 |
+
else:
|
| 143 |
+
noisy_bgr = img_bgr
|
| 144 |
+
|
| 145 |
+
# ---- 预处理(对加噪图) ----
|
| 146 |
+
tensor, (orig_h, orig_w) = preprocess(noisy_bgr, model_h, model_w)
|
| 147 |
+
|
| 148 |
+
# ---- ONNX 推理 ----
|
| 149 |
+
result = session.run(None, {input_name: tensor})[0]
|
| 150 |
+
|
| 151 |
+
# ---- 后处理 & 还原原始尺寸 ----
|
| 152 |
+
result_bgr = postprocess(result, orig_h, orig_w)
|
| 153 |
+
|
| 154 |
+
# ---- 三图拼接输出 ----
|
| 155 |
+
label_mid = f"Noisy (sigma={NOISE_SIGMA})" if NOISE_SIGMA > 0 else "Input"
|
| 156 |
+
concat = make_concat(img_bgr, noisy_bgr, result_bgr, label_mid=label_mid)
|
| 157 |
+
|
| 158 |
+
name = os.path.splitext(os.path.basename(img_path))[0]
|
| 159 |
+
out_path = os.path.join(OUTPUT_DIR, f"{name}_concat.png")
|
| 160 |
+
cv2.imwrite(out_path, concat)
|
| 161 |
+
print(f"输出已保存: {out_path}")
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
if __name__ == "__main__":
|
| 165 |
+
main()
|
DnCNN/res/axmodel_res.png
ADDED
|
Git LFS Details
|
FFDNet/model_convert/FFDNet.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"input": "./ffdnet_color_fixed_sigma10_416x416_sim.onnx",
|
| 3 |
+
"output_dir": "./FFDNet",
|
| 4 |
+
"output_name": "ffdnet_color_fixed_sigma10_416x416_sim.axmodel",
|
| 5 |
+
"model_type": "ONNX",
|
| 6 |
+
"target_hardware": "AX650",
|
| 7 |
+
"npu_mode": "NPU3",
|
| 8 |
+
"quant": {
|
| 9 |
+
"input_configs": [
|
| 10 |
+
{
|
| 11 |
+
"tensor_name": "input",
|
| 12 |
+
"calibration_dataset": "./BSD.tar.gz",
|
| 13 |
+
"calibration_size": 32,
|
| 14 |
+
"calibration_mean": [0.0, 0.0, 0.0],
|
| 15 |
+
"calibration_std": [255.0, 255.0, 255.0]
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"calibration_method": "MinMax",
|
| 19 |
+
"precision_analysis": true,
|
| 20 |
+
"transformer_opt_level":1,
|
| 21 |
+
// "enable_smooth_quant": true,
|
| 22 |
+
"precision_analysis_method": "EndToEnd",
|
| 23 |
+
"precision_analysis_mode": "NPUBackend",
|
| 24 |
+
"device": "cuda:1",
|
| 25 |
+
// "layer_configs": [
|
| 26 |
+
// {
|
| 27 |
+
// "start_tensor_names": ["DEFAULT"],
|
| 28 |
+
// "end_tensor_names": ["DEFAULT"],
|
| 29 |
+
// "data_type": "U16"
|
| 30 |
+
// }
|
| 31 |
+
// ]
|
| 32 |
+
},
|
| 33 |
+
"input_processors": [
|
| 34 |
+
{
|
| 35 |
+
"tensor_name": "input",
|
| 36 |
+
"tensor_format": "RGB",
|
| 37 |
+
"tensor_layout": "NCHW",
|
| 38 |
+
"src_format": "BGR",
|
| 39 |
+
"src_dtype": "U8",
|
| 40 |
+
"src_layout": "NCHW",
|
| 41 |
+
"csc_mode": "NoCSC"
|
| 42 |
+
}
|
| 43 |
+
// {
|
| 44 |
+
// "tensor_name": "input",
|
| 45 |
+
// "tensor_format": "RGB",
|
| 46 |
+
// "tensor_layout": "NCHW",
|
| 47 |
+
// "src_format": "YUV420SP",
|
| 48 |
+
// "src_layout": "NHWC",
|
| 49 |
+
// "src_dtype": "U8",
|
| 50 |
+
// "csc_mode": "FullRange",
|
| 51 |
+
// "csc_mat": [1.164, 0, 1.596, -222.912, 1.164, -0.392, -0.813, 135.616, 1.164, 2.017, 0, -276.8]
|
| 52 |
+
// }
|
| 53 |
+
],
|
| 54 |
+
"compiler": {
|
| 55 |
+
"check": 2
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
FFDNet/model_convert/axmodel/ffdnet_color_fixed_sigma10_640x640_sim.axmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe16b107c8a141e2d59e6090c4f4728b0238c7855156df2876181356a9c38778
|
| 3 |
+
size 1101373
|
FFDNet/pic/3096.png
ADDED
|
Git LFS Details
|
FFDNet/python/axmodel_infer.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import cv2
|
| 5 |
+
import numpy as np
|
| 6 |
+
import axengine as axe
|
| 7 |
+
|
| 8 |
+
# ============================================================
|
| 9 |
+
# 默认参数(可按需修改)
|
| 10 |
+
# ============================================================
|
| 11 |
+
AXMODEL_PATH = './ffdnet_color_fixed_sigma10_640x640_sim.axmodel'
|
| 12 |
+
OUTPUT_DIR = './'
|
| 13 |
+
# AX 量化模型输入格式: "float32" 或 "uint8",取决于编译时的量化配置
|
| 14 |
+
INPUT_DTYPE = "uint8" # 量化模型通常为 uint8;float 模型设为 "float32"
|
| 15 |
+
USE_FIXED_SIGMA = True # True: 单输入模型;False: 双输入模型(input + sigma)
|
| 16 |
+
MODEL_SIGMA = 10 # 模型 sigma(uint8 尺度,仅 USE_FIXED_SIGMA=False 时生效)
|
| 17 |
+
NOISE_SIGMA = 10 # 对原图施加的 AWGN 噪声强度(uint8 尺度,设为 0 则不加噪)
|
| 18 |
+
|
| 19 |
+
# ============================================================
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def load_image(img_path):
|
| 23 |
+
"""读取图像,返回 BGR uint8 原始图"""
|
| 24 |
+
img_bgr = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
|
| 25 |
+
if img_bgr is None:
|
| 26 |
+
raise FileNotFoundError(f"无法读取图像: {img_path}")
|
| 27 |
+
if img_bgr.ndim == 2:
|
| 28 |
+
img_bgr = cv2.cvtColor(img_bgr, cv2.COLOR_GRAY2BGR)
|
| 29 |
+
return img_bgr
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def add_awgn(img_bgr, sigma):
|
| 33 |
+
"""对 BGR uint8 图像施加 AWGN,返回加噪后的 BGR uint8 图像"""
|
| 34 |
+
noise = np.random.randn(*img_bgr.shape).astype(np.float32) * sigma
|
| 35 |
+
noisy = img_bgr.astype(np.float32) + noise
|
| 36 |
+
noisy = np.clip(noisy, 0, 255).astype(np.uint8)
|
| 37 |
+
return noisy
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def preprocess(img_bgr, model_h, model_w):
|
| 41 |
+
"""
|
| 42 |
+
预处理: BGR->RGB, resize to model size, 按 INPUT_DTYPE 做归一化或保持 uint8
|
| 43 |
+
返回: 模型输入张量, 原始尺寸 (h, w)
|
| 44 |
+
"""
|
| 45 |
+
orig_h, orig_w = img_bgr.shape[:2]
|
| 46 |
+
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 47 |
+
img_resized = cv2.resize(img_rgb, (model_w, model_h), interpolation=cv2.INTER_LINEAR)
|
| 48 |
+
|
| 49 |
+
if INPUT_DTYPE == "float32":
|
| 50 |
+
img_float = img_resized.astype(np.float32) / 255.0
|
| 51 |
+
tensor = np.transpose(img_float, (2, 0, 1))[np.newaxis, ...].astype(np.float32)
|
| 52 |
+
else:
|
| 53 |
+
tensor = np.transpose(img_resized, (2, 0, 1))[np.newaxis, ...].astype(np.uint8)
|
| 54 |
+
return tensor, (orig_h, orig_w)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def postprocess(tensor, orig_h, orig_w):
|
| 58 |
+
"""
|
| 59 |
+
后处理: squeeze batch, CHW->HWC, clip, uint8, resize 回原始尺寸
|
| 60 |
+
返回: BGR uint8 图像
|
| 61 |
+
"""
|
| 62 |
+
if tensor.dtype == np.uint8:
|
| 63 |
+
arr = np.squeeze(tensor, axis=0)
|
| 64 |
+
arr = np.transpose(arr, (1, 2, 0))
|
| 65 |
+
arr_uint = arr
|
| 66 |
+
else:
|
| 67 |
+
arr = np.clip(np.squeeze(tensor, axis=0), 0.0, 1.0)
|
| 68 |
+
arr = np.transpose(arr, (1, 2, 0))
|
| 69 |
+
arr_uint = (arr * 255.0).round().astype(np.uint8)
|
| 70 |
+
arr_uint = cv2.resize(arr_uint, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 71 |
+
result_bgr = cv2.cvtColor(arr_uint, cv2.COLOR_RGB2BGR)
|
| 72 |
+
return result_bgr
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def make_concat(original_bgr, noisy_bgr, result_bgr,
|
| 76 |
+
label_left="Original", label_mid="Noisy", label_right="FFDNet AXModel"):
|
| 77 |
+
"""左中右拼接原图、加噪图与结果图,并添加顶部标注"""
|
| 78 |
+
imgs = [original_bgr, noisy_bgr, result_bgr]
|
| 79 |
+
labels = [label_left, label_mid, label_right]
|
| 80 |
+
hs = [im.shape[0] for im in imgs]
|
| 81 |
+
h = max(hs)
|
| 82 |
+
|
| 83 |
+
resized = []
|
| 84 |
+
for im in imgs:
|
| 85 |
+
hh, ww = im.shape[:2]
|
| 86 |
+
if hh != h:
|
| 87 |
+
im = cv2.resize(im, (int(ww * h / hh), h), interpolation=cv2.INTER_LINEAR)
|
| 88 |
+
resized.append(im)
|
| 89 |
+
|
| 90 |
+
ws = [im.shape[1] for im in resized]
|
| 91 |
+
total_w = sum(ws)
|
| 92 |
+
label_h = max(30, h // 25)
|
| 93 |
+
canvas = np.full((h + label_h, total_w, 3), 255, dtype=np.uint8)
|
| 94 |
+
|
| 95 |
+
x = 0
|
| 96 |
+
for idx, im in enumerate(resized):
|
| 97 |
+
canvas[label_h:, x:x + ws[idx]] = im
|
| 98 |
+
x += ws[idx]
|
| 99 |
+
|
| 100 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 101 |
+
font_scale = label_h / 30.0
|
| 102 |
+
thickness = max(1, int(font_scale))
|
| 103 |
+
color = (0, 0, 0)
|
| 104 |
+
|
| 105 |
+
x = 0
|
| 106 |
+
for idx, (label, w) in enumerate(zip(labels, ws)):
|
| 107 |
+
(tw, th), _ = cv2.getTextSize(label, font, font_scale, thickness)
|
| 108 |
+
cv2.putText(canvas, label, (x + w // 2 - tw // 2, label_h - (label_h - th) // 2),
|
| 109 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 110 |
+
x += w
|
| 111 |
+
|
| 112 |
+
return canvas
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def main():
|
| 116 |
+
img_path = './3096.png' # 输入图像路径,可按需修改
|
| 117 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 118 |
+
|
| 119 |
+
# ---- 加载 AX 模型 ----
|
| 120 |
+
session = axe.InferenceSession(AXMODEL_PATH, providers=["AxEngineExecutionProvider"])
|
| 121 |
+
input_name = session.get_inputs()[0].name
|
| 122 |
+
_, _, model_h, model_w = session.get_inputs()[0].shape
|
| 123 |
+
|
| 124 |
+
# ---- 读取原图 ----
|
| 125 |
+
img_bgr = load_image(img_path)
|
| 126 |
+
|
| 127 |
+
# ---- 加噪 ----
|
| 128 |
+
if NOISE_SIGMA > 0:
|
| 129 |
+
noisy_bgr = add_awgn(img_bgr, NOISE_SIGMA)
|
| 130 |
+
else:
|
| 131 |
+
noisy_bgr = img_bgr
|
| 132 |
+
|
| 133 |
+
# ---- 预处理(对加噪图) ----
|
| 134 |
+
tensor, (orig_h, orig_w) = preprocess(noisy_bgr, model_h, model_w)
|
| 135 |
+
|
| 136 |
+
# ---- AX 推理 ----
|
| 137 |
+
if USE_FIXED_SIGMA:
|
| 138 |
+
result = session.run(None, {input_name: tensor})[0]
|
| 139 |
+
else:
|
| 140 |
+
sigma_dtype = np.float32 if INPUT_DTYPE == "float32" else np.uint8
|
| 141 |
+
sigma_tensor = np.full((1, 1, 1, 1), MODEL_SIGMA / 255.0 if INPUT_DTYPE == "float32" else MODEL_SIGMA, dtype=sigma_dtype)
|
| 142 |
+
sigma_name = session.get_inputs()[1].name
|
| 143 |
+
result = session.run(None, {input_name: tensor, sigma_name: sigma_tensor})[0]
|
| 144 |
+
|
| 145 |
+
# ---- 后处理 & 还原原始尺寸 ----
|
| 146 |
+
result_bgr = postprocess(result, orig_h, orig_w)
|
| 147 |
+
|
| 148 |
+
# ---- 三图拼接输出 ----
|
| 149 |
+
label_right = f"FFDNet AXModel (sigma={MODEL_SIGMA})" if not USE_FIXED_SIGMA else "FFDNet AXModel"
|
| 150 |
+
label_mid = f"Noisy (sigma={NOISE_SIGMA})" if NOISE_SIGMA > 0 else "Input"
|
| 151 |
+
concat = make_concat(img_bgr, noisy_bgr, result_bgr,
|
| 152 |
+
label_mid=label_mid, label_right=label_right)
|
| 153 |
+
|
| 154 |
+
name = os.path.splitext(os.path.basename(img_path))[0]
|
| 155 |
+
out_path = os.path.join(OUTPUT_DIR, "axmodel_res.png")
|
| 156 |
+
cv2.imwrite(out_path, concat)
|
| 157 |
+
print(f"输出已保存: {out_path}")
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
if __name__ == "__main__":
|
| 161 |
+
main()
|
FFDNet/python/onnx_infer.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
FFDNet ONNX 单图推理脚本
|
| 4 |
+
- 读取原图 → 加 AWGN 噪声 → 预处理 → ONNX 推理 → 后处理 → 还原原图大小
|
| 5 |
+
- 输出: 原图 + 加噪图 + 结果图 的三图左右拼接(带标注)
|
| 6 |
+
- 无 torch 依赖,仅需 cv2 / numpy / onnxruntime
|
| 7 |
+
- 所有参数已写为 default 常量,可直接运行
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
|
| 13 |
+
import cv2
|
| 14 |
+
import numpy as np
|
| 15 |
+
import onnxruntime as ort
|
| 16 |
+
|
| 17 |
+
# ============================================================
|
| 18 |
+
# 默认参数(可按需修改)
|
| 19 |
+
# ============================================================
|
| 20 |
+
ONNX_PATH = os.path.join(os.path.dirname(__file__), "..", "..", "model_zoo", "ffdnet_color_fixed_sigma10_640x640_sim.onnx")
|
| 21 |
+
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "results", "ffdnet_onnx_infer")
|
| 22 |
+
# 若使用非固定 sigma 的模型,设置 USE_FIXED_SIGMA = False 并填写 MODEL_SIGMA
|
| 23 |
+
USE_FIXED_SIGMA = True # True: 单输入 fixed-sigma 模型
|
| 24 |
+
MODEL_SIGMA = 10 # 模型 sigma(uint8 尺度,仅 USE_FIXED_SIGMA=False 时生效)
|
| 25 |
+
NOISE_SIGMA = 10 # 对原图施加的 AWGN 噪声强度(uint8 尺度,设为 0 则不加噪)
|
| 26 |
+
PROVIDERS = ["CPUExecutionProvider"] # ONNX Runtime 执行后端
|
| 27 |
+
|
| 28 |
+
# ============================================================
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def load_image(img_path):
|
| 32 |
+
"""读取图像,返回 BGR uint8 原始图"""
|
| 33 |
+
img_bgr = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
|
| 34 |
+
if img_bgr is None:
|
| 35 |
+
raise FileNotFoundError(f"无法读取图像: {img_path}")
|
| 36 |
+
if img_bgr.ndim == 2:
|
| 37 |
+
img_bgr = cv2.cvtColor(img_bgr, cv2.COLOR_GRAY2BGR)
|
| 38 |
+
return img_bgr
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def add_awgn(img_bgr, sigma):
|
| 42 |
+
"""对 BGR uint8 图像施加 AWGN,返回加噪后的 BGR uint8 图像"""
|
| 43 |
+
noise = np.random.randn(*img_bgr.shape).astype(np.float32) * sigma
|
| 44 |
+
noisy = img_bgr.astype(np.float32) + noise
|
| 45 |
+
noisy = np.clip(noisy, 0, 255).astype(np.uint8)
|
| 46 |
+
return noisy
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def preprocess(img_bgr, model_h, model_w):
|
| 50 |
+
"""
|
| 51 |
+
预处理: BGR->RGB, resize to model size, normalize [0,1], HWC->CHW, add batch
|
| 52 |
+
返回: (1, 3, H, W) float32 张量, 原始尺寸 (h, w)
|
| 53 |
+
"""
|
| 54 |
+
orig_h, orig_w = img_bgr.shape[:2]
|
| 55 |
+
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
| 56 |
+
img_resized = cv2.resize(img_rgb, (model_w, model_h), interpolation=cv2.INTER_LINEAR)
|
| 57 |
+
img_float = img_resized.astype(np.float32) / 255.0
|
| 58 |
+
tensor = np.transpose(img_float, (2, 0, 1))[np.newaxis, ...].astype(np.float32)
|
| 59 |
+
return tensor, (orig_h, orig_w)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def postprocess(tensor, orig_h, orig_w):
|
| 63 |
+
"""
|
| 64 |
+
后处理: squeeze batch, CHW->HWC, clip [0,1], uint8, resize 回原始尺寸
|
| 65 |
+
返回: BGR uint8 图像
|
| 66 |
+
"""
|
| 67 |
+
arr = np.clip(np.squeeze(tensor, axis=0), 0.0, 1.0) # (3, H, W)
|
| 68 |
+
arr = np.transpose(arr, (1, 2, 0)) # (H, W, 3)
|
| 69 |
+
arr_uint = (arr * 255.0).round().astype(np.uint8)
|
| 70 |
+
arr_uint = cv2.resize(arr_uint, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)
|
| 71 |
+
result_bgr = cv2.cvtColor(arr_uint, cv2.COLOR_RGB2BGR)
|
| 72 |
+
return result_bgr
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def make_concat(original_bgr, noisy_bgr, result_bgr,
|
| 76 |
+
label_left="Original", label_mid="Noisy", label_right="FFDNet ONNX"):
|
| 77 |
+
"""左中右拼接原图、加噪图与结果图,并添加顶部标注"""
|
| 78 |
+
imgs = [original_bgr, noisy_bgr, result_bgr]
|
| 79 |
+
labels = [label_left, label_mid, label_right]
|
| 80 |
+
hs = [im.shape[0] for im in imgs]
|
| 81 |
+
h = max(hs)
|
| 82 |
+
|
| 83 |
+
# 统一高度
|
| 84 |
+
resized = []
|
| 85 |
+
for im in imgs:
|
| 86 |
+
hh, ww = im.shape[:2]
|
| 87 |
+
if hh != h:
|
| 88 |
+
im = cv2.resize(im, (int(ww * h / hh), h), interpolation=cv2.INTER_LINEAR)
|
| 89 |
+
resized.append(im)
|
| 90 |
+
|
| 91 |
+
ws = [im.shape[1] for im in resized]
|
| 92 |
+
total_w = sum(ws)
|
| 93 |
+
label_h = max(30, h // 25)
|
| 94 |
+
canvas = np.full((h + label_h, total_w, 3), 255, dtype=np.uint8)
|
| 95 |
+
|
| 96 |
+
x = 0
|
| 97 |
+
for idx, im in enumerate(resized):
|
| 98 |
+
canvas[label_h:, x:x + ws[idx]] = im
|
| 99 |
+
x += ws[idx]
|
| 100 |
+
|
| 101 |
+
# 标注文字
|
| 102 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 103 |
+
font_scale = label_h / 30.0
|
| 104 |
+
thickness = max(1, int(font_scale))
|
| 105 |
+
color = (0, 0, 0)
|
| 106 |
+
|
| 107 |
+
x = 0
|
| 108 |
+
for idx, (label, w) in enumerate(zip(labels, ws)):
|
| 109 |
+
(tw, th), _ = cv2.getTextSize(label, font, font_scale, thickness)
|
| 110 |
+
cv2.putText(canvas, label, (x + w // 2 - tw // 2, label_h - (label_h - th) // 2),
|
| 111 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 112 |
+
x += w
|
| 113 |
+
|
| 114 |
+
return canvas
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def main():
|
| 118 |
+
if len(sys.argv) < 2:
|
| 119 |
+
print(f"用法: python {os.path.basename(__file__)} <图像路径>")
|
| 120 |
+
print(f"默认 ONNX 模型: {ONNX_PATH}")
|
| 121 |
+
print(f"噪声 sigma: {NOISE_SIGMA}")
|
| 122 |
+
print(f"输出目录: {OUTPUT_DIR}")
|
| 123 |
+
sys.exit(1)
|
| 124 |
+
|
| 125 |
+
img_path = sys.argv[1]
|
| 126 |
+
if not os.path.isfile(img_path):
|
| 127 |
+
print(f"错误: 图像不存在: {img_path}")
|
| 128 |
+
sys.exit(1)
|
| 129 |
+
if not os.path.isfile(ONNX_PATH):
|
| 130 |
+
print(f"错误: ONNX 模型不存在: {ONNX_PATH}")
|
| 131 |
+
print(f"请修改脚本顶��� ONNX_PATH 常量,或先导出 ONNX 模型。")
|
| 132 |
+
sys.exit(1)
|
| 133 |
+
|
| 134 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 135 |
+
|
| 136 |
+
# ---- 加载 ONNX 模型 ----
|
| 137 |
+
session = ort.InferenceSession(ONNX_PATH, providers=PROVIDERS)
|
| 138 |
+
input_name = session.get_inputs()[0].name
|
| 139 |
+
_, _, model_h, model_w = session.get_inputs()[0].shape
|
| 140 |
+
|
| 141 |
+
# ---- 读取原图 ----
|
| 142 |
+
img_bgr = load_image(img_path)
|
| 143 |
+
|
| 144 |
+
# ---- 加噪 ----
|
| 145 |
+
if NOISE_SIGMA > 0:
|
| 146 |
+
noisy_bgr = add_awgn(img_bgr, NOISE_SIGMA)
|
| 147 |
+
else:
|
| 148 |
+
noisy_bgr = img_bgr
|
| 149 |
+
|
| 150 |
+
# ---- 预处理(对加噪图) ----
|
| 151 |
+
tensor, (orig_h, orig_w) = preprocess(noisy_bgr, model_h, model_w)
|
| 152 |
+
|
| 153 |
+
# ---- ONNX 推理 ----
|
| 154 |
+
if USE_FIXED_SIGMA:
|
| 155 |
+
result = session.run(None, {input_name: tensor})[0]
|
| 156 |
+
else:
|
| 157 |
+
sigma_tensor = np.full((1, 1, 1, 1), MODEL_SIGMA / 255.0, dtype=np.float32)
|
| 158 |
+
sigma_name = session.get_inputs()[1].name
|
| 159 |
+
result = session.run(None, {input_name: tensor, sigma_name: sigma_tensor})[0]
|
| 160 |
+
|
| 161 |
+
# ---- 后处理 & 还原原始尺寸 ----
|
| 162 |
+
result_bgr = postprocess(result, orig_h, orig_w)
|
| 163 |
+
|
| 164 |
+
# ---- 三图拼接输出 ----
|
| 165 |
+
label_right = f"FFDNet ONNX (sigma={MODEL_SIGMA})" if not USE_FIXED_SIGMA else "FFDNet ONNX"
|
| 166 |
+
label_mid = f"Noisy (sigma={NOISE_SIGMA})" if NOISE_SIGMA > 0 else "Input"
|
| 167 |
+
concat = make_concat(img_bgr, noisy_bgr, result_bgr,
|
| 168 |
+
label_mid=label_mid, label_right=label_right)
|
| 169 |
+
|
| 170 |
+
name = os.path.splitext(os.path.basename(img_path))[0]
|
| 171 |
+
out_path = os.path.join(OUTPUT_DIR, f"{name}_concat.png")
|
| 172 |
+
cv2.imwrite(out_path, concat)
|
| 173 |
+
print(f"输出已保存: {out_path}")
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
if __name__ == "__main__":
|
| 177 |
+
main()
|
FFDNet/res/axmodel_res.png
ADDED
|
Git LFS Details
|
NAFNet/demo/noisy.png
ADDED
|
Git LFS Details
|
NAFNet/model_convert/NAFNet.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"input": "./NAFNet-SIDD-width64-256x256.onnx",
|
| 3 |
+
"output_dir": "./NAFNet",
|
| 4 |
+
"output_name": "NAFNet_1_3_256_256.axmodel",
|
| 5 |
+
"model_type": "ONNX",
|
| 6 |
+
"target_hardware": "AX650",
|
| 7 |
+
"npu_mode": "NPU3",
|
| 8 |
+
"quant": {
|
| 9 |
+
"input_configs": [
|
| 10 |
+
{
|
| 11 |
+
"tensor_name": "input",
|
| 12 |
+
"calibration_dataset": "./SIDD.tar.gz",
|
| 13 |
+
"calibration_size": 32,
|
| 14 |
+
"calibration_mean": [0.0, 0.0, 0.0],
|
| 15 |
+
"calibration_std": [255.0, 255.0, 255.0]
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"calibration_method": "MinMax",
|
| 19 |
+
"precision_analysis": true,
|
| 20 |
+
"transformer_opt_level":1,
|
| 21 |
+
// "enable_smooth_quant": true,
|
| 22 |
+
"precision_analysis_method": "EndToEnd",
|
| 23 |
+
"precision_analysis_mode": "NPUBackend",
|
| 24 |
+
"device": "cuda:1",
|
| 25 |
+
// "layer_configs": [
|
| 26 |
+
// {
|
| 27 |
+
// "start_tensor_names": ["DEFAULT"],
|
| 28 |
+
// "end_tensor_names": ["DEFAULT"],
|
| 29 |
+
// "data_type": "U16"
|
| 30 |
+
// }
|
| 31 |
+
// ]
|
| 32 |
+
},
|
| 33 |
+
"input_processors": [
|
| 34 |
+
{
|
| 35 |
+
"tensor_name": "input",
|
| 36 |
+
"tensor_format": "RGB",
|
| 37 |
+
"tensor_layout": "NCHW",
|
| 38 |
+
"src_format": "BGR",
|
| 39 |
+
"src_dtype": "U8",
|
| 40 |
+
"src_layout": "NCHW",
|
| 41 |
+
"csc_mode": "NoCSC"
|
| 42 |
+
}
|
| 43 |
+
// {
|
| 44 |
+
// "tensor_name": "input",
|
| 45 |
+
// "tensor_format": "RGB",
|
| 46 |
+
// "tensor_layout": "NCHW",
|
| 47 |
+
// "src_format": "YUV420SP",
|
| 48 |
+
// "src_layout": "NHWC",
|
| 49 |
+
// "src_dtype": "U8",
|
| 50 |
+
// "csc_mode": "FullRange",
|
| 51 |
+
// "csc_mat": [1.164, 0, 1.596, -222.912, 1.164, -0.392, -0.813, 135.616, 1.164, 2.017, 0, -276.8]
|
| 52 |
+
// }
|
| 53 |
+
],
|
| 54 |
+
"compiler": {
|
| 55 |
+
"check": 2
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
NAFNet/model_convert/axmodel/NAFNet_1_3_256_256.axmodel
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:486ca86f7cf099d827ae9c468821287843cd804a1a649a804d5c7d3e71107ac8
|
| 3 |
+
size 134584082
|
NAFNet/python/axmodel_infer.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Single-image NAFNet axmodel inference with side-by-side comparison output."""
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
import cv2
|
| 7 |
+
import numpy as np
|
| 8 |
+
import axengine as axe
|
| 9 |
+
|
| 10 |
+
# ============================================================
|
| 11 |
+
# 默认参数(按需修改)
|
| 12 |
+
# ============================================================
|
| 13 |
+
AXMODEL_PATH = 'NAFNet_1_3_256_256.axmodel'
|
| 14 |
+
INPUT_PATH = 'demo/noisy.png'
|
| 15 |
+
OUTPUT_PATH = 'axmodel_compare.png'
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def read_image(path):
|
| 19 |
+
bgr = cv2.imread(path, cv2.IMREAD_COLOR)
|
| 20 |
+
if bgr is None:
|
| 21 |
+
raise FileNotFoundError(f'Cannot read image: {path}')
|
| 22 |
+
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB).astype(np.uint8)
|
| 23 |
+
tensor = np.transpose(rgb, (2, 0, 1))[None, ...]
|
| 24 |
+
return tensor, bgr
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def save_comparison(orig_bgr, output_tensor, path):
|
| 28 |
+
"""拼接原图与结果图并标注文字,输出图还原到原图大小后再拼接"""
|
| 29 |
+
output = np.squeeze(output_tensor, axis=0)
|
| 30 |
+
output = np.clip(output, 0.0, 1.0)
|
| 31 |
+
output_rgb = np.transpose(output, (1, 2, 0))
|
| 32 |
+
output_bgr = cv2.cvtColor((output_rgb * 255.0).round().astype(np.uint8), cv2.COLOR_RGB2BGR)
|
| 33 |
+
|
| 34 |
+
h, w = orig_bgr.shape[:2]
|
| 35 |
+
if output_bgr.shape[:2] != (h, w):
|
| 36 |
+
output_bgr = cv2.resize(output_bgr, (w, h), interpolation=cv2.INTER_LINEAR)
|
| 37 |
+
|
| 38 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 39 |
+
font_scale = max(h, w) / 512.0
|
| 40 |
+
thickness = max(1, int(font_scale * 2))
|
| 41 |
+
color = (255, 255, 255)
|
| 42 |
+
|
| 43 |
+
cv2.putText(orig_bgr, 'noisy', (int(10 * font_scale), int(30 * font_scale)),
|
| 44 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 45 |
+
cv2.putText(output_bgr, 'denoised', (int(10 * font_scale), int(30 * font_scale)),
|
| 46 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 47 |
+
|
| 48 |
+
compare = np.concatenate([orig_bgr, output_bgr], axis=1)
|
| 49 |
+
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
| 50 |
+
cv2.imwrite(path, compare)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def main():
|
| 54 |
+
inp, orig_bgr = read_image(INPUT_PATH)
|
| 55 |
+
|
| 56 |
+
session = axe.InferenceSession(AXMODEL_PATH, providers=['AxEngineExecutionProvider'])
|
| 57 |
+
input_name = session.get_inputs()[0].name
|
| 58 |
+
output_name = session.get_outputs()[0].name
|
| 59 |
+
expected_shape = session.get_inputs()[0].shape
|
| 60 |
+
if list(inp.shape) != expected_shape:
|
| 61 |
+
raise ValueError(f'Input shape {list(inp.shape)} does not match fixed axmodel shape {expected_shape}.')
|
| 62 |
+
|
| 63 |
+
out = session.run([output_name], {input_name: inp})[0]
|
| 64 |
+
save_comparison(orig_bgr, out, OUTPUT_PATH)
|
| 65 |
+
print(f'axmodel inference finished: {OUTPUT_PATH}')
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
if __name__ == '__main__':
|
| 69 |
+
main()
|
NAFNet/python/onnx_infer.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Single-image NAFNet ONNX inference with side-by-side comparison output."""
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
import cv2
|
| 7 |
+
import numpy as np
|
| 8 |
+
import onnxruntime as ort
|
| 9 |
+
|
| 10 |
+
# ============================================================
|
| 11 |
+
# 默认参数(按需修改)
|
| 12 |
+
# ============================================================
|
| 13 |
+
ONNX_PATH = 'experiments/onnx/NAFNet-SIDD-width64-256x256.onnx'
|
| 14 |
+
INPUT_PATH = 'demo/noisy.png'
|
| 15 |
+
OUTPUT_PATH = 'demo/denoise_onnx_compare.png'
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def read_image(path):
|
| 19 |
+
bgr = cv2.imread(path, cv2.IMREAD_COLOR)
|
| 20 |
+
if bgr is None:
|
| 21 |
+
raise FileNotFoundError(f'Cannot read image: {path}')
|
| 22 |
+
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
|
| 23 |
+
tensor = np.transpose(rgb, (2, 0, 1))[None, ...]
|
| 24 |
+
return tensor, bgr
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def save_comparison(orig_bgr, output_tensor, path):
|
| 28 |
+
"""拼接原图与结果图并标注文字,输出图还原到原图大小后再拼接"""
|
| 29 |
+
output = np.squeeze(output_tensor, axis=0)
|
| 30 |
+
output = np.clip(output, 0.0, 1.0)
|
| 31 |
+
output_rgb = np.transpose(output, (1, 2, 0))
|
| 32 |
+
output_bgr = cv2.cvtColor((output_rgb * 255.0).round().astype(np.uint8), cv2.COLOR_RGB2BGR)
|
| 33 |
+
|
| 34 |
+
h, w = orig_bgr.shape[:2]
|
| 35 |
+
if output_bgr.shape[:2] != (h, w):
|
| 36 |
+
output_bgr = cv2.resize(output_bgr, (w, h), interpolation=cv2.INTER_LINEAR)
|
| 37 |
+
|
| 38 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 39 |
+
font_scale = max(h, w) / 512.0
|
| 40 |
+
thickness = max(1, int(font_scale * 2))
|
| 41 |
+
color = (255, 255, 255)
|
| 42 |
+
|
| 43 |
+
cv2.putText(orig_bgr, 'input', (int(10 * font_scale), int(30 * font_scale)),
|
| 44 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 45 |
+
cv2.putText(output_bgr, 'onnx', (int(10 * font_scale), int(30 * font_scale)),
|
| 46 |
+
font, font_scale, color, thickness, cv2.LINE_AA)
|
| 47 |
+
|
| 48 |
+
compare = np.concatenate([orig_bgr, output_bgr], axis=1)
|
| 49 |
+
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
| 50 |
+
cv2.imwrite(path, compare)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def main():
|
| 54 |
+
inp, orig_bgr = read_image(INPUT_PATH)
|
| 55 |
+
|
| 56 |
+
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if ort.get_device() == 'GPU' else ['CPUExecutionProvider']
|
| 57 |
+
session = ort.InferenceSession(ONNX_PATH, providers=providers)
|
| 58 |
+
input_name = session.get_inputs()[0].name
|
| 59 |
+
output_name = session.get_outputs()[0].name
|
| 60 |
+
expected_shape = session.get_inputs()[0].shape
|
| 61 |
+
if list(inp.shape) != expected_shape:
|
| 62 |
+
raise ValueError(f'Input shape {list(inp.shape)} does not match fixed ONNX shape {expected_shape}.')
|
| 63 |
+
|
| 64 |
+
out = session.run([output_name], {input_name: inp.astype(np.float32)})[0]
|
| 65 |
+
save_comparison(orig_bgr, out, OUTPUT_PATH)
|
| 66 |
+
print(f'ONNX inference finished: {OUTPUT_PATH}')
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
if __name__ == '__main__':
|
| 70 |
+
main()
|
NAFNet/res/axmodel_compare.png
ADDED
|
Git LFS Details
|
README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
pipeline_tag: image-to-image
|
| 6 |
+
tags:
|
| 7 |
+
- denosing
|
| 8 |
+
- ImageEnhancement
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# ImageDenosing
|
| 12 |
+
|
| 13 |
+
This is a collection of image denosing algorithms, models have been converted to run on the Axera NPU using **w8a8** quantization.
|
| 14 |
+
|
| 15 |
+
This model has been optimized with the following LoRA:
|
| 16 |
+
|
| 17 |
+
Compatible with Pulsar2 version: 6.0 115775d3
|
| 18 |
+
|
| 19 |
+
## Convert tools links:
|
| 20 |
+
|
| 21 |
+
For those who are interested in model conversion, you can try to export axmodel through
|
| 22 |
+
|
| 23 |
+
- [Pulsar2 Link, How to Convert ONNX to axmodel](https://pulsar2-docs.readthedocs.io/en/latest/pulsar2/introduction.html)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
## Support Platform
|
| 27 |
+
|
| 28 |
+
- AX650
|
| 29 |
+
- [M4N-Dock(爱芯派Pro)](https://wiki.sipeed.com/hardware/zh/maixIV/m4ndock/m4ndock.html)
|
| 30 |
+
- [M.2 Accelerator card](https://docs.m5stack.com/en/ai_hardware/LLM-8850_Card)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
## 性能基准测试 (Performance Benchmark)
|
| 34 |
+
|
| 35 |
+
| 模型 | 输入分辨率 | AX650板端耗时 |
|
| 36 |
+
|------|-----------|--------------|
|
| 37 |
+
| NAFNet | 256×256 | 87ms |
|
| 38 |
+
| DnCNN | 416×416 | 22ms |
|
| 39 |
+
| FFDNet | 640×640 | 19.3ms |
|
| 40 |
+
| FastDVDnet | 640×480 | 61.2ms |
|
| 41 |
+
|
| 42 |
+
## How to use
|
| 43 |
+
|
| 44 |
+
Download all files from this repository to the device
|
| 45 |
+
|
| 46 |
+
```
|
| 47 |
+
模型文件组织方式如下:
|
| 48 |
+
.
|
| 49 |
+
|-- demo
|
| 50 |
+
| `-- noisy.png
|
| 51 |
+
|-- model_convert
|
| 52 |
+
| |-- NAFNet.json
|
| 53 |
+
| `-- axmodel
|
| 54 |
+
| `-- NAFNet_1_3_256_256.axmodel
|
| 55 |
+
|-- python
|
| 56 |
+
| |-- axmodel_infer.py
|
| 57 |
+
| `-- onnx_infer.py
|
| 58 |
+
`-- res
|
| 59 |
+
`-- axmodel_compare.png
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### Inference
|
| 65 |
+
#### Image Denoising
|
| 66 |
+
|
| 67 |
+
图片推理,执行命令`python3 axmodel_infer.py`:
|
| 68 |
+
```
|
| 69 |
+
(base) root@ax650:~/NAFNet# python3 axmodel_infer.py
|
| 70 |
+
[INFO] Available providers: ['AxEngineExecutionProvider', 'AXCLRTExecutionProvider']
|
| 71 |
+
[INFO] Using provider: AxEngineExecutionProvider
|
| 72 |
+
[INFO] Chip type: ChipType.MC50
|
| 73 |
+
[INFO] VNPU type: VNPUType.DISABLED
|
| 74 |
+
[INFO] Engine version: 2.12.0s
|
| 75 |
+
[INFO] Model type: 2 (triple core)
|
| 76 |
+
[INFO] Compiler version: 6.0 115775d3
|
| 77 |
+
axmodel inference finished: axmodel_compare.png
|
| 78 |
+
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
推理结果样例:
|
| 82 |
+

|
| 83 |
+
|
| 84 |
+
#### Video Denoising
|
| 85 |
+
|
| 86 |
+
视频推理,执行命令`python3 axmodel_video_infer.py`:
|
| 87 |
+
```
|
| 88 |
+
(base) root@ax650:~/fastdvdnet# python3 axmodel_video_infer.py
|
| 89 |
+
[INFO] Available providers: ['AxEngineExecutionProvider', 'AXCLRTExecutionProvider']
|
| 90 |
+
[INFO] Using provider: AxEngineExecutionProvider
|
| 91 |
+
[INFO] Chip type: ChipType.MC50
|
| 92 |
+
[INFO] VNPU type: VNPUType.DISABLED
|
| 93 |
+
[INFO] Engine version: 2.12.0s
|
| 94 |
+
[INFO] Model type: 2 (triple core)
|
| 95 |
+
[INFO] Compiler version: 6.0 115775d3
|
| 96 |
+
providers: AxEngineExecutionProvider
|
| 97 |
+
inputs: [('noisy', [1, 15, 480, 640], 'float32'), ('noise_map', [1, 1, 480, 640], 'float32')]
|
| 98 |
+
outputs: [('denoised', [1, 3, 480, 640])]
|
| 99 |
+
video: 854x480 @ 25.0fps, 67 frames
|
| 100 |
+
axmodel input: 640x480
|
| 101 |
+
[OK] ./people-sunset.mp4 -> ./people-sunset_axmodel_side_by_side.mp4
|
| 102 |
+
frames=67 time=17.57s fps=3.81
|
| 103 |
+
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
视频降噪样例:
|
| 107 |
+
<video controls src="fastDVDnet/res/people-sunset_axmodel_side_by_side.mp4" title="Title"></video>
|
config.json
ADDED
|
File without changes
|
fastDVDnet/model_quant/fastDVDnet.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 临时跑通版,真实需要拼接图做15通道输入
|
| 2 |
+
{
|
| 3 |
+
"input": "./fastdvdnet_640x480.onnx",
|
| 4 |
+
"output_dir": "./fastdvdnet",
|
| 5 |
+
"output_name": "fastdvdnet_640x480.axmodel",
|
| 6 |
+
"model_type": "ONNX",
|
| 7 |
+
"target_hardware": "AX650",
|
| 8 |
+
"npu_mode": "NPU3",
|
| 9 |
+
"quant": {
|
| 10 |
+
"input_configs": [
|
| 11 |
+
{
|
| 12 |
+
"tensor_name": "noisy",
|
| 13 |
+
"calibration_dataset": "fastdvdnet.tar.gz",
|
| 14 |
+
"calibration_format": "Numpy",
|
| 15 |
+
"calibration_size": 5,
|
| 16 |
+
"calibration_mean": [0.0],
|
| 17 |
+
"calibration_std": [1.0]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"tensor_name": "noise_map",
|
| 21 |
+
"calibration_dataset": "./fastDVD_noise_map.tar.gz",
|
| 22 |
+
"calibration_format": "Numpy",
|
| 23 |
+
"calibration_size": 5,
|
| 24 |
+
"calibration_mean": [0.0],
|
| 25 |
+
"calibration_std": [1.0]
|
| 26 |
+
}
|
| 27 |
+
],
|
| 28 |
+
"calibration_method": "MinMax",
|
| 29 |
+
"precision_analysis": true,
|
| 30 |
+
"transformer_opt_level": 1,
|
| 31 |
+
"precision_analysis_method": "EndToEnd",
|
| 32 |
+
"precision_analysis_mode": "NPUBackend",
|
| 33 |
+
"device": "cuda:1"
|
| 34 |
+
},
|
| 35 |
+
"input_processors": [
|
| 36 |
+
{
|
| 37 |
+
"tensor_name": "noisy",
|
| 38 |
+
"tensor_layout": "NCHW",
|
| 39 |
+
"src_dtype": "FP32",
|
| 40 |
+
"src_layout": "NCHW",
|
| 41 |
+
"csc_mode": "NoCSC",
|
| 42 |
+
"mean": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
| 43 |
+
"std": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"tensor_name": "noise_map",
|
| 47 |
+
"tensor_layout": "NCHW",
|
| 48 |
+
"src_dtype": "FP32",
|
| 49 |
+
"src_layout": "NCHW",
|
| 50 |
+
"csc_mode": "NoCSC",
|
| 51 |
+
"mean": [0.0],
|
| 52 |
+
"std": [1.0]
|
| 53 |
+
}
|
| 54 |
+
],
|
| 55 |
+
"compiler": {
|
| 56 |
+
"check": 2
|
| 57 |
+
}
|
| 58 |
+
}
|
fastDVDnet/python/axmodel_video_infer.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""FastDVDnet axmodel video inference, side-by-side output.
|
| 3 |
+
Input: mp4 video -> resize to axmodel fixed input -> 5-frame window -> axmodel -> output video.
|
| 4 |
+
Input dtype is inferred from the axmodel session so both float and uint8 models work."""
|
| 5 |
+
import argparse, os, time
|
| 6 |
+
import cv2, numpy as np
|
| 7 |
+
import axengine as axe
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _resolve_numpy_dtype(dtype, fallback):
|
| 11 |
+
try:
|
| 12 |
+
return np.dtype(dtype)
|
| 13 |
+
except TypeError:
|
| 14 |
+
name = str(dtype).lower()
|
| 15 |
+
if "float16" in name or "fp16" in name:
|
| 16 |
+
return np.dtype(np.float16)
|
| 17 |
+
if "float" in name:
|
| 18 |
+
return np.dtype(np.float32)
|
| 19 |
+
if "uint8" in name:
|
| 20 |
+
return np.dtype(np.uint8)
|
| 21 |
+
if "int8" in name:
|
| 22 |
+
return np.dtype(np.int8)
|
| 23 |
+
return np.dtype(fallback)
|
| 24 |
+
|
| 25 |
+
def read_video(video_path, max_frames=0):
|
| 26 |
+
cap=cv2.VideoCapture(video_path)
|
| 27 |
+
if not cap.isOpened(): raise RuntimeError('open fail: '+video_path)
|
| 28 |
+
fps=cap.get(cv2.CAP_PROP_FPS) or 25.0
|
| 29 |
+
ow=int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)); oh=int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 30 |
+
frames=[]
|
| 31 |
+
while True:
|
| 32 |
+
ok,f=cap.read()
|
| 33 |
+
if not ok: break
|
| 34 |
+
frames.append(f)
|
| 35 |
+
if max_frames and len(frames)>=max_frames: break
|
| 36 |
+
cap.release()
|
| 37 |
+
if not frames: raise RuntimeError('no frames')
|
| 38 |
+
return frames,fps,ow,oh
|
| 39 |
+
|
| 40 |
+
def reflect_index(idx,length):
|
| 41 |
+
if length<=1: return 0
|
| 42 |
+
while idx<0 or idx>=length:
|
| 43 |
+
if idx<0: idx=-idx
|
| 44 |
+
if idx>=length: idx=2*(length-1)-idx
|
| 45 |
+
return idx
|
| 46 |
+
|
| 47 |
+
def bgr_to_ax_input(fb, ah, aw, noisy_dtype):
|
| 48 |
+
r = cv2.resize(fb, (aw, ah), interpolation=cv2.INTER_AREA)
|
| 49 |
+
r = cv2.cvtColor(r, cv2.COLOR_BGR2RGB)
|
| 50 |
+
chw = r.transpose(2, 0, 1)
|
| 51 |
+
noisy_dtype = _resolve_numpy_dtype(noisy_dtype, np.float32)
|
| 52 |
+
if np.issubdtype(noisy_dtype, np.floating):
|
| 53 |
+
return (chw.astype(np.float32) / 255.0).astype(noisy_dtype, copy=False)
|
| 54 |
+
return chw.astype(noisy_dtype, copy=False)
|
| 55 |
+
|
| 56 |
+
def chw_float_to_bgr_u8(chw,tw,th):
|
| 57 |
+
hwc=(chw*255.).clip(0,255).astype(np.uint8).transpose(1,2,0)
|
| 58 |
+
bgr=cv2.cvtColor(hwc,cv2.COLOR_RGB2BGR)
|
| 59 |
+
if bgr.shape[1]!=tw or bgr.shape[0]!=th:
|
| 60 |
+
bgr=cv2.resize(bgr,(tw,th),interpolation=cv2.INTER_LINEAR)
|
| 61 |
+
return bgr
|
| 62 |
+
|
| 63 |
+
def denoise_axmodel(frames_bgr, noise_sigma_01, sess, ax_h, ax_w,
|
| 64 |
+
input_names, output_names, noisy_dtype, noise_map_dtype):
|
| 65 |
+
numframes = len(frames_bgr)
|
| 66 |
+
temp_psz, ctrl = 5, 2
|
| 67 |
+
|
| 68 |
+
chw_cache = {}
|
| 69 |
+
def get_chw(i):
|
| 70 |
+
i = i % numframes
|
| 71 |
+
if i not in chw_cache:
|
| 72 |
+
chw_cache[i] = bgr_to_ax_input(
|
| 73 |
+
frames_bgr[reflect_index(i, numframes)], ax_h, ax_w, noisy_dtype)
|
| 74 |
+
return chw_cache[i]
|
| 75 |
+
|
| 76 |
+
den_frames_bgr = []
|
| 77 |
+
inframes = []
|
| 78 |
+
|
| 79 |
+
for fridx in range(numframes):
|
| 80 |
+
if not inframes:
|
| 81 |
+
for off in range(temp_psz):
|
| 82 |
+
inframes.append(get_chw(fridx + off - ctrl))
|
| 83 |
+
else:
|
| 84 |
+
del inframes[0]
|
| 85 |
+
inframes.append(get_chw(fridx + ctrl))
|
| 86 |
+
|
| 87 |
+
noisy = np.concatenate(inframes, axis=0)[None, :, :, :]
|
| 88 |
+
noise_map = np.full(
|
| 89 |
+
(1, 1, ax_h, ax_w),
|
| 90 |
+
noise_sigma_01,
|
| 91 |
+
dtype=_resolve_numpy_dtype(noise_map_dtype, np.float32),
|
| 92 |
+
)
|
| 93 |
+
feeds = {input_names[0]: noisy, input_names[1]: noise_map}
|
| 94 |
+
out = sess.run(output_names, feeds)[0]
|
| 95 |
+
out = np.clip(out, 0.0, 1.0)
|
| 96 |
+
den_bgr = chw_float_to_bgr_u8(
|
| 97 |
+
out[0], frames_bgr[0].shape[1], frames_bgr[0].shape[0])
|
| 98 |
+
den_frames_bgr.append(den_bgr)
|
| 99 |
+
|
| 100 |
+
return den_frames_bgr
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def write_side_by_side(frames_orig, frames_den, out_path, fps, label=True):
|
| 104 |
+
h, w = frames_orig[0].shape[:2]
|
| 105 |
+
os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
|
| 106 |
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
| 107 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (w * 2, h))
|
| 108 |
+
if not writer.isOpened():
|
| 109 |
+
raise RuntimeError("video writer fail: {}".format(out_path))
|
| 110 |
+
for orig, den in zip(frames_orig, frames_den):
|
| 111 |
+
canvas = np.concatenate([orig, den], axis=1)
|
| 112 |
+
if label:
|
| 113 |
+
cv2.putText(canvas, "Original", (16, 34), cv2.FONT_HERSHEY_SIMPLEX,
|
| 114 |
+
1.0, (0, 255, 255), 2, cv2.LINE_AA)
|
| 115 |
+
cv2.putText(canvas, "AXModel Denoised", (w + 16, 34), cv2.FONT_HERSHEY_SIMPLEX,
|
| 116 |
+
1.0, (0, 255, 255), 2, cv2.LINE_AA)
|
| 117 |
+
writer.write(canvas)
|
| 118 |
+
writer.release()
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def main():
|
| 122 |
+
parser = argparse.ArgumentParser(description="FastDVDnet axmodel video inference")
|
| 123 |
+
parser.add_argument("--axmodel", type=str, default="./fastdvdnet_640x480.axmodel")
|
| 124 |
+
parser.add_argument("--video", type=str, default='./people-sunset.mp4', help="input mp4 video")
|
| 125 |
+
parser.add_argument("--noise_sigma", type=float, default=25.0, help="0-255")
|
| 126 |
+
parser.add_argument("--out_dir", type=str, default="./")
|
| 127 |
+
parser.add_argument("--max_frames", type=int, default=0, help="0=all")
|
| 128 |
+
parser.add_argument("--no_label", action="store_true")
|
| 129 |
+
args = parser.parse_args()
|
| 130 |
+
|
| 131 |
+
sess = axe.InferenceSession(args.axmodel, providers=["AxEngineExecutionProvider"])
|
| 132 |
+
inputs = sess.get_inputs()
|
| 133 |
+
input_names = [i.name for i in inputs]
|
| 134 |
+
output_names = [o.name for o in sess.get_outputs()]
|
| 135 |
+
noisy_dtype = getattr(inputs[0], "dtype", np.float32)
|
| 136 |
+
noise_map_dtype = getattr(inputs[1], "dtype", np.float32)
|
| 137 |
+
ax_h = inputs[0].shape[2]
|
| 138 |
+
ax_w = inputs[0].shape[3]
|
| 139 |
+
|
| 140 |
+
print("providers:", sess.get_providers())
|
| 141 |
+
print("inputs:", [(n, list(i.shape), str(getattr(i, "dtype", "unknown"))) for n, i in zip(input_names, inputs)])
|
| 142 |
+
print("outputs:", [(n, list(o.shape)) for n, o in zip(output_names, sess.get_outputs())])
|
| 143 |
+
|
| 144 |
+
base = os.path.splitext(os.path.basename(args.video))[0]
|
| 145 |
+
out_path = os.path.join(args.out_dir, base + "_axmodel_side_by_side.mp4")
|
| 146 |
+
|
| 147 |
+
t0 = time.time()
|
| 148 |
+
frames_bgr, fps, orig_w, orig_h = read_video(args.video, args.max_frames)
|
| 149 |
+
print("video: {}x{} @ {:.1f}fps, {} frames".format(orig_w, orig_h, fps, len(frames_bgr)))
|
| 150 |
+
print("axmodel input: {}x{}".format(ax_w, ax_h))
|
| 151 |
+
|
| 152 |
+
sigma01 = args.noise_sigma / 255.0
|
| 153 |
+
den = denoise_axmodel(
|
| 154 |
+
frames_bgr, sigma01, sess, ax_h, ax_w,
|
| 155 |
+
input_names, output_names, noisy_dtype, noise_map_dtype,
|
| 156 |
+
)
|
| 157 |
+
write_side_by_side(frames_bgr, den, out_path, fps, label=not args.no_label)
|
| 158 |
+
dt = time.time() - t0
|
| 159 |
+
|
| 160 |
+
print("[OK] {} -> {}".format(args.video, out_path))
|
| 161 |
+
print(" frames={} time={:.2f}s fps={:.2f}".format(
|
| 162 |
+
len(frames_bgr), dt, len(frames_bgr) / max(dt, 0.001)))
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
if __name__ == "__main__":
|
| 166 |
+
main()
|
fastDVDnet/python/onnx_video_infer.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""
|
| 3 |
+
FastDVDnet ONNX 视频推理脚本,输出 原帧 | ONNX 去噪结果 拼接视频。
|
| 4 |
+
|
| 5 |
+
输入视频逐帧 resize 到 ONNX 固定输入尺寸 (HxW),组成 5 帧窗口
|
| 6 |
+
[t-2,t-1,t,t+1,t+2] 推理,输出 resize 回原始尺寸生成拼接视频。
|
| 7 |
+
|
| 8 |
+
用法:
|
| 9 |
+
python onnx_video_infer.py \
|
| 10 |
+
--onnx ./fastdvdnet_640x480.onnx \
|
| 11 |
+
--video mp4/drone.mp4 \
|
| 12 |
+
--noise_sigma 25 \
|
| 13 |
+
--out_dir ./video_infer_results/onnx_test
|
| 14 |
+
"""
|
| 15 |
+
import argparse
|
| 16 |
+
import os
|
| 17 |
+
import time
|
| 18 |
+
|
| 19 |
+
import cv2
|
| 20 |
+
import numpy as np
|
| 21 |
+
import onnxruntime as ort
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def get_onnx_shapes(onnx_path):
|
| 25 |
+
import onnx
|
| 26 |
+
m = onnx.load(onnx_path)
|
| 27 |
+
x_dims = [d.dim_value for d in m.graph.input[0].type.tensor_type.shape.dim]
|
| 28 |
+
return x_dims # [N, 15, H, W]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def read_video(video_path, max_frames=0):
|
| 32 |
+
cap = cv2.VideoCapture(video_path)
|
| 33 |
+
if not cap.isOpened():
|
| 34 |
+
raise RuntimeError("failed to open video: {}".format(video_path))
|
| 35 |
+
fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
|
| 36 |
+
orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 37 |
+
orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 38 |
+
frames_bgr = []
|
| 39 |
+
while True:
|
| 40 |
+
ok, frame = cap.read()
|
| 41 |
+
if not ok:
|
| 42 |
+
break
|
| 43 |
+
frames_bgr.append(frame)
|
| 44 |
+
if max_frames and len(frames_bgr) >= max_frames:
|
| 45 |
+
break
|
| 46 |
+
cap.release()
|
| 47 |
+
if not frames_bgr:
|
| 48 |
+
raise RuntimeError("no frames read from {}".format(video_path))
|
| 49 |
+
return frames_bgr, fps, orig_w, orig_h
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def reflect_index(idx, length):
|
| 53 |
+
if length <= 1:
|
| 54 |
+
return 0
|
| 55 |
+
while idx < 0 or idx >= length:
|
| 56 |
+
if idx < 0:
|
| 57 |
+
idx = -idx
|
| 58 |
+
if idx >= length:
|
| 59 |
+
idx = 2 * (length - 1) - idx
|
| 60 |
+
return idx
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def bgr_to_onnx_input(frame_bgr, onnx_h, onnx_w):
|
| 64 |
+
"""resize BGR uint8 to ONNX RGB float32 [1,3,H,W] in [0,1]"""
|
| 65 |
+
resized = cv2.resize(frame_bgr, (onnx_w, onnx_h), interpolation=cv2.INTER_AREA)
|
| 66 |
+
rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
|
| 67 |
+
chw = rgb.astype(np.float32).transpose(2, 0, 1) / 255.0
|
| 68 |
+
return chw # [3, H, W]
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def chw_to_bgr_uint8(chw, target_w, target_h):
|
| 72 |
+
"""[3,H,W] float32 in [0,1] -> BGR uint8 resized to target_w x target_h"""
|
| 73 |
+
hwc = (chw * 255.0).clip(0, 255).astype(np.uint8).transpose(1, 2, 0)
|
| 74 |
+
rgb = hwc # already RGB from ONNX output
|
| 75 |
+
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
|
| 76 |
+
if bgr.shape[1] != target_w or bgr.shape[0] != target_h:
|
| 77 |
+
bgr = cv2.resize(bgr, (target_w, target_h), interpolation=cv2.INTER_LINEAR)
|
| 78 |
+
return bgr
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def denoise_onnx(frames_bgr, noise_sigma_01, sess, x_shape, input_names, output_names):
|
| 82 |
+
numframes = len(frames_bgr)
|
| 83 |
+
_, _, onnx_h, onnx_w = x_shape
|
| 84 |
+
temp_psz, ctrl = 5, 2
|
| 85 |
+
|
| 86 |
+
# 缓存 resize 后的 CHW 帧
|
| 87 |
+
chw_cache = {}
|
| 88 |
+
def get_chw(i):
|
| 89 |
+
i = i % numframes
|
| 90 |
+
if i not in chw_cache:
|
| 91 |
+
chw_cache[i] = bgr_to_onnx_input(frames_bgr[reflect_index(i, numframes)], onnx_h, onnx_w)
|
| 92 |
+
return chw_cache[i]
|
| 93 |
+
|
| 94 |
+
den_frames_bgr = []
|
| 95 |
+
inframes = [] # numpy [3,H,W] each
|
| 96 |
+
|
| 97 |
+
for fridx in range(numframes):
|
| 98 |
+
if not inframes:
|
| 99 |
+
for off in range(temp_psz):
|
| 100 |
+
inframes.append(get_chw(fridx + off - ctrl))
|
| 101 |
+
else:
|
| 102 |
+
del inframes[0]
|
| 103 |
+
inframes.append(get_chw(fridx + ctrl))
|
| 104 |
+
|
| 105 |
+
# concat 5 frames -> [1, 15, H, W]
|
| 106 |
+
noisy = np.concatenate(inframes, axis=0)[None, :, :, :].astype(np.float32)
|
| 107 |
+
noise_map = np.full((1, 1, onnx_h, onnx_w), noise_sigma_01, dtype=np.float32)
|
| 108 |
+
feeds = {input_names[0]: noisy, input_names[1]: noise_map}
|
| 109 |
+
out = sess.run(output_names, feeds)[0] # [1, 3, H, W]
|
| 110 |
+
out = np.clip(out, 0.0, 1.0)
|
| 111 |
+
den_bgr = chw_to_bgr_uint8(out[0], frames_bgr[0].shape[1], frames_bgr[0].shape[0])
|
| 112 |
+
den_frames_bgr.append(den_bgr)
|
| 113 |
+
|
| 114 |
+
return den_frames_bgr
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def write_side_by_side(frames_orig, frames_den, out_path, fps, label=True):
|
| 118 |
+
h, w = frames_orig[0].shape[:2]
|
| 119 |
+
os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
|
| 120 |
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
| 121 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (w * 2, h))
|
| 122 |
+
if not writer.isOpened():
|
| 123 |
+
raise RuntimeError("failed to create video writer: {}".format(out_path))
|
| 124 |
+
for orig, den in zip(frames_orig, frames_den):
|
| 125 |
+
canvas = np.concatenate([orig, den], axis=1)
|
| 126 |
+
if label:
|
| 127 |
+
cv2.putText(canvas, "Original", (16, 34), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2, cv2.LINE_AA)
|
| 128 |
+
cv2.putText(canvas, "ONNX Denoised", (w + 16, 34), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2, cv2.LINE_AA)
|
| 129 |
+
writer.write(canvas)
|
| 130 |
+
writer.release()
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def main():
|
| 134 |
+
parser = argparse.ArgumentParser(description="FastDVDnet ONNX video inference side-by-side")
|
| 135 |
+
parser.add_argument("--onnx", type=str, default="./fastdvdnet_640x480.onnx")
|
| 136 |
+
parser.add_argument("--video", type=str, required=True, help="input mp4 video")
|
| 137 |
+
parser.add_argument("--noise_sigma", type=float, default=25.0, help="noise sigma 0-255")
|
| 138 |
+
parser.add_argument("--out_dir", type=str, default="./video_infer_results/onnx_test")
|
| 139 |
+
parser.add_argument("--max_frames", type=int, default=0, help="0 means all frames")
|
| 140 |
+
parser.add_argument("--no_label", action="store_true")
|
| 141 |
+
args = parser.parse_args()
|
| 142 |
+
|
| 143 |
+
x_shape = get_onnx_shapes(args.onnx)
|
| 144 |
+
_, _, onnx_h, onnx_w = x_shape
|
| 145 |
+
print("ONNX input shape: {}".format(x_shape))
|
| 146 |
+
|
| 147 |
+
sess = ort.InferenceSession(args.onnx, providers=["CPUExecutionProvider"])
|
| 148 |
+
input_names = [i.name for i in sess.get_inputs()]
|
| 149 |
+
output_names = [o.name for o in sess.get_outputs()]
|
| 150 |
+
print("providers:", sess.get_providers())
|
| 151 |
+
print("inputs:", [(n, list(i.shape)) for n, i in zip(input_names, sess.get_inputs())])
|
| 152 |
+
print("outputs:", [(n, list(o.shape)) for n, o in zip(output_names, sess.get_outputs())])
|
| 153 |
+
|
| 154 |
+
base = os.path.splitext(os.path.basename(args.video))[0]
|
| 155 |
+
out_path = os.path.join(args.out_dir, "{}_onnx_side_by_side.mp4".format(base))
|
| 156 |
+
|
| 157 |
+
t0 = time.time()
|
| 158 |
+
frames_bgr, fps, orig_w, orig_h = read_video(args.video, args.max_frames)
|
| 159 |
+
print("video: {}x{} @ {:.1f}fps, {} frames".format(orig_w, orig_h, fps, len(frames_bgr)))
|
| 160 |
+
print("onnx input: {}x{}".format(onnx_w, onnx_h))
|
| 161 |
+
|
| 162 |
+
sigma01 = args.noise_sigma / 255.0
|
| 163 |
+
den = denoise_onnx(frames_bgr, sigma01, sess, x_shape, input_names, output_names)
|
| 164 |
+
write_side_by_side(frames_bgr, den, out_path, fps, label=not args.no_label)
|
| 165 |
+
dt = time.time() - t0
|
| 166 |
+
|
| 167 |
+
print("[OK] {} -> {}".format(args.video, out_path))
|
| 168 |
+
print(" frames={} time={:.2f}s fps={:.2f}".format(len(frames_bgr), dt, len(frames_bgr) / max(dt, 0.001)))
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
if __name__ == "__main__":
|
| 172 |
+
main()
|
fastDVDnet/res/people-sunset_axmodel_side_by_side.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1d30d4383609735400fd7a496e366bc836ead97f5a34fae4a7eb806db43e3e40
|
| 3 |
+
size 2146980
|
fastDVDnet/video/people-sunset.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f6fd9f0057d761a0eafb8dc47e0bf046387e3cb0950004916b6569dc6effaf72
|
| 3 |
+
size 2008424
|