Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
LoyalBlanc committed May 2, 2020
0 parents commit b3c0e00
Show file tree
Hide file tree
Showing 21 changed files with 1,249 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
127 changes: 127 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

.idea
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SPFCN: Select and Prune the Fully Convolutional Networks for Real-time Parking Slot Detection

The pytorch implement of the real-time parking slot detection method SPFCN.

Paper link: https://arxiv.org/abs/2003.11337

## Table of Content
- [Abstract](#Abstract)
- [Usage](#Usage)
- [Performance](#Performance)
- [Demo](#Demo)

## Abstract
For passenger cars equipped with automatic parking function, convolutional neural networks(CNN) are employed to detect parking slots on the panoramic surround view, which is an overhead image synthesized by four calibrated fish-eye images, The accuracy is obtained at the price of low speed or expensive computation equipments, which are sensitive for many car manufacturers. In this paper, the same accuracy is challenged by the proposed parking slot detector, which leverages deep convolutional networks for the faster speed and smaller model while keep the accuracy by simultaneously training and pruning it. To achieve the optimal trade-off, we developed a strategy to select the best receptive fields and prune the redundant channels automatically during training. The proposed model is capable of jointly detecting corners and line features of parking slots while running efficiently in real time on average CPU. Even without any specific computing devices, the model outperforms existing counterparts, at a frame rate of about 30 FPS on a 2.3 GHz CPU core, getting parking slot corner localization error of 1.51±2.14 cm (std. err.) and slot detection accuracy of 98%, generally satisfying the requirements in both speed and accuracy on on-board mobile terminals.

## Usage
Detailed instructions will be given soon.

## Performance
The training and test data set is https://cslinzhang.github.io/deepps/
| Method | Allowable deviation/cm | Parameter size/MB | Precision | Recall |
| :------: | :------: | :------:| :------: | :------: |
| PSD\_L| 16 | 8.38 | 0.9855 | 0.8464 |
| DeepPS| 16 | 255 | **0.9954** | **0.9889** |
| SPFCN(ours)| 6 |**2.39** | 0.9801 | 0.9731 |

## Demo
![image](https://github.com/LoyalBlanc/SPFCN-ParkingSlotDetection/blob/master/Demo/SPFCN-5s.gif)

A one-minute demo video can be seen in Demo/IV2020_SPFCN.mp4


23 changes: 23 additions & 0 deletions SPFCN/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import torch
from torch.backends import cudnn

from .dataset import get_training_set, get_validating_set
from .model.network import SlotNetwork
from .train import auto_train, auto_validate


def setup(seed):
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
cudnn.benchmark = True
cudnn.deterministic = True


def slot_network_training(device_id=1):
# Initial
setup(19960229)
net = SlotNetwork([32, 44, 64, 92, 128], device_id=device_id)

# Train
auto_train(get_training_set(6535, 50, 224, device_id), net, device_id=device_id,
epoch_limit=1000, save_path="parameters/")
40 changes: 40 additions & 0 deletions SPFCN/dataset/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import torch
from torch.utils.data import DataLoader

from .dataset import VisionParkingSlotDataset
from .prefetcher import DataPrefetcher


def get_training_set(data_size: int,
batch_size: int,
resolution: int = 224,
device_id: int = 0):
assert 0 < data_size < 6596 and 0 < batch_size and 0 < resolution

vps_set = VisionParkingSlotDataset(
image_path="/mnt/Airdrop/ps_zhanglin/training/",
label_path="/mnt/Airdrop/ps_zhanglin/training_raw_label/",
data_size=data_size,
resolution=resolution)
if device_id < 0:
return DataLoader(dataset=vps_set, shuffle=True, batch_size=batch_size, num_workers=4)
else:
return DataPrefetcher(device=torch.device('cuda:%d' % device_id),
dataset=vps_set, batch_size=batch_size, shuffle=True)


def get_validating_set(data_size: int,
batch_size: int,
resolution: int = 224,
device_id: int = 0):
assert 0 < data_size < 1538 and 0 < batch_size and 0 < resolution
vps_set = VisionParkingSlotDataset(
image_path="/mnt/Airdrop/ps_zhanglin/testing/all/all/",
label_path="/mnt/Airdrop/ps_zhanglin/testing/all/raw_label/",
data_size=data_size,
resolution=resolution)
if device_id < 0:
return DataLoader(dataset=vps_set, shuffle=True, batch_size=batch_size, num_workers=4)
else:
return DataPrefetcher(device=torch.device('cuda:%d' % device_id),
dataset=vps_set, batch_size=batch_size, shuffle=False)
88 changes: 88 additions & 0 deletions SPFCN/dataset/dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os

import cv2
import numpy as np
from scipy.io import loadmat
from torch.utils.data import Dataset

GAUSSIAN_VALUE = np.array([[0.0111, 0.0388, 0.0821, 0.1054, 0.0821, 0.0388, 0.0111],
[0.0388, 0.1353, 0.2865, 0.3679, 0.2865, 0.1353, 0.0388],
[0.0821, 0.2865, 0.6065, 0.7788, 0.6065, 0.2865, 0.0821],
[0.1054, 0.3679, 0.7788, 1.0000, 0.7788, 0.3679, 0.1054],
[0.0821, 0.2865, 0.6065, 0.7788, 0.6065, 0.2865, 0.0821],
[0.0388, 0.1353, 0.2865, 0.3679, 0.2865, 0.1353, 0.0388],
[0.0111, 0.0388, 0.0821, 0.1054, 0.0821, 0.0388, 0.0111]])


class VisionParkingSlotDataset(Dataset):
def __init__(self, image_path, label_path, data_size, resolution):
self.length = data_size
self.image_list = []
self.label_list = []
index = 0
for item_name in os.listdir(image_path):
item_label = loadmat("%s%s.mat" % (label_path, item_name[:-4]))
slots = item_label['slots']
if len(slots) > 0:
item_image = cv2.resize(cv2.imread(image_path + item_name), (resolution, resolution))
item_image = np.transpose(item_image, (2, 0, 1))
self.image_list.append(item_image)

marks = item_label['marks']
mark_label = self._get_mark_label(marks, slots, resolution)
slot_label = np.zeros([3, resolution, resolution])
for mark in mark_label:
slot_label[0, mark[1] - 3:mark[1] + 4, mark[0] - 3:mark[0] + 4] += GAUSSIAN_VALUE
slot_label[1, mark[1] - 3:mark[1] + 4, mark[0] - 3:mark[0] + 4] += mark[2]
slot_label[2, mark[1] - 3:mark[1] + 4, mark[0] - 3:mark[0] + 4] += mark[3]
self.label_list.append(slot_label)

index += 1
if index == data_size:
break

@staticmethod
def _get_mark_label(marks, slots, resolution):
"""
:param marks: x, y
:param slots: pt1, pt2, _, rotate_angle
:param resolution: 224x224
:return:
"""
temp_mark_label = []
for mark in marks:
mark_x_re, mark_y_re = mark[0] * resolution / 600, mark[1] * resolution / 600
temp_mark_label.append([mark_x_re, mark_y_re, [], []])

for slot in slots:
mark_vector = marks[slot[1] - 1] - marks[slot[0] - 1]
mark_vector_length = np.sqrt(mark_vector[0] ** 2 + mark_vector[1] ** 2)
if mark_vector[0] > 0:
mark_direction = np.arcsin(mark_vector[1] / mark_vector_length)
else:
mark_direction = np.pi - np.arcsin(mark_vector[1] / mark_vector_length)
slot_direction = mark_direction - slot[3] * np.pi / 180
slot_cos = np.cos(slot_direction)
slot_sin = np.sin(slot_direction)

temp_mark_label[slot[0] - 1][2].append(slot_cos)
temp_mark_label[slot[0] - 1][3].append(slot_sin)
temp_mark_label[slot[1] - 1][2].append(slot_cos)
temp_mark_label[slot[1] - 1][3].append(slot_sin)

mark_label = []
for mark in temp_mark_label:
if len(mark[2]) > 0:
mark_cos = np.mean(mark[2])
mark_sin = np.mean(mark[3])
mark_angle_base = np.sqrt(mark_cos ** 2 + mark_sin ** 2)
mark_cos = mark_cos / mark_angle_base
mark_sin = mark_sin / mark_angle_base
mark_label.append([int(round(mark[0])), int(round(mark[1])), mark_cos, mark_sin])
return mark_label

def __getitem__(self, item):
return self.image_list[item], self.label_list[item]

def __len__(self):
return self.length
39 changes: 39 additions & 0 deletions SPFCN/dataset/prefetcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import torch
from torch.utils.data import DataLoader


class DataPrefetcher(object):
def __init__(self, dataset, batch_size, shuffle, device):
self.stream = torch.cuda.Stream(device=device)
self.device = device

self.loader = DataLoader(dataset=dataset, shuffle=shuffle, batch_size=batch_size,
num_workers=4, pin_memory=True)
self.fetcher = None
self.next_images = None
self.next_labels = None

def refresh(self):
self.fetcher = iter(self.loader)
self.preload()

def preload(self):
try:
self.next_images, self.next_labels = next(self.fetcher)
except StopIteration:
self.next_images = None
self.next_labels = None
else:
with torch.cuda.stream(self.stream):
self.next_images = self.next_images.to(self.device).float()
self.next_labels = self.next_labels.to(self.device).float()

def next(self):
torch.cuda.current_stream(device=self.device).wait_stream(self.stream)
current_images = self.next_images
current_labels = self.next_labels
if current_images is not None:
current_images.record_stream(torch.cuda.current_stream(device=self.device))
current_labels.record_stream(torch.cuda.current_stream(device=self.device))
self.preload()
return current_images, current_labels
1 change: 1 addition & 0 deletions SPFCN/model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .network import SlotNetwork
Loading

0 comments on commit b3c0e00

Please sign in to comment.