La profondeur de la scène est estimée à partir de la photo (MiDaS small ou Depth Anything V2 Small) et mélangée aux gris : les planches suivent les volumes du sujet plutôt que ses lumières et ses ombres. - Onglet « Profondeur » : part de la carte de profondeur, modèle, et détails fins de la photo gravés dans la profondeur (yeux, bouche). - Répartition des niveaux : rapproche les planches d'aires égales quand les valeurs sont tassées sur quelques gris. - Images 16 bits lues sur toute leur plage (elles étaient écrêtées à 255). - Le Python d'Inkscape (MinGW, sans pip) ne peut pas charger onnxruntime : le calcul tourne dans un environnement à part, installé par deploy.ps1 -Depth, lancé en sous-processus, résultat gardé en cache. - Depth Anything sur la carte graphique (DirectML) avec repli sur le processeur ; MiDaS small toujours sur le processeur, DirectML lui faisant rendre une carte fausse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AumyrbcuQobB3GbPrFjG9i
123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
# coding=utf-8
|
|
"""
|
|
Calcul d'une carte de profondeur, pour l'extension « Gray Iso-Layers ».
|
|
|
|
python depth_estimate.py image.png sortie.npy modele.onnx --model midas_small
|
|
|
|
Ce script ne tourne pas dans le Python d'Inkscape (une construction MinGW,
|
|
sans pip, ou onnxruntime ne s'installe pas) mais dans un environnement a
|
|
part, cree par `deploy.ps1 -Depth` ; l'extension le lance en sous-processus
|
|
(voir `gray_iso_layers_depth`). Dependances : onnxruntime (ou
|
|
onnxruntime-directml pour le calcul sur carte graphique), numpy, Pillow.
|
|
|
|
La sortie est un tableau numpy 2D float32 de profondeur inverse : plus la
|
|
valeur est haute, plus le point est proche.
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
import numpy as np
|
|
|
|
# Cote du carre presente a chaque modele.
|
|
MODELS = {"midas_small": 256, "depth_anything_v2_small": 518}
|
|
|
|
# Modeles calcules sur le processeur meme si une carte graphique est la :
|
|
# sous DirectML, MiDaS small rend une carte fausse, et son calcul est de
|
|
# toute facon plus court que l'initialisation de la carte graphique.
|
|
CPU_ONLY = {"midas_small"}
|
|
|
|
# Normalisation ImageNet, commune aux deux modeles.
|
|
_MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)
|
|
_STD = np.array([0.229, 0.224, 0.225], dtype=np.float32)
|
|
|
|
_GPU = "DmlExecutionProvider"
|
|
_CPU = "CPUExecutionProvider"
|
|
|
|
|
|
def preprocess(picture, size):
|
|
"""Image PIL -> tenseur (1, 3, size, size) float32 attendu par le modele."""
|
|
from PIL import Image
|
|
|
|
rgb = picture.convert("RGB").resize((size, size), Image.BICUBIC)
|
|
pixels = (np.asarray(rgb, dtype=np.float32) / 255.0 - _MEAN) / _STD
|
|
return np.ascontiguousarray(pixels.transpose(2, 0, 1)[None], dtype=np.float32)
|
|
|
|
|
|
def choose_providers(available, cpu=False):
|
|
"""Fournisseurs de calcul a essayer, dans l'ordre : la carte graphique
|
|
(DirectML) si elle est disponible et non ecartee par `cpu`, puis le
|
|
processeur, toujours present."""
|
|
if cpu or _GPU not in available:
|
|
return [_CPU]
|
|
return [_GPU, _CPU]
|
|
|
|
|
|
def _run(weights, tensor, provider):
|
|
import onnxruntime
|
|
|
|
options = onnxruntime.SessionOptions()
|
|
options.log_severity_level = 3
|
|
if provider == _GPU:
|
|
# Exigences de DirectML.
|
|
options.enable_mem_pattern = False
|
|
options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
|
|
# Carte designee explicitement : sans cela, l'initialisation echoue
|
|
# sur une machine a deux cartes graphiques.
|
|
provider = (_GPU, {"device_id": 0})
|
|
session = onnxruntime.InferenceSession(weights, options, providers=[provider])
|
|
name = session.get_inputs()[0].name
|
|
# onnxruntime se replie de lui-meme sur le processeur si la carte
|
|
# graphique ne s'initialise pas : on rend le fournisseur reellement pris.
|
|
return session.run(None, {name: tensor})[0], session.get_providers()[0]
|
|
|
|
|
|
def estimate(picture, weights, model, cpu=False):
|
|
"""Profondeur inverse de l'image : `(tableau 2D float32, fournisseur)`.
|
|
|
|
Le calcul se fait sur la carte graphique si le modele s'y prete (voir
|
|
`CPU_ONLY`) ; s'il y echoue (pilote, memoire), il est refait sur le
|
|
processeur.
|
|
"""
|
|
import onnxruntime
|
|
|
|
tensor = preprocess(picture, MODELS[model])
|
|
providers = choose_providers(onnxruntime.get_available_providers(),
|
|
cpu or model in CPU_ONLY)
|
|
for provider in providers:
|
|
try:
|
|
depth, provider = _run(weights, tensor, provider)
|
|
depth = np.squeeze(depth)
|
|
except Exception:
|
|
if provider == providers[-1]:
|
|
raise
|
|
continue
|
|
if depth.ndim != 2:
|
|
raise ValueError("sortie du modele inattendue : {}".format(depth.shape))
|
|
return depth.astype(np.float32), provider
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__.strip().splitlines()[0])
|
|
parser.add_argument("image")
|
|
parser.add_argument("output")
|
|
parser.add_argument("weights")
|
|
parser.add_argument("--model", choices=sorted(MODELS), default="midas_small")
|
|
parser.add_argument("--cpu", action="store_true",
|
|
help="ne pas utiliser la carte graphique")
|
|
args = parser.parse_args(argv)
|
|
|
|
from PIL import Image
|
|
|
|
with Image.open(args.image) as picture:
|
|
depth, provider = estimate(picture, args.weights, args.model, args.cpu)
|
|
with open(args.output, "wb") as handle:
|
|
np.save(handle, depth, allow_pickle=False)
|
|
print(provider)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|