Commit ·
9ffc678
1
Parent(s): 3d5e43c
Initial upload of YOLO-style face detection model
Browse files- MIT License.txt +21 -0
- README.md +15 -3
- dataset.py +50 -0
- face_detection_checkpoint_epoch40.pth +3 -0
- inference.py +27 -0
- loss.py +16 -0
- model.py +31 -0
- requirements.txt +4 -0
MIT License.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 YOUR NAME
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in
|
| 13 |
+
all copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 21 |
+
THE SOFTWARE.
|
README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Face Detection (WIDER FACE, YOLO-style)
|
| 3 |
+
|
| 4 |
+
## Train
|
| 5 |
+
```
|
| 6 |
+
python train.py
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
## Inference
|
| 10 |
+
```
|
| 11 |
+
python inference.py
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
## Model
|
| 15 |
+
- S=20, B=3 YOLO-style bounding boxes
|
dataset.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import torch
|
| 4 |
+
from torch.utils.data import Dataset
|
| 5 |
+
from PIL import Image
|
| 6 |
+
|
| 7 |
+
class WiderFaceMultiBoxDataset(Dataset):
|
| 8 |
+
def __init__(self, images_dir, annotation_file, S=20, B=3, transform=None):
|
| 9 |
+
self.images_dir = images_dir
|
| 10 |
+
self.annotation_file = annotation_file
|
| 11 |
+
self.transform = transform
|
| 12 |
+
self.S = S
|
| 13 |
+
self.B = B
|
| 14 |
+
self.data = self.parse_annotations()
|
| 15 |
+
|
| 16 |
+
def parse_annotations(self):
|
| 17 |
+
with open(self.annotation_file, "r") as f:
|
| 18 |
+
lines = [l.strip() for l in f]
|
| 19 |
+
data = []
|
| 20 |
+
i = 0
|
| 21 |
+
while i < len(lines):
|
| 22 |
+
fn = lines[i]; i += 1
|
| 23 |
+
num = int(lines[i]); i += 1
|
| 24 |
+
boxes = []
|
| 25 |
+
for _ in range(num):
|
| 26 |
+
x,y,w,h = map(int, lines[i].split()[:4]); i += 1
|
| 27 |
+
if w>0 and h>0: boxes.append((x,y,w,h))
|
| 28 |
+
if boxes: data.append((fn, boxes))
|
| 29 |
+
return data
|
| 30 |
+
|
| 31 |
+
def encode_target(self, boxes, W, H):
|
| 32 |
+
t = torch.zeros((self.S,self.S,self.B*5))
|
| 33 |
+
for x,y,w,h in boxes:
|
| 34 |
+
cx=(x+w/2)/W; cy=(y+h/2)/H; wn=w/W; hn=h/H
|
| 35 |
+
gx=int(cx*self.S); gy=int(cy*self.S)
|
| 36 |
+
if 0<=gx<self.S and 0<=gy<self.S:
|
| 37 |
+
for b in range(self.B):
|
| 38 |
+
if t[gy,gx,b*5+4]==0:
|
| 39 |
+
t[gy,gx,b*5:b*5+5]=torch.tensor([cx,cy,wn,hn,1.0])
|
| 40 |
+
break
|
| 41 |
+
return t
|
| 42 |
+
|
| 43 |
+
def __getitem__(self, idx):
|
| 44 |
+
fn, boxes = self.data[idx]
|
| 45 |
+
img = Image.open(os.path.join(self.images_dir, fn)).convert("RGB")
|
| 46 |
+
W,H = img.size
|
| 47 |
+
if self.transform: img = self.transform(img)
|
| 48 |
+
return img, self.encode_target(boxes,W,H)
|
| 49 |
+
|
| 50 |
+
def __len__(self): return len(self.data)
|
face_detection_checkpoint_epoch40.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fec8368ba1b01937f1f30ed7e01bb641fd9896e70e0b2300daa8103a1658ad84
|
| 3 |
+
size 68801828
|
inference.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import torch, matplotlib.pyplot as plt, matplotlib.patches as patches
|
| 3 |
+
|
| 4 |
+
def draw_prediction(img, pred, S=20, B=3, thr=0.15):
|
| 5 |
+
img_np = img.permute(1,2,0).numpy()
|
| 6 |
+
H,W,_ = img_np.shape
|
| 7 |
+
fig,ax=plt.subplots(1,figsize=(6,6)); ax.imshow(img_np)
|
| 8 |
+
c=0
|
| 9 |
+
for i in range(S):
|
| 10 |
+
for j in range(S):
|
| 11 |
+
for b in range(B):
|
| 12 |
+
off=b*5
|
| 13 |
+
conf=float(torch.sigmoid(pred[i,j,off+4]))
|
| 14 |
+
if conf>thr:
|
| 15 |
+
cx,cy,w,h = [float(pred[i,j,off+k]) for k in range(4)]
|
| 16 |
+
cx*=W; cy*=H; w*=W; h*=H
|
| 17 |
+
x=cx-w/2; y=cy-h/2
|
| 18 |
+
ax.add_patch(patches.Rectangle((x,y),w,h,edgecolor='red',facecolor='none',linewidth=2))
|
| 19 |
+
c+=1
|
| 20 |
+
ax.set_title(f"{c} boxes"); plt.axis('off'); plt.show()
|
| 21 |
+
|
| 22 |
+
def load_and_predict(model_path, img_tensor, model):
|
| 23 |
+
model.load_state_dict(torch.load(model_path, map_location='cpu'))
|
| 24 |
+
model.eval()
|
| 25 |
+
with torch.no_grad():
|
| 26 |
+
out=model(img_tensor.unsqueeze(0)).squeeze(0)
|
| 27 |
+
draw_prediction(img_tensor, out)
|
loss.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
def yolo_style_loss(pred, target, S=20, B=3):
|
| 6 |
+
loss=0
|
| 7 |
+
for b in range(B):
|
| 8 |
+
pb = pred[...,b*5:b*5+4]
|
| 9 |
+
pc = pred[...,b*5+4]
|
| 10 |
+
tb = target[...,b*5:b*5+4]
|
| 11 |
+
tc = target[...,b*5+4]
|
| 12 |
+
pc=torch.sigmoid(pc)
|
| 13 |
+
mask = tc>0
|
| 14 |
+
loss += F.smooth_l1_loss(pb[mask], tb[mask], reduction='sum')
|
| 15 |
+
loss += F.binary_cross_entropy(pc, tc, reduction='sum')
|
| 16 |
+
return loss
|
model.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
|
| 5 |
+
class FaceDetectionMultiBoxCNN(nn.Module):
|
| 6 |
+
def __init__(self, S=20, B=3):
|
| 7 |
+
super().__init__()
|
| 8 |
+
self.S = S
|
| 9 |
+
self.B = B
|
| 10 |
+
self.output_size = S * S * B * 5
|
| 11 |
+
|
| 12 |
+
self.features = nn.Sequential(
|
| 13 |
+
nn.Conv2d(3, 32, 3, 1, 1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2),
|
| 14 |
+
nn.Conv2d(32, 64, 3, 1, 1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2),
|
| 15 |
+
nn.Conv2d(64, 128, 3, 1, 1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2),
|
| 16 |
+
nn.Conv2d(128, 256, 3, 1, 1), nn.BatchNorm2d(256), nn.ReLU(), nn.MaxPool2d(2),
|
| 17 |
+
nn.Conv2d(256, 512, 3, 1, 1), nn.BatchNorm2d(512), nn.ReLU(), nn.MaxPool2d(2),
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
dummy = torch.zeros(1, 3, 320, 320)
|
| 21 |
+
with torch.no_grad():
|
| 22 |
+
flatten = self.features(dummy).view(1, -1).shape[1]
|
| 23 |
+
|
| 24 |
+
self.classifier = nn.Sequential(
|
| 25 |
+
nn.Flatten(),
|
| 26 |
+
nn.Linear(flatten, 1024), nn.ReLU(),
|
| 27 |
+
nn.Linear(1024, self.output_size)
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
def forward(self, x):
|
| 31 |
+
return self.classifier(self.features(x)).view(-1, self.S, self.S, self.B * 5)
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
matplotlib
|
| 4 |
+
Pillow
|