DeMuS: Decoupled Mutual Scoring for Batch Zero-Shot Industrial Anomaly Detection
June 30, 2026 · View on GitHub
Official implementation of DeMuS (Decoupled Mutual Scoring), a two-stage learning framework for Batch Zero-Shot Industrial Anomaly Detection (IAD).
DeMuS: Learning Decoupled Matching and Scoring for Batch Zero-Shot Industrial Anomaly Detection Zhengyang Zhao, Hailong Sun, Binhang Qi, Hongrui Yu, Zhongchi Wang, Hang Xu
DeMuS targets a realistic deployment setting where products are inspected in small batches (e.g. 4–8 images) with no target-domain training data and where intra-batch samples exhibit significant pose variation (rotation, translation). Applying conventional mutual-scoring directly in this setting suffers from two failure modes — the anomaly-to-anomaly shortcut during matching, and a lack of anomaly-aware semantics during scoring. DeMuS resolves both by decoupling the problem into two lightweight, separately trained adapters on top of a frozen backbone:
- a Matching Adapter (
g_m, where-to-match) that learns pose-invariant part-level correspondence, and - a Scoring Adapter (
g_s, how-to-score) that learns discriminative anomaly semantics.
At inference, a single top-k nearest-neighbor matching step produces soft correspondences, and anomaly scores are aggregated only over aligned parts, preserving the efficiency of baseline mutual scoring.
Results
Image / pixel level, mean over 5 random seeds (from the paper). The released weights reproduce the MVTec AD and VisA numbers below.
| Dataset | Image AUROC | Image AP | Pixel AUROC | Pixel AUPRO |
|---|---|---|---|---|
| MVTec AD | 94.17 | 96.89 | 96.15 | 93.50 |
| VisA | 90.16 | 91.70 | 96.94 | 92.23 |
Following the cross-dataset auxiliary-training protocol, the model evaluated on MVTec AD uses VisA as the auxiliary set, and vice versa.
Installation
git clone https://github.com/evoLonation/DeMuS.git
cd DeMuS
pip install -r requirements.txt
The released weight files are tracked with Git LFS.
Install LFS (git lfs install) before cloning so the .pt files are fetched,
or run git lfs pull afterwards.
DINOv3 backbone
DeMuS uses a frozen DINOv3 ViT-L/16 backbone. Obtain the DINOv3 code and pretrained weights from the official release and accept their license:
- Repository and weights: https://github.com/facebookresearch/dinov3
Then point DeMuS at your local copies via environment variables:
export DINOV3_REPO_DIR=/path/to/dinov3 # the cloned DINOv3 repo
export DINOV3_WEIGHTS_DIR=/path/to/dinov3_weights # dir with the *.pth checkpoints
The ViT-L/16 weight file expected by default is
dinov3_vitl16_pretrain_lvd1689m-8aa4cbdd.pth.
Datasets
Download MVTec AD, VisA, and/or Real-IAD from their official sources and place them under a single root, then point DeMuS at it:
export ANOMALY_DATA_ROOT=/path/to/datasets
# expected subdirectories, e.g.:
# $ANOMALY_DATA_ROOT/mvtec_anomaly_detection
# $ANOMALY_DATA_ROOT/VisA_pytorch/1cls
# $ANOMALY_DATA_ROOT/Real-IAD
You can also pass an explicit path= to any dataset constructor instead of
relying on the environment variable.
Reproducing the paper results
Two released weight files reproduce the reported numbers:
| File | Target dataset | Auxiliary dataset |
|---|---|---|
demus_aux_visa_for_mvtec_ad.pt | MVTec AD | VisA |
demus_aux_mvtec_for_visa.pt | VisA | MVTec AD |
Each file holds the matching- and scoring-branch adapter state_dicts plus a
reported_metrics summary; the frozen DINOv3 backbone is loaded separately
(see above).
Build a detector and run inference
import torch
from demus.release import load_detector
# DINOV3_REPO_DIR / DINOV3_WEIGHTS_DIR must be set in the environment.
detector = load_detector("weights/demus_aux_visa_for_mvtec_ad.pt", topk=3)
# A batch of intra-batch images, resized/cropped to 512x512.
images = torch.randn(4, 3, 512, 512, device="cuda")
result = detector.model(images)
print(result.scores.shape) # image-level scores: (B,)
print(result.scores_pixel.shape) # pixel-level maps: (B, H, W)
Evaluate on a benchmark
detector is a standard detector that plugs into the evaluation loop. With
ANOMALY_DATA_ROOT configured:
from pathlib import Path
from data import MVTecAD
from evaluator.evaluation import evaluation_detection
from evaluator.sampler import random_sampler_getter
evaluation_detection(
Path("results/demus_mvtec_ad"),
detector,
MVTecAD(),
batch_size=4,
sampler_getter=random_sampler_getter,
save_anomaly_score=True,
)
This writes per-category metrics (AUROC / AP / pixel AUROC / AUPRO) under the
output directory. Use demus_aux_mvtec_for_visa.pt with VisA() to reproduce
the VisA row. DeMuS uses batch size 4 for MVTec AD and VisA (8 for Real-IAD),
input resolution 512×512, and topk=3.
Method at a glance
- Frozen backbone + multi-scale adapters. Multi-layer patch tokens from a frozen DINOv3 are refined by an Inception-style trainable adapter (parallel 1×1 / 3×3 / 5×5 convolutions with a residual connection), applied independently for the matching and scoring branches.
- Stage 1 — Matching Adapter. Trained with explicit "same-part" correspondence supervision (derived from an alignment oracle on auxiliary data) plus distractor pairs, via MSE regression. This removes the anomaly-to-anomaly shortcut and makes matching robust to pose.
- Stage 2 — Scoring Adapter. Trained to pull normal–normal pairs together and push anomalous pairs apart, again via MSE regression on the same-part candidate set, yielding anomaly-aware features.
- Inference. For each target patch, retrieve top-k correspondences in each reference via the matching features, softmax-weight them, and aggregate scoring-feature distances over those correspondences. Scores are fused across references by taking the most "normal" explanation.
Citation
@inproceedings{zhao2026demus,
title = {DeMuS: Learning Decoupled Matching and Scoring for Batch Zero-Shot Industrial Anomaly Detection},
author = {Zhao, Zhengyang and Sun, Hailong and Qi, Binhang and Yu, Hongrui and Wang, Zhongchi and Xu, Hang},
year = {2026}
}
License
This code is released under the Apache-2.0 License. The DINOv3 backbone, datasets, and any other third-party assets are subject to their own licenses; obtain and use them accordingly.