Repository: opencv/opencv
File: samples/dnn/auto_white_balance.py (Python)

Code before:
    "webnn":   cv.dnn.DNN_BACKEND_WEBNN,
}
_TARGETS = {
    "cpu":         cv.dnn.DNN_TARGET_CPU,
    "opencl":      cv.dnn.DNN_TARGET_OPENCL,
    "opencl_fp16": cv.dnn.DNN_TARGET_OPENCL_FP16,
    "vpu":         cv.dnn.DNN_TARGET_MYRIAD,
    "vulkan":      cv.dnn.DNN_TARGET_VULKAN,
    "cuda":        cv.dnn.DNN_TARGET_CUDA,
    "cuda_fp16":   cv.dnn.DNN_TARGET_CUDA_FP16,
}


def main():
    p = argparse.ArgumentParser(
        description="FC4 Color Constancy (ONNX): " \
        "predicts illuminant and applies Von Kries white balance."
    )
    p.add_argument("--model", required=True, help="Path to ONNX model file")
    p.add_argument("--input", required=True, help="Path to input image")
    p.add_argument("--scale", type=float, default=1.0, help="Input scaling factor (e.g., 1/255)")
    p.add_argument("--mean", type=float, nargs=3, default=[0.0, 0.0, 0.0],
                   help="Mean to subtract (B G R)")
    p.add_argument("--rgb", action="store_true", help="Swap BGR->RGB for model input")
    p.add_argument("--backend", default="default", choices=_BACKENDS.keys(), help="DNN backend")
    p.add_argument("--target", default="cpu", choices=_TARGETS.keys(), help="DNN target")
    args = p.parse_args()

    try:
        net = cv.dnn.readNetFromONNX(args.model)
        net.setPreferableBackend(_BACKENDS[args.backend])
        net.setPreferableTarget(_TARGETS[args.target])
    except cv.error as e:
        print(f"Error loading model: {e}", file=sys.stderr)
        sys.exit(1)

    img = cv.imread(args.input, cv.IMREAD_COLOR)
    if img is None:
        print(f"Cannot load image: {args.input}", file=sys.stderr)
        sys.exit(1)

    blob = cv.dnn.blobFromImage(
        img, scalefactor=args.scale, size=(img.shape[1], img.shape[0]),
        mean=tuple(args.mean), swapRB=args.rgb, crop=False, ddepth=cv.CV_32F
    )
    net.setInput(blob)

    try:
        out = net.forward()
    except cv.error as e:
        print(f"Forward error: {e}", file=sys.stderr)

Review comment (suggestion):
Update these like this

```
    parser.add_argument('--backend', default="default", type=str, choices=backends,
            help="Choose one of computation backends: "
            "default: automatically (by default), "
            "openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
            "opencv: OpenCV implementation, "
            "vkcom: VKCOM, "
            "cuda: CUDA, "
            "webnn: WebNN")
    parser.add_argument('--target', default="cpu", type=str, choices=targets,
            help="Choose one of target computation devices: "
            "cpu: CPU target (by default), "
            "opencl: OpenCL, "
            "opencl_fp16: OpenCL fp16 (half-float precision), "
            "ncs2_vpu: NCS2 VPU, "
            "hddl_vpu: HDDL VPU, "
            "vulkan: Vulkan, "
            "cuda: CUDA, "
            "cuda_fp16: CUDA fp16 (half-float preprocess)")
```

Code after:
# SRGB_SLOPE: slope of the linear segment near black
# SRGB_ALPHA: offset to ensure continuity at the threshold
# SRGB_EXP: gamma exponent
SRGB_THRESHOLD = 0.04045
SRGB_ALPHA     = 0.055
SRGB_SLOPE     = 12.92
SRGB_EXP       = 2.4
LINEAR_THRESHOLD = 0.0031308
EPS = 1e-10

def srgb_to_linear(rgb: np.ndarray) -> np.ndarray:
    low  = rgb / SRGB_SLOPE
    high = np.power((rgb + SRGB_ALPHA) / (1.0 + SRGB_ALPHA), SRGB_EXP, dtype=np.float32)
    return np.where(rgb <= SRGB_THRESHOLD, low, high).astype(np.float32)

def linear_to_srgb(lin: np.ndarray) -> np.ndarray:
    low  = lin * SRGB_SLOPE
    high = (1.0 + SRGB_ALPHA) * np.power(lin, 1.0 / SRGB_EXP, dtype=np.float32) - SRGB_ALPHA
    return np.where(lin <= LINEAR_THRESHOLD, low, high).astype(np.float32)

def correct(bgr8u: np.ndarray, illum_rgb_linear: np.ndarray) -> np.ndarray:
    assert bgr8u.dtype == np.uint8 and bgr8u.ndim == 3 and bgr8u.shape[2] == 3

    bgr = bgr8u.astype(np.float32) * NORMALIZE_FACTOR
    lin = srgb_to_linear(bgr)
    e_r = max(float(illum_rgb_linear[0]), EPS)
    e_g = max(float(illum_rgb_linear[1]), EPS)
    e_b = max(float(illum_rgb_linear[2]), EPS)
    s3 = np.float32(np.sqrt(3.0))
    corr_bgr = np.array([e_b * s3 + EPS,
                         e_g * s3 + EPS,
                         e_r * s3 + EPS],
                        dtype=np.float32)

    corrected = lin / corr_bgr.reshape(1, 1, 3)

    max_val = float(corrected.max()) + EPS
    corrected /= max_val
    corrected = np.clip(corrected, 0.0, 1.0)

    srgb = linear_to_srgb(corrected)

    out_bgr8 = (srgb * 255.0 + 0.5).astype(np.uint8)
    return out_bgr8

def annotate(img_bgr: np.ndarray, title: str) -> None:
    fs = max(0.5, min(img_bgr.shape[1], img_bgr.shape[0]) / 800.0)
    th = max(1, int(round(fs * 2)))
    cv.putText(img_bgr, title, (10, 30), cv.FONT_HERSHEY_SIMPLEX, fs, (0,255,0), th)

def get_args_parser(func_args):