Organize repo: add examples, model code and updated model card
Browse files- .gitattributes +6 -0
- README.md +99 -23
- panner.png → assets/banner.png +0 -0
- best_model13.pth → assets/detection_example.png +2 -2
- examples/inputs/house_and_plate.png +3 -0
- examples/inputs/plate.jpg +0 -0
- examples/inputs/street.png +3 -0
- examples/outputs/house_and_plate_anonymized.png +3 -0
- examples/outputs/plate_anonymized.jpg +3 -0
- examples/outputs/street_anonymized.png +3 -0
- models/__init__.py +0 -0
- models/backbone.py +35 -0
- models/faster_rcnn.py +89 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/detection_example.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/inputs/house_and_plate.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
examples/inputs/street.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
examples/outputs/house_and_plate_anonymized.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/outputs/plate_anonymized.jpg filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/outputs/street_anonymized.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,42 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
<p align="center">
|
| 2 |
-
<img src="
|
| 3 |
</p>
|
| 4 |
|
| 5 |
-
# Vision-AN18
|
| 6 |
|
| 7 |
Privacy-preserving computer vision for detecting and anonymizing sensitive visual information in street images.
|
| 8 |
|
| 9 |
-
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
-
- a Faster R-CNN detection model,
|
| 17 |
-
- an anonymization module,
|
| 18 |
-
- a FastAPI inference service,
|
| 19 |
-
- a simple Gradio demo for testing.
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
| 24 |
-
- House number detection
|
| 25 |
-
- Gaussian blur anonymization
|
| 26 |
-
- Dataset preprocessing pipeline
|
| 27 |
-
- Inference and deployment support
|
| 28 |
-
- GPU-ready training workflow
|
| 29 |
|
| 30 |
## Model
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
## Repository
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
##
|
| 41 |
|
| 42 |
-
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: pytorch
|
| 3 |
+
pipeline_tag: object-detection
|
| 4 |
+
tags:
|
| 5 |
+
- object-detection
|
| 6 |
+
- faster-rcnn
|
| 7 |
+
- torchvision
|
| 8 |
+
- license-plate-detection
|
| 9 |
+
- house-number-detection
|
| 10 |
+
- anonymization
|
| 11 |
+
- privacy
|
| 12 |
+
- street-view
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
<p align="center">
|
| 16 |
+
<img src="assets/banner.png" width="1200" alt="Vision-AN18 banner" />
|
| 17 |
</p>
|
| 18 |
|
| 19 |
+
# Vision-AN18 — Faster R-CNN
|
| 20 |
|
| 21 |
Privacy-preserving computer vision for detecting and anonymizing sensitive visual information in street images.
|
| 22 |
|
| 23 |
+
Vision-AN18 detects **license plates** and **house numbers** in street-level images (Google Street View style), then blurs the detected regions to preserve privacy while keeping the rest of the image useful.
|
| 24 |
+
|
| 25 |
+
## Examples
|
| 26 |
|
| 27 |
+
Original image on the left, output of the anonymization pipeline on the right (Gaussian blur applied to each detected box).
|
| 28 |
|
| 29 |
+
| Original | Anonymized |
|
| 30 |
+
| :---: | :---: |
|
| 31 |
+
|  |  |
|
| 32 |
+
|  |  |
|
| 33 |
+
|  |  |
|
| 34 |
|
| 35 |
+
### Raw detection
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
<p align="center">
|
| 38 |
+
<img src="assets/detection_example.png" width="700" alt="License plate detection example" />
|
| 39 |
+
</p>
|
| 40 |
|
| 41 |
+
> ⚠️ **Known limitations** — license plate detection is reliable. House number detection is still imperfect (scale bias inherited from the SVHN dataset used for this class), and the model can produce false positives on large flat regions such as windows or walls (see the third example).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
## Model
|
| 44 |
|
| 45 |
+
| | |
|
| 46 |
+
| --- | --- |
|
| 47 |
+
| Architecture | Faster R-CNN |
|
| 48 |
+
| Backbone | ResNet-50 + FPN |
|
| 49 |
+
| Framework | PyTorch / torchvision |
|
| 50 |
+
| Classes | `0: background`, `1: license_plate`, `2: house_number` |
|
| 51 |
+
| Input size | min 800 / max 1333 px (resized internally) |
|
| 52 |
+
| Training data | OpenLPR (plates) + SVHN (house numbers) |
|
| 53 |
+
| Training hardware | NVIDIA RTX 5050 |
|
| 54 |
+
|
| 55 |
+
## Usage
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
import torch
|
| 59 |
+
from PIL import Image
|
| 60 |
+
import torchvision.transforms.functional as TF
|
| 61 |
+
from huggingface_hub import snapshot_download
|
| 62 |
+
import sys
|
| 63 |
+
|
| 64 |
+
repo_dir = snapshot_download("ApyHTML19/Faster-RCNN-Vision-ANN18")
|
| 65 |
+
sys.path.insert(0, repo_dir)
|
| 66 |
+
from models.faster_rcnn import create_model
|
| 67 |
+
|
| 68 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 69 |
+
|
| 70 |
+
model = create_model(num_classes=3, pretrained_backbone=False)
|
| 71 |
+
checkpoint = torch.load(f"{repo_dir}/best_model.pth", map_location=device)
|
| 72 |
+
model.load_state_dict(checkpoint.get("model_state_dict", checkpoint))
|
| 73 |
+
model.to(device).eval()
|
| 74 |
+
|
| 75 |
+
CLASS_NAMES = {1: "license_plate", 2: "house_number"}
|
| 76 |
+
|
| 77 |
+
image = Image.open(f"{repo_dir}/examples/inputs/plate.jpg").convert("RGB")
|
| 78 |
+
with torch.inference_mode():
|
| 79 |
+
output = model([TF.to_tensor(image).to(device)])[0]
|
| 80 |
+
|
| 81 |
+
for box, label, score in zip(output["boxes"], output["labels"], output["scores"]):
|
| 82 |
+
if score >= 0.5:
|
| 83 |
+
print(CLASS_NAMES[int(label)], f"{score:.3f}", [round(v) for v in box.tolist()])
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
### Blurring the detections
|
| 87 |
+
|
| 88 |
+
```python
|
| 89 |
+
import cv2
|
| 90 |
|
| 91 |
+
img = cv2.imread(f"{repo_dir}/examples/inputs/plate.jpg")
|
| 92 |
+
for box, score in zip(output["boxes"], output["scores"]):
|
| 93 |
+
if score < 0.5:
|
| 94 |
+
continue
|
| 95 |
+
x1, y1, x2, y2 = map(int, box.tolist())
|
| 96 |
+
img[y1:y2, x1:x2] = cv2.GaussianBlur(img[y1:y2, x1:x2], (51, 51), 0)
|
| 97 |
+
cv2.imwrite("anonymized.jpg", img)
|
| 98 |
+
```
|
| 99 |
|
| 100 |
+
## Repository structure
|
| 101 |
|
| 102 |
+
```text
|
| 103 |
+
Faster-RCNN-Vision-ANN18/
|
| 104 |
+
├── README.md # this model card
|
| 105 |
+
├── best_model.pth # trained checkpoint
|
| 106 |
+
├── models/ # model definition (Faster R-CNN + ResNet-50 FPN backbone)
|
| 107 |
+
│ ├── __init__.py
|
| 108 |
+
│ ├── backbone.py
|
| 109 |
+
│ └── faster_rcnn.py
|
| 110 |
+
├── examples/
|
| 111 |
+
│ ├── inputs/ # original test images
|
| 112 |
+
│ └── outputs/ # anonymized results
|
| 113 |
+
└── assets/ # banner and illustration images
|
| 114 |
+
```
|
| 115 |
|
| 116 |
+
## Intended use
|
| 117 |
|
| 118 |
+
Research and privacy-oriented computer vision experiments: anonymizing license plates and house numbers in street-level imagery before storage or publication. Not intended for surveillance or for identifying individuals.
|
panner.png → assets/banner.png
RENAMED
|
File without changes
|
best_model13.pth → assets/detection_example.png
RENAMED
|
File without changes
|
examples/inputs/house_and_plate.png
ADDED
|
Git LFS Details
|
examples/inputs/plate.jpg
ADDED
|
examples/inputs/street.png
ADDED
|
Git LFS Details
|
examples/outputs/house_and_plate_anonymized.png
ADDED
|
Git LFS Details
|
examples/outputs/plate_anonymized.jpg
ADDED
|
Git LFS Details
|
examples/outputs/street_anonymized.png
ADDED
|
Git LFS Details
|
models/__init__.py
ADDED
|
File without changes
|
models/backbone.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torchvision.models import resnet50, ResNet50_Weights
|
| 4 |
+
from torchvision.models._utils import IntermediateLayerGetter
|
| 5 |
+
from torchvision.ops import FeaturePyramidNetwork
|
| 6 |
+
from torchvision.ops.feature_pyramid_network import LastLevelMaxPool
|
| 7 |
+
class ResNet50FPNBackbone(nn.Module):
|
| 8 |
+
def __init__(self, pretrained=True, out_channels=256):
|
| 9 |
+
super().__init__()
|
| 10 |
+
weights = ResNet50_Weights.DEFAULT if pretrained else None
|
| 11 |
+
resnet = resnet50(weights=weights)
|
| 12 |
+
self.body = IntermediateLayerGetter(
|
| 13 |
+
resnet,
|
| 14 |
+
return_layers={
|
| 15 |
+
"layer1": "c2",
|
| 16 |
+
"layer2": "c3",
|
| 17 |
+
"layer3": "c4",
|
| 18 |
+
"layer4": "c5"
|
| 19 |
+
}
|
| 20 |
+
)
|
| 21 |
+
self.fpn = FeaturePyramidNetwork(
|
| 22 |
+
in_channels_list=[
|
| 23 |
+
256,
|
| 24 |
+
512,
|
| 25 |
+
1024,
|
| 26 |
+
2048
|
| 27 |
+
],
|
| 28 |
+
out_channels=out_channels,
|
| 29 |
+
extra_blocks=LastLevelMaxPool()
|
| 30 |
+
)
|
| 31 |
+
self.out_channels = out_channels
|
| 32 |
+
def forward(self, x):
|
| 33 |
+
features = self.body(x)
|
| 34 |
+
features = self.fpn(features)
|
| 35 |
+
return features
|
models/faster_rcnn.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torchvision.models.detection import FasterRCNN
|
| 3 |
+
from torchvision.models.detection.rpn import AnchorGenerator
|
| 4 |
+
from torchvision.ops import MultiScaleRoIAlign
|
| 5 |
+
from .backbone import ResNet50FPNBackbone
|
| 6 |
+
class VisionAN18FasterRCNN(FasterRCNN):
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
num_classes=3,
|
| 10 |
+
pretrained_backbone=True
|
| 11 |
+
):
|
| 12 |
+
backbone = ResNet50FPNBackbone(
|
| 13 |
+
pretrained=pretrained_backbone,
|
| 14 |
+
out_channels=256
|
| 15 |
+
)
|
| 16 |
+
anchor_generator = AnchorGenerator(
|
| 17 |
+
sizes=(
|
| 18 |
+
(32,),
|
| 19 |
+
(64,),
|
| 20 |
+
(128,),
|
| 21 |
+
(256,),
|
| 22 |
+
(512,)
|
| 23 |
+
),
|
| 24 |
+
aspect_ratios=(
|
| 25 |
+
(0.5, 1.0, 2.0),
|
| 26 |
+
(0.5, 1.0, 2.0),
|
| 27 |
+
(0.5, 1.0, 2.0),
|
| 28 |
+
(0.5, 1.0, 2.0),
|
| 29 |
+
(0.5, 1.0, 2.0)
|
| 30 |
+
)
|
| 31 |
+
)
|
| 32 |
+
roi_pooler = MultiScaleRoIAlign(
|
| 33 |
+
featmap_names=[
|
| 34 |
+
"c2",
|
| 35 |
+
"c3",
|
| 36 |
+
"c4",
|
| 37 |
+
"c5",
|
| 38 |
+
"pool"
|
| 39 |
+
],
|
| 40 |
+
output_size=7,
|
| 41 |
+
sampling_ratio=2
|
| 42 |
+
)
|
| 43 |
+
super().__init__(
|
| 44 |
+
backbone=backbone,
|
| 45 |
+
num_classes=num_classes,
|
| 46 |
+
rpn_anchor_generator=anchor_generator,
|
| 47 |
+
box_roi_pool=roi_pooler,
|
| 48 |
+
min_size=800,
|
| 49 |
+
max_size=1333,
|
| 50 |
+
rpn_pre_nms_top_n_train=2000,
|
| 51 |
+
rpn_pre_nms_top_n_test=1000,
|
| 52 |
+
rpn_post_nms_top_n_train=1000,
|
| 53 |
+
rpn_post_nms_top_n_test=300,
|
| 54 |
+
rpn_nms_thresh=0.7,
|
| 55 |
+
rpn_fg_iou_thresh=0.7,
|
| 56 |
+
rpn_bg_iou_thresh=0.3,
|
| 57 |
+
box_score_thresh=0.05,
|
| 58 |
+
box_nms_thresh=0.5,
|
| 59 |
+
box_detections_per_img=100
|
| 60 |
+
)
|
| 61 |
+
self.class_names = {
|
| 62 |
+
0: "background",
|
| 63 |
+
1: "license_plate",
|
| 64 |
+
2: "house_number"
|
| 65 |
+
}
|
| 66 |
+
def print_architecture(self):
|
| 67 |
+
print("=" * 60)
|
| 68 |
+
print("VISION-AN18 FASTER R-CNN")
|
| 69 |
+
print("=" * 60)
|
| 70 |
+
print("Backbone : ResNet50")
|
| 71 |
+
print("Backbone : Pretrained ImageNet")
|
| 72 |
+
print("Neck : Feature Pyramid Network")
|
| 73 |
+
print("RPN : Region Proposal Network")
|
| 74 |
+
print("ROI Pooler : MultiScale RoI Align")
|
| 75 |
+
print("Detection : Fast R-CNN Head")
|
| 76 |
+
print("Classes : 3")
|
| 77 |
+
print(" 0 : background")
|
| 78 |
+
print(" 1 : license_plate")
|
| 79 |
+
print(" 2 : house_number")
|
| 80 |
+
print("=" * 60)
|
| 81 |
+
def create_model(
|
| 82 |
+
num_classes=3,
|
| 83 |
+
pretrained_backbone=True
|
| 84 |
+
):
|
| 85 |
+
model = VisionAN18FasterRCNN(
|
| 86 |
+
num_classes=num_classes,
|
| 87 |
+
pretrained_backbone=pretrained_backbone
|
| 88 |
+
)
|
| 89 |
+
return model
|