HandSegNet

HandSegNet is a SAM ViT-B fine-tune specialized in segmenting hands on anime-style illustrations. It exists because generic SAM (sam_vit_b_01ec64.pth), when prompted with a rough hand region, is unreliable on stylized/anime art — flat shading, gloves, overlapping fingers, and non-photoreal proportions confuse it in ways it isn't confused on photos. HandSegNet fine-tunes only SAM's mask decoder on a small hand-annotated anime dataset to fix that, while keeping the rest of SAM (image encoder, prompt encoder) frozen and untouched. Same architecture and training recipe as FootSegNet (sibling model, feet/shoes), released earlier from the same pipeline.

It was built as a component of a personal anime image/video generation pipeline (Ov3rLoRd, Onibaku Gumi), to drive targeted inpainting of hands without disturbing the rest of a frame — but the model itself is a plain SAM checkpoint with no pipeline-specific code baked in, and works standalone for anyone who needs hand masks on anime-style images (dataset curation, cutout tools, your own inpainting pipeline, etc.).

Results — read this before trusting the headline number

Base model SAM ViT-B (sam_vit_b_01ec64.pth, Meta)
Training data 320 hand-annotated anime images (1024×1280, fabricatedXL renders), pixel-perfect masks drawn with a stylus/mouse over a SAM pre-fill
Augmentation horizontal flip × ±15° rotation → 1728 train samples from 288 base images (32 held out for validation, unaugmented)
Trainable parameters mask decoder only, ~4.1M of the model's 93.7M total (image encoder + prompt encoder frozen)
Validation IoU (ground-truth box) 0.8876
Validation IoU (automatic box, real pipeline) 0.722 mean / 0.773 median (min 0.083)
Checkpoint size 375 MB (.pth) / 375 MB (.safetensors, lossless conversion — see below)

These are two different numbers measuring two different things, and the gap between them matters more here than it did for FootSegNet:

  • 0.8876 is measured the same way FootSegNet's headline number was: the mask decoder is prompted with the ground-truth hand bounding box (+ small random jitter), i.e. "if you already know exactly where the hand is, how good is the segmentation." This isolates mask_decoder quality.
  • 0.722 mean / 0.773 median is measured with boxes produced by an automatic upstream detector (VLM grounding, asked to locate "the character's hands") instead of ground truth — i.e. closer to what you'd actually get running this model end-to-end without hand-supplying boxes yourself. It was measured over the same 32-image validation set.

Unlike feet (reliably boxed from ankle keypoints via pose estimation), hands don't have an equally reliable off-the-shelf box source in the pipeline this model was built in — no dedicated hand-pose detector was available, so an open-vocabulary VLM grounding call was used instead, and it is noticeably less reliable on scenes with two hands (missing one hand entirely, or placing the box on a wristband/sleeve instead of the fingers, happened repeatedly in the 32-image check). This is not a statement about mask_decoder quality — it's a statement about how much the box source matters for hands specifically. If you supply your own well-localized per-hand boxes (manual annotation, a detector trained on your domain, a hand-pose model), expect results closer to the 0.8876 number than the 0.722 one.

Usage

Requires torch, torchvision, Pillow, numpy, and Meta's segment-anything package. safetensors is only needed if you use the .safetensors checkpoint instead of the original .pth.

pip install torch torchvision pillow numpy
pip install git+https://github.com/facebookresearch/segment-anything.git

Minimal example (see standalone_infer.py in this repo for a complete, dependency-free CLI script):

import torch
from segment_anything import sam_model_registry, SamPredictor
from PIL import Image
import numpy as np

sam = sam_model_registry["vit_b"](checkpoint=None)
state_dict = torch.load("handsegnet_vit_b_best.pth", map_location="cpu", weights_only=True)
# or: from safetensors.torch import load_file; state_dict = load_file("handsegnet_vit_b_best.safetensors")
sam.load_state_dict(state_dict)
sam.to("cuda").eval()

predictor = SamPredictor(sam)
image = np.array(Image.open("character.png").convert("RGB"))
predictor.set_image(image)

# box = (x1, y1, x2, y2) around ONE hand — see Limitations for why box quality matters
box = np.array([137, 612, 547, 1152])
masks, scores, _ = predictor.predict(box=box, multimask_output=False)
Image.fromarray((masks[0] * 255).astype("uint8")).save("hand_mask.png")

Two hands in frame → two calls. This model prompts SAM with a single box per call, same as the underlying architecture. For a scene with both hands visible, run inference once per hand box and union the two output masks (np.maximum(mask1, mask2)) yourself — there is no built-in multi-instance mode in this checkpoint.

Output convention: white (255) = hand, black (0) = background (standard segmentation convention). If you're wiring this into a pipeline that expects the inverse (black = region to inpaint), invert the mask yourself — the Ov3rLoRd pipeline's own infer.py does exactly that for its internal use.

Converting to safetensors

convert_to_safetensors.py in this repo does a lossless conversion of the original .pth state dict (verified tensor-for-tensor identical after round-trip — no quantization, no precision loss):

python convert_to_safetensors.py --input handsegnet_vit_b_best.pth --output handsegnet_vit_b_best.safetensors --verify

Limitations

This model needs a bounding box around each hand — it is not a full-image detector, and it is not multi-instance. SAM is a promptable segmenter, not a classifier: if you run it with a box covering the whole image, it will segment whatever prominent subject best fits that box (typically the entire character silhouette), not just a hand. You need an upstream step (pose estimation, an object/hand detector, manual annotation) to produce a box per hand first — see the Results section above for how much this upstream step matters for hands specifically (0.888 with a good box vs. 0.722 mean with an imperfect automatic one).

Beyond the box-dependency itself, three recurring failure patterns were identified during evaluation, specific to hand anatomy (not shared with FootSegNet, whose defects are milder: soft/"melted" edges, small over-segmentation — a simpler shape has fewer ways to go wrong):

Pattern What happens When it shows up
Mirroring collapse The two hands fuse into one symmetric decorative blob instead of two anatomically distinct hands — no individual fingers or thumbs delineated Compositions where both hands touch or interlock (praying/cupping gestures, hands clasped together, especially when also holding an object)
Finger dropout One or more individual fingers are missing or truncated in the predicted mask even though the box is correctly placed Splayed/spread-fingers poses where fingers are clearly separated (not touching) — a different failure mode from mirroring, not caused by finger contact
Box misses a hand entirely Upstream box detector (whatever you use) places the box on the wrong region (wrist, sleeve, accessory) or only finds one of two hands in frame Two-hand compositions, hands partially occluded by an interaction (typing, holding an object), hands close together

The first two are limits of the current training data/model itself and are not expected to be fixed by more pipeline engineering. The third is a property of whatever upstream box source you use, not of this checkpoint — but it dominates real end-to-end error more than the first two combined in the evaluation behind the 0.722 number above, so don't skip investing in a good box source.

Other things to know:

  • Trained exclusively on a specific anime rendering style (fabricatedXL, 1024×1280 compositions) and not benchmarked outside that distribution — no claim is made about how it performs on other anime/illustration styles, and it is likely to degrade further on styles further from that distribution (chibi, western cartoon, photo).
  • Validation set is small (32 images) and drawn from the same generation pipeline as training data, not an independent held-out distribution — treat both IoU numbers above as in-distribution estimates, not a general-purpose benchmark.
  • Two known-bad training images (fully collapsed into the mirroring pattern above) were identified and excluded from both train and validation sets rather than "fixed" — the pattern itself remains present in the model's behavior on similar new inputs, exclusion only kept unrecoverable examples out of the training signal.

License

Apache 2.0. Attribution appreciated: HandSegNet by Ov3rLoRd / Jérémy Gourlain.

Support this project

If HandSegNet is useful to you, consider supporting further development: Ko-fi

(GitHub Sponsors is pending approval — will be added here once available.)

Acknowledgements

Built on Segment Anything (Meta AI, Apache 2.0). Part of the Ov3rLoRd pipeline (Onibaku Gumi). Code repository: github.com/Ov3rLoRd-MLEngineer/handsegnet. Sibling model: FootSegNet.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support