Keypoint Detection
Transformers
Safetensors
rampnet
feature-extraction
curb-ramp-detection
accessibility
street-view
keypoint-heatmap
custom_code
Instructions to use projectsidewalk/rampnet-model with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use projectsidewalk/rampnet-model with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("projectsidewalk/rampnet-model", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Re-export with transformers-compatible RampNetModel wrapper + corrected card (fixes #19); weights unchanged from v1.0-paper
Browse files- README.md +141 -82
- config.json +33 -11
- configuration_rampnet.py +25 -0
- model.safetensors +2 -2
- modeling.py +0 -27
- modeling_rampnet.py +26 -0
- rampnet_model.py +44 -0
README.md
CHANGED
|
@@ -1,82 +1,141 @@
|
|
| 1 |
-
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
-
|
| 8 |
-
|
| 9 |
-
-
|
| 10 |
-
|
| 11 |
-
--
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
``
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
library_name: transformers
|
| 4 |
+
pipeline_tag: keypoint-detection
|
| 5 |
+
tags:
|
| 6 |
+
- curb-ramp-detection
|
| 7 |
+
- accessibility
|
| 8 |
+
- street-view
|
| 9 |
+
- keypoint-heatmap
|
| 10 |
+
datasets:
|
| 11 |
+
- projectsidewalk/rampnet-dataset
|
| 12 |
+
base_model:
|
| 13 |
+
- timm/convnextv2_base.fcmae_ft_in22k_in1k_384
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# RampNet Curb Ramp Detection Model
|
| 17 |
+
|
| 18 |
+
Stage-2 model from **RampNet: A Two-Stage Pipeline for Bootstrapping Curb Ramp Detection in
|
| 19 |
+
Streetscape Images from Open Government Metadata** (O'Meara et al., ICCV'25 CV4A11y workshop,
|
| 20 |
+
[arXiv:2508.09415](https://arxiv.org/abs/2508.09415)).
|
| 21 |
+
|
| 22 |
+
Takes a 2048x4096 equirectangular street-view panorama (ImageNet-normalized) and predicts a
|
| 23 |
+
512x1024 heatmap of curb ramp locations. Extract detections with `skimage.feature.peak_local_max`.
|
| 24 |
+
|
| 25 |
+
## Provenance
|
| 26 |
+
|
| 27 |
+
| Field | Value |
|
| 28 |
+
| :--- | :--- |
|
| 29 |
+
| Training code | https://github.com/ProjectSidewalk/RampNet @ `842ec33` |
|
| 30 |
+
| Source checkpoint | `epoch_1_step_9378.pth` (sha256 prefix `b0c3ff7a10fc`) |
|
| 31 |
+
| Training dataset | [projectsidewalk/rampnet-dataset](https://huggingface.co/datasets/projectsidewalk/rampnet-dataset) revision `main` |
|
| 32 |
+
| Exported | 2026-07-24 by `scripts/export_hf_model.py` |
|
| 33 |
+
|
| 34 |
+
The model **weights are unchanged** from the originally published artifact — this revision only
|
| 35 |
+
fixes the packaging (a `transformers`-compatible wrapper; see [Requirements](#requirements)) and
|
| 36 |
+
corrects the evaluation numbers (see [Erratum](#erratum-evaluation-metrics)). The prior artifact
|
| 37 |
+
remains addressable at its tagged Hub revision.
|
| 38 |
+
|
| 39 |
+
## Evaluation (1,000-panorama manually labeled gold set)
|
| 40 |
+
|
| 41 |
+
| Metric | Value |
|
| 42 |
+
| :--- | :--- |
|
| 43 |
+
| Average Precision (interpolated, full sweep) | 0.9205 |
|
| 44 |
+
| Precision @ threshold 0.55 | 0.9492 |
|
| 45 |
+
| Recall @ threshold 0.55 | 0.8727 |
|
| 46 |
+
| Ground-truth points | 3919 |
|
| 47 |
+
| Matching radius (normalized) | 0.022 |
|
| 48 |
+
| Flip TTA | on |
|
| 49 |
+
|
| 50 |
+
**Important:** these numbers were measured **with horizontal-flip test-time augmentation**
|
| 51 |
+
(evaluate the original and mirrored panorama, combine heatmaps with elementwise max). Single-pass
|
| 52 |
+
inference will land somewhat below them; derive your own threshold curve without TTA before
|
| 53 |
+
choosing an operating point. Average Precision is the interpolated area under the full
|
| 54 |
+
precision–recall sweep; precision and recall are reported at the operating threshold.
|
| 55 |
+
|
| 56 |
+
### Erratum: Evaluation Metrics
|
| 57 |
+
|
| 58 |
+
After publication we found that the evaluation protocol described in §3.3 of the paper (and
|
| 59 |
+
implemented at tag [`v1.0-iccv2025`](https://github.com/ProjectSidewalk/RampNet/tree/v1.0-iccv2025))
|
| 60 |
+
differs from standard detection evaluation in two ways that bias precision and recall upward:
|
| 61 |
+
predictions matching an already-claimed ground-truth point were counted as additional true
|
| 62 |
+
positives rather than false positives, and one prediction could satisfy multiple ground-truth
|
| 63 |
+
points. The numbers above use corrected one-to-one matching. For transparency, the originally
|
| 64 |
+
published figures are kept here alongside the corrected ones:
|
| 65 |
+
|
| 66 |
+
| Metric | Originally published | Corrected (this revision) |
|
| 67 |
+
| :--- | :--- | :--- |
|
| 68 |
+
| Precision @ 0.55 | 0.938 | **0.949** |
|
| 69 |
+
| Recall @ 0.55 | 0.935 | **0.873** |
|
| 70 |
+
| Average Precision | 0.9236 | **0.9205** |
|
| 71 |
+
|
| 72 |
+
Swapping only the matching rule on identical model outputs moves precision by −1.0 points and
|
| 73 |
+
recall by −4.4 points; the remainder is reproduction drift (environment, JPEG re-encoding). The
|
| 74 |
+
comparison with prior work is unaffected (both systems were scored under the same protocol, and the
|
| 75 |
+
gap is far larger than the correction). Full write-up:
|
| 76 |
+
[`docs/eval_protocol_verification.html`](https://github.com/ProjectSidewalk/RampNet/blob/main/docs/eval_protocol_verification.html)
|
| 77 |
+
and the repository README's erratum.
|
| 78 |
+
|
| 79 |
+
## Choosing a detection threshold
|
| 80 |
+
|
| 81 |
+
- `0.55` is the recommended default operating point (see evaluation above).
|
| 82 |
+
- Sweeping thresholds: run `stage_two/evaluate.py` in the training repo, which emits full
|
| 83 |
+
precision/recall-vs-confidence curves as CSV.
|
| 84 |
+
- Per-city deployments should calibrate on ~100 locally labeled panoramas; see the repo README's
|
| 85 |
+
"Choosing a Detection Threshold" section.
|
| 86 |
+
|
| 87 |
+
## Requirements
|
| 88 |
+
|
| 89 |
+
Loads via `trust_remote_code`, so a compatible `transformers` is required:
|
| 90 |
+
|
| 91 |
+
- `transformers >= 5.13` — supported (the custom-model auto-class contract this
|
| 92 |
+
version introduced is satisfied; see issue #19).
|
| 93 |
+
- `transformers 5.12.x` — supported.
|
| 94 |
+
|
| 95 |
+
Both are covered by a round-trip load smoke test (`tests/test_hf_load.py` in the
|
| 96 |
+
training repo). Earlier revisions of this model fail to load on
|
| 97 |
+
`transformers >= 5.13` with `AttributeError: ... 'KeypointModel' has no attribute
|
| 98 |
+
'register_for_auto_class'` (or `... has no attribute 'generate'`) — pull the current revision.
|
| 99 |
+
|
| 100 |
+
## Usage
|
| 101 |
+
|
| 102 |
+
*For a step-by-step walkthrough, see the [Google Colab notebook](https://colab.research.google.com/drive/1TOtScud5ac2McXJmg1n_YkOoZBchdn3w?usp=sharing),
|
| 103 |
+
which adds a visualization to the code below.*
|
| 104 |
+
|
| 105 |
+
```python
|
| 106 |
+
import torch
|
| 107 |
+
from transformers import AutoModel
|
| 108 |
+
from PIL import Image
|
| 109 |
+
import numpy as np
|
| 110 |
+
from torchvision import transforms
|
| 111 |
+
from skimage.feature import peak_local_max
|
| 112 |
+
|
| 113 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 114 |
+
model = AutoModel.from_pretrained("projectsidewalk/rampnet-model", trust_remote_code=True).to(DEVICE).eval()
|
| 115 |
+
|
| 116 |
+
preprocess = transforms.Compose([
|
| 117 |
+
transforms.Resize((2048, 4096), interpolation=transforms.InterpolationMode.BILINEAR),
|
| 118 |
+
transforms.ToTensor(),
|
| 119 |
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
| 120 |
+
])
|
| 121 |
+
|
| 122 |
+
img = Image.open("panorama.jpg").convert("RGB")
|
| 123 |
+
with torch.no_grad():
|
| 124 |
+
heatmap = model(preprocess(img).unsqueeze(0).to(DEVICE)).squeeze().cpu().numpy()
|
| 125 |
+
|
| 126 |
+
peaks = peak_local_max(np.clip(heatmap, 0, 1), min_distance=10, threshold_abs=0.55)
|
| 127 |
+
scale_w, scale_h = img.width / heatmap.shape[1], img.height / heatmap.shape[0]
|
| 128 |
+
print([(int(c * scale_w), int(r * scale_h)) for r, c in peaks])
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## Citation
|
| 132 |
+
|
| 133 |
+
```bibtex
|
| 134 |
+
@inproceedings{omeara2025rampnet,
|
| 135 |
+
author = {John S. O'Meara and Jared Hwang and Zeyu Wang and Michael Saugstad and Jon E. Froehlich},
|
| 136 |
+
title = {{RampNet: A Two-Stage Pipeline for Bootstrapping Curb Ramp Detection in Streetscape Images from Open Government Metadata}},
|
| 137 |
+
booktitle = {{ICCV'25 Workshop on Vision Foundation Models and Generative AI for Accessibility: Challenges and Opportunities (ICCV 2025 Workshop)}},
|
| 138 |
+
year = {2025},
|
| 139 |
+
doi = {https://doi.org/10.48550/arXiv.2508.09415},
|
| 140 |
+
}
|
| 141 |
+
```
|
config.json
CHANGED
|
@@ -1,11 +1,33 @@
|
|
| 1 |
-
{
|
| 2 |
-
"
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
"AutoModel": "
|
| 8 |
-
},
|
| 9 |
-
"
|
| 10 |
-
"
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"RampNetModel"
|
| 4 |
+
],
|
| 5 |
+
"auto_map": {
|
| 6 |
+
"AutoConfig": "configuration_rampnet.RampNetConfig",
|
| 7 |
+
"AutoModel": "modeling_rampnet.RampNetModel"
|
| 8 |
+
},
|
| 9 |
+
"dtype": "float32",
|
| 10 |
+
"heatmap_size": [
|
| 11 |
+
512,
|
| 12 |
+
1024
|
| 13 |
+
],
|
| 14 |
+
"image_mean": [
|
| 15 |
+
0.485,
|
| 16 |
+
0.456,
|
| 17 |
+
0.406
|
| 18 |
+
],
|
| 19 |
+
"image_std": [
|
| 20 |
+
0.229,
|
| 21 |
+
0.224,
|
| 22 |
+
0.225
|
| 23 |
+
],
|
| 24 |
+
"input_size": [
|
| 25 |
+
2048,
|
| 26 |
+
4096
|
| 27 |
+
],
|
| 28 |
+
"model_type": "rampnet",
|
| 29 |
+
"recommended_min_distance": 10,
|
| 30 |
+
"recommended_threshold": 0.55,
|
| 31 |
+
"transformers_version": "5.14.1",
|
| 32 |
+
"tta_recommended": true
|
| 33 |
+
}
|
configuration_rampnet.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class RampNetConfig(PretrainedConfig):
|
| 5 |
+
model_type = "rampnet"
|
| 6 |
+
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
input_size=(2048, 4096),
|
| 10 |
+
heatmap_size=(512, 1024),
|
| 11 |
+
image_mean=(0.485, 0.456, 0.406),
|
| 12 |
+
image_std=(0.229, 0.224, 0.225),
|
| 13 |
+
recommended_threshold=0.55,
|
| 14 |
+
recommended_min_distance=10,
|
| 15 |
+
tta_recommended=True,
|
| 16 |
+
**kwargs,
|
| 17 |
+
):
|
| 18 |
+
self.input_size = list(input_size)
|
| 19 |
+
self.heatmap_size = list(heatmap_size)
|
| 20 |
+
self.image_mean = list(image_mean)
|
| 21 |
+
self.image_std = list(image_std)
|
| 22 |
+
self.recommended_threshold = recommended_threshold
|
| 23 |
+
self.recommended_min_distance = recommended_min_distance
|
| 24 |
+
self.tta_recommended = tta_recommended
|
| 25 |
+
super().__init__(**kwargs)
|
model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f2119e3becb0b551fa1470f7b7ba85b82122a3f73a6ed2a85609dd57617866b5
|
| 3 |
+
size 360247780
|
modeling.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
import torch
|
| 3 |
-
import torch.nn as nn
|
| 4 |
-
import timm
|
| 5 |
-
from huggingface_hub import PyTorchModelHubMixin
|
| 6 |
-
|
| 7 |
-
class KeypointModel(nn.Module, PyTorchModelHubMixin):
|
| 8 |
-
def __init__(self, config, **kwargs):
|
| 9 |
-
super().__init__()
|
| 10 |
-
|
| 11 |
-
upsample_size = config.heatmap_size
|
| 12 |
-
|
| 13 |
-
backbone = timm.create_model('convnextv2_base.fcmae_ft_in22k_in1k_384', pretrained=False)
|
| 14 |
-
|
| 15 |
-
self.feature_extractor = nn.Sequential(*list(backbone.children())[:-2])
|
| 16 |
-
in_channels = backbone.num_features
|
| 17 |
-
self.head = nn.Sequential(
|
| 18 |
-
nn.Conv2d(in_channels, 256, kernel_size=3, padding=1),
|
| 19 |
-
nn.ReLU(inplace=True),
|
| 20 |
-
nn.Upsample(size=upsample_size, mode='bilinear', align_corners=False),
|
| 21 |
-
nn.Conv2d(256, 1, kernel_size=1)
|
| 22 |
-
)
|
| 23 |
-
|
| 24 |
-
def forward(self, image):
|
| 25 |
-
features = self.feature_extractor(image)
|
| 26 |
-
heatmap = self.head(features)
|
| 27 |
-
return heatmap
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modeling_rampnet.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import PreTrainedModel
|
| 2 |
+
|
| 3 |
+
from .configuration_rampnet import RampNetConfig
|
| 4 |
+
# rampnet_model.py is copied verbatim from rampnet/model.py by
|
| 5 |
+
# scripts/export_hf_model.py at export time — it is generated, not a fork.
|
| 6 |
+
from .rampnet_model import KeypointModel
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class RampNetModel(PreTrainedModel):
|
| 10 |
+
config_class = RampNetConfig
|
| 11 |
+
main_input_name = "pixel_values"
|
| 12 |
+
|
| 13 |
+
def __init__(self, config):
|
| 14 |
+
super().__init__(config)
|
| 15 |
+
self.model = KeypointModel(
|
| 16 |
+
heatmap_size=tuple(config.heatmap_size),
|
| 17 |
+
pretrained_backbone=False,
|
| 18 |
+
)
|
| 19 |
+
# Required under transformers >= 5.x: initializes loading-related state
|
| 20 |
+
# (e.g. all_tied_weights_keys) that from_pretrained expects on every
|
| 21 |
+
# PreTrainedModel. Harmless no-op extras under 4.x.
|
| 22 |
+
self.post_init()
|
| 23 |
+
|
| 24 |
+
def forward(self, pixel_values):
|
| 25 |
+
"""Returns the predicted curb ramp keypoint heatmap (B, 1, H, W)."""
|
| 26 |
+
return self.model(pixel_values)
|
rampnet_model.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import timm
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
BACKBONE_NAME = 'convnextv2_base.fcmae_ft_in22k_in1k_384'
|
| 5 |
+
|
| 6 |
+
# Panorama model (stage_two): equirectangular 2048x4096 input -> 512x1024 heatmap.
|
| 7 |
+
PANO_INPUT_SIZE = (2048, 4096)
|
| 8 |
+
PANO_HEATMAP_SIZE = (512, 1024)
|
| 9 |
+
|
| 10 |
+
# Crop model (stage_one): perspective 1024x352 input -> 256x88 heatmap.
|
| 11 |
+
CROP_INPUT_SIZE = (1024, 352)
|
| 12 |
+
CROP_HEATMAP_SIZE = (256, 88)
|
| 13 |
+
|
| 14 |
+
IMAGENET_MEAN = [0.485, 0.456, 0.406]
|
| 15 |
+
IMAGENET_STD = [0.229, 0.224, 0.225]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class KeypointModel(nn.Module):
|
| 19 |
+
"""The one canonical RampNet keypoint-heatmap model.
|
| 20 |
+
|
| 21 |
+
The state_dict key layout (feature_extractor as Sequential over
|
| 22 |
+
backbone.children()[:-2], i.e. dropping norm_pre and head) is the layout
|
| 23 |
+
every released checkpoint was trained and saved with — including the
|
| 24 |
+
HuggingFace weights. Do not restructure the modules (e.g. features_only=True
|
| 25 |
+
or named submodules): that changes the keys and breaks strict loading of
|
| 26 |
+
all existing checkpoints.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(self, heatmap_size=PANO_HEATMAP_SIZE, pretrained_backbone=False):
|
| 30 |
+
super().__init__()
|
| 31 |
+
backbone = timm.create_model(BACKBONE_NAME, pretrained=pretrained_backbone)
|
| 32 |
+
self.feature_extractor = nn.Sequential(*list(backbone.children())[:-2])
|
| 33 |
+
in_channels = backbone.num_features
|
| 34 |
+
self.head = nn.Sequential(
|
| 35 |
+
nn.Conv2d(in_channels, 256, kernel_size=3, padding=1),
|
| 36 |
+
nn.ReLU(inplace=True),
|
| 37 |
+
nn.Upsample(size=heatmap_size, mode='bilinear', align_corners=False),
|
| 38 |
+
nn.Conv2d(256, 1, kernel_size=1)
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
def forward(self, image):
|
| 42 |
+
features = self.feature_extractor(image)
|
| 43 |
+
heatmap = self.head(features)
|
| 44 |
+
return heatmap
|