Image Segmentation
PyTorch
English
painting-vision-robotics-kit
semantic-segmentation
robotics
edge-ai
construction-ai
autonomous-painting
wall-painting-robot
paint-coverage-estimation
building-facade
drywall
skirting-detection
window-detection
lidar
depth-validation
segformer
deeplabv3
Eval Results (legacy)
Upload models.py with huggingface_hub
Browse files
models.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model factory: shared semantic + drywall-material heads over several backbones.
|
| 2 |
+
|
| 3 |
+
The original prototype hard-coded MobileNetV3-Large/DeepLabV3. That is small and
|
| 4 |
+
fast but well behind modern open segmentation models. This module keeps the
|
| 5 |
+
two-head design (semantic coverage + independent drywall substrate) but lets you
|
| 6 |
+
choose the encoder:
|
| 7 |
+
|
| 8 |
+
* ``deeplabv3_mobilenet_v3_large`` - the original; edge/latency oriented.
|
| 9 |
+
* ``deeplabv3_resnet50`` / ``deeplabv3_resnet101`` - stronger torchvision
|
| 10 |
+
baselines (ASPP + ResNet), no extra dependencies.
|
| 11 |
+
* ``segformer_b0`` .. ``segformer_b5`` - modern transformer segmentation from
|
| 12 |
+
Hugging Face (requires ``transformers``); the recommended accuracy option.
|
| 13 |
+
|
| 14 |
+
All variants return ``{"semantic": (B, C, H, W), "drywall": (B, 2, H, W)}`` at
|
| 15 |
+
input resolution, so ``train.py`` and ``predict.py`` are architecture-agnostic.
|
| 16 |
+
Normalisation is shared (ImageNet mean/std), which SegFormer also expects.
|
| 17 |
+
"""
|
| 18 |
+
import torch
|
| 19 |
+
from torch import nn
|
| 20 |
+
from torch.nn import functional as F
|
| 21 |
+
from torchvision.models import MobileNet_V3_Large_Weights, ResNet50_Weights, ResNet101_Weights
|
| 22 |
+
from torchvision.models.segmentation import (
|
| 23 |
+
deeplabv3_mobilenet_v3_large,
|
| 24 |
+
deeplabv3_resnet50,
|
| 25 |
+
deeplabv3_resnet101,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
# torchvision DeepLabV3 variants: name -> (builder, pretrained backbone weights).
|
| 29 |
+
DEEPLAB_ARCHITECTURES = {
|
| 30 |
+
"deeplabv3_mobilenet_v3_large": (deeplabv3_mobilenet_v3_large, MobileNet_V3_Large_Weights.DEFAULT),
|
| 31 |
+
"deeplabv3_resnet50": (deeplabv3_resnet50, ResNet50_Weights.DEFAULT),
|
| 32 |
+
"deeplabv3_resnet101": (deeplabv3_resnet101, ResNet101_Weights.DEFAULT),
|
| 33 |
+
}
|
| 34 |
+
# SegFormer variants: name -> Hugging Face id of a pretrained encoder/decoder.
|
| 35 |
+
SEGFORMER_ARCHITECTURES = {
|
| 36 |
+
"segformer_b0": "nvidia/segformer-b0-finetuned-ade-512-512",
|
| 37 |
+
"segformer_b1": "nvidia/segformer-b1-finetuned-ade-512-512",
|
| 38 |
+
"segformer_b2": "nvidia/segformer-b2-finetuned-ade-512-512",
|
| 39 |
+
"segformer_b3": "nvidia/segformer-b3-finetuned-ade-512-512",
|
| 40 |
+
"segformer_b4": "nvidia/segformer-b4-finetuned-ade-512-512",
|
| 41 |
+
"segformer_b5": "nvidia/segformer-b5-finetuned-ade-512-512",
|
| 42 |
+
}
|
| 43 |
+
DEFAULT_ARCH = "deeplabv3_mobilenet_v3_large"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def available_architectures():
|
| 47 |
+
return sorted(list(DEEPLAB_ARCHITECTURES) + list(SEGFORMER_ARCHITECTURES))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _drywall_head(channels, hidden=128, dropout=0.1):
|
| 51 |
+
return nn.Sequential(
|
| 52 |
+
nn.Conv2d(channels, hidden, kernel_size=3, padding=1, bias=False),
|
| 53 |
+
nn.BatchNorm2d(hidden),
|
| 54 |
+
nn.ReLU(inplace=True),
|
| 55 |
+
nn.Dropout2d(dropout),
|
| 56 |
+
nn.Conv2d(hidden, 2, kernel_size=1),
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class WallPaintNet(nn.Module):
|
| 61 |
+
"""torchvision DeepLabV3 semantic head plus an independent drywall head."""
|
| 62 |
+
|
| 63 |
+
def __init__(self, num_semantic_classes, pretrained_backbone=False, arch=DEFAULT_ARCH):
|
| 64 |
+
super().__init__()
|
| 65 |
+
if arch not in DEEPLAB_ARCHITECTURES:
|
| 66 |
+
raise ValueError(f"unknown DeepLab architecture {arch!r}; choose from {sorted(DEEPLAB_ARCHITECTURES)}")
|
| 67 |
+
builder, default_weights = DEEPLAB_ARCHITECTURES[arch]
|
| 68 |
+
weights = default_weights if pretrained_backbone else None
|
| 69 |
+
self.arch = arch
|
| 70 |
+
self.segmenter = builder(weights=None, weights_backbone=weights, num_classes=num_semantic_classes)
|
| 71 |
+
was_training = self.segmenter.training
|
| 72 |
+
self.segmenter.eval()
|
| 73 |
+
with torch.inference_mode():
|
| 74 |
+
features = self.segmenter.backbone(torch.zeros(1, 3, 128, 128))["out"]
|
| 75 |
+
self.segmenter.train(was_training)
|
| 76 |
+
self.drywall_head = _drywall_head(features.shape[1])
|
| 77 |
+
|
| 78 |
+
def forward(self, x):
|
| 79 |
+
size = x.shape[-2:]
|
| 80 |
+
features = self.segmenter.backbone(x)
|
| 81 |
+
semantic = self.segmenter.classifier(features["out"])
|
| 82 |
+
drywall = self.drywall_head(features["out"])
|
| 83 |
+
return {
|
| 84 |
+
"semantic": F.interpolate(semantic, size=size, mode="bilinear", align_corners=False),
|
| 85 |
+
"drywall": F.interpolate(drywall, size=size, mode="bilinear", align_corners=False),
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class SegformerPaintNet(nn.Module):
|
| 90 |
+
"""Hugging Face SegFormer semantic head plus an independent drywall head.
|
| 91 |
+
|
| 92 |
+
SegFormer predicts logits at 1/4 resolution; the last encoder hidden state
|
| 93 |
+
feeds the drywall head. Heavy enough to matter, but a genuine modern
|
| 94 |
+
transformer backbone rather than a 2021 MobileNet.
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
def __init__(self, num_semantic_classes, pretrained_backbone=False, arch="segformer_b2"):
|
| 98 |
+
super().__init__()
|
| 99 |
+
if arch not in SEGFORMER_ARCHITECTURES:
|
| 100 |
+
raise ValueError(f"unknown SegFormer architecture {arch!r}; choose from {sorted(SEGFORMER_ARCHITECTURES)}")
|
| 101 |
+
try:
|
| 102 |
+
from transformers import SegformerConfig, SegformerForSemanticSegmentation
|
| 103 |
+
except ImportError as exc: # pragma: no cover - optional dependency
|
| 104 |
+
raise ImportError("SegFormer architectures require `pip install transformers`") from exc
|
| 105 |
+
hf_id = SEGFORMER_ARCHITECTURES[arch]
|
| 106 |
+
self.arch = arch
|
| 107 |
+
if pretrained_backbone:
|
| 108 |
+
self.segmenter = SegformerForSemanticSegmentation.from_pretrained(
|
| 109 |
+
hf_id, num_labels=num_semantic_classes, ignore_mismatched_sizes=True)
|
| 110 |
+
else:
|
| 111 |
+
config = SegformerConfig.from_pretrained(hf_id, num_labels=num_semantic_classes)
|
| 112 |
+
self.segmenter = SegformerForSemanticSegmentation(config)
|
| 113 |
+
self.drywall_head = _drywall_head(self.segmenter.config.hidden_sizes[-1])
|
| 114 |
+
|
| 115 |
+
def forward(self, x):
|
| 116 |
+
size = x.shape[-2:]
|
| 117 |
+
outputs = self.segmenter(pixel_values=x, output_hidden_states=True)
|
| 118 |
+
semantic = F.interpolate(outputs.logits, size=size, mode="bilinear", align_corners=False)
|
| 119 |
+
drywall = self.drywall_head(outputs.hidden_states[-1])
|
| 120 |
+
drywall = F.interpolate(drywall, size=size, mode="bilinear", align_corners=False)
|
| 121 |
+
return {"semantic": semantic, "drywall": drywall}
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def build_model(arch, num_semantic_classes, pretrained_backbone=False):
|
| 125 |
+
"""Construct the dual-head model for ``arch`` (see :func:`available_architectures`)."""
|
| 126 |
+
if arch in DEEPLAB_ARCHITECTURES:
|
| 127 |
+
return WallPaintNet(num_semantic_classes, pretrained_backbone, arch)
|
| 128 |
+
if arch in SEGFORMER_ARCHITECTURES:
|
| 129 |
+
return SegformerPaintNet(num_semantic_classes, pretrained_backbone, arch)
|
| 130 |
+
raise ValueError(f"unknown architecture {arch!r}; choose from {available_architectures()}")
|