Files
AnyImage/tools/models/exports.py
nan e8129fe495 Implement BiRefNet and HAT Sharper models with background removal capabilities
- Added validation documentation for AI model tiers.
- Introduced `background.py` for background removal logic, including inference functions for alpha and foreground extraction.
- Created `onnx_birefnet.py` to handle BiRefNet model sessions and alpha inference.
- Developed tests for background models, ensuring proper dispatch and session reuse.
- Added tests for BiRefNet ONNX model to validate alpha output and error handling for invalid values.
- Implemented model export scripts for BiRefNet and HAT Sharper, ensuring proper weight compression and licensing.
- Created `deform_sampling.py` for deformable convolution operations used in BiRefNet.
- Added utility functions for compressing ONNX weights without altering graph arithmetic.
- Introduced a reference plane node for depth field adjustments in geometry processing.
2026-09-10 20:50:44 +08:00

110 lines
3.5 KiB
Python

"""Export official PyTorch super-resolution models to ONNX."""
import numpy as np
OPSET_VERSION = 17
def _require_build_dependencies():
try:
import onnx
import onnxruntime
import torch
except ImportError as error:
raise RuntimeError(
"Model build dependencies are missing. Run with: "
"uv sync"
) from error
return onnx, onnxruntime, torch
def _load_weights(torch, checkpoint_path, key):
checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True)
return checkpoint[key]
def _validate_export(onnx, onnxruntime, torch, model, sample, destination):
onnx.checker.check_model(onnx.load(destination))
expected = model(sample).detach().numpy()
session = onnxruntime.InferenceSession(
str(destination),
providers=["CPUExecutionProvider"],
)
actual = session.run(None, {"input": sample.numpy()})[0]
np.testing.assert_allclose(actual, expected, rtol=1e-4, atol=1e-5)
def _export(torch, model, sample, destination, dynamic_axes=None):
model.eval()
torch.onnx.export(
model,
sample,
destination,
export_params=True,
opset_version=OPSET_VERSION,
do_constant_folding=True,
input_names=["input"],
output_names=["output"],
dynamic_axes=dynamic_axes,
dynamo=False,
)
def _restore_realesrgan_scope_names(onnx, destination):
model = onnx.load(destination)
for node in model.graph.node:
node.name = node.name.replace("/body.", "/body/body.")
for values in (node.input, node.output):
for index, value in enumerate(values):
values[index] = value.replace("/body.", "/body/body.")
onnx.save(model, destination)
def export_realesrgan_wdn_x4(checkpoint_path, destination):
onnx, onnxruntime, torch = _require_build_dependencies()
nn = torch.nn
class SRVGGNetCompact(nn.Module):
def __init__(self):
super().__init__()
self.upscale = 4
self.body = nn.ModuleList([nn.Conv2d(3, 64, 3, 1, 1), nn.PReLU(64)])
for _ in range(32):
self.body.extend([nn.Conv2d(64, 64, 3, 1, 1), nn.PReLU(64)])
self.body.append(nn.Conv2d(64, 3 * self.upscale**2, 3, 1, 1))
self.upsampler = nn.PixelShuffle(self.upscale)
def forward(self, value):
result = value
for layer in self.body:
result = layer(result)
result = self.upsampler(result)
return result + torch.nn.functional.interpolate(
value,
scale_factor=self.upscale,
mode="nearest",
)
model = SRVGGNetCompact()
model.load_state_dict(_load_weights(torch, checkpoint_path, "params"), strict=True)
sample = torch.rand(1, 3, 16, 16)
dynamic_axes = {
"input": {2: "height", 3: "width"},
"output": {2: "height4", 3: "width4"},
}
_export(torch, model, sample, destination, dynamic_axes)
_restore_realesrgan_scope_names(onnx, destination)
_validate_export(onnx, onnxruntime, torch, model, sample, destination)
def export_model(exporter, checkpoint_path, destination):
exporters = {
"realesrgan_wdn_x4": export_realesrgan_wdn_x4,
}
try:
export = exporters[exporter]
except KeyError as error:
raise ValueError(f"Unknown model exporter: {exporter}") from error
export(checkpoint_path, destination)