Compare commits
37 Commits
master
...
gait-recog
| Author | SHA1 | Date |
|---|---|---|
|
|
82f34834dc | |
|
|
8dbf077cac | |
|
|
d061e57824 | |
|
|
d1f8646456 | |
|
|
95a60ecc3d | |
|
|
f60fce3855 | |
|
|
9cd19143b4 | |
|
|
c7ecef9037 | |
|
|
e4f18db0fa | |
|
|
55318d0132 | |
|
|
394eb23827 | |
|
|
6a81371e41 | |
|
|
5005870cd4 | |
|
|
76bc24a398 | |
|
|
f34ed24551 | |
|
|
73d27b012d | |
|
|
e44c1810ad | |
|
|
3e8d9d777a | |
|
|
d16fba841b | |
|
|
6d5d1122dd | |
|
|
2f59e2a465 | |
|
|
ab16cf1cff | |
|
|
b23f1a7516 | |
|
|
e5cd9ddb1e | |
|
|
a4996d8f6a | |
|
|
34694b5274 | |
|
|
0fc7a6d3cf | |
|
|
b48374c7e9 | |
|
|
efd5ae9051 | |
|
|
ddf66c7131 | |
|
|
a3c8f9ff45 | |
|
|
5d249046f3 | |
|
|
37ac2fdc76 | |
|
|
5d3f734944 | |
|
|
d71ed681c7 | |
|
|
7afe0ef597 | |
|
|
f569f04fca |
|
|
@ -0,0 +1,192 @@
|
|||
|
||||
import os
|
||||
from scipy import misc as scisc
|
||||
import cv2
|
||||
import numpy as np
|
||||
from warnings import warn
|
||||
from time import sleep
|
||||
import argparse
|
||||
|
||||
from multiprocessing import Pool
|
||||
from multiprocessing import TimeoutError as MP_TimeoutError
|
||||
|
||||
START = "START"
|
||||
FINISH = "FINISH"
|
||||
WARNING = "WARNING"
|
||||
FAIL = "FAIL"
|
||||
|
||||
|
||||
def boolean_string(s):
|
||||
if s.upper() not in {'FALSE', 'TRUE'}:
|
||||
raise ValueError('Not a valid boolean string')
|
||||
return s.upper() == 'TRUE'
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Test')
|
||||
parser.add_argument('--input_path', default='', type=str,
|
||||
help='Root path of raw dataset.')
|
||||
parser.add_argument('--output_path', default='', type=str,
|
||||
help='Root path for output.')
|
||||
parser.add_argument('--log_file', default='./pretreatment.log', type=str,
|
||||
help='Log file path. Default: ./pretreatment.log')
|
||||
parser.add_argument('--log', default=False, type=boolean_string,
|
||||
help='If set as True, all logs will be saved. '
|
||||
'Otherwise, only warnings and errors will be saved.'
|
||||
'Default: False')
|
||||
parser.add_argument('--worker_num', default=1, type=int,
|
||||
help='How many subprocesses to use for data pretreatment. '
|
||||
'Default: 1')
|
||||
opt = parser.parse_args()
|
||||
|
||||
INPUT_PATH = opt.input_path
|
||||
OUTPUT_PATH = opt.output_path
|
||||
IF_LOG = opt.log
|
||||
LOG_PATH = opt.log_file
|
||||
WORKERS = opt.worker_num
|
||||
|
||||
T_H = 64
|
||||
T_W = 64
|
||||
|
||||
|
||||
def log2str(pid, comment, logs):
|
||||
str_log = ''
|
||||
if type(logs) is str:
|
||||
logs = [logs]
|
||||
for log in logs:
|
||||
str_log += "# JOB %d : --%s-- %s\n" % (
|
||||
pid, comment, log)
|
||||
return str_log
|
||||
|
||||
|
||||
def log_print(pid, comment, logs):
|
||||
str_log = log2str(pid, comment, logs)
|
||||
if comment in [WARNING, FAIL]:
|
||||
with open(LOG_PATH, 'a') as log_f:
|
||||
log_f.write(str_log)
|
||||
if comment in [START, FINISH]:
|
||||
if pid % 500 != 0:
|
||||
return
|
||||
print(str_log, end='')
|
||||
|
||||
|
||||
def cut_img(img, seq_info, frame_name, pid):
|
||||
# A silhouette contains too little white pixels
|
||||
# might be not valid for identification.
|
||||
if img.sum() <= 10000:
|
||||
message = 'seq:%s, frame:%s, no data, %d.' % (
|
||||
'-'.join(seq_info), frame_name, img.sum())
|
||||
warn(message)
|
||||
log_print(pid, WARNING, message)
|
||||
return None
|
||||
# Get the top and bottom point
|
||||
y = img.sum(axis=1)
|
||||
y_top = (y != 0).argmax(axis=0)
|
||||
y_btm = (y != 0).cumsum(axis=0).argmax(axis=0)
|
||||
img = img[y_top:y_btm + 1, :]
|
||||
# As the height of a person is larger than the width,
|
||||
# use the height to calculate resize ratio.
|
||||
_r = img.shape[1] / img.shape[0]
|
||||
_t_w = int(T_H * _r)
|
||||
img = cv2.resize(img, (_t_w, T_H), interpolation=cv2.INTER_CUBIC)
|
||||
# Get the median of x axis and regard it as the x center of the person.
|
||||
sum_point = img.sum()
|
||||
sum_column = img.sum(axis=0).cumsum()
|
||||
x_center = -1
|
||||
for i in range(sum_column.size):
|
||||
if sum_column[i] > sum_point / 2:
|
||||
x_center = i
|
||||
break
|
||||
if x_center < 0:
|
||||
message = 'seq:%s, frame:%s, no center.' % (
|
||||
'-'.join(seq_info), frame_name)
|
||||
warn(message)
|
||||
log_print(pid, WARNING, message)
|
||||
return None
|
||||
h_T_W = int(T_W / 2)
|
||||
left = x_center - h_T_W
|
||||
right = x_center + h_T_W
|
||||
if left <= 0 or right >= img.shape[1]:
|
||||
left += h_T_W
|
||||
right += h_T_W
|
||||
_ = np.zeros((img.shape[0], h_T_W))
|
||||
img = np.concatenate([_, img, _], axis=1)
|
||||
img = img[:, left:right]
|
||||
return img.astype('uint8')
|
||||
|
||||
|
||||
def cut_pickle(seq_info, pid):
|
||||
seq_name = '-'.join(seq_info)
|
||||
log_print(pid, START, seq_name)
|
||||
seq_path = os.path.join(INPUT_PATH, *seq_info)
|
||||
out_dir = os.path.join(OUTPUT_PATH, *seq_info)
|
||||
frame_list = os.listdir(seq_path)
|
||||
frame_list.sort()
|
||||
count_frame = 0
|
||||
for _frame_name in frame_list:
|
||||
frame_path = os.path.join(seq_path, _frame_name)
|
||||
img = cv2.imread(frame_path)[:, :, 0]
|
||||
img = cut_img(img, seq_info, _frame_name, pid)
|
||||
if img is not None:
|
||||
# Save the cut img
|
||||
save_path = os.path.join(out_dir, _frame_name)
|
||||
scisc.imsave(save_path, img)
|
||||
count_frame += 1
|
||||
# Warn if the sequence contains less than 5 frames
|
||||
if count_frame < 5:
|
||||
message = 'seq:%s, less than 5 valid data.' % (
|
||||
'-'.join(seq_info))
|
||||
warn(message)
|
||||
log_print(pid, WARNING, message)
|
||||
|
||||
log_print(pid, FINISH,
|
||||
'Contain %d valid frames. Saved to %s.'
|
||||
% (count_frame, out_dir))
|
||||
|
||||
|
||||
pool = Pool(WORKERS)
|
||||
results = list()
|
||||
pid = 0
|
||||
|
||||
print('Pretreatment Start.\n'
|
||||
'Input path: %s\n'
|
||||
'Output path: %s\n'
|
||||
'Log file: %s\n'
|
||||
'Worker num: %d' % (
|
||||
INPUT_PATH, OUTPUT_PATH, LOG_PATH, WORKERS))
|
||||
|
||||
id_list = os.listdir(INPUT_PATH)
|
||||
id_list.sort()
|
||||
# Walk the input path
|
||||
for _id in id_list:
|
||||
seq_type = os.listdir(os.path.join(INPUT_PATH, _id))
|
||||
seq_type.sort()
|
||||
for _seq_type in seq_type:
|
||||
view = os.listdir(os.path.join(INPUT_PATH, _id, _seq_type))
|
||||
view.sort()
|
||||
for _view in view:
|
||||
seq_info = [_id, _seq_type, _view]
|
||||
out_dir = os.path.join(OUTPUT_PATH, *seq_info)
|
||||
os.makedirs(out_dir)
|
||||
results.append(
|
||||
pool.apply_async(
|
||||
cut_pickle,
|
||||
args=(seq_info, pid)))
|
||||
sleep(0.02)
|
||||
pid += 1
|
||||
|
||||
pool.close()
|
||||
unfinish = 1
|
||||
while unfinish > 0:
|
||||
unfinish = 0
|
||||
for i, res in enumerate(results):
|
||||
try:
|
||||
res.get(timeout=0.1)
|
||||
except Exception as e:
|
||||
if type(e) == MP_TimeoutError:
|
||||
unfinish += 1
|
||||
continue
|
||||
else:
|
||||
print('\n\n\nERROR OCCUR: PID ##%d##, ERRORTYPE: %s\n\n\n',
|
||||
i, type(e))
|
||||
raise e
|
||||
pool.join()
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
# GaitSet
|
||||
|
||||
[-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
|
||||
[](https://996.icu)
|
||||
|
||||
GaitSet is a **flexible**, **effective** and **fast** network for cross-view gait recognition. The [paper](https://ieeexplore.ieee.org/document/9351667) has been published on IEEE TPAMI.
|
||||
|
||||
#### Flexible
|
||||
The input of GaitSet is a set of silhouettes.
|
||||
|
||||
- There are **NOT ANY constrains** on an input,
|
||||
which means it can contain **any number** of **non-consecutive** silhouettes filmed under **different viewpoints**
|
||||
with **different walking conditions**.
|
||||
|
||||
- As the input is a set, the **permutation** of the elements in the input
|
||||
will **NOT change** the output at all.
|
||||
|
||||
#### Effective
|
||||
It achieves **Rank@1=95.0%** on [CASIA-B](http://www.cbsr.ia.ac.cn/english/Gait%20Databases.asp)
|
||||
and **Rank@1=87.1%** on [OU-MVLP](http://www.am.sanken.osaka-u.ac.jp/BiometricDB/GaitMVLP.html),
|
||||
excluding identical-view cases.
|
||||
|
||||
#### Fast
|
||||
With 8 NVIDIA 1080TI GPUs, it only takes **7 minutes** to conduct an evaluation on
|
||||
[OU-MVLP](http://www.am.sanken.osaka-u.ac.jp/BiometricDB/GaitMVLP.html) which contains 133,780 sequences
|
||||
and average 70 frames per sequence.
|
||||
|
||||
## What's new
|
||||
The code and checkpoint for OUMVLP dataset have been released.
|
||||
See [OUMVLP](#oumvlp) for details.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.6
|
||||
- PyTorch 0.4+
|
||||
- GPU
|
||||
|
||||
|
||||
## Getting started
|
||||
### Installation
|
||||
|
||||
- (Not necessary) Install [Anaconda3](https://www.anaconda.com/download/)
|
||||
- Install [CUDA 9.0](https://developer.nvidia.com/cuda-90-download-archive)
|
||||
- install [cuDNN7.0](https://developer.nvidia.com/cudnn)
|
||||
- Install [PyTorch](http://pytorch.org/)
|
||||
|
||||
Noted that our code is tested based on [PyTorch 0.4](http://pytorch.org/)
|
||||
|
||||
### Dataset & Preparation
|
||||
Download [CASIA-B Dataset](http://www.cbsr.ia.ac.cn/english/Gait%20Databases.asp)
|
||||
|
||||
**!!! ATTENTION !!! ATTENTION !!! ATTENTION !!!**
|
||||
|
||||
Before training or test, please make sure you have prepared the dataset
|
||||
by this two steps:
|
||||
- **Step1:** Organize the directory as:
|
||||
`your_dataset_path/subject_ids/walking_conditions/views`.
|
||||
E.g. `CASIA-B/001/nm-01/000/`.
|
||||
- **Step2:** Cut and align the raw silhouettes with `pretreatment.py`.
|
||||
(See [pretreatment](#pretreatment) for details.)
|
||||
Welcome to try different ways of pretreatment but note that
|
||||
the silhouettes after pretreatment **MUST have a size of 64x64**.
|
||||
|
||||
Futhermore, you also can test our code on [OU-MVLP Dataset](http://www.am.sanken.osaka-u.ac.jp/BiometricDB/GaitMVLP.html).
|
||||
The number of channels and the training batchsize is slightly different for this dataset.
|
||||
For more detail, please refer to [our paper](https://arxiv.org/abs/1811.06186).
|
||||
|
||||
#### Pretreatment
|
||||
`pretreatment.py` uses the alignment method in
|
||||
[this paper](https://ipsjcva.springeropen.com/articles/10.1186/s41074-018-0039-6).
|
||||
Pretreatment your dataset by
|
||||
```
|
||||
python pretreatment.py --input_path='root_path_of_raw_dataset' --output_path='root_path_for_output'
|
||||
```
|
||||
- `--input_path` **(NECESSARY)** Root path of raw dataset.
|
||||
- `--output_path` **(NECESSARY)** Root path for output.
|
||||
- `--log_file` Log file path. #Default: './pretreatment.log'
|
||||
- `--log` If set as True, all logs will be saved.
|
||||
Otherwise, only warnings and errors will be saved. #Default: False
|
||||
- `--worker_num` How many subprocesses to use for data pretreatment. Default: 1
|
||||
|
||||
### Configuration
|
||||
|
||||
In `config.py`, you might want to change the following settings:
|
||||
- `dataset_path` **(NECESSARY)** root path of the dataset
|
||||
(for the above example, it is "gaitdata")
|
||||
- `WORK_PATH` path to save/load checkpoints
|
||||
- `CUDA_VISIBLE_DEVICES` indices of GPUs
|
||||
|
||||
### Train
|
||||
Train a model by
|
||||
```bash
|
||||
python train.py
|
||||
```
|
||||
- `--cache` if set as TRUE all the training data will be loaded at once before the training start.
|
||||
This will accelerate the training.
|
||||
**Note that** if this arg is set as FALSE, samples will NOT be kept in the memory
|
||||
even they have been used in the former iterations. #Default: TRUE
|
||||
|
||||
### Evaluation
|
||||
Evaluate the trained model by
|
||||
```bash
|
||||
python test.py
|
||||
```
|
||||
- `--iter` iteration of the checkpoint to load. #Default: 80000
|
||||
- `--batch_size` batch size of the parallel test. #Default: 1
|
||||
- `--cache` if set as TRUE all the test data will be loaded at once before the transforming start.
|
||||
This might accelerate the testing. #Default: FALSE
|
||||
|
||||
It will output Rank@1 of all three walking conditions.
|
||||
Note that the test is **parallelizable**.
|
||||
To conduct a faster evaluation, you could use `--batch_size` to change the batch size for test.
|
||||
|
||||
#### OUMVLP
|
||||
Since the huge differences between OUMVLP and CASIA-B, the network setting on OUMVLP is slightly different.
|
||||
- The alternated network's code can be found at `./work/OUMVLP_network`. Use them to replace the corresponding files in `./model/network`.
|
||||
- The checkpoint can be found [here](https://1drv.ms/u/s!AurT2TsSKdxQuWN8drzIv_phTR5m?e=Gfbl3m).
|
||||
- In `./config.py`, modify `'batch_size': (8, 16)` into `'batch_size': (32,16)`.
|
||||
- Prepare your OUMVLP dataset according to the instructions in [Dataset & Preparation](#dataset--preparation).
|
||||
|
||||
## To Do List
|
||||
- Transformation: The script for transforming a set of silhouettes into a discriminative representation.
|
||||
|
||||
## Authors & Contributors
|
||||
GaitSet is authored by
|
||||
[Hanqing Chao](https://www.linkedin.com/in/hanqing-chao-9aa42412b/),
|
||||
[Yiwei He](https://www.linkedin.com/in/yiwei-he-4a6a6bbb/),
|
||||
[Junping Zhang](http://www.pami.fudan.edu.cn/~jpzhang/)
|
||||
and JianFeng Feng from Fudan Universiy.
|
||||
[Junping Zhang](http://www.pami.fudan.edu.cn/~jpzhang/)
|
||||
is the corresponding author.
|
||||
The code is developed by
|
||||
[Hanqing Chao](https://www.linkedin.com/in/hanqing-chao-9aa42412b/)
|
||||
and [Yiwei He](https://www.linkedin.com/in/yiwei-he-4a6a6bbb/).
|
||||
Currently, it is being maintained by
|
||||
[Hanqing Chao](https://www.linkedin.com/in/hanqing-chao-9aa42412b/)
|
||||
and Kun Wang.
|
||||
|
||||
|
||||
## Citation
|
||||
Please cite these papers in your publications if it helps your research:
|
||||
```
|
||||
@ARTICLE{chao2019gaitset,
|
||||
author={Chao, Hanqing and Wang, Kun and He, Yiwei and Zhang, Junping and Feng, Jianfeng},
|
||||
journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},
|
||||
title={GaitSet: Cross-view Gait Recognition through Utilizing Gait as a Deep Set},
|
||||
year={2021},
|
||||
pages={1-1},
|
||||
doi={10.1109/TPAMI.2021.3057879}}
|
||||
```
|
||||
Link to paper:
|
||||
- [GaitSet: Cross-view Gait Recognition through Utilizing Gait as a Deep Set](https://ieeexplore.ieee.org/document/9351667)
|
||||
|
||||
|
||||
## License
|
||||
GaitSet is freely available for free non-commercial use, and may be redistributed under these conditions.
|
||||
For commercial queries, contact [Junping Zhang](http://www.pami.fudan.edu.cn/~jpzhang/).
|
||||
140
README.md
140
README.md
|
|
@ -1,144 +1,16 @@
|
|||
# 基于 OpenPifPaf 的多摄像头多人实时跌倒等异常行为识别预警应用研究
|
||||
<p align="center">
|
||||
<img src="https://git.trustie.net/pkwhiuqat/HumanFallDetectionLSTM/raw/branch/master/documents/outfallingdown1.gif?raw=true" alt="outfallingdown"/>
|
||||
<p align="center">
|
||||
<img src="https://git.trustie.net/pkwhiuqat/HumanFallDetectionLSTM/raw/branch/master/documents/outfallingdown2.gif?raw=true" alt="outfallingdown" style="zoom:90%;"/>
|
||||
|
||||
|
||||
利用 OpenPifPaf 对输入视频进行人体姿势估计,然后通过长短时记忆神经网络(LSTM)从前面得到的姿势信息中提取五个时间和空间特征(作为当前的 *X*<sub>n</sub> 输入)以预测"跌倒"动作,支持多摄像头和多人实时检测。模型在 UP-Fall Detection 数据集上训练,基于 PyTorch 实现。
|
||||
|
||||
<p align="center">
|
||||
<img src="https://git.trustie.net/pkwhiuqat/HumanFallDetectionLSTM/raw/branch/master/flowchart.png?raw=true" alt="LSTM" style="zoom:68%;" />
|
||||
<p align="center">
|
||||
<img src="https://git.trustie.net/pkwhiuqat/HumanFallDetectionLSTM/raw/branch/master/LSTM.png?raw=true" alt="LSTM" style="zoom:45%;" />
|
||||
|
||||
# 基于 OpenPifPaf 的多摄像头、多人实时跌倒检测模型
|
||||
利用 OpenPifPaf 对输入视频进行人体姿势估计,然后通过长短时记忆神经网络(LSTM)从前面得到的姿势信息中提取五个时间和空间特征以预测"跌倒"动作,支持多摄像头和多人实时检测。
|
||||
## 检测实例见 examples 文件夹
|
||||
## 安装
|
||||
|
||||
```shell script
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 使用
|
||||
```shell script
|
||||
python fall_detector.py --num_cams=1
|
||||
python3 fall_detector.py --num_cams=1
|
||||
```
|
||||
|
||||
|
||||
## 完整运行代码
|
||||
|
||||
usage: fall_detector.py [-h] [--seed-threshold SEED_THRESHOLD]
|
||||
[--instance-threshold INSTANCE_THRESHOLD]
|
||||
[--keypoint-threshold KEYPOINT_THRESHOLD]
|
||||
[--decoder-workers DECODER_WORKERS]
|
||||
[--dense-connections]
|
||||
[--dense-coupling DENSE_COUPLING] [--caf-seeds]
|
||||
[--no-force-complete-pose]
|
||||
[--profile-decoder [PROFILE_DECODER]]
|
||||
[--cif-th CIF_TH] [--caf-th CAF_TH]
|
||||
[--connection-method {max,blend}] [--greedy]
|
||||
[--checkpoint CHECKPOINT] [--basenet BASENET]
|
||||
[--headnets HEADNETS [HEADNETS ...]] [--no-pretrain]
|
||||
[--two-scale] [--multi-scale] [--no-multi-scale-hflip]
|
||||
[--cross-talk CROSS_TALK] [--no-download-progress]
|
||||
[--head-dropout HEAD_DROPOUT] [--head-quad HEAD_QUAD]
|
||||
[--resolution RESOLUTION] [--resize RESIZE]
|
||||
[--num_cams NUM_CAMS] [--video VIDEO] [--debug]
|
||||
[--disable_cuda] [--plot_graph] [--joints]
|
||||
[--skeleton] [--coco_points] [--save_output]
|
||||
[--fps FPS] [--out-path OUT_PATH]
|
||||
[--input_direct INPUT_DIRECT]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--resolution RESOLUTION
|
||||
Resolution prescale factor from 640x480. Will be
|
||||
rounded to multiples of 16. (default: 0.4)
|
||||
--resize RESIZE Force input image resize. Example WIDTHxHEIGHT.
|
||||
(default: None)
|
||||
--num_cams NUM_CAMS Number of Cameras. (default: 1)
|
||||
--video VIDEO Path to the video file. For single video fall
|
||||
detection(--num_cams=1), save your videos as abc.xyz
|
||||
and set --video=abc.xyz For 2 video fall
|
||||
detection(--num_cams=2), save your videos as abc1.xyz
|
||||
& abc2.xyz and set --video=abc.xyz (default: None)
|
||||
--debug debug messages and autoreload (default: False)
|
||||
--disable_cuda disables cuda support and runs from gpu (default:
|
||||
False)
|
||||
|
||||
decoder configuration:
|
||||
--seed-threshold SEED_THRESHOLD
|
||||
minimum threshold for seeds (default: 0.5)
|
||||
--instance-threshold INSTANCE_THRESHOLD
|
||||
filter instances by score (default: 0.2)
|
||||
--keypoint-threshold KEYPOINT_THRESHOLD
|
||||
filter keypoints by score (default: None)
|
||||
--decoder-workers DECODER_WORKERS
|
||||
number of workers for pose decoding (default: None)
|
||||
--dense-connections use dense connections (default: False)
|
||||
--dense-coupling DENSE_COUPLING
|
||||
dense coupling (default: 0.01)
|
||||
--caf-seeds [experimental] (default: False)
|
||||
--no-force-complete-pose
|
||||
--profile-decoder [PROFILE_DECODER]
|
||||
specify out .prof file or nothing for default file
|
||||
name (default: None)
|
||||
|
||||
CifCaf decoders:
|
||||
--cif-th CIF_TH cif threshold (default: 0.1)
|
||||
--caf-th CAF_TH caf threshold (default: 0.1)
|
||||
--connection-method {max,blend}
|
||||
connection method to use, max is faster (default:
|
||||
blend)
|
||||
--greedy greedy decoding (default: False)
|
||||
|
||||
network configuration:
|
||||
--checkpoint CHECKPOINT
|
||||
Load a model from a checkpoint. Use "resnet50",
|
||||
"shufflenetv2k16w" or "shufflenetv2k30w" for
|
||||
pretrained OpenPifPaf models. (default: None)
|
||||
--basenet BASENET base network, e.g. resnet50 (default: None)
|
||||
--headnets HEADNETS [HEADNETS ...]
|
||||
head networks (default: None)
|
||||
--no-pretrain create model without ImageNet pretraining (default: True)
|
||||
--two-scale [experimental] (default: False)
|
||||
--multi-scale [experimental] (default: False)
|
||||
--no-multi-scale-hflip
|
||||
[experimental] (default: True)
|
||||
--cross-talk CROSS_TALK
|
||||
[experimental] (default: 0.0)
|
||||
--no-download-progress
|
||||
suppress model download progress bar (default: True)
|
||||
|
||||
head:
|
||||
--head-dropout HEAD_DROPOUT
|
||||
[experimental] zeroing probability of feature in head
|
||||
input (default: 0.0)
|
||||
--head-quad HEAD_QUAD
|
||||
number of times to apply quad (subpixel conv) to heads
|
||||
(default: 1)
|
||||
|
||||
Visualisation:
|
||||
--plot_graph Plot the graph of features extracted from keypoints of
|
||||
pose. (default: False)
|
||||
--joints Draw joints keypoints on the output video. (default: True)
|
||||
--skeleton Draw skeleton on the output video. (default: True)
|
||||
--coco_points Visualises the COCO points of the human pose. (default: False)
|
||||
--save_output Save the result in a video file. Output videos are
|
||||
saved in the same directory as input videos with "out"
|
||||
appended at the start of the title (default: False)
|
||||
--fps FPS FPS for the output video. (default: 18)
|
||||
--out-path OUT_PATH Save the output video at the path specified. .avi file
|
||||
format. (default: result.avi)
|
||||
--input_direct INPUT_DIRECT
|
||||
Save the input link to images directory. (default: None)
|
||||
- 模型输入可以直接为摄像头作为视频源或者用下载好的视频作为视频源。
|
||||
- 如果在非服务器端可以通过设置在窗口进行实时画面的显示。
|
||||
|
||||
## 参考
|
||||
- [OpenPifPaf](https://github.com/openpifpaf/openpifpaf)
|
||||
- [UP-fall detection Dataset](https://dx.doi.org/10.3390/s19091988)
|
||||
- [Multi-camera, multi-person, and real-time fall detection using long short term memory](https://doi.org/10.1117/12.2580700)
|
||||
- Lei Wang, Du Q. Huynh, Piotr Koniusz. A Comparative Review of Recent Kinect-based Action Recognition Algorithms[J]. IEEE TRANSACTIONS ON IMAGE PROCESSING,2019.
|
||||
- Nusrat Tasnim , Mohammad Khairul Islam and Joong-Hwan Baek. Deep Learning Based Human Activity Recognition Using Spatio-Temporal Image Formation of Skeleton Joints[J].applied sciences.
|
||||
- Mickael Delamare, Cyril Laville, Adnane Cabani and Houcine Chafouk. Graph Convolutional Networks Skeleton-based Action Recognition for Continuous Data Stream: A Sliding Window Approach[J]. 16th International Conference on Computer Vision Theory and Applications.
|
||||
- Tasweer Ahmad, Lianwen Jin, Xin Zhang, Songxuan Lai, Guozhi Tang, and Luojun Lin. Graph Convolutional Neural Network for Human Action Recognition: A Comprehensive Survey[J].
|
||||
- Zehua Sun, Jun Liu, Qiuhong Ke, Hossein Rahmani, Mohammed Bennamoun, and Gang Wang. Human Action Recognition from Various Data Modalities: A Review[J].
|
||||
https://github.com/openpifpaf/openpifpaf
|
||||
|
|
@ -35,6 +35,7 @@ def get_source(args):
|
|||
logging.debug('Image shape:', img.shape)
|
||||
return cam, tagged_df
|
||||
|
||||
|
||||
def resize(img, resize, resolution):
|
||||
# Resize the video
|
||||
if resize is None:
|
||||
|
|
@ -123,8 +124,10 @@ def extract_keypoints_parallel(queue, args, self_counter, other_counter, consecu
|
|||
queue.put(None)
|
||||
return
|
||||
|
||||
|
||||
###################################################### Post human estimation ###########################################################
|
||||
|
||||
|
||||
def show_tracked_img(img_dict, ip_set, num_matched, output_video, args):
|
||||
img = img_dict["img"]
|
||||
tagged_df = img_dict["tagged_df"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
conf = {
|
||||
"WORK_PATH": "./work",
|
||||
"CUDA_VISIBLE_DEVICES": "0,1,2,3",
|
||||
"data": {
|
||||
'dataset_path': "your_dataset_path",
|
||||
'resolution': '64',
|
||||
'dataset': 'CASIA-B',
|
||||
# In CASIA-B, data of subject #5 is incomplete.
|
||||
# Thus, we ignore it in training.
|
||||
# For more detail, please refer to
|
||||
# function: utils.data_loader.load_data
|
||||
'pid_num': 73,
|
||||
'pid_shuffle': False,
|
||||
},
|
||||
"model": {
|
||||
'hidden_dim': 256,
|
||||
'lr': 1e-4,
|
||||
'hard_or_full_trip': 'full',
|
||||
'batch_size': (8, 16),
|
||||
'restore_iter': 0,
|
||||
'total_iter': 80000,
|
||||
'margin': 0.2,
|
||||
'num_workers': 3,
|
||||
'frame_num': 30,
|
||||
'model_name': 'GaitSet',
|
||||
},
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
flowchart.png
BIN
flowchart.png
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
|
@ -0,0 +1,382 @@
|
|||
from scipy.misc import imresize
|
||||
|
||||
import utils
|
||||
import numpy as np
|
||||
|
||||
from scipy import ndimage
|
||||
from utils import to_int
|
||||
|
||||
|
||||
def get_random_transform_params(input_shape, rotation_range = 0., height_shift_range = 0., width_shift_range = 0.,
|
||||
shear_range = 0., zoom_range = (1, 1), horizontal_flip = False, resize_range = None,
|
||||
distortion_prob = 0., additive_gaussian_noise_range = None, multiplication_gaussian = 0,
|
||||
transform_colorspace_param = None, transform_colorspace_bounds = (-1, 1)):
|
||||
"""
|
||||
This closure function returns generative function that gets random instance trough parameter and
|
||||
together with closed input parameters generates random parameters for transformation matrix.
|
||||
|
||||
:param distortion_prob: Probability of the downsampling and upsampling of the image
|
||||
:param resize_range: Defines uniform interval of downsampling factor
|
||||
:param input_shape: Shape of images to be transformed with matrix with this parameters
|
||||
:param rotation_range: Interval of rotation in degrees (used for in both direction)
|
||||
:param height_shift_range: Value of two-sided interval of random shift in vertical direction
|
||||
:param width_shift_range: Value of two-sided interval of random shift in horizontal direction
|
||||
:param shear_range: Value of two-sided interval of random shear in horizontal direction
|
||||
:param zoom_range: Tuple with 2 values representing range of random zoom (values > 1.0 is for zoom out)
|
||||
:param horizontal_flip: Whether do random horizontal flip image
|
||||
:return: Function that with given random instance generates random parameters for transformation matrix
|
||||
"""
|
||||
|
||||
def get_instance(rnd):
|
||||
U = rnd.uniform
|
||||
N = rnd.normal
|
||||
|
||||
rr = rotation_range
|
||||
hs = height_shift_range
|
||||
ws = width_shift_range
|
||||
sr = shear_range
|
||||
agn = additive_gaussian_noise_range
|
||||
dp = distortion_prob
|
||||
mg = multiplication_gaussian
|
||||
tcp = transform_colorspace_param
|
||||
tcb = transform_colorspace_bounds
|
||||
|
||||
return {
|
||||
'input_shape': input_shape,
|
||||
'theta': np.pi / 180 * U(-rr, rr) if rr else 0,
|
||||
'ty': U(-hs, hs) * input_shape[0] if hs else 0,
|
||||
'tx': U(-ws, ws) * input_shape[1] if ws else 0,
|
||||
'shear': U(-sr, sr) if shear_range else 0,
|
||||
'z': U(zoom_range[0], zoom_range[1]) if zoom_range != (1, 1) else 1,
|
||||
'h_flip': rnd.rand() < 0.5 if horizontal_flip else False,
|
||||
'add_noise': N(0, U(agn[0], agn[1]), input_shape) if agn is not None else None,
|
||||
'resize': U(*resize_range) if U(0, 1) < dp else None,
|
||||
'resize_smooth': U(0, 1) < 0.5,
|
||||
'mul': N(1, mg) if mg > 0 else None,
|
||||
'color_m': utils.crop_value(N(tcp[0], tcp[1], (3, 3)), tcb) if tcp is not None else None,
|
||||
'agn': agn
|
||||
}
|
||||
|
||||
return get_instance
|
||||
|
||||
|
||||
def assemble_transformation_matrix(input_shape, theta = 0, tx = 0, ty = 0, shear = 0, z = 1):
|
||||
"""
|
||||
Creates transformation matrix with given parameters. That resulting matrix has origin in centre of the image
|
||||
|
||||
:param input_shape: Shape of images to be transformed with matrix. Origin of transformation matrix is set
|
||||
in the middle of image.
|
||||
|
||||
:param theta: Rotation in radians
|
||||
:param tx: Translation in X axis
|
||||
:param ty: Translation in Y axis
|
||||
:param shear: Shear in horizontal direction
|
||||
:param z: Image zoom
|
||||
:return: Transformation matrix
|
||||
"""
|
||||
|
||||
def transform_matrix_offset_center(matrix, x, y):
|
||||
"""
|
||||
Creates translation matrix from input matrix with origin in the centre of image
|
||||
|
||||
:param matrix: Input matrix
|
||||
:param x: Width of the image
|
||||
:param y: Height of the image
|
||||
:return: Returns shifted input matrix with origin in [y/2, x/2]
|
||||
"""
|
||||
o_x = float(x) / 2 + 0.5
|
||||
o_y = float(y) / 2 + 0.5
|
||||
offset_matrix = np.array([[1, 0, o_x], [0, 1, o_y], [0, 0, 1]])
|
||||
reset_matrix = np.array([[1, 0, -o_x], [0, 1, -o_y], [0, 0, 1]])
|
||||
t_matrix = np.dot(np.dot(offset_matrix, matrix), reset_matrix)
|
||||
return t_matrix
|
||||
|
||||
rotation_matrix = np.array([[np.cos(theta), -np.sin(theta), 0],
|
||||
[np.sin(theta), np.cos(theta), 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
translation_matrix = np.array([[1, 0, ty],
|
||||
[0, 1, tx],
|
||||
[0, 0, 1]])
|
||||
|
||||
shear_matrix = np.array([[1, -np.sin(shear), 0],
|
||||
[0, np.cos(shear), 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
zoom_matrix = np.array([[z, 0, 0],
|
||||
[0, z, 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
# Assembling transformation matrix
|
||||
transform_matrix = np.dot(np.dot(np.dot(rotation_matrix, translation_matrix), shear_matrix), zoom_matrix)
|
||||
|
||||
# Set origin of transformation to center of the image
|
||||
h, w = input_shape[0], input_shape[1]
|
||||
transform_matrix = transform_matrix_offset_center(transform_matrix, h, w)
|
||||
|
||||
return transform_matrix
|
||||
|
||||
|
||||
def transform(v, t_matrix, h_flip = False, add_noise = None, resize = None, resize_smooth = None,
|
||||
mul = None, color_m = None):
|
||||
"""
|
||||
Transform image with (inverted) transformation matrix
|
||||
|
||||
:param v: Input image to be transformed
|
||||
:param t_matrix: Transformation matrix
|
||||
:param h_flip: Whether do horizontal flip
|
||||
:return: Transformed image
|
||||
"""
|
||||
|
||||
def apply_transform(x, transform_matrix, channel_index = 0, fill_mode = 'nearest', cval = 0.):
|
||||
x = np.rollaxis(x, channel_index, 0)
|
||||
final_affine_matrix = transform_matrix[:2, :2]
|
||||
final_offset = transform_matrix[:2, 2]
|
||||
channel_images = [ndimage.interpolation.affine_transform(x_channel, final_affine_matrix,
|
||||
final_offset, order = 2, mode = fill_mode,
|
||||
cval = cval)
|
||||
for x_channel in x]
|
||||
x = np.stack(channel_images, axis = 0)
|
||||
x = np.rollaxis(x, 0, channel_index + 1)
|
||||
|
||||
return x
|
||||
|
||||
def flip_axis(x, axis):
|
||||
x = np.asarray(x).swapaxes(axis, 0)
|
||||
x = x[::-1, ...]
|
||||
x = x.swapaxes(0, axis)
|
||||
return x
|
||||
|
||||
v = apply_transform(v, t_matrix, 2)
|
||||
|
||||
if h_flip:
|
||||
v = flip_axis(v, 1)
|
||||
|
||||
if color_m is not None or mul is not None or add_noise is not None:
|
||||
v = v.astype(np.float32)
|
||||
shape = v.shape
|
||||
|
||||
if mul is not None:
|
||||
v *= mul
|
||||
|
||||
if color_m is not None:
|
||||
v = np.reshape(v, [-1, 3])
|
||||
v = np.matmul(v, color_m)
|
||||
v = np.reshape(v, shape)
|
||||
|
||||
if add_noise is not None:
|
||||
v += add_noise
|
||||
|
||||
if resize is not None:
|
||||
interpolation = 'bilinear' if resize_smooth else 'nearest'
|
||||
|
||||
v = imresize(v, (resize * np.array(shape[:2])).astype(np.uint16), interpolation)
|
||||
v = imresize(v, shape[:2], interpolation)
|
||||
|
||||
v = utils.crop_value(v, [np.zeros(shape), np.ones(shape) * 255])
|
||||
|
||||
return v.astype(np.uint8)
|
||||
|
||||
|
||||
def crop_data(img, labels, new_img_size, new_label_size = None, crop_label = True):
|
||||
"""
|
||||
Both images and labels will be cropped to match the given size
|
||||
|
||||
:param img: Images to be cropped
|
||||
:param labels: Labels to be cropped
|
||||
:param new_img_size: New image size
|
||||
:param new_label_size: New labels size
|
||||
:return: Cropped image and labels
|
||||
"""
|
||||
|
||||
img_size = img.shape[-3]
|
||||
r = to_int((img_size - new_img_size) / 2)
|
||||
|
||||
img = img[..., r:r + new_img_size, r:r + new_img_size, :]
|
||||
|
||||
if crop_label:
|
||||
labels -= r
|
||||
|
||||
if new_label_size is not None:
|
||||
labels = np.array((labels / new_img_size) * new_label_size, dtype = np.int32)
|
||||
|
||||
return img, labels
|
||||
|
||||
|
||||
def flip_body_joints(points):
|
||||
"""
|
||||
Change semantic of labels after flip transformation - i.e. left leg will be now right and so on.
|
||||
|
||||
:param points: Body joints to be changed
|
||||
"""
|
||||
|
||||
def swap(a, b):
|
||||
points[:, [a, b]] = points[:, [b, a]]
|
||||
|
||||
# Leg
|
||||
swap(0, 5)
|
||||
swap(1, 4)
|
||||
swap(2, 3)
|
||||
|
||||
# Arm
|
||||
swap(10, 15)
|
||||
swap(11, 14)
|
||||
swap(12, 13)
|
||||
|
||||
|
||||
def generate_random__transformation(X, rseed = 0, t_params_f = None):
|
||||
rnd = np.random.RandomState(rseed)
|
||||
|
||||
if not t_params_f:
|
||||
raise Exception('No attributes given!')
|
||||
|
||||
n = X.shape[0]
|
||||
X_t = []
|
||||
|
||||
t_params = t_params_f(rnd)
|
||||
h_flip = t_params.pop('h_flip')
|
||||
add_noise = t_params.pop('add_noise')
|
||||
resize = t_params.pop('resize')
|
||||
mul = t_params.pop('mul')
|
||||
agn = t_params.pop('agn')
|
||||
color_m = t_params.pop('color_m')
|
||||
resize_smooth = t_params.pop('resize_smooth')
|
||||
|
||||
t_matrix = assemble_transformation_matrix(**t_params)
|
||||
|
||||
for k in range(n):
|
||||
inp = np.squeeze(X[k])
|
||||
|
||||
if agn is not None:
|
||||
gauss = rnd.normal(0, rnd.uniform(agn[0], agn[1]), inp.shape)
|
||||
else:
|
||||
gauss = None
|
||||
|
||||
x_t = transform(inp, t_matrix, h_flip, gauss, resize, resize_smooth, mul, color_m)
|
||||
X_t.append(x_t)
|
||||
|
||||
return np.array(X_t)
|
||||
|
||||
|
||||
def generate_random_sequences(X, Y, sequence_size = 32, shift = 16, rseed = 0, final_size = None,
|
||||
t_params_f = None, final_heatmap_size = None):
|
||||
rnd = np.random.RandomState(rseed)
|
||||
|
||||
if not t_params_f:
|
||||
raise Exception('No attributes given!')
|
||||
|
||||
if final_size is None:
|
||||
final_size = min(X.shape[2], X.shape[3])
|
||||
|
||||
n = X.shape[0]
|
||||
perm = rnd.permutation(range(0, n, shift))
|
||||
perm_n = perm.shape[0]
|
||||
|
||||
for idx in range(perm_n):
|
||||
b = range(perm[idx], min(perm[idx] + sequence_size, n))
|
||||
|
||||
X_t = []
|
||||
Y_t = []
|
||||
|
||||
t_params = t_params_f(rnd)
|
||||
h_flip = t_params.pop('h_flip')
|
||||
add_noise = t_params.pop('add_noise')
|
||||
resize = t_params.pop('resize')
|
||||
mul = t_params.pop('mul')
|
||||
agn = t_params.pop('agn')
|
||||
color_m = t_params.pop('color_m')
|
||||
resize_smooth = t_params.pop('resize_smooth')
|
||||
|
||||
t_matrix = assemble_transformation_matrix(**t_params)
|
||||
|
||||
for k in b:
|
||||
inp = np.squeeze(X[k])
|
||||
|
||||
if agn is not None:
|
||||
gauss = rnd.normal(0, rnd.uniform(agn[0], agn[1]), inp.shape)
|
||||
else:
|
||||
gauss = None
|
||||
|
||||
x_t = transform(inp, t_matrix, h_flip, gauss, resize, resize_smooth, mul, color_m)
|
||||
y_t = utils.get_affine_transform(np.squeeze(Y[k]), np.linalg.inv(t_matrix)) if Y is not None else None
|
||||
|
||||
x_t, y_t = crop_data(x_t, y_t, final_size, final_heatmap_size)
|
||||
|
||||
X_t.append(x_t)
|
||||
if Y is not None:
|
||||
if h_flip:
|
||||
y_t[1, :] = (final_size if final_heatmap_size is None else final_heatmap_size) - y_t[1, :]
|
||||
flip_body_joints(y_t)
|
||||
|
||||
Y_t.append(y_t)
|
||||
|
||||
if Y is not None:
|
||||
yield np.array(X_t), np.array(Y_t), idx
|
||||
else:
|
||||
yield np.array(X_t), idx
|
||||
|
||||
|
||||
def generate_minibatches(X, Y = None, batch_size = 32, rseed = 0,
|
||||
final_size = None, t_params_f = None, final_heatmap_size = None):
|
||||
"""
|
||||
This function splits whole input batch of images into minibatches of given size. All images in batch are
|
||||
transformed using affine transformations in order to prevent over-fitting during training.
|
||||
|
||||
:param X: Batch of input images to be divided. It has to be 4D tensor [batch, channel, height, width]
|
||||
:param Y: Labels of input images (joint positions on heatmap). 3D tensor [batch, image dimension, joint].
|
||||
E.g. joint with index 4 present in 10th image (i.e. index 9) that is in position [50, 80] is in
|
||||
indexes: Y[9, :, 4] == [50, 80]
|
||||
:param batch_size: Size of each mini-batch
|
||||
:param rseed: Random seed
|
||||
:param t_params_f: Function that generates parameters for transformation matrix (see get_random_transform_params)
|
||||
:param final_size: Transformed images are cropped to match the given size
|
||||
:param final_heatmap_size: Size of heatmaps
|
||||
:return: Sequence of randomly ordered and transformed mini-batches
|
||||
"""
|
||||
|
||||
rnd = np.random.RandomState(rseed)
|
||||
|
||||
if not t_params_f:
|
||||
raise Exception('No attributes given!')
|
||||
|
||||
if final_size is None:
|
||||
final_size = min(X.shape[2], X.shape[3])
|
||||
|
||||
n = X.shape[0]
|
||||
perm = rnd.permutation(n)
|
||||
|
||||
for idx in range(0, n, batch_size):
|
||||
b = perm[idx:min(idx + batch_size, n)]
|
||||
|
||||
X_t = []
|
||||
Y_t = []
|
||||
|
||||
for k in b:
|
||||
t_params = t_params_f(rnd)
|
||||
h_flip = t_params.pop('h_flip')
|
||||
add_noise = t_params.pop('add_noise')
|
||||
resize = t_params.pop('resize')
|
||||
mul = t_params.pop('mul')
|
||||
color_m = t_params.pop('color_m')
|
||||
agn = t_params.pop('agn')
|
||||
resize_smooth = t_params.pop('resize_smooth')
|
||||
|
||||
t_matrix = assemble_transformation_matrix(**t_params)
|
||||
|
||||
x_t = transform(np.squeeze(X[k]), t_matrix, h_flip, add_noise, resize, resize_smooth, mul, color_m)
|
||||
y_t = utils.get_affine_transform(np.squeeze(Y[k]), np.linalg.inv(t_matrix)) if Y is not None else None
|
||||
|
||||
x_t, y_t = crop_data(x_t, y_t, final_size, final_heatmap_size)
|
||||
|
||||
X_t.append(x_t)
|
||||
if Y is not None:
|
||||
if h_flip:
|
||||
y_t[1, :] = (final_size if final_heatmap_size is None else final_heatmap_size) - y_t[1, :]
|
||||
flip_body_joints(y_t)
|
||||
|
||||
Y_t.append(y_t)
|
||||
|
||||
if Y is not None:
|
||||
yield np.array(X_t), np.array(Y_t), b
|
||||
else:
|
||||
yield np.array(X_t), b
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
mpl.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.misc import imresize, imread
|
||||
|
||||
from human_pose_nn import HumanPoseIRNetwork
|
||||
|
||||
net_pose = HumanPoseIRNetwork()
|
||||
net_pose.restore('models/MPII+LSP.ckpt')
|
||||
|
||||
img = imread('images/dummy.jpg')
|
||||
img = imresize(img, [299, 299])
|
||||
img_batch = np.expand_dims(img, 0)
|
||||
|
||||
y, x, a = net_pose.estimate_joints(img_batch)
|
||||
y, x, a = np.squeeze(y), np.squeeze(x), np.squeeze(a)
|
||||
|
||||
joint_names = [
|
||||
'right ankle ',
|
||||
'right knee ',
|
||||
'right hip',
|
||||
'left hip',
|
||||
'left knee',
|
||||
'left ankle',
|
||||
'pelvis',
|
||||
'thorax',
|
||||
'upper neck',
|
||||
'head top',
|
||||
'right wrist',
|
||||
'right elbow',
|
||||
'right shoulder',
|
||||
'left shoulder',
|
||||
'left elbow',
|
||||
'left wrist'
|
||||
]
|
||||
|
||||
# Print probabilities of each estimation
|
||||
for i in range(16):
|
||||
print('%s: %.02f%%' % (joint_names[i], a[i] * 100))
|
||||
|
||||
# Create image
|
||||
colors = ['r', 'r', 'b', 'm', 'm', 'y', 'g', 'g', 'b', 'c', 'r', 'r', 'b', 'm', 'm', 'c']
|
||||
for i in range(16):
|
||||
if i < 15 and i not in {5, 9}:
|
||||
plt.plot([x[i], x[i + 1]], [y[i], y[i + 1]], color = colors[i], linewidth = 5)
|
||||
|
||||
plt.imshow(img)
|
||||
plt.savefig('images/dummy_pose.jpg')
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
import settings
|
||||
import os
|
||||
|
||||
import tensorflow as tf
|
||||
import tensorflow.contrib.layers as layers
|
||||
import numpy as np
|
||||
|
||||
from abc import abstractmethod
|
||||
|
||||
slim = tf.contrib.slim
|
||||
|
||||
SUMMARY_PATH = settings.LOGDIR_GAIT_PATH
|
||||
KEY_SUMMARIES = tf.GraphKeys.SUMMARIES
|
||||
|
||||
SEED = 0
|
||||
np.random.seed(SEED)
|
||||
|
||||
|
||||
class GaitNN(object):
|
||||
def __init__(self, name, input_tensor, features, num_of_persons, reuse = False, is_train = True,
|
||||
count_of_training_examples = 1000):
|
||||
self.input_tensor = input_tensor
|
||||
self.is_train = is_train
|
||||
self.name = name
|
||||
|
||||
self.FEATURES = features
|
||||
|
||||
net = self.pre_process(input_tensor)
|
||||
net, gait_signature, state = self.get_network(net, is_train, reuse)
|
||||
|
||||
self.network = net
|
||||
self.gait_signature = gait_signature
|
||||
self.state = state
|
||||
|
||||
if is_train:
|
||||
# Initialize placeholders
|
||||
self.desired_person = tf.placeholder(
|
||||
dtype = tf.int32,
|
||||
shape = [],
|
||||
name = 'desired_person')
|
||||
|
||||
self.desired_person_one_hot = tf.one_hot(self.desired_person, num_of_persons, dtype = tf.float32)
|
||||
self.loss = self._sigm_ce_loss()
|
||||
|
||||
self.global_step = tf.Variable(0, name = 'global_step', trainable = False)
|
||||
|
||||
self.learning_rate = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = [],
|
||||
name = 'learning_rate')
|
||||
|
||||
def _learning_rate_decay_fn(learning_rate, global_step):
|
||||
return tf.train.exponential_decay(
|
||||
learning_rate,
|
||||
global_step,
|
||||
decay_steps = count_of_training_examples * 2,
|
||||
decay_rate = 0.96,
|
||||
staircase = True)
|
||||
|
||||
self.optimize = layers.optimize_loss(loss = self.loss,
|
||||
global_step = self.global_step,
|
||||
learning_rate = self.learning_rate,
|
||||
summaries = layers.optimizers.OPTIMIZER_SUMMARIES,
|
||||
optimizer = tf.train.RMSPropOptimizer,
|
||||
learning_rate_decay_fn = _learning_rate_decay_fn,
|
||||
clip_gradients = 0.1,
|
||||
)
|
||||
|
||||
self.sess = tf.Session()
|
||||
self.sess.run(tf.global_variables_initializer())
|
||||
|
||||
# Initialize summaries
|
||||
if name is not None:
|
||||
if is_train:
|
||||
logdir = os.path.join(SUMMARY_PATH, self.name, 'train')
|
||||
self.summary_writer = tf.train.SummaryWriter(logdir)
|
||||
|
||||
self.ALL_SUMMARIES = tf.merge_all_summaries(KEY_SUMMARIES)
|
||||
else:
|
||||
self.summary_writer_d = {}
|
||||
|
||||
for t in ['avg', 'n', 'b', 's']:
|
||||
logdir = os.path.join(SUMMARY_PATH, self.name, 'val_%s' % t)
|
||||
self.summary_writer_d[t] = tf.train.SummaryWriter(logdir)
|
||||
|
||||
tf.set_random_seed(SEED)
|
||||
|
||||
@staticmethod
|
||||
def pre_process(inp):
|
||||
return inp / 100.0
|
||||
|
||||
@staticmethod
|
||||
def get_arg_scope(is_training):
|
||||
weight_decay_l2 = 0.1
|
||||
batch_norm_decay = 0.999
|
||||
batch_norm_epsilon = 0.0001
|
||||
|
||||
with slim.arg_scope([slim.conv2d, slim.fully_connected, layers.separable_convolution2d],
|
||||
weights_regularizer = slim.l2_regularizer(weight_decay_l2),
|
||||
biases_regularizer = slim.l2_regularizer(weight_decay_l2),
|
||||
weights_initializer = layers.variance_scaling_initializer(),
|
||||
):
|
||||
batch_norm_params = {
|
||||
'decay': batch_norm_decay,
|
||||
'epsilon': batch_norm_epsilon
|
||||
}
|
||||
with slim.arg_scope([slim.batch_norm, slim.dropout],
|
||||
is_training = is_training):
|
||||
with slim.arg_scope([slim.batch_norm],
|
||||
**batch_norm_params):
|
||||
with slim.arg_scope([slim.conv2d, layers.separable_convolution2d, layers.fully_connected],
|
||||
activation_fn = tf.nn.elu,
|
||||
normalizer_fn = slim.batch_norm,
|
||||
normalizer_params = batch_norm_params) as scope:
|
||||
return scope
|
||||
|
||||
def _sigm_ce_loss(self):
|
||||
ce = tf.nn.softmax_cross_entropy_with_logits(logits = self.network, labels = self.desired_person_one_hot)
|
||||
loss = tf.reduce_mean(ce)
|
||||
|
||||
return loss
|
||||
|
||||
def train(self, input_tensor, desired_person, learning_rate):
|
||||
if not self.is_train:
|
||||
raise Exception('Network is not in training mode!')
|
||||
|
||||
self.sess.run(self.optimize, feed_dict = {
|
||||
self.input_tensor: input_tensor,
|
||||
self.desired_person: desired_person,
|
||||
self.learning_rate: learning_rate
|
||||
})
|
||||
|
||||
def feed_forward(self, x):
|
||||
out, states = self.sess.run([self.gait_signature, self.state], feed_dict = {self.input_tensor: x})
|
||||
|
||||
return out, states
|
||||
|
||||
def write_test_summary(self, err, epoch, t = 'all'):
|
||||
loss_summ = tf.Summary()
|
||||
loss_summ.value.add(
|
||||
tag = 'Classification in percent',
|
||||
simple_value = float(err))
|
||||
|
||||
self.summary_writer_d[t].add_summary(loss_summ, epoch)
|
||||
self.summary_writer_d[t].flush()
|
||||
|
||||
def write_summary(self, inputs, desired_person, learning_rate, write_frequency = 50):
|
||||
step = tf.train.global_step(self.sess, self.global_step)
|
||||
|
||||
if step % write_frequency == 0:
|
||||
feed_dict = {
|
||||
self.input_tensor: inputs,
|
||||
self.desired_person: desired_person,
|
||||
self.learning_rate: learning_rate,
|
||||
}
|
||||
|
||||
summary, loss = self.sess.run([self.ALL_SUMMARIES, self.loss], feed_dict = feed_dict)
|
||||
self.summary_writer.add_summary(summary, step)
|
||||
self.summary_writer.flush()
|
||||
|
||||
def save(self, checkpoint_path, name):
|
||||
if not os.path.exists(checkpoint_path):
|
||||
os.mkdir(checkpoint_path)
|
||||
|
||||
checkpoint_name_path = os.path.join(checkpoint_path, '%s.ckpt' % name)
|
||||
all_vars = tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'GaitNN')
|
||||
|
||||
saver = tf.train.Saver(all_vars)
|
||||
saver.save(self.sess, checkpoint_name_path)
|
||||
|
||||
def restore(self, checkpoint_path):
|
||||
all_vars = tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'GaitNN')
|
||||
|
||||
saver = tf.train.Saver(all_vars)
|
||||
saver.restore(self.sess, checkpoint_path)
|
||||
|
||||
@staticmethod
|
||||
def residual_block(net, ch = 256, ch_inner = 128, scope = None, reuse = None, stride = 1):
|
||||
"""
|
||||
Bottleneck v2
|
||||
"""
|
||||
|
||||
with slim.arg_scope([layers.convolution2d],
|
||||
activation_fn = None,
|
||||
normalizer_fn = None):
|
||||
with tf.variable_scope(scope, 'ResidualBlock', reuse = reuse):
|
||||
in_net = net
|
||||
|
||||
if stride > 1:
|
||||
net = layers.convolution2d(net, ch, kernel_size = 1, stride = stride)
|
||||
|
||||
in_net = layers.batch_norm(in_net)
|
||||
in_net = tf.nn.relu(in_net)
|
||||
in_net = layers.convolution2d(in_net, ch_inner, 1)
|
||||
|
||||
in_net = layers.batch_norm(in_net)
|
||||
in_net = tf.nn.relu(in_net)
|
||||
in_net = layers.convolution2d(in_net, ch_inner, 3, stride = stride)
|
||||
|
||||
in_net = layers.batch_norm(in_net)
|
||||
in_net = tf.nn.relu(in_net)
|
||||
in_net = layers.convolution2d(in_net, ch, 1, activation_fn = None)
|
||||
|
||||
net = tf.nn.relu(in_net + net)
|
||||
|
||||
return net
|
||||
|
||||
@abstractmethod
|
||||
def get_network(self, input_tensor, is_training, reuse = False):
|
||||
pass
|
||||
|
||||
|
||||
class GaitNetwork(GaitNN):
|
||||
FEATURES = 512
|
||||
|
||||
def __init__(self, name = None, num_of_persons = 0, recurrent_unit = 'GRU', rnn_layers = 1,
|
||||
reuse = False, is_training = False, input_net = None):
|
||||
tf.set_random_seed(SEED)
|
||||
|
||||
if num_of_persons <= 0 and is_training:
|
||||
raise Exception('Parameter num_of_persons has to be greater than zero when thaining')
|
||||
|
||||
self.num_of_persons = num_of_persons
|
||||
self.rnn_layers = rnn_layers
|
||||
self.recurrent_unit = recurrent_unit
|
||||
|
||||
if input_net is None:
|
||||
input_tensor = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = (None, 17, 17, 32),
|
||||
name = 'input_image')
|
||||
else:
|
||||
input_tensor = input_net
|
||||
|
||||
super().__init__(name, input_tensor, self.FEATURES, num_of_persons, reuse, is_training)
|
||||
|
||||
def get_network(self, input_tensor, is_training, reuse = False):
|
||||
net = input_tensor
|
||||
|
||||
with tf.variable_scope('GaitNN', reuse = reuse):
|
||||
with slim.arg_scope(self.get_arg_scope(is_training)):
|
||||
with tf.variable_scope('DownSampling'):
|
||||
with tf.variable_scope('17x17'):
|
||||
net = layers.convolution2d(net, num_outputs = 256, kernel_size = 1)
|
||||
slim.repeat(net, 3, self.residual_block, ch = 256, ch_inner = 64)
|
||||
|
||||
with tf.variable_scope('8x8'):
|
||||
net = self.residual_block(net, ch = 512, ch_inner = 64, stride = 2)
|
||||
slim.repeat(net, 2, self.residual_block, ch = 512, ch_inner = 128)
|
||||
|
||||
with tf.variable_scope('4x4'):
|
||||
net = self.residual_block(net, ch = 512, ch_inner = 128, stride = 2)
|
||||
slim.repeat(net, 1, self.residual_block, ch = 512, ch_inner = 256)
|
||||
|
||||
net = layers.convolution2d(net, num_outputs = 256, kernel_size = 1)
|
||||
net = layers.convolution2d(net, num_outputs = 256, kernel_size = 3)
|
||||
|
||||
with tf.variable_scope('FullyConnected'):
|
||||
# net = tf.reduce_mean(net, [1, 2], name = 'GlobalPool')
|
||||
net = layers.flatten(net)
|
||||
net = layers.fully_connected(net, 512, activation_fn = None, normalizer_fn = None)
|
||||
|
||||
with tf.variable_scope('Recurrent', initializer = tf.contrib.layers.xavier_initializer()):
|
||||
cell_type = {
|
||||
'GRU': tf.nn.rnn_cell.GRUCell,
|
||||
'LSTM': tf.nn.rnn_cell.LSTMCell
|
||||
}
|
||||
|
||||
cell = cell_type[self.recurrent_unit](self.FEATURES)
|
||||
cell = tf.nn.rnn_cell.MultiRNNCell([cell] * self.rnn_layers, state_is_tuple = True)
|
||||
|
||||
net = tf.expand_dims(net, 0)
|
||||
net, state = tf.nn.dynamic_rnn(cell, net, initial_state = cell.zero_state(1, dtype = tf.float32))
|
||||
net = tf.reshape(net, [-1, self.FEATURES])
|
||||
|
||||
# Temporal Avg-Pooling
|
||||
gait_signature = tf.reduce_mean(net, 0)
|
||||
|
||||
if is_training:
|
||||
net = tf.expand_dims(gait_signature, 0)
|
||||
net = layers.dropout(net, 0.7)
|
||||
|
||||
with tf.variable_scope('Logits'):
|
||||
net = layers.fully_connected(net, self.num_of_persons, activation_fn = None,
|
||||
normalizer_fn = None)
|
||||
|
||||
return net, gait_signature, state
|
||||
|
|
@ -0,0 +1,504 @@
|
|||
import tensorflow as tf
|
||||
import numpy as np
|
||||
import part_detector
|
||||
import settings
|
||||
import utils
|
||||
import os
|
||||
|
||||
from abc import abstractmethod
|
||||
from functools import lru_cache
|
||||
from scipy.stats import norm
|
||||
|
||||
from inception_resnet_v2 import inception_resnet_v2_arg_scope, inception_resnet_v2
|
||||
|
||||
import tensorflow.contrib.layers as layers
|
||||
|
||||
slim = tf.contrib.slim
|
||||
|
||||
SUMMARY_PATH = settings.LOGDIR_PATH
|
||||
|
||||
KEY_SUMMARIES = tf.GraphKeys.SUMMARIES
|
||||
KEY_SUMMARIES_PER_JOINT = ['summary_joint_%02d' % i for i in range(16)]
|
||||
|
||||
|
||||
class HumanPoseNN(object):
|
||||
"""
|
||||
The neural network used for pose estimation.
|
||||
"""
|
||||
|
||||
def __init__(self, log_name, heatmap_size, image_size, loss_type = 'SCE', is_training = True):
|
||||
tf.set_random_seed(0)
|
||||
|
||||
if loss_type not in { 'MSE', 'SCE' }:
|
||||
raise NotImplementedError('Loss function should be either MSE or SCE!')
|
||||
|
||||
self.log_name = log_name
|
||||
self.heatmap_size = heatmap_size
|
||||
self.image_size = image_size
|
||||
self.is_train = is_training
|
||||
self.loss_type = loss_type
|
||||
|
||||
# Initialize placeholders
|
||||
self.input_tensor = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = (None, image_size, image_size, 3),
|
||||
name = 'input_image')
|
||||
|
||||
self.present_joints = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = (None, 16),
|
||||
name = 'present_joints')
|
||||
|
||||
self.inside_box_joints = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = (None, 16),
|
||||
name = 'inside_box_joints')
|
||||
|
||||
self.desired_heatmap = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = (None, heatmap_size, heatmap_size, 16),
|
||||
name = 'desired_heatmap')
|
||||
|
||||
self.desired_points = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = (None, 2, 16),
|
||||
name = 'desired_points')
|
||||
|
||||
self.network = self.pre_process(self.input_tensor)
|
||||
self.network, self.feature_tensor = self.get_network(self.network, is_training)
|
||||
|
||||
self.sigm_network = tf.sigmoid(self.network)
|
||||
self.smoothed_sigm_network = self._get_gauss_smoothing_net(self.sigm_network, std = 0.7)
|
||||
|
||||
self.loss_err = self._get_loss_function(loss_type)
|
||||
self.euclidean_dist = self._euclidean_dist_err()
|
||||
self.euclidean_dist_per_joint = self._euclidean_dist_per_joint_err()
|
||||
|
||||
if is_training:
|
||||
self.global_step = tf.Variable(0, name = 'global_step', trainable = False)
|
||||
|
||||
self.learning_rate = tf.placeholder(
|
||||
dtype = tf.float32,
|
||||
shape = [],
|
||||
name = 'learning_rate')
|
||||
|
||||
self.optimize = layers.optimize_loss(loss = self.loss_err,
|
||||
global_step = self.global_step,
|
||||
learning_rate = self.learning_rate,
|
||||
optimizer = tf.train.RMSPropOptimizer(self.learning_rate),
|
||||
clip_gradients = 2.0
|
||||
)
|
||||
|
||||
self.sess = tf.Session()
|
||||
self.sess.run(tf.global_variables_initializer())
|
||||
|
||||
if log_name is not None:
|
||||
self._init_summaries()
|
||||
|
||||
def _init_summaries(self):
|
||||
if self.is_train:
|
||||
logdir = os.path.join(SUMMARY_PATH, self.log_name, 'train')
|
||||
|
||||
self.summary_writer = tf.summary.FileWriter(logdir)
|
||||
self.summary_writer_by_points = [tf.summary.FileWriter(os.path.join(logdir, 'point_%02d' % i))
|
||||
for i in range(16)]
|
||||
|
||||
tf.scalar_summary('Average euclidean distance', self.euclidean_dist, collections = [KEY_SUMMARIES])
|
||||
|
||||
for i in range(16):
|
||||
tf.scalar_summary('Joint euclidean distance', self.euclidean_dist_per_joint[i],
|
||||
collections = [KEY_SUMMARIES_PER_JOINT[i]])
|
||||
|
||||
self.create_summary_from_weights()
|
||||
|
||||
self.ALL_SUMMARIES = tf.merge_all_summaries(KEY_SUMMARIES)
|
||||
self.SUMMARIES_PER_JOINT = [tf.merge_all_summaries(KEY_SUMMARIES_PER_JOINT[i]) for i in range(16)]
|
||||
else:
|
||||
logdir = os.path.join(SUMMARY_PATH, self.log_name, 'test')
|
||||
self.summary_writer = tf.summary.FileWriter(logdir)
|
||||
|
||||
def _get_loss_function(self, loss_type):
|
||||
loss_dict = {
|
||||
'MSE': self._loss_mse(),
|
||||
'SCE': self._loss_cross_entropy()
|
||||
}
|
||||
|
||||
return loss_dict[loss_type]
|
||||
|
||||
@staticmethod
|
||||
@lru_cache()
|
||||
def _get_gauss_filter(size = 15, std = 1.0, kernel_sum = 1.0):
|
||||
samples = norm.pdf(np.linspace(-2, 2, size), 0, std)
|
||||
samples /= np.sum(samples)
|
||||
samples *= kernel_sum ** 0.5
|
||||
|
||||
samples = np.expand_dims(samples, 0)
|
||||
weights = np.zeros(shape = (1, size, 16, 1), dtype = np.float32)
|
||||
|
||||
for i in range(16):
|
||||
weights[:, :, i, 0] = samples
|
||||
|
||||
return weights
|
||||
|
||||
@staticmethod
|
||||
def _get_gauss_smoothing_net(net, size = 15, std = 1.0, kernel_sum = 1.0):
|
||||
filter_h = HumanPoseNN._get_gauss_filter(size, std, kernel_sum)
|
||||
filter_v = filter_h.swapaxes(0, 1)
|
||||
|
||||
net = tf.nn.depthwise_conv2d(net, filter = filter_h, strides = [1, 1, 1, 1], padding = 'SAME',
|
||||
name = 'SmoothingHorizontal')
|
||||
|
||||
net = tf.nn.depthwise_conv2d(net, filter = filter_v, strides = [1, 1, 1, 1], padding = 'SAME',
|
||||
name = 'SmoothingVertical')
|
||||
|
||||
return net
|
||||
|
||||
def generate_output(self, shape, presented_parts, labels, sigma):
|
||||
heatmap_dict = {
|
||||
'MSE': utils.get_gauss_heat_map(
|
||||
shape = shape, is_present = presented_parts,
|
||||
mean = labels, sigma = sigma),
|
||||
'SCE': utils.get_binary_heat_map(
|
||||
shape = shape, is_present = presented_parts,
|
||||
centers = labels, diameter = sigma)
|
||||
}
|
||||
|
||||
return heatmap_dict[self.loss_type]
|
||||
|
||||
def _adjust_loss(self, loss_err):
|
||||
# Shape: [batch, joints]
|
||||
loss = tf.reduce_sum(loss_err, [1, 2])
|
||||
|
||||
# Stop error propagation of joints that are not presented
|
||||
loss = tf.multiply(loss, self.present_joints)
|
||||
|
||||
# Compute average loss of presented joints
|
||||
num_of_visible_joints = tf.reduce_sum(self.present_joints)
|
||||
loss = tf.reduce_sum(loss) / num_of_visible_joints
|
||||
|
||||
return loss
|
||||
|
||||
def _loss_mse(self):
|
||||
sq = tf.squared_difference(self.sigm_network, self.desired_heatmap)
|
||||
loss = self._adjust_loss(sq)
|
||||
|
||||
return loss
|
||||
|
||||
def _loss_cross_entropy(self):
|
||||
ce = tf.nn.sigmoid_cross_entropy_with_logits(logits = self.network, labels = self.desired_heatmap)
|
||||
loss = self._adjust_loss(ce)
|
||||
|
||||
return loss
|
||||
|
||||
def _joint_highest_activations(self):
|
||||
highest_activation = tf.reduce_max(self.smoothed_sigm_network, [1, 2])
|
||||
|
||||
return highest_activation
|
||||
|
||||
def _joint_positions(self):
|
||||
highest_activation = tf.reduce_max(self.sigm_network, [1, 2])
|
||||
x = tf.argmax(tf.reduce_max(self.smoothed_sigm_network, 1), 1)
|
||||
y = tf.argmax(tf.reduce_max(self.smoothed_sigm_network, 2), 1)
|
||||
|
||||
x = tf.cast(x, tf.float32)
|
||||
y = tf.cast(y, tf.float32)
|
||||
a = tf.cast(highest_activation, tf.float32)
|
||||
|
||||
scale_coef = (self.image_size / self.heatmap_size)
|
||||
x *= scale_coef
|
||||
y *= scale_coef
|
||||
|
||||
out = tf.stack([y, x, a])
|
||||
|
||||
return out
|
||||
|
||||
def _euclidean_dist_err(self):
|
||||
# Work only with joints that are presented inside frame
|
||||
l2_dist = tf.multiply(self.euclidean_distance(), self.inside_box_joints)
|
||||
|
||||
# Compute average loss of presented joints
|
||||
num_of_visible_joints = tf.reduce_sum(self.inside_box_joints)
|
||||
l2_dist = tf.reduce_sum(l2_dist) / num_of_visible_joints
|
||||
|
||||
return l2_dist
|
||||
|
||||
def _euclidean_dist_per_joint_err(self):
|
||||
# Work only with joints that are presented inside frame
|
||||
l2_dist = tf.multiply(self.euclidean_distance(), self.inside_box_joints)
|
||||
|
||||
# Average euclidean distance of presented joints
|
||||
present_joints = tf.reduce_sum(self.inside_box_joints, 0)
|
||||
err = tf.reduce_sum(l2_dist, 0) / present_joints
|
||||
|
||||
return err
|
||||
|
||||
def _restore(self, checkpoint_path, variables):
|
||||
saver = tf.train.Saver(variables)
|
||||
saver.restore(self.sess, checkpoint_path)
|
||||
|
||||
def _save(self, checkpoint_path, name, variables):
|
||||
if not os.path.exists(checkpoint_path):
|
||||
os.mkdir(checkpoint_path)
|
||||
|
||||
checkpoint_name_path = os.path.join(checkpoint_path, '%s.ckpt' % name)
|
||||
|
||||
saver = tf.train.Saver(variables)
|
||||
saver.save(self.sess, checkpoint_name_path)
|
||||
|
||||
def euclidean_distance(self):
|
||||
x = tf.argmax(tf.reduce_max(self.smoothed_sigm_network, 1), 1)
|
||||
y = tf.argmax(tf.reduce_max(self.smoothed_sigm_network, 2), 1)
|
||||
|
||||
x = tf.cast(x, tf.float32)
|
||||
y = tf.cast(y, tf.float32)
|
||||
|
||||
dy = tf.squeeze(self.desired_points[:, 0, :])
|
||||
dx = tf.squeeze(self.desired_points[:, 1, :])
|
||||
|
||||
sx = tf.squared_difference(x, dx)
|
||||
sy = tf.squared_difference(y, dy)
|
||||
|
||||
l2_dist = tf.sqrt(sx + sy)
|
||||
|
||||
return l2_dist
|
||||
|
||||
def feed_forward(self, x):
|
||||
out = self.sess.run(self.sigm_network, feed_dict = {
|
||||
self.input_tensor: x
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
def heat_maps(self, x):
|
||||
out = self.sess.run(self.smoothed_sigm_network, feed_dict = {
|
||||
self.input_tensor: x
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
def feed_forward_pure(self, x):
|
||||
out = self.sess.run(self.network, feed_dict = {
|
||||
self.input_tensor: x
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
def feed_forward_features(self, x):
|
||||
out = self.sess.run(self.feature_tensor, feed_dict = {
|
||||
self.input_tensor: x,
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
def test_euclidean_distance(self, x, points, present_joints, inside_box_joints):
|
||||
err = self.sess.run(self.euclidean_dist, feed_dict = {
|
||||
self.input_tensor: x,
|
||||
self.desired_points: points,
|
||||
self.present_joints: present_joints,
|
||||
self.inside_box_joints: inside_box_joints
|
||||
})
|
||||
|
||||
return err
|
||||
|
||||
def test_joint_distances(self, x, y):
|
||||
err = self.sess.run(self.euclidean_distance(), feed_dict = {
|
||||
self.input_tensor: x,
|
||||
self.desired_points: y
|
||||
})
|
||||
|
||||
return err
|
||||
|
||||
def test_joint_activations(self, x):
|
||||
err = self.sess.run(self._joint_highest_activations(), feed_dict = {
|
||||
self.input_tensor: x
|
||||
})
|
||||
|
||||
return err
|
||||
|
||||
def estimate_joints(self, x):
|
||||
out = self.sess.run(self._joint_positions(), feed_dict = {
|
||||
self.input_tensor: x
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
def train(self, x, heatmaps, present_joints, learning_rate, is_inside_box):
|
||||
if not self.is_train:
|
||||
raise Exception('Network is not in train mode!')
|
||||
|
||||
self.sess.run(self.optimize, feed_dict = {
|
||||
self.input_tensor: x,
|
||||
self.desired_heatmap: heatmaps,
|
||||
self.present_joints: present_joints,
|
||||
self.learning_rate: learning_rate,
|
||||
self.inside_box_joints: is_inside_box
|
||||
})
|
||||
|
||||
def write_test_summary(self, epoch, loss):
|
||||
loss_sum = tf.Summary()
|
||||
loss_sum.value.add(
|
||||
tag = 'Average Euclidean Distance',
|
||||
simple_value = float(loss))
|
||||
self.summary_writer.add_summary(loss_sum, epoch)
|
||||
self.summary_writer.flush()
|
||||
|
||||
def write_summary(self, inp, desired_points, heatmaps, present_joints, learning_rate, is_inside_box,
|
||||
write_frequency = 20, write_per_joint_frequency = 100):
|
||||
step = tf.train.global_step(self.sess, self.global_step)
|
||||
|
||||
if step % write_frequency == 0:
|
||||
feed_dict = {
|
||||
self.input_tensor: inp,
|
||||
self.desired_points: desired_points,
|
||||
self.desired_heatmap: heatmaps,
|
||||
self.present_joints: present_joints,
|
||||
self.learning_rate: learning_rate,
|
||||
self.inside_box_joints: is_inside_box
|
||||
}
|
||||
|
||||
summary, loss = self.sess.run([self.ALL_SUMMARIES, self.loss_err], feed_dict = feed_dict)
|
||||
self.summary_writer.add_summary(summary, step)
|
||||
|
||||
if step % write_per_joint_frequency == 0:
|
||||
summaries = self.sess.run(self.SUMMARIES_PER_JOINT, feed_dict = feed_dict)
|
||||
|
||||
for i in range(16):
|
||||
self.summary_writer_by_points[i].add_summary(summaries[i], step)
|
||||
|
||||
for i in range(16):
|
||||
self.summary_writer_by_points[i].flush()
|
||||
|
||||
self.summary_writer.flush()
|
||||
|
||||
@abstractmethod
|
||||
def pre_process(self, inp):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_network(self, input_tensor, is_training):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_summary_from_weights(self):
|
||||
pass
|
||||
|
||||
|
||||
class HumanPoseIRNetwork(HumanPoseNN):
|
||||
"""
|
||||
The first part of our network that exposes as an extractor of spatial features. It s derived from
|
||||
Inception-Resnet-v2 architecture and modified for generating heatmaps - i.e. dense predictions of body joints.
|
||||
"""
|
||||
|
||||
FEATURES = 32
|
||||
IMAGE_SIZE = 299
|
||||
HEATMAP_SIZE = 289
|
||||
POINT_DIAMETER = 15
|
||||
SMOOTH_SIZE = 21
|
||||
|
||||
def __init__(self, log_name = None, loss_type = 'SCE', is_training = False):
|
||||
super().__init__(log_name, self.HEATMAP_SIZE, self.IMAGE_SIZE, loss_type, is_training)
|
||||
|
||||
def pre_process(self, inp):
|
||||
return ((inp / 255) - 0.5) * 2.0
|
||||
|
||||
def get_network(self, input_tensor, is_training):
|
||||
# Load pre-trained inception-resnet model
|
||||
with slim.arg_scope(inception_resnet_v2_arg_scope(batch_norm_decay = 0.999, weight_decay = 0.0001)):
|
||||
net, end_points = inception_resnet_v2(input_tensor, is_training = is_training)
|
||||
|
||||
# Adding some modification to original InceptionResnetV2 - changing scoring of AUXILIARY TOWER
|
||||
weight_decay = 0.0005
|
||||
with tf.variable_scope('NewInceptionResnetV2'):
|
||||
with tf.variable_scope('AuxiliaryScoring'):
|
||||
with slim.arg_scope([layers.convolution2d, layers.convolution2d_transpose],
|
||||
weights_regularizer = slim.l2_regularizer(weight_decay),
|
||||
biases_regularizer = slim.l2_regularizer(weight_decay),
|
||||
activation_fn = None):
|
||||
tf.summary.histogram('Last_layer/activations', net, [KEY_SUMMARIES])
|
||||
|
||||
# Scoring
|
||||
net = slim.dropout(net, 0.7, is_training = is_training, scope = 'Dropout')
|
||||
net = layers.convolution2d(net, num_outputs = self.FEATURES, kernel_size = 1, stride = 1,
|
||||
scope = 'Scoring_layer')
|
||||
feature = net
|
||||
tf.summary.histogram('Scoring_layer/activations', net, [KEY_SUMMARIES])
|
||||
|
||||
# Upsampling
|
||||
net = layers.convolution2d_transpose(net, num_outputs = 16, kernel_size = 17, stride = 17,
|
||||
padding = 'VALID', scope = 'Upsampling_layer')
|
||||
|
||||
tf.summary.histogram('Upsampling_layer/activations', net, [KEY_SUMMARIES])
|
||||
|
||||
# Smoothing layer - separable gaussian filters
|
||||
net = super()._get_gauss_smoothing_net(net, size = self.SMOOTH_SIZE, std = 1.0, kernel_sum = 0.2)
|
||||
|
||||
return net, feature
|
||||
|
||||
def restore(self, checkpoint_path, is_pre_trained_imagenet_checkpoint = False):
|
||||
all_vars = tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'InceptionResnetV2')
|
||||
if not is_pre_trained_imagenet_checkpoint:
|
||||
all_vars += tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'NewInceptionResnetV2/AuxiliaryScoring')
|
||||
|
||||
super()._restore(checkpoint_path, all_vars)
|
||||
|
||||
def save(self, checkpoint_path, name):
|
||||
all_vars = tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'InceptionResnetV2')
|
||||
all_vars += tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'NewInceptionResnetV2/AuxiliaryScoring')
|
||||
|
||||
super()._save(checkpoint_path, name, all_vars)
|
||||
|
||||
def create_summary_from_weights(self):
|
||||
with tf.variable_scope('NewInceptionResnetV2/AuxiliaryScoring', reuse = True):
|
||||
tf.summary.histogram('Scoring_layer/biases', tf.get_variable('Scoring_layer/biases'), [KEY_SUMMARIES])
|
||||
tf.summary.histogram('Upsampling_layer/biases', tf.get_variable('Upsampling_layer/biases'), [KEY_SUMMARIES])
|
||||
tf.summary.histogram('Scoring_layer/weights', tf.get_variable('Scoring_layer/weights'), [KEY_SUMMARIES])
|
||||
tf.summary.histogram('Upsampling_layer/weights', tf.get_variable('Upsampling_layer/weights'),
|
||||
[KEY_SUMMARIES])
|
||||
|
||||
with tf.variable_scope('InceptionResnetV2/AuxLogits', reuse = True):
|
||||
tf.summary.histogram('Last_layer/weights', tf.get_variable('Conv2d_2a_5x5/weights'), [KEY_SUMMARIES])
|
||||
tf.summary.histogram('Last_layer/beta', tf.get_variable('Conv2d_2a_5x5/BatchNorm/beta'), [KEY_SUMMARIES])
|
||||
tf.summary.histogram('Last_layer/moving_mean', tf.get_variable('Conv2d_2a_5x5/BatchNorm/moving_mean'),
|
||||
[KEY_SUMMARIES])
|
||||
|
||||
|
||||
class PartDetector(HumanPoseNN):
|
||||
"""
|
||||
Architecture of Part Detector network, as was described in https://arxiv.org/abs/1609.01743
|
||||
"""
|
||||
|
||||
IMAGE_SIZE = 256
|
||||
HEATMAP_SIZE = 256
|
||||
POINT_DIAMETER = 11
|
||||
|
||||
def __init__(self, log_name = None, init_from_checkpoint = None, loss_type = 'SCE', is_training = False):
|
||||
if init_from_checkpoint is not None:
|
||||
part_detector.init_model_variables(init_from_checkpoint, is_training)
|
||||
self.reuse = True
|
||||
else:
|
||||
self.reuse = False
|
||||
|
||||
super().__init__(log_name, self.HEATMAP_SIZE, self.IMAGE_SIZE, loss_type, is_training)
|
||||
|
||||
def pre_process(self, inp):
|
||||
return inp / 255
|
||||
|
||||
def create_summary_from_weights(self):
|
||||
pass
|
||||
|
||||
def restore(self, checkpoint_path):
|
||||
all_vars = tf.get_collection(tf.GraphKeys.VARIABLES, scope = 'HumanPoseResnet')
|
||||
all_vars += tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'NewHumanPoseResnet/Scoring')
|
||||
|
||||
super()._restore(checkpoint_path, all_vars)
|
||||
|
||||
def save(self, checkpoint_path, name):
|
||||
all_vars = tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'HumanPoseResnet')
|
||||
all_vars += tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, scope = 'NewHumanPoseResnet/Scoring')
|
||||
|
||||
super()._save(checkpoint_path, name, all_vars)
|
||||
|
||||
def get_network(self, input_tensor, is_training):
|
||||
net_end, end_points = part_detector.human_pose_resnet(input_tensor, reuse = self.reuse, training = is_training)
|
||||
|
||||
return net_end, end_points['features']
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
# The content is derived from https://github.com/tensorflow/models/blob/master/slim/nets/inception_resnet_v2.py
|
||||
# ==============================================================================
|
||||
|
||||
"""Contains the definition of the Inception Resnet V2 architecture.
|
||||
|
||||
As described in http://arxiv.org/abs/1602.07261.
|
||||
|
||||
Inception-v4, Inception-ResNet and the Impact of Residual Connections
|
||||
on Learning
|
||||
Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke, Alex Alemi
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import tensorflow as tf
|
||||
|
||||
slim = tf.contrib.slim
|
||||
|
||||
|
||||
def block35(net, scale = 1.0, activation_fn = tf.nn.relu, scope = None, reuse = None):
|
||||
"""Builds the 35x35 resnet block."""
|
||||
with tf.variable_scope(scope, 'Block35', [net], reuse = reuse):
|
||||
with tf.variable_scope('Branch_0'):
|
||||
tower_conv = slim.conv2d(net, 32, 1, scope = 'Conv2d_1x1')
|
||||
with tf.variable_scope('Branch_1'):
|
||||
tower_conv1_0 = slim.conv2d(net, 32, 1, scope = 'Conv2d_0a_1x1')
|
||||
tower_conv1_1 = slim.conv2d(tower_conv1_0, 32, 3, scope = 'Conv2d_0b_3x3')
|
||||
with tf.variable_scope('Branch_2'):
|
||||
tower_conv2_0 = slim.conv2d(net, 32, 1, scope = 'Conv2d_0a_1x1')
|
||||
tower_conv2_1 = slim.conv2d(tower_conv2_0, 48, 3, scope = 'Conv2d_0b_3x3')
|
||||
tower_conv2_2 = slim.conv2d(tower_conv2_1, 64, 3, scope = 'Conv2d_0c_3x3')
|
||||
mixed = tf.concat(axis = 3, values = [tower_conv, tower_conv1_1, tower_conv2_2])
|
||||
up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn = None,
|
||||
activation_fn = None, scope = 'Conv2d_1x1')
|
||||
net += scale * up
|
||||
if activation_fn:
|
||||
net = activation_fn(net)
|
||||
return net
|
||||
|
||||
|
||||
def block17(net, scale = 1.0, activation_fn = tf.nn.relu, scope = None, reuse = None):
|
||||
"""Builds the 17x17 resnet block."""
|
||||
with tf.variable_scope(scope, 'Block17', [net], reuse = reuse):
|
||||
with tf.variable_scope('Branch_0'):
|
||||
tower_conv = slim.conv2d(net, 192, 1, scope = 'Conv2d_1x1')
|
||||
with tf.variable_scope('Branch_1'):
|
||||
tower_conv1_0 = slim.conv2d(net, 128, 1, scope = 'Conv2d_0a_1x1')
|
||||
tower_conv1_1 = slim.conv2d(tower_conv1_0, 160, [1, 7],
|
||||
scope = 'Conv2d_0b_1x7')
|
||||
tower_conv1_2 = slim.conv2d(tower_conv1_1, 192, [7, 1],
|
||||
scope = 'Conv2d_0c_7x1')
|
||||
mixed = tf.concat(axis = 3, values = [tower_conv, tower_conv1_2])
|
||||
up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn = None,
|
||||
activation_fn = None, scope = 'Conv2d_1x1')
|
||||
net += scale * up
|
||||
if activation_fn:
|
||||
net = activation_fn(net)
|
||||
return net
|
||||
|
||||
|
||||
def block8(net, scale = 1.0, activation_fn = tf.nn.relu, scope = None, reuse = None):
|
||||
"""Builds the 8x8 resnet block."""
|
||||
with tf.variable_scope(scope, 'Block8', [net], reuse = reuse):
|
||||
with tf.variable_scope('Branch_0'):
|
||||
tower_conv = slim.conv2d(net, 192, 1, scope = 'Conv2d_1x1')
|
||||
with tf.variable_scope('Branch_1'):
|
||||
tower_conv1_0 = slim.conv2d(net, 192, 1, scope = 'Conv2d_0a_1x1')
|
||||
tower_conv1_1 = slim.conv2d(tower_conv1_0, 224, [1, 3],
|
||||
scope = 'Conv2d_0b_1x3')
|
||||
tower_conv1_2 = slim.conv2d(tower_conv1_1, 256, [3, 1],
|
||||
scope = 'Conv2d_0c_3x1')
|
||||
mixed = tf.concat(axis = 3, values = [tower_conv, tower_conv1_2])
|
||||
up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn = None,
|
||||
activation_fn = None, scope = 'Conv2d_1x1')
|
||||
net += scale * up
|
||||
if activation_fn:
|
||||
net = activation_fn(net)
|
||||
return net
|
||||
|
||||
|
||||
def inception_resnet_v2(inputs, is_training = True,
|
||||
reuse = None,
|
||||
scope = 'InceptionResnetV2'):
|
||||
"""Creates the Inception Resnet V2 model.
|
||||
|
||||
Args:
|
||||
inputs: a 4-D tensor of size [batch_size, height, width, 3].
|
||||
num_classes: number of predicted classes.
|
||||
is_training: whether is training or not.
|
||||
dropout_keep_prob: float, the fraction to keep before final layer.
|
||||
reuse: whether or not the network and its variables should be reused. To be
|
||||
able to reuse 'scope' must be given.
|
||||
scope: Optional variable_scope.
|
||||
|
||||
Returns:
|
||||
logits: the logits outputs of the model.
|
||||
end_points: the set of end_points from the inception model.
|
||||
"""
|
||||
end_points = { }
|
||||
|
||||
with tf.variable_scope(scope, 'InceptionResnetV2', [inputs], reuse = reuse):
|
||||
with slim.arg_scope([slim.batch_norm, slim.dropout],
|
||||
is_training = is_training):
|
||||
with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d],
|
||||
stride = 1, padding = 'SAME'):
|
||||
# 149 x 149 x 32
|
||||
net = slim.conv2d(inputs, 32, 3, stride = 2, padding = 'VALID',
|
||||
scope = 'Conv2d_1a_3x3')
|
||||
end_points['Conv2d_1a_3x3'] = net
|
||||
# 147 x 147 x 32
|
||||
net = slim.conv2d(net, 32, 3, padding = 'VALID',
|
||||
scope = 'Conv2d_2a_3x3')
|
||||
end_points['Conv2d_2a_3x3'] = net
|
||||
# 147 x 147 x 64
|
||||
net = slim.conv2d(net, 64, 3, scope = 'Conv2d_2b_3x3')
|
||||
end_points['Conv2d_2b_3x3'] = net
|
||||
# 73 x 73 x 64
|
||||
net = slim.max_pool2d(net, 3, stride = 2, padding = 'VALID',
|
||||
scope = 'MaxPool_3a_3x3')
|
||||
end_points['MaxPool_3a_3x3'] = net
|
||||
# 73 x 73 x 80
|
||||
net = slim.conv2d(net, 80, 1, padding = 'VALID',
|
||||
scope = 'Conv2d_3b_1x1')
|
||||
end_points['Conv2d_3b_1x1'] = net
|
||||
# 71 x 71 x 192
|
||||
net = slim.conv2d(net, 192, 3, padding = 'VALID',
|
||||
scope = 'Conv2d_4a_3x3')
|
||||
end_points['Conv2d_4a_3x3'] = net
|
||||
# 35 x 35 x 192
|
||||
net = slim.max_pool2d(net, 3, stride = 2, padding = 'VALID',
|
||||
scope = 'MaxPool_5a_3x3')
|
||||
end_points['MaxPool_5a_3x3'] = net
|
||||
|
||||
# 35 x 35 x 320
|
||||
with tf.variable_scope('Mixed_5b'):
|
||||
with tf.variable_scope('Branch_0'):
|
||||
tower_conv = slim.conv2d(net, 96, 1, scope = 'Conv2d_1x1')
|
||||
with tf.variable_scope('Branch_1'):
|
||||
tower_conv1_0 = slim.conv2d(net, 48, 1, scope = 'Conv2d_0a_1x1')
|
||||
tower_conv1_1 = slim.conv2d(tower_conv1_0, 64, 5,
|
||||
scope = 'Conv2d_0b_5x5')
|
||||
with tf.variable_scope('Branch_2'):
|
||||
tower_conv2_0 = slim.conv2d(net, 64, 1, scope = 'Conv2d_0a_1x1')
|
||||
tower_conv2_1 = slim.conv2d(tower_conv2_0, 96, 3,
|
||||
scope = 'Conv2d_0b_3x3')
|
||||
tower_conv2_2 = slim.conv2d(tower_conv2_1, 96, 3,
|
||||
scope = 'Conv2d_0c_3x3')
|
||||
with tf.variable_scope('Branch_3'):
|
||||
tower_pool = slim.avg_pool2d(net, 3, stride = 1, padding = 'SAME',
|
||||
scope = 'AvgPool_0a_3x3')
|
||||
tower_pool_1 = slim.conv2d(tower_pool, 64, 1,
|
||||
scope = 'Conv2d_0b_1x1')
|
||||
net = tf.concat(axis = 3, values = [tower_conv, tower_conv1_1,
|
||||
tower_conv2_2, tower_pool_1])
|
||||
|
||||
end_points['Mixed_5b'] = net
|
||||
net = slim.repeat(net, 10, block35, scale = 0.17)
|
||||
|
||||
# 17 x 17 x 1024
|
||||
with tf.variable_scope('Mixed_6a'):
|
||||
with tf.variable_scope('Branch_0'):
|
||||
tower_conv = slim.conv2d(net, 384, 3, stride = 2, padding = 'VALID',
|
||||
scope = 'Conv2d_1a_3x3')
|
||||
with tf.variable_scope('Branch_1'):
|
||||
tower_conv1_0 = slim.conv2d(net, 256, 1, scope = 'Conv2d_0a_1x1')
|
||||
tower_conv1_1 = slim.conv2d(tower_conv1_0, 256, 3,
|
||||
scope = 'Conv2d_0b_3x3')
|
||||
tower_conv1_2 = slim.conv2d(tower_conv1_1, 384, 3,
|
||||
stride = 2, padding = 'VALID',
|
||||
scope = 'Conv2d_1a_3x3')
|
||||
with tf.variable_scope('Branch_2'):
|
||||
tower_pool = slim.max_pool2d(net, 3, stride = 2, padding = 'VALID',
|
||||
scope = 'MaxPool_1a_3x3')
|
||||
net = tf.concat(axis = 3, values = [tower_conv, tower_conv1_2, tower_pool])
|
||||
|
||||
end_points['Mixed_6a'] = net
|
||||
net = slim.repeat(net, 20, block17, scale = 0.10)
|
||||
|
||||
end_points['BeforeAux'] = net
|
||||
|
||||
# Auxiliary tower
|
||||
with tf.variable_scope('AuxLogits'):
|
||||
aux = slim.avg_pool2d(net, 5, stride = 1, padding = 'SAME',
|
||||
scope = 'Conv2d_1a_3x3')
|
||||
aux = slim.conv2d(aux, 128, 1, scope = 'Conv2d_1b_1x1')
|
||||
aux = slim.conv2d(aux, 768, 5,
|
||||
padding = 'SAME', scope = 'Conv2d_2a_5x5')
|
||||
|
||||
end_points['AuxBeforeScoring'] = aux
|
||||
|
||||
return aux, end_points
|
||||
|
||||
inception_resnet_v2.default_image_size = 299
|
||||
|
||||
|
||||
def inception_resnet_v2_arg_scope(weight_decay = 0.00004,
|
||||
batch_norm_decay = 0.9997,
|
||||
batch_norm_epsilon = 0.001):
|
||||
"""Yields the scope with the default parameters for inception_resnet_v2.
|
||||
|
||||
Args:
|
||||
weight_decay: the weight decay for weights variables.
|
||||
batch_norm_decay: decay for the moving average of batch_norm momentums.
|
||||
batch_norm_epsilon: small float added to variance to avoid dividing by zero.
|
||||
|
||||
Returns:
|
||||
a arg_scope with the parameters needed for inception_resnet_v2.
|
||||
"""
|
||||
# Set weight_decay for weights in conv2d and fully_connected layers.
|
||||
|
||||
with slim.arg_scope([slim.conv2d, slim.fully_connected],
|
||||
weights_regularizer = slim.l2_regularizer(weight_decay),
|
||||
biases_regularizer = slim.l2_regularizer(weight_decay)):
|
||||
batch_norm_params = {
|
||||
'decay': batch_norm_decay,
|
||||
'epsilon': batch_norm_epsilon
|
||||
}
|
||||
# Set activation_fn and parameters for batch_norm.
|
||||
with slim.arg_scope([slim.conv2d], activation_fn = tf.nn.relu,
|
||||
normalizer_fn = slim.batch_norm,
|
||||
normalizer_params = batch_norm_params) as scope:
|
||||
return scope
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.misc import imresize, imread
|
||||
|
||||
from human_pose_nn import HumanPoseIRNetwork
|
||||
mpl.use('Agg')
|
||||
|
||||
net_pose = HumanPoseIRNetwork()
|
||||
net_pose.restore('../Thesis_solution/models/MPII+LSP.ckpt')
|
||||
|
||||
img = imread('images/dummy.jpg')
|
||||
img = imresize(img, [299, 299])
|
||||
img_batch = np.expand_dims(img, 0)
|
||||
|
||||
y, x, a = net_pose.estimate_joints(img_batch)
|
||||
y, x, a = np.squeeze(y), np.squeeze(x), np.squeeze(a)
|
||||
|
||||
joint_names = [
|
||||
'right ankle ',
|
||||
'right knee ',
|
||||
'right hip',
|
||||
'left hip',
|
||||
'left knee',
|
||||
'left ankle',
|
||||
'pelvis',
|
||||
'thorax',
|
||||
'upper neck',
|
||||
'head top',
|
||||
'right wrist',
|
||||
'right elbow',
|
||||
'right shoulder',
|
||||
'left shoulder',
|
||||
'left elbow',
|
||||
'left wrist'
|
||||
]
|
||||
|
||||
# Print probabilities of each estimation
|
||||
for i in range(16):
|
||||
print('%s: %.02f%%' % (joint_names[i], a[i] * 100))
|
||||
|
||||
colors = ['r', 'r', 'b', 'm', 'm', 'y', 'g', 'g', 'b', 'c', 'r', 'r', 'b', 'm', 'm', 'c']
|
||||
for i in range(16):
|
||||
if i < 15 and i not in {5, 9}:
|
||||
plt.plot([x[i], x[i + 1]], [y[i], y[i + 1]], color = colors[i], linewidth = 5)
|
||||
|
||||
plt.imshow(img)
|
||||
plt.savefig('images/dummy_pose.jpg')
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
import tensorflow as tf
|
||||
import tensorflow.contrib.layers as layers
|
||||
import torchfile as th
|
||||
|
||||
|
||||
def init_model_variables(file_path, trainable = True):
|
||||
"""
|
||||
Initialize all model variables of a given torch model. The torch model pre-trained on MPII or MPII+LSP can be
|
||||
downloaded from author's pages: https://www.adrianbulat.com/human-pose-estimation
|
||||
|
||||
:param file_path: path to serialized torch model (.th)
|
||||
:param trainable: if the loaded variables should be trainable
|
||||
"""
|
||||
|
||||
def load_conv2(obj, scope = 'Conv'):
|
||||
with tf.variable_scope(scope, reuse = False):
|
||||
w = obj[b'weight'].swapaxes(0, 3).swapaxes(1, 2).swapaxes(0, 1)
|
||||
b = obj[b'bias']
|
||||
|
||||
tf.get_variable('weights', w.shape, initializer = tf.constant_initializer(w), trainable = trainable)
|
||||
tf.get_variable('biases', b.shape, initializer = tf.constant_initializer(b), trainable = trainable)
|
||||
|
||||
def load_batch_norm(obj, scope = 'BatchNorm'):
|
||||
with tf.variable_scope(scope, reuse = False):
|
||||
gamma = obj[b'weight']
|
||||
beta = obj[b'bias']
|
||||
mean = obj[b'running_mean']
|
||||
var = obj[b'running_var']
|
||||
|
||||
tf.get_variable('gamma', gamma.shape, dtype = tf.float32, initializer = tf.constant_initializer(gamma),
|
||||
trainable = trainable)
|
||||
tf.get_variable('beta', beta.shape, dtype = tf.float32, initializer = tf.constant_initializer(beta),
|
||||
trainable = trainable)
|
||||
|
||||
tf.get_variable('moving_variance', var.shape, dtype = tf.float32,
|
||||
initializer = tf.constant_initializer(var), trainable = False)
|
||||
tf.get_variable('moving_mean', mean.shape, dtype = tf.float32, initializer = tf.constant_initializer(mean),
|
||||
trainable = False)
|
||||
|
||||
def load_bottlenecks(bottlenecks):
|
||||
for idx, bottleneck in enumerate(bottlenecks):
|
||||
with tf.variable_scope('Bottleneck_%d' % idx, reuse = False):
|
||||
connections = bottleneck[b'modules'][0][b'modules']
|
||||
|
||||
res_conn = connections[0][b'modules']
|
||||
skip_conn = connections[1][b'modules']
|
||||
|
||||
# Load skip connection
|
||||
if idx == 0:
|
||||
# Skip connection involves conv + batch norm
|
||||
load_conv2(skip_conn[0], scope = 'Conv_skip')
|
||||
load_batch_norm(skip_conn[1], scope = 'BatchNorm_skip')
|
||||
|
||||
# Load residual connection
|
||||
for l in range(3):
|
||||
load_conv2(res_conn[l * 3], scope = 'Conv_%d' % (l + 1))
|
||||
load_batch_norm(res_conn[l * 3 + 1], scope = 'BatchNorm_%d' % (l + 1))
|
||||
|
||||
file = th.load(file_path)
|
||||
|
||||
with tf.variable_scope('HumanPoseResnet', reuse = False):
|
||||
resnet = file[b'modules'][0][b'modules'][1][b'modules']
|
||||
|
||||
with tf.variable_scope('Block_0', reuse = False):
|
||||
load_conv2(resnet[0])
|
||||
load_batch_norm(resnet[1])
|
||||
|
||||
for i in range(4):
|
||||
with tf.variable_scope('Block_%d' % (i + 1), reuse = False):
|
||||
load_bottlenecks(resnet[i + 4][b'modules'])
|
||||
|
||||
with tf.variable_scope('Block_5', reuse = False):
|
||||
load_conv2(resnet[8])
|
||||
# Transpose convolution
|
||||
load_conv2(resnet[9], scope = 'Conv2d_transpose')
|
||||
|
||||
|
||||
def human_pose_resnet(net, reuse = False, training = False):
|
||||
"""
|
||||
Architecture of Part Detector network, as was described in https://arxiv.org/abs/1609.01743
|
||||
|
||||
:param net: input tensor
|
||||
:param reuse: whether reuse variables or not. Use False if the variables are initialized with init_model_variables
|
||||
:param training: if the variables should be trainable. It has no effect if the 'reuse' param is set to True
|
||||
:return: output tensor and dictionary of named endpoints
|
||||
"""
|
||||
|
||||
def batch_normalization(input_net, act_f = None, scope = None):
|
||||
return layers.batch_norm(input_net, center = True, scale = True, epsilon = 1e-5,
|
||||
activation_fn = act_f, is_training = training,
|
||||
scope = scope)
|
||||
|
||||
def conv_2d(input_net, num_outputs, kernel_size, stride = 1, padding_mod = 'SAME', scope = None):
|
||||
return layers.convolution2d(input_net, num_outputs = num_outputs, kernel_size = kernel_size,
|
||||
stride = stride, padding = padding_mod,
|
||||
activation_fn = None, scope = scope)
|
||||
|
||||
def padding(input_net, w, h):
|
||||
return tf.pad(input_net, [[0, 0], [h, h], [w, w], [0, 0]], "CONSTANT")
|
||||
|
||||
def bottleneck(input_net, depth, depth_bottleneck, stride, i):
|
||||
with tf.variable_scope('Bottleneck_%d' % i, reuse = reuse):
|
||||
res_conv = stride > 1 or stride < 0
|
||||
stride = abs(stride)
|
||||
|
||||
# Res connection
|
||||
out_net = conv_2d(input_net, num_outputs = depth_bottleneck, kernel_size = 1,
|
||||
stride = 1, padding_mod = 'VALID', scope = 'Conv_1')
|
||||
|
||||
out_net = batch_normalization(out_net, tf.nn.relu, 'BatchNorm_1')
|
||||
|
||||
out_net = padding(out_net, 1, 1)
|
||||
|
||||
out_net = conv_2d(out_net, num_outputs = depth_bottleneck, kernel_size = 3,
|
||||
stride = stride, padding_mod = 'VALID', scope = 'Conv_2')
|
||||
|
||||
out_net = batch_normalization(out_net, tf.nn.relu, 'BatchNorm_2')
|
||||
|
||||
out_net = conv_2d(out_net, num_outputs = depth, kernel_size = 1,
|
||||
stride = 1, padding_mod = 'VALID', scope = 'Conv_3')
|
||||
|
||||
out_net = batch_normalization(out_net, scope = 'BatchNorm_3')
|
||||
|
||||
# Skip connection
|
||||
if res_conv:
|
||||
input_net = conv_2d(input_net, num_outputs = depth, kernel_size = 1,
|
||||
stride = stride, padding_mod = 'VALID', scope = 'Conv_skip')
|
||||
|
||||
input_net = batch_normalization(input_net, scope = 'BatchNorm_skip')
|
||||
|
||||
out_net += input_net
|
||||
out_net = tf.nn.relu(out_net)
|
||||
|
||||
return out_net
|
||||
|
||||
def repeat_bottleneck(input_net, all_params):
|
||||
for i, (depth, depth_bottleneck, stride) in enumerate(all_params):
|
||||
input_net = bottleneck(input_net, depth, depth_bottleneck, stride, i)
|
||||
|
||||
return input_net
|
||||
|
||||
end_points = { }
|
||||
|
||||
with tf.variable_scope('HumanPoseResnet', reuse = reuse):
|
||||
with tf.variable_scope('Block_0', reuse = reuse):
|
||||
net = padding(net, 3, 3)
|
||||
|
||||
net = conv_2d(net, num_outputs = 64, kernel_size = 7, stride = 2, padding_mod = 'VALID')
|
||||
|
||||
net = batch_normalization(net, tf.nn.relu)
|
||||
|
||||
net = padding(net, 1, 1)
|
||||
|
||||
net = layers.max_pool2d(net, 3, 2, padding = 'VALID')
|
||||
|
||||
with tf.variable_scope('Block_1', reuse = reuse):
|
||||
net = repeat_bottleneck(net, [(256, 64, -1)] + [(256, 64, 1)] * 2)
|
||||
|
||||
with tf.variable_scope('Block_2', reuse = reuse):
|
||||
net = repeat_bottleneck(net, [(512, 128, 2)] + [(512, 128, 1)] * 7)
|
||||
|
||||
with tf.variable_scope('Block_3', reuse = reuse):
|
||||
net = repeat_bottleneck(net, [(1024, 256, 2)] + [(1024, 256, 1)] * 35)
|
||||
|
||||
with tf.variable_scope('Block_4', reuse = reuse):
|
||||
net = repeat_bottleneck(net, [(2048, 512, -1)] + [(2048, 512, 1)] * 2)
|
||||
|
||||
end_points['resnet_end'] = net
|
||||
with tf.variable_scope('Block_5', reuse = reuse):
|
||||
net = conv_2d(net, num_outputs = 16, kernel_size = 1, stride = 1, padding_mod = 'VALID')
|
||||
end_points['features'] = net
|
||||
|
||||
net = layers.convolution2d_transpose(net, num_outputs = 16, kernel_size = 16, stride = 16,
|
||||
activation_fn = None, padding = 'VALID')
|
||||
|
||||
# net = tf.nn.sigmoid(net)
|
||||
|
||||
return net, end_points
|
||||
|
||||
# with tf.Graph().as_default():
|
||||
# init_model_variables('/home/margeta/data/hp.t7')
|
||||
#
|
||||
# input_tensor = tf.placeholder(tf.float32, shape = (None, 256, 256, 3), name = 'input_image')
|
||||
# hp_net = human_pose_resnet(input_tensor, reuse = True, training = False)
|
||||
#
|
||||
# # config = tf.ConfigProto()
|
||||
# # config.gpu_options.per_process_gpu_memory_fraction = 0.5
|
||||
# # sess = tf.Session(config=config)
|
||||
# sess = tf.Session()
|
||||
# sess.run(tf.initialize_all_variables())
|
||||
# print('Model was loaded!')
|
||||
#
|
||||
# img = np.reshape(th.load('img').swapaxes(0, 1).swapaxes(1, 2), [-1, 256, 256, 3])
|
||||
#
|
||||
# res = sess.run(hp_net, feed_dict = {input_tensor: img})
|
||||
#
|
||||
# res = np.squeeze(res)
|
||||
#
|
||||
# print(res.shape)
|
||||
# print(www.shape)
|
||||
# print(res[200,160,:])
|
||||
# print(www[200,160,:])
|
||||
#
|
||||
# img = res[:,:,0]
|
||||
# fig = plt.figure()
|
||||
# plt.imshow(img)
|
||||
# fig.savefig('img.png')
|
||||
#
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
LOGDIR_GAIT_PATH = 'logdir_gait'
|
||||
LOGDIR_PATH = 'logdir'
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
"""
|
||||
Copyright (c) 2016, Brendan Shillingford
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||
disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
----------------------------------------------------------------------------------------------------------------------
|
||||
The file was taken from https://github.com/bshillingford/python-torchfile and slightly modified
|
||||
----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Mostly direct port of the Lua and C serialization implementation to
|
||||
Python, depending only on `struct`, `array`, and numpy.
|
||||
|
||||
Supported types:
|
||||
* `nil` to Python `None`
|
||||
* numbers to Python floats, or by default a heuristic changes them to ints or
|
||||
longs if they are integral
|
||||
* booleans
|
||||
* strings: read as byte strings (Python 3) or normal strings (Python 2), like
|
||||
lua strings which don't support unicode, and that can contain null chars
|
||||
* tables converted to a special dict (*); if they are list-like (i.e. have
|
||||
numeric keys from 1 through n) they become a python list by default
|
||||
* Torch classes: supports Tensors and Storages, and most classes such as
|
||||
modules. Trivially extensible much like the Torch serialization code.
|
||||
Trivial torch classes like most `nn.Module` subclasses become
|
||||
`TorchObject`s. The `torch_readers` dict contains the mapping from class
|
||||
names to reading functions.
|
||||
* functions: loaded into the `LuaFunction` `namedtuple`,
|
||||
which simply wraps the raw serialized data, i.e. upvalues and code.
|
||||
These are mostly useless, but exist so you can deserialize anything.
|
||||
|
||||
(*) Since Lua allows you to index a table with a table but Python does not, we
|
||||
replace dicts with a subclass that is hashable, and change its
|
||||
equality comparison behaviour to compare by reference.
|
||||
See `hashable_uniq_dict`.
|
||||
|
||||
Currently, the implementation assumes the system-dependent binary Torch
|
||||
format, but minor refactoring can give support for the ascii format as well.
|
||||
"""
|
||||
|
||||
TYPE_NIL = 0
|
||||
TYPE_NUMBER = 1
|
||||
TYPE_STRING = 2
|
||||
TYPE_TABLE = 3
|
||||
TYPE_TORCH = 4
|
||||
TYPE_BOOLEAN = 5
|
||||
TYPE_FUNCTION = 6
|
||||
TYPE_RECUR_FUNCTION = 8
|
||||
LEGACY_TYPE_RECUR_FUNCTION = 7
|
||||
|
||||
import struct
|
||||
from array import array
|
||||
import numpy as np
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
LuaFunction = namedtuple('LuaFunction',
|
||||
['size', 'dumped', 'upvalues'])
|
||||
|
||||
|
||||
class hashable_uniq_dict(dict):
|
||||
"""
|
||||
Subclass of dict with equality and hashing semantics changed:
|
||||
equality and hashing is purely by reference/instance, to match
|
||||
the behaviour of lua tables.
|
||||
|
||||
Supports lua-style dot indexing.
|
||||
|
||||
This way, dicts can be keys of other dicts.
|
||||
"""
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
def __eq__(self, other):
|
||||
return id(self) == id(other)
|
||||
# TODO: dict's __lt__ etc. still exist
|
||||
|
||||
torch_readers = {}
|
||||
|
||||
|
||||
def add_tensor_reader(typename, dtype):
|
||||
def read_tensor_generic(reader, version):
|
||||
# source:
|
||||
# https://github.com/torch/torch7/blob/master/generic/Tensor.c#L1243
|
||||
ndim = reader.read_int()
|
||||
|
||||
# read size:
|
||||
size = reader.read_long_array(ndim)
|
||||
# read stride:
|
||||
stride = reader.read_long_array(ndim)
|
||||
# storage offset:
|
||||
storage_offset = reader.read_long() - 1
|
||||
# read storage:
|
||||
storage = reader.read_obj()
|
||||
|
||||
if storage is None or ndim == 0 or len(size) == 0 or len(stride) == 0:
|
||||
# empty torch tensor
|
||||
return np.empty((0), dtype=dtype)
|
||||
|
||||
# convert stride to numpy style (i.e. in bytes)
|
||||
stride = [storage.dtype.itemsize * x for x in stride]
|
||||
|
||||
# create numpy array that indexes into the storage:
|
||||
return np.lib.stride_tricks.as_strided(
|
||||
storage[storage_offset:],
|
||||
shape=size,
|
||||
strides=stride)
|
||||
torch_readers[typename] = read_tensor_generic
|
||||
add_tensor_reader(b'torch.ByteTensor', dtype=np.uint8)
|
||||
add_tensor_reader(b'torch.CharTensor', dtype=np.int8)
|
||||
add_tensor_reader(b'torch.ShortTensor', dtype=np.int16)
|
||||
add_tensor_reader(b'torch.IntTensor', dtype=np.int32)
|
||||
add_tensor_reader(b'torch.LongTensor', dtype=np.int64)
|
||||
add_tensor_reader(b'torch.FloatTensor', dtype=np.float32)
|
||||
add_tensor_reader(b'torch.DoubleTensor', dtype=np.float64)
|
||||
add_tensor_reader(b'torch.CudaTensor', np.float32) # float
|
||||
add_tensor_reader(b'torch.CudaByteTensor', dtype=np.uint8)
|
||||
add_tensor_reader(b'torch.CudaCharTensor', dtype=np.int8)
|
||||
add_tensor_reader(b'torch.CudaShortTensor', dtype=np.int16)
|
||||
add_tensor_reader(b'torch.CudaIntTensor', dtype=np.int32)
|
||||
add_tensor_reader(b'torch.CudaDoubleTensor', dtype=np.float64)
|
||||
|
||||
|
||||
def add_storage_reader(typename, dtype):
|
||||
def read_storage(reader, version):
|
||||
# source:
|
||||
# https://github.com/torch/torch7/blob/master/generic/Storage.c#L244
|
||||
size = reader.read_long()
|
||||
return np.fromfile(reader.f, dtype=dtype, count=size)
|
||||
torch_readers[typename] = read_storage
|
||||
add_storage_reader(b'torch.ByteStorage', dtype=np.uint8)
|
||||
add_storage_reader(b'torch.CharStorage', dtype=np.int8)
|
||||
add_storage_reader(b'torch.ShortStorage', dtype=np.int16)
|
||||
add_storage_reader(b'torch.IntStorage', dtype=np.int32)
|
||||
add_storage_reader(b'torch.LongStorage', dtype=np.int64)
|
||||
add_storage_reader(b'torch.FloatStorage', dtype=np.float32)
|
||||
add_storage_reader(b'torch.DoubleStorage', dtype=np.float64)
|
||||
add_storage_reader(b'torch.CudaStorage', dtype=np.float32) # float
|
||||
add_storage_reader(b'torch.CudaByteStorage', dtype=np.uint8)
|
||||
add_storage_reader(b'torch.CudaCharStorage', dtype=np.int8)
|
||||
add_storage_reader(b'torch.CudaShortStorage', dtype=np.int16)
|
||||
add_storage_reader(b'torch.CudaIntStorage', dtype=np.int32)
|
||||
add_storage_reader(b'torch.CudaDoubleStorage', dtype=np.float64)
|
||||
|
||||
|
||||
class TorchObject(object):
|
||||
"""
|
||||
Simple torch object, used by `add_trivial_class_reader`.
|
||||
Supports both forms of lua-style indexing, i.e. getattr and getitem.
|
||||
Use the `torch_typename` method to get the object's torch class name.
|
||||
|
||||
Equality is by reference, as usual for lua (and the default for Python
|
||||
objects).
|
||||
"""
|
||||
|
||||
def __init__(self, typename, obj):
|
||||
self._typename = typename
|
||||
self._obj = obj
|
||||
|
||||
def __getattr__(self, k):
|
||||
return self._obj.get(k)
|
||||
|
||||
def __getitem__(self, k):
|
||||
return self._obj.get(k)
|
||||
|
||||
def torch_typename(self):
|
||||
return self._typename
|
||||
|
||||
def __repr__(self):
|
||||
return "TorchObject(%s, %s)" % (self._typename, repr(self._obj))
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
def __dir__(self):
|
||||
keys = list(self._obj.keys())
|
||||
keys.append('torch_typename')
|
||||
return keys
|
||||
|
||||
|
||||
def add_trivial_class_reader(typename):
|
||||
def reader(reader, version):
|
||||
obj = reader.read_obj()
|
||||
return TorchObject(typename, obj)
|
||||
torch_readers[typename] = reader
|
||||
for mod in [b"nn.ConcatTable", b"nn.SpatialAveragePooling",
|
||||
b"nn.TemporalConvolutionFB", b"nn.BCECriterion", b"nn.Reshape", b"nn.gModule",
|
||||
b"nn.SparseLinear", b"nn.WeightedLookupTable", b"nn.CAddTable",
|
||||
b"nn.TemporalConvolution", b"nn.PairwiseDistance", b"nn.WeightedMSECriterion",
|
||||
b"nn.SmoothL1Criterion", b"nn.TemporalSubSampling", b"nn.TanhShrink",
|
||||
b"nn.MixtureTable", b"nn.Mul", b"nn.LogSoftMax", b"nn.Min", b"nn.Exp", b"nn.Add",
|
||||
b"nn.BatchNormalization", b"nn.AbsCriterion", b"nn.MultiCriterion",
|
||||
b"nn.LookupTableGPU", b"nn.Max", b"nn.MulConstant", b"nn.NarrowTable", b"nn.View",
|
||||
b"nn.ClassNLLCriterionWithUNK", b"nn.VolumetricConvolution",
|
||||
b"nn.SpatialSubSampling", b"nn.HardTanh", b"nn.DistKLDivCriterion",
|
||||
b"nn.SplitTable", b"nn.DotProduct", b"nn.HingeEmbeddingCriterion",
|
||||
b"nn.SpatialBatchNormalization", b"nn.DepthConcat", b"nn.Sigmoid",
|
||||
b"nn.SpatialAdaptiveMaxPooling", b"nn.Parallel", b"nn.SoftShrink",
|
||||
b"nn.SpatialSubtractiveNormalization", b"nn.TrueNLLCriterion", b"nn.Log",
|
||||
b"nn.SpatialDropout", b"nn.LeakyReLU", b"nn.VolumetricMaxPooling",
|
||||
b"nn.KMaxPooling", b"nn.Linear", b"nn.Euclidean", b"nn.CriterionTable",
|
||||
b"nn.SpatialMaxPooling", b"nn.TemporalKMaxPooling", b"nn.MultiMarginCriterion",
|
||||
b"nn.ELU", b"nn.CSubTable", b"nn.MultiLabelMarginCriterion", b"nn.Copy",
|
||||
b"nn.CuBLASWrapper", b"nn.L1HingeEmbeddingCriterion",
|
||||
b"nn.VolumetricAveragePooling", b"nn.StochasticGradient",
|
||||
b"nn.SpatialContrastiveNormalization", b"nn.CosineEmbeddingCriterion",
|
||||
b"nn.CachingLookupTable", b"nn.FeatureLPPooling", b"nn.Padding", b"nn.Container",
|
||||
b"nn.MarginRankingCriterion", b"nn.Module", b"nn.ParallelCriterion",
|
||||
b"nn.DataParallelTable", b"nn.Concat", b"nn.CrossEntropyCriterion",
|
||||
b"nn.LookupTable", b"nn.SpatialSoftMax", b"nn.HardShrink", b"nn.Abs", b"nn.SoftMin",
|
||||
b"nn.WeightedEuclidean", b"nn.Replicate", b"nn.DataParallel",
|
||||
b"nn.OneBitQuantization", b"nn.OneBitDataParallel", b"nn.AddConstant", b"nn.L1Cost",
|
||||
b"nn.HSM", b"nn.PReLU", b"nn.JoinTable", b"nn.ClassNLLCriterion", b"nn.CMul",
|
||||
b"nn.CosineDistance", b"nn.Index", b"nn.Mean", b"nn.FFTWrapper", b"nn.Dropout",
|
||||
b"nn.SpatialConvolutionCuFFT", b"nn.SoftPlus", b"nn.AbstractParallel",
|
||||
b"nn.SequentialCriterion", b"nn.LocallyConnected",
|
||||
b"nn.SpatialDivisiveNormalization", b"nn.L1Penalty", b"nn.Threshold", b"nn.Power",
|
||||
b"nn.Sqrt", b"nn.MM", b"nn.GroupKMaxPooling", b"nn.CrossMapNormalization",
|
||||
b"nn.ReLU", b"nn.ClassHierarchicalNLLCriterion", b"nn.Optim", b"nn.SoftMax",
|
||||
b"nn.SpatialConvolutionMM", b"nn.Cosine", b"nn.Clamp", b"nn.CMulTable",
|
||||
b"nn.LogSigmoid", b"nn.LinearNB", b"nn.TemporalMaxPooling", b"nn.MSECriterion",
|
||||
b"nn.Sum", b"nn.SoftSign", b"nn.Normalize", b"nn.ParallelTable", b"nn.FlattenTable",
|
||||
b"nn.CDivTable", b"nn.Tanh", b"nn.ModuleFromCriterion", b"nn.Square", b"nn.Select",
|
||||
b"nn.GradientReversal", b"nn.SpatialFullConvolutionMap", b"nn.SpatialConvolution",
|
||||
b"nn.Criterion", b"nn.SpatialConvolutionMap", b"nn.SpatialLPPooling",
|
||||
b"nn.Sequential", b"nn.Transpose", b"nn.SpatialUpSamplingNearest",
|
||||
b"nn.SpatialFullConvolution", b"nn.ModelParallel", b"nn.RReLU",
|
||||
b"nn.SpatialZeroPadding", b"nn.Identity", b"nn.Narrow", b"nn.MarginCriterion",
|
||||
b"nn.SelectTable", b"nn.VolumetricFullConvolution",
|
||||
b"nn.SpatialFractionalMaxPooling", b"fbnn.ProjectiveGradientNormalization",
|
||||
b"fbnn.Probe", b"fbnn.SparseLinear", b"cudnn._Pooling3D",
|
||||
b"cudnn.VolumetricMaxPooling", b"cudnn.SpatialCrossEntropyCriterion",
|
||||
b"cudnn.VolumetricConvolution", b"cudnn.SpatialAveragePooling", b"cudnn.Tanh",
|
||||
b"cudnn.LogSoftMax", b"cudnn.SpatialConvolution", b"cudnn._Pooling",
|
||||
b"cudnn.SpatialMaxPooling", b"cudnn.ReLU", b"cudnn.SpatialCrossMapLRN",
|
||||
b"cudnn.SoftMax", b"cudnn._Pointwise", b"cudnn.SpatialSoftMax", b"cudnn.Sigmoid",
|
||||
b"cudnn.SpatialLogSoftMax", b"cudnn.VolumetricAveragePooling", b"nngraph.Node",
|
||||
b"nngraph.JustTable", b"graph.Edge", b"graph.Node", b"graph.Graph"]:
|
||||
|
||||
add_trivial_class_reader(mod)
|
||||
|
||||
|
||||
class T7ReaderException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class T7Reader:
|
||||
|
||||
def __init__(self,
|
||||
fileobj,
|
||||
use_list_heuristic=True,
|
||||
use_int_heuristic=True,
|
||||
force_deserialize_classes=True,
|
||||
force_8bytes_long=True):
|
||||
"""
|
||||
Params:
|
||||
* `fileobj` file object to read from, must be actual file object
|
||||
as it must support array, struct, and numpy
|
||||
* `use_list_heuristic`: automatically turn tables with only consecutive
|
||||
positive integral indices into lists
|
||||
(default True)
|
||||
* `use_int_heuristic`: cast all whole floats into ints (default True)
|
||||
* `force_deserialize_classes`: deserialize all classes, not just the
|
||||
whitelisted ones (default True)
|
||||
"""
|
||||
self.f = fileobj
|
||||
self.objects = {} # read objects so far
|
||||
|
||||
self.use_list_heuristic = use_list_heuristic
|
||||
self.use_int_heuristic = use_int_heuristic
|
||||
self.force_deserialize_classes = force_deserialize_classes
|
||||
self.force_8bytes_long = force_8bytes_long
|
||||
|
||||
def _read(self, fmt):
|
||||
sz = struct.calcsize(fmt)
|
||||
b = self.f.read(sz)
|
||||
if b == b'':
|
||||
# print('x')
|
||||
s = (0,)
|
||||
else:
|
||||
s = struct.unpack(fmt, b)
|
||||
|
||||
# print(s)
|
||||
return s
|
||||
|
||||
def read_boolean(self):
|
||||
return self.read_int() == 1
|
||||
|
||||
def read_int(self):
|
||||
return self._read('i')[0]
|
||||
|
||||
def read_long(self):
|
||||
if self.force_8bytes_long:
|
||||
return self._read('q')[0]
|
||||
else:
|
||||
return self._read('l')[0]
|
||||
|
||||
def read_long_array(self, n):
|
||||
if self.force_8bytes_long:
|
||||
lst = []
|
||||
for i in range(n):
|
||||
lst.append(self.read_long())
|
||||
return lst
|
||||
else:
|
||||
arr = array('l')
|
||||
arr.fromfile(self.f, n)
|
||||
return arr.tolist()
|
||||
|
||||
def read_float(self):
|
||||
return self._read('f')[0]
|
||||
|
||||
def read_double(self):
|
||||
return self._read('d')[0]
|
||||
|
||||
def read_string(self):
|
||||
size = self.read_int()
|
||||
return self.f.read(size)
|
||||
|
||||
def read_obj(self):
|
||||
typeidx = self.read_int()
|
||||
if typeidx == TYPE_NIL:
|
||||
return None
|
||||
elif typeidx == TYPE_NUMBER:
|
||||
x = self.read_double()
|
||||
# Extra checking for integral numbers:
|
||||
if self.use_int_heuristic and x.is_integer():
|
||||
return int(x)
|
||||
return x
|
||||
elif typeidx == TYPE_BOOLEAN:
|
||||
return self.read_boolean()
|
||||
elif typeidx == TYPE_STRING:
|
||||
return self.read_string()
|
||||
elif (typeidx == TYPE_TABLE or typeidx == TYPE_TORCH
|
||||
or typeidx == TYPE_FUNCTION or typeidx == TYPE_RECUR_FUNCTION
|
||||
or typeidx == LEGACY_TYPE_RECUR_FUNCTION):
|
||||
# read the index
|
||||
index = self.read_int()
|
||||
|
||||
# check it is loaded already
|
||||
if index in self.objects:
|
||||
return self.objects[index]
|
||||
|
||||
# otherwise read it
|
||||
if (typeidx == TYPE_FUNCTION or typeidx == TYPE_RECUR_FUNCTION
|
||||
or typeidx == LEGACY_TYPE_RECUR_FUNCTION):
|
||||
size = self.read_int()
|
||||
dumped = self.f.read(size)
|
||||
upvalues = self.read_obj()
|
||||
obj = LuaFunction(size, dumped, upvalues)
|
||||
self.objects[index] = obj
|
||||
return obj
|
||||
elif typeidx == TYPE_TORCH:
|
||||
version = self.read_string()
|
||||
if version.startswith(b'V '):
|
||||
versionNumber = int(version.partition(b' ')[2])
|
||||
className = self.read_string()
|
||||
else:
|
||||
className = version
|
||||
versionNumber = 0 # created before existence of versioning
|
||||
# print(className)
|
||||
if className not in torch_readers:
|
||||
if not self.force_deserialize_classes:
|
||||
raise T7ReaderException(
|
||||
'unsupported torch class: <%s>' % className)
|
||||
obj = TorchObject(className, self.read_obj())
|
||||
else:
|
||||
obj = torch_readers[className](self, version)
|
||||
self.objects[index] = obj
|
||||
return obj
|
||||
else: # it is a table: returns a custom dict or a list
|
||||
size = self.read_int()
|
||||
obj = hashable_uniq_dict() # custom hashable dict, can be a key
|
||||
key_sum = 0 # for checking if keys are consecutive
|
||||
keys_natural = True # and also natural numbers 1..n.
|
||||
# If so, returns a list with indices converted to 0-indices.
|
||||
for i in range(size):
|
||||
k = self.read_obj()
|
||||
v = self.read_obj()
|
||||
obj[k] = v
|
||||
|
||||
if self.use_list_heuristic:
|
||||
if not isinstance(k, int) or k <= 0:
|
||||
keys_natural = False
|
||||
elif isinstance(k, int):
|
||||
key_sum += k
|
||||
if self.use_list_heuristic:
|
||||
# n(n+1)/2 = sum <=> consecutive and natural numbers
|
||||
n = len(obj)
|
||||
if keys_natural and n * (n + 1) == 2 * key_sum:
|
||||
lst = []
|
||||
for i in range(len(obj)):
|
||||
lst.append(obj[i + 1])
|
||||
obj = lst
|
||||
self.objects[index] = obj
|
||||
return obj
|
||||
else:
|
||||
raise T7ReaderException("unknown object")
|
||||
|
||||
|
||||
def load(filename, **kwargs):
|
||||
"""
|
||||
Loads the given t7 file using default settings; kwargs are forwarded
|
||||
to `T7Reader`.
|
||||
"""
|
||||
with open(filename, 'rb') as f:
|
||||
reader = T7Reader(f, **kwargs)
|
||||
return reader.read_obj()
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import numpy as np
|
||||
|
||||
from functools import lru_cache
|
||||
from scipy.stats import multivariate_normal
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_gauss_pdf(sigma):
|
||||
n = sigma * 8
|
||||
|
||||
x, y = np.mgrid[0:n, 0:n]
|
||||
pos = np.empty(x.shape + (2,))
|
||||
pos[:, :, 0] = x
|
||||
pos[:, :, 1] = y
|
||||
|
||||
rv = multivariate_normal([n / 2, n / 2], [[sigma ** 2, 0], [0, sigma ** 2]])
|
||||
pdf = rv.pdf(pos)
|
||||
|
||||
heatmap = pdf / np.max(pdf)
|
||||
|
||||
return heatmap
|
||||
|
||||
|
||||
def to_int(num):
|
||||
return int(round(num))
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_binary_mask(diameter):
|
||||
d = diameter
|
||||
_map = np.zeros((d, d), dtype = np.float32)
|
||||
|
||||
r = d / 2
|
||||
s = int(d / 2)
|
||||
|
||||
y, x = np.ogrid[-s:d - s, -s:d - s]
|
||||
mask = x * x + y * y <= r * r
|
||||
|
||||
_map[mask] = 1.0
|
||||
|
||||
return _map
|
||||
|
||||
|
||||
def get_binary_heat_map(shape, is_present, centers, diameter = 9):
|
||||
n = diameter
|
||||
r = int(n / 2)
|
||||
hn = int(2 * n)
|
||||
qn = int(4 * n)
|
||||
pl = np.zeros((shape[0], shape[1] + qn, shape[2] + qn, shape[3]), dtype = np.float32)
|
||||
|
||||
for i in range(shape[0]):
|
||||
for j in range(shape[3]):
|
||||
my = centers[i, 0, j] - r
|
||||
mx = centers[i, 1, j] - r
|
||||
|
||||
if -n < my < shape[1] and -n < mx < shape[2] and is_present[i, j]:
|
||||
pl[i, my + hn:my + 3 * n, mx + hn:mx + 3 * n, j] = get_binary_mask(diameter)
|
||||
|
||||
return pl[:, hn:-hn, hn:-hn, :]
|
||||
|
||||
|
||||
def get_gauss_heat_map(shape, is_present, mean, sigma = 5):
|
||||
n = to_int(sigma * 8)
|
||||
hn = to_int(n / 2)
|
||||
dn = int(2 * n)
|
||||
qn = int(4 * n)
|
||||
pl = np.zeros((shape[0], shape[1] + qn, shape[2] + qn, shape[3]), dtype = np.float32)
|
||||
|
||||
for i in range(shape[0]):
|
||||
for j in range(shape[3]):
|
||||
my = mean[i, 0, j] - hn
|
||||
mx = mean[i, 1, j] - hn
|
||||
|
||||
if -n < my < shape[1] and -n < mx < shape[2] and is_present[i, j]:
|
||||
pl[i, my + dn:my + 3 * n, mx + dn:mx + 3 * n, j] = get_gauss_pdf(sigma)
|
||||
# else:
|
||||
# print(my, mx)
|
||||
|
||||
return pl[:, dn:-dn, dn:-dn, :]
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
# GaitSet
|
||||
|
||||
[-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
|
||||
[](https://996.icu)
|
||||
|
||||
GaitSet is a **flexible**, **effective** and **fast** network for cross-view gait recognition. The [paper](https://ieeexplore.ieee.org/document/9351667) has been published on IEEE TPAMI.
|
||||
|
||||
#### Flexible
|
||||
The input of GaitSet is a set of silhouettes.
|
||||
|
||||
- There are **NOT ANY constrains** on an input,
|
||||
which means it can contain **any number** of **non-consecutive** silhouettes filmed under **different viewpoints**
|
||||
with **different walking conditions**.
|
||||
|
||||
- As the input is a set, the **permutation** of the elements in the input
|
||||
will **NOT change** the output at all.
|
||||
|
||||
#### Effective
|
||||
It achieves **Rank@1=95.0%** on [CASIA-B](http://www.cbsr.ia.ac.cn/english/Gait%20Databases.asp)
|
||||
and **Rank@1=87.1%** on [OU-MVLP](http://www.am.sanken.osaka-u.ac.jp/BiometricDB/GaitMVLP.html),
|
||||
excluding identical-view cases.
|
||||
|
||||
#### Fast
|
||||
With 8 NVIDIA 1080TI GPUs, it only takes **7 minutes** to conduct an evaluation on
|
||||
[OU-MVLP](http://www.am.sanken.osaka-u.ac.jp/BiometricDB/GaitMVLP.html) which contains 133,780 sequences
|
||||
and average 70 frames per sequence.
|
||||
|
||||
## What's new
|
||||
The code and checkpoint for OUMVLP dataset have been released.
|
||||
See [OUMVLP](#oumvlp) for details.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.6
|
||||
- PyTorch 0.4+
|
||||
- GPU
|
||||
|
||||
|
||||
## Getting started
|
||||
### Installation
|
||||
|
||||
- (Not necessary) Install [Anaconda3](https://www.anaconda.com/download/)
|
||||
- Install [CUDA 9.0](https://developer.nvidia.com/cuda-90-download-archive)
|
||||
- install [cuDNN7.0](https://developer.nvidia.com/cudnn)
|
||||
- Install [PyTorch](http://pytorch.org/)
|
||||
|
||||
Noted that our code is tested based on [PyTorch 0.4](http://pytorch.org/)
|
||||
|
||||
### Dataset & Preparation
|
||||
Download [CASIA-B Dataset](http://www.cbsr.ia.ac.cn/english/Gait%20Databases.asp)
|
||||
|
||||
**!!! ATTENTION !!! ATTENTION !!! ATTENTION !!!**
|
||||
|
||||
Before training or test, please make sure you have prepared the dataset
|
||||
by this two steps:
|
||||
- **Step1:** Organize the directory as:
|
||||
`your_dataset_path/subject_ids/walking_conditions/views`.
|
||||
E.g. `CASIA-B/001/nm-01/000/`.
|
||||
- **Step2:** Cut and align the raw silhouettes with `pretreatment.py`.
|
||||
(See [pretreatment](#pretreatment) for details.)
|
||||
Welcome to try different ways of pretreatment but note that
|
||||
the silhouettes after pretreatment **MUST have a size of 64x64**.
|
||||
|
||||
Futhermore, you also can test our code on [OU-MVLP Dataset](http://www.am.sanken.osaka-u.ac.jp/BiometricDB/GaitMVLP.html).
|
||||
The number of channels and the training batchsize is slightly different for this dataset.
|
||||
For more detail, please refer to [our paper](https://arxiv.org/abs/1811.06186).
|
||||
|
||||
#### Pretreatment
|
||||
`pretreatment.py` uses the alignment method in
|
||||
[this paper](https://ipsjcva.springeropen.com/articles/10.1186/s41074-018-0039-6).
|
||||
Pretreatment your dataset by
|
||||
```
|
||||
python pretreatment.py --input_path='root_path_of_raw_dataset' --output_path='root_path_for_output'
|
||||
```
|
||||
- `--input_path` **(NECESSARY)** Root path of raw dataset.
|
||||
- `--output_path` **(NECESSARY)** Root path for output.
|
||||
- `--log_file` Log file path. #Default: './pretreatment.log'
|
||||
- `--log` If set as True, all logs will be saved.
|
||||
Otherwise, only warnings and errors will be saved. #Default: False
|
||||
- `--worker_num` How many subprocesses to use for data pretreatment. Default: 1
|
||||
|
||||
### Configuration
|
||||
|
||||
In `config.py`, you might want to change the following settings:
|
||||
- `dataset_path` **(NECESSARY)** root path of the dataset
|
||||
(for the above example, it is "gaitdata")
|
||||
- `WORK_PATH` path to save/load checkpoints
|
||||
- `CUDA_VISIBLE_DEVICES` indices of GPUs
|
||||
|
||||
### Train
|
||||
Train a model by
|
||||
```bash
|
||||
python train.py
|
||||
```
|
||||
- `--cache` if set as TRUE all the training data will be loaded at once before the training start.
|
||||
This will accelerate the training.
|
||||
**Note that** if this arg is set as FALSE, samples will NOT be kept in the memory
|
||||
even they have been used in the former iterations. #Default: TRUE
|
||||
|
||||
### Evaluation
|
||||
Evaluate the trained model by
|
||||
```bash
|
||||
python test.py
|
||||
```
|
||||
- `--iter` iteration of the checkpoint to load. #Default: 80000
|
||||
- `--batch_size` batch size of the parallel test. #Default: 1
|
||||
- `--cache` if set as TRUE all the test data will be loaded at once before the transforming start.
|
||||
This might accelerate the testing. #Default: FALSE
|
||||
|
||||
It will output Rank@1 of all three walking conditions.
|
||||
Note that the test is **parallelizable**.
|
||||
To conduct a faster evaluation, you could use `--batch_size` to change the batch size for test.
|
||||
|
||||
#### OUMVLP
|
||||
Since the huge differences between OUMVLP and CASIA-B, the network setting on OUMVLP is slightly different.
|
||||
- The alternated network's code can be found at `./work/OUMVLP_network`. Use them to replace the corresponding files in `./model/network`.
|
||||
- The checkpoint can be found [here](https://1drv.ms/u/s!AurT2TsSKdxQuWN8drzIv_phTR5m?e=Gfbl3m).
|
||||
- In `./config.py`, modify `'batch_size': (8, 16)` into `'batch_size': (32,16)`.
|
||||
- Prepare your OUMVLP dataset according to the instructions in [Dataset & Preparation](#dataset--preparation).
|
||||
|
||||
## To Do List
|
||||
- Transformation: The script for transforming a set of silhouettes into a discriminative representation.
|
||||
|
||||
## Authors & Contributors
|
||||
GaitSet is authored by
|
||||
[Hanqing Chao](https://www.linkedin.com/in/hanqing-chao-9aa42412b/),
|
||||
[Yiwei He](https://www.linkedin.com/in/yiwei-he-4a6a6bbb/),
|
||||
[Junping Zhang](http://www.pami.fudan.edu.cn/~jpzhang/)
|
||||
and JianFeng Feng from Fudan Universiy.
|
||||
[Junping Zhang](http://www.pami.fudan.edu.cn/~jpzhang/)
|
||||
is the corresponding author.
|
||||
The code is developed by
|
||||
[Hanqing Chao](https://www.linkedin.com/in/hanqing-chao-9aa42412b/)
|
||||
and [Yiwei He](https://www.linkedin.com/in/yiwei-he-4a6a6bbb/).
|
||||
Currently, it is being maintained by
|
||||
[Hanqing Chao](https://www.linkedin.com/in/hanqing-chao-9aa42412b/)
|
||||
and Kun Wang.
|
||||
|
||||
|
||||
## Citation
|
||||
Please cite these papers in your publications if it helps your research:
|
||||
```
|
||||
@ARTICLE{chao2019gaitset,
|
||||
author={Chao, Hanqing and Wang, Kun and He, Yiwei and Zhang, Junping and Feng, Jianfeng},
|
||||
journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},
|
||||
title={GaitSet: Cross-view Gait Recognition through Utilizing Gait as a Deep Set},
|
||||
year={2021},
|
||||
pages={1-1},
|
||||
doi={10.1109/TPAMI.2021.3057879}}
|
||||
```
|
||||
Link to paper:
|
||||
- [GaitSet: Cross-view Gait Recognition through Utilizing Gait as a Deep Set](https://ieeexplore.ieee.org/document/9351667)
|
||||
|
||||
|
||||
## License
|
||||
GaitSet is freely available for free non-commercial use, and may be redistributed under these conditions.
|
||||
For commercial queries, contact [Junping Zhang](http://www.pami.fudan.edu.cn/~jpzhang/).
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
class BasicConv2d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, **kwargs):
|
||||
super(BasicConv2d, self).__init__()
|
||||
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, bias=False, **kwargs)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
return F.leaky_relu(x, inplace=True)
|
||||
|
||||
|
||||
class SetBlock(nn.Module):
|
||||
def __init__(self, forward_block, pooling=False):
|
||||
super(SetBlock, self).__init__()
|
||||
self.forward_block = forward_block
|
||||
self.pooling = pooling
|
||||
if pooling:
|
||||
self.pool2d = nn.MaxPool2d(2)
|
||||
def forward(self, x):
|
||||
n, s, c, h, w = x.size()
|
||||
x = self.forward_block(x.view(-1,c,h,w))
|
||||
if self.pooling:
|
||||
x = self.pool2d(x)
|
||||
_, c, h, w = x.size()
|
||||
return x.view(n, s, c, h ,w)
|
||||
|
||||
|
||||
class HPM(nn.Module):
|
||||
def __init__(self, in_dim, out_dim, bin_level_num=5):
|
||||
super(HPM, self).__init__()
|
||||
self.bin_num = [2**i for i in range(bin_level_num)]
|
||||
self.fc_bin = nn.ParameterList([
|
||||
nn.Parameter(
|
||||
nn.init.xavier_uniform(
|
||||
torch.zeros(sum(self.bin_num), in_dim, out_dim)))])
|
||||
def forward(self, x):
|
||||
feature = list()
|
||||
n, c, h, w = x.size()
|
||||
for num_bin in self.bin_num:
|
||||
z = x.view(n, c, num_bin, -1)
|
||||
z = z.mean(3)+z.max(3)[0]
|
||||
feature.append(z)
|
||||
feature = torch.cat(feature, 2).permute(2, 0, 1).contiguous()
|
||||
|
||||
feature = feature.matmul(self.fc_bin[0])
|
||||
return feature.permute(1, 0, 2).contiguous()
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
conf = {
|
||||
"WORK_PATH": "./work",
|
||||
"CUDA_VISIBLE_DEVICES": "0,1,2,3",
|
||||
"data": {
|
||||
'dataset_path': "your_dataset_path",
|
||||
'resolution': '64',
|
||||
'dataset': 'CASIA-B',
|
||||
# In CASIA-B, data of subject #5 is incomplete.
|
||||
# Thus, we ignore it in training.
|
||||
# For more detail, please refer to
|
||||
# function: utils.data_loader.load_data
|
||||
'pid_num': 73,
|
||||
'pid_shuffle': False,
|
||||
},
|
||||
"model": {
|
||||
'hidden_dim': 256,
|
||||
'lr': 1e-4,
|
||||
'hard_or_full_trip': 'full',
|
||||
'batch_size': (8, 16),
|
||||
'restore_iter': 0,
|
||||
'total_iter': 80000,
|
||||
'margin': 0.2,
|
||||
'num_workers': 3,
|
||||
'frame_num': 30,
|
||||
'model_name': 'GaitSet',
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Author : admin
|
||||
# @Time : 2018/11/16
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Author : admin
|
||||
# @Time : 2018/11/15
|
||||
import os
|
||||
from copy import deepcopy
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .utils import load_data
|
||||
from .model import Model
|
||||
|
||||
|
||||
def initialize_data(config, train=False, test=False):
|
||||
print("Initializing data source...")
|
||||
train_source, test_source = load_data(**config['data'], cache=(train or test))
|
||||
if train:
|
||||
print("Loading training data...")
|
||||
train_source.load_all_data()
|
||||
if test:
|
||||
print("Loading test data...")
|
||||
test_source.load_all_data()
|
||||
print("Data initialization complete.")
|
||||
return train_source, test_source
|
||||
|
||||
|
||||
def initialize_model(config, train_source, test_source):
|
||||
print("Initializing model...")
|
||||
data_config = config['data']
|
||||
model_config = config['model']
|
||||
model_param = deepcopy(model_config)
|
||||
model_param['train_source'] = train_source
|
||||
model_param['test_source'] = test_source
|
||||
model_param['train_pid_num'] = data_config['pid_num']
|
||||
batch_size = int(np.prod(model_config['batch_size']))
|
||||
model_param['save_name'] = '_'.join(map(str,[
|
||||
model_config['model_name'],
|
||||
data_config['dataset'],
|
||||
data_config['pid_num'],
|
||||
data_config['pid_shuffle'],
|
||||
model_config['hidden_dim'],
|
||||
model_config['margin'],
|
||||
batch_size,
|
||||
model_config['hard_or_full_trip'],
|
||||
model_config['frame_num'],
|
||||
]))
|
||||
|
||||
m = Model(**model_param)
|
||||
print("Model initialization complete.")
|
||||
return m, model_param['save_name']
|
||||
|
||||
|
||||
def initialization(config, train=False, test=False):
|
||||
print("Initialzing...")
|
||||
WORK_PATH = config['WORK_PATH']
|
||||
os.chdir(WORK_PATH)
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = config["CUDA_VISIBLE_DEVICES"]
|
||||
train_source, test_source = initialize_data(config, train, test)
|
||||
return initialize_model(config, train_source, test_source)
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
import math
|
||||
import os
|
||||
import os.path as osp
|
||||
import random
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.autograd as autograd
|
||||
import torch.optim as optim
|
||||
import torch.utils.data as tordata
|
||||
|
||||
from .network import TripletLoss, SetNet
|
||||
from .utils import TripletSampler
|
||||
|
||||
|
||||
class Model:
|
||||
def __init__(self,
|
||||
hidden_dim,
|
||||
lr,
|
||||
hard_or_full_trip,
|
||||
margin,
|
||||
num_workers,
|
||||
batch_size,
|
||||
restore_iter,
|
||||
total_iter,
|
||||
save_name,
|
||||
train_pid_num,
|
||||
frame_num,
|
||||
model_name,
|
||||
train_source,
|
||||
test_source,
|
||||
img_size=64):
|
||||
|
||||
self.save_name = save_name
|
||||
self.train_pid_num = train_pid_num
|
||||
self.train_source = train_source
|
||||
self.test_source = test_source
|
||||
|
||||
self.hidden_dim = hidden_dim
|
||||
self.lr = lr
|
||||
self.hard_or_full_trip = hard_or_full_trip
|
||||
self.margin = margin
|
||||
self.frame_num = frame_num
|
||||
self.num_workers = num_workers
|
||||
self.batch_size = batch_size
|
||||
self.model_name = model_name
|
||||
self.P, self.M = batch_size
|
||||
|
||||
self.restore_iter = restore_iter
|
||||
self.total_iter = total_iter
|
||||
|
||||
self.img_size = img_size
|
||||
|
||||
self.encoder = SetNet(self.hidden_dim).float()
|
||||
self.encoder = nn.DataParallel(self.encoder)
|
||||
self.triplet_loss = TripletLoss(self.P * self.M, self.hard_or_full_trip, self.margin).float()
|
||||
self.triplet_loss = nn.DataParallel(self.triplet_loss)
|
||||
self.encoder.cuda()
|
||||
self.triplet_loss.cuda()
|
||||
|
||||
self.optimizer = optim.Adam([
|
||||
{'params': self.encoder.parameters()},
|
||||
], lr=self.lr)
|
||||
|
||||
self.hard_loss_metric = []
|
||||
self.full_loss_metric = []
|
||||
self.full_loss_num = []
|
||||
self.dist_list = []
|
||||
self.mean_dist = 0.01
|
||||
|
||||
self.sample_type = 'all'
|
||||
|
||||
def collate_fn(self, batch):
|
||||
batch_size = len(batch)
|
||||
feature_num = len(batch[0][0])
|
||||
seqs = [batch[i][0] for i in range(batch_size)]
|
||||
frame_sets = [batch[i][1] for i in range(batch_size)]
|
||||
view = [batch[i][2] for i in range(batch_size)]
|
||||
seq_type = [batch[i][3] for i in range(batch_size)]
|
||||
label = [batch[i][4] for i in range(batch_size)]
|
||||
batch = [seqs, view, seq_type, label, None]
|
||||
|
||||
def select_frame(index):
|
||||
sample = seqs[index]
|
||||
frame_set = frame_sets[index]
|
||||
if self.sample_type == 'random':
|
||||
frame_id_list = random.choices(frame_set, k=self.frame_num)
|
||||
_ = [feature.loc[frame_id_list].values for feature in sample]
|
||||
else:
|
||||
_ = [feature.values for feature in sample]
|
||||
return _
|
||||
|
||||
seqs = list(map(select_frame, range(len(seqs))))
|
||||
|
||||
if self.sample_type == 'random':
|
||||
seqs = [np.asarray([seqs[i][j] for i in range(batch_size)]) for j in range(feature_num)]
|
||||
else:
|
||||
gpu_num = min(torch.cuda.device_count(), batch_size)
|
||||
batch_per_gpu = math.ceil(batch_size / gpu_num)
|
||||
batch_frames = [[
|
||||
len(frame_sets[i])
|
||||
for i in range(batch_per_gpu * _, batch_per_gpu * (_ + 1))
|
||||
if i < batch_size
|
||||
] for _ in range(gpu_num)]
|
||||
if len(batch_frames[-1]) != batch_per_gpu:
|
||||
for _ in range(batch_per_gpu - len(batch_frames[-1])):
|
||||
batch_frames[-1].append(0)
|
||||
max_sum_frame = np.max([np.sum(batch_frames[_]) for _ in range(gpu_num)])
|
||||
seqs = [[
|
||||
np.concatenate([
|
||||
seqs[i][j]
|
||||
for i in range(batch_per_gpu * _, batch_per_gpu * (_ + 1))
|
||||
if i < batch_size
|
||||
], 0) for _ in range(gpu_num)]
|
||||
for j in range(feature_num)]
|
||||
seqs = [np.asarray([
|
||||
np.pad(seqs[j][_],
|
||||
((0, max_sum_frame - seqs[j][_].shape[0]), (0, 0), (0, 0)),
|
||||
'constant',
|
||||
constant_values=0)
|
||||
for _ in range(gpu_num)])
|
||||
for j in range(feature_num)]
|
||||
batch[4] = np.asarray(batch_frames)
|
||||
|
||||
batch[0] = seqs
|
||||
return batch
|
||||
|
||||
def fit(self):
|
||||
if self.restore_iter != 0:
|
||||
self.load(self.restore_iter)
|
||||
|
||||
self.encoder.train()
|
||||
self.sample_type = 'random'
|
||||
for param_group in self.optimizer.param_groups:
|
||||
param_group['lr'] = self.lr
|
||||
triplet_sampler = TripletSampler(self.train_source, self.batch_size)
|
||||
train_loader = tordata.DataLoader(
|
||||
dataset=self.train_source,
|
||||
batch_sampler=triplet_sampler,
|
||||
collate_fn=self.collate_fn,
|
||||
num_workers=self.num_workers)
|
||||
|
||||
train_label_set = list(self.train_source.label_set)
|
||||
train_label_set.sort()
|
||||
|
||||
_time1 = datetime.now()
|
||||
for seq, view, seq_type, label, batch_frame in train_loader:
|
||||
self.restore_iter += 1
|
||||
self.optimizer.zero_grad()
|
||||
|
||||
for i in range(len(seq)):
|
||||
seq[i] = self.np2var(seq[i]).float()
|
||||
if batch_frame is not None:
|
||||
batch_frame = self.np2var(batch_frame).int()
|
||||
|
||||
feature, label_prob = self.encoder(*seq, batch_frame)
|
||||
|
||||
target_label = [train_label_set.index(l) for l in label]
|
||||
target_label = self.np2var(np.array(target_label)).long()
|
||||
|
||||
triplet_feature = feature.permute(1, 0, 2).contiguous()
|
||||
triplet_label = target_label.unsqueeze(0).repeat(triplet_feature.size(0), 1)
|
||||
(full_loss_metric, hard_loss_metric, mean_dist, full_loss_num
|
||||
) = self.triplet_loss(triplet_feature, triplet_label)
|
||||
if self.hard_or_full_trip == 'hard':
|
||||
loss = hard_loss_metric.mean()
|
||||
elif self.hard_or_full_trip == 'full':
|
||||
loss = full_loss_metric.mean()
|
||||
|
||||
self.hard_loss_metric.append(hard_loss_metric.mean().data.cpu().numpy())
|
||||
self.full_loss_metric.append(full_loss_metric.mean().data.cpu().numpy())
|
||||
self.full_loss_num.append(full_loss_num.mean().data.cpu().numpy())
|
||||
self.dist_list.append(mean_dist.mean().data.cpu().numpy())
|
||||
|
||||
if loss > 1e-9:
|
||||
loss.backward()
|
||||
self.optimizer.step()
|
||||
|
||||
if self.restore_iter % 1000 == 0:
|
||||
print(datetime.now() - _time1)
|
||||
_time1 = datetime.now()
|
||||
|
||||
if self.restore_iter % 100 == 0:
|
||||
self.save()
|
||||
print('iter {}:'.format(self.restore_iter), end='')
|
||||
print(', hard_loss_metric={0:.8f}'.format(np.mean(self.hard_loss_metric)), end='')
|
||||
print(', full_loss_metric={0:.8f}'.format(np.mean(self.full_loss_metric)), end='')
|
||||
print(', full_loss_num={0:.8f}'.format(np.mean(self.full_loss_num)), end='')
|
||||
self.mean_dist = np.mean(self.dist_list)
|
||||
print(', mean_dist={0:.8f}'.format(self.mean_dist), end='')
|
||||
print(', lr=%f' % self.optimizer.param_groups[0]['lr'], end='')
|
||||
print(', hard or full=%r' % self.hard_or_full_trip)
|
||||
sys.stdout.flush()
|
||||
self.hard_loss_metric = []
|
||||
self.full_loss_metric = []
|
||||
self.full_loss_num = []
|
||||
self.dist_list = []
|
||||
|
||||
# Visualization using t-SNE
|
||||
# if self.restore_iter % 500 == 0:
|
||||
# pca = TSNE(2)
|
||||
# pca_feature = pca.fit_transform(feature.view(feature.size(0), -1).data.cpu().numpy())
|
||||
# for i in range(self.P):
|
||||
# plt.scatter(pca_feature[self.M * i:self.M * (i + 1), 0],
|
||||
# pca_feature[self.M * i:self.M * (i + 1), 1], label=label[self.M * i])
|
||||
#
|
||||
# plt.show()
|
||||
|
||||
if self.restore_iter == self.total_iter:
|
||||
break
|
||||
|
||||
def ts2var(self, x):
|
||||
return autograd.Variable(x).cuda()
|
||||
|
||||
def np2var(self, x):
|
||||
return self.ts2var(torch.from_numpy(x))
|
||||
|
||||
def transform(self, flag, batch_size=1):
|
||||
self.encoder.eval()
|
||||
source = self.test_source if flag == 'test' else self.train_source
|
||||
self.sample_type = 'all'
|
||||
data_loader = tordata.DataLoader(
|
||||
dataset=source,
|
||||
batch_size=batch_size,
|
||||
sampler=tordata.sampler.SequentialSampler(source),
|
||||
collate_fn=self.collate_fn,
|
||||
num_workers=self.num_workers)
|
||||
|
||||
feature_list = list()
|
||||
view_list = list()
|
||||
seq_type_list = list()
|
||||
label_list = list()
|
||||
|
||||
for i, x in enumerate(data_loader):
|
||||
seq, view, seq_type, label, batch_frame = x
|
||||
for j in range(len(seq)):
|
||||
seq[j] = self.np2var(seq[j]).float()
|
||||
if batch_frame is not None:
|
||||
batch_frame = self.np2var(batch_frame).int()
|
||||
# print(batch_frame, np.sum(batch_frame))
|
||||
|
||||
feature, _ = self.encoder(*seq, batch_frame)
|
||||
n, num_bin, _ = feature.size()
|
||||
feature_list.append(feature.view(n, -1).data.cpu().numpy())
|
||||
view_list += view
|
||||
seq_type_list += seq_type
|
||||
label_list += label
|
||||
|
||||
return np.concatenate(feature_list, 0), view_list, seq_type_list, label_list
|
||||
|
||||
def save(self):
|
||||
os.makedirs(osp.join('checkpoint', self.model_name), exist_ok=True)
|
||||
torch.save(self.encoder.state_dict(),
|
||||
osp.join('checkpoint', self.model_name,
|
||||
'{}-{:0>5}-encoder.ptm'.format(
|
||||
self.save_name, self.restore_iter)))
|
||||
torch.save(self.optimizer.state_dict(),
|
||||
osp.join('checkpoint', self.model_name,
|
||||
'{}-{:0>5}-optimizer.ptm'.format(
|
||||
self.save_name, self.restore_iter)))
|
||||
|
||||
# restore_iter: iteration index of the checkpoint to load
|
||||
def load(self, restore_iter):
|
||||
self.encoder.load_state_dict(torch.load(osp.join(
|
||||
'checkpoint', self.model_name,
|
||||
'{}-{:0>5}-encoder.ptm'.format(self.save_name, restore_iter))))
|
||||
self.optimizer.load_state_dict(torch.load(osp.join(
|
||||
'checkpoint', self.model_name,
|
||||
'{}-{:0>5}-optimizer.ptm'.format(self.save_name, restore_iter))))
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from .triplet import TripletLoss
|
||||
from .gaitset import SetNet
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
class BasicConv2d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, **kwargs):
|
||||
super(BasicConv2d, self).__init__()
|
||||
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, bias=False, **kwargs)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
return F.leaky_relu(x, inplace=True)
|
||||
|
||||
|
||||
class SetBlock(nn.Module):
|
||||
def __init__(self, forward_block, pooling=False):
|
||||
super(SetBlock, self).__init__()
|
||||
self.forward_block = forward_block
|
||||
self.pooling = pooling
|
||||
if pooling:
|
||||
self.pool2d = nn.MaxPool2d(2)
|
||||
def forward(self, x):
|
||||
n, s, c, h, w = x.size()
|
||||
x = self.forward_block(x.view(-1,c,h,w))
|
||||
if self.pooling:
|
||||
x = self.pool2d(x)
|
||||
_, c, h, w = x.size()
|
||||
return x.view(n, s, c, h ,w)
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
|
||||
from .basic_blocks import SetBlock, BasicConv2d
|
||||
|
||||
|
||||
class SetNet(nn.Module):
|
||||
def __init__(self, hidden_dim):
|
||||
super(SetNet, self).__init__()
|
||||
self.hidden_dim = hidden_dim
|
||||
self.batch_frame = None
|
||||
|
||||
_set_in_channels = 1
|
||||
_set_channels = [32, 64, 128]
|
||||
self.set_layer1 = SetBlock(BasicConv2d(_set_in_channels, _set_channels[0], 5, padding=2))
|
||||
self.set_layer2 = SetBlock(BasicConv2d(_set_channels[0], _set_channels[0], 3, padding=1), True)
|
||||
self.set_layer3 = SetBlock(BasicConv2d(_set_channels[0], _set_channels[1], 3, padding=1))
|
||||
self.set_layer4 = SetBlock(BasicConv2d(_set_channels[1], _set_channels[1], 3, padding=1), True)
|
||||
self.set_layer5 = SetBlock(BasicConv2d(_set_channels[1], _set_channels[2], 3, padding=1))
|
||||
self.set_layer6 = SetBlock(BasicConv2d(_set_channels[2], _set_channels[2], 3, padding=1))
|
||||
|
||||
_gl_in_channels = 32
|
||||
_gl_channels = [64, 128]
|
||||
self.gl_layer1 = BasicConv2d(_gl_in_channels, _gl_channels[0], 3, padding=1)
|
||||
self.gl_layer2 = BasicConv2d(_gl_channels[0], _gl_channels[0], 3, padding=1)
|
||||
self.gl_layer3 = BasicConv2d(_gl_channels[0], _gl_channels[1], 3, padding=1)
|
||||
self.gl_layer4 = BasicConv2d(_gl_channels[1], _gl_channels[1], 3, padding=1)
|
||||
self.gl_pooling = nn.MaxPool2d(2)
|
||||
|
||||
self.bin_num = [1, 2, 4, 8, 16]
|
||||
self.fc_bin = nn.ParameterList([
|
||||
nn.Parameter(
|
||||
nn.init.xavier_uniform_(
|
||||
torch.zeros(sum(self.bin_num) * 2, 128, hidden_dim)))])
|
||||
|
||||
for m in self.modules():
|
||||
if isinstance(m, (nn.Conv2d, nn.Conv1d)):
|
||||
nn.init.xavier_uniform_(m.weight.data)
|
||||
elif isinstance(m, nn.Linear):
|
||||
nn.init.xavier_uniform_(m.weight.data)
|
||||
nn.init.constant(m.bias.data, 0.0)
|
||||
elif isinstance(m, (nn.BatchNorm2d, nn.BatchNorm1d)):
|
||||
nn.init.normal(m.weight.data, 1.0, 0.02)
|
||||
nn.init.constant(m.bias.data, 0.0)
|
||||
|
||||
def frame_max(self, x):
|
||||
if self.batch_frame is None:
|
||||
return torch.max(x, 1)
|
||||
else:
|
||||
_tmp = [
|
||||
torch.max(x[:, self.batch_frame[i]:self.batch_frame[i + 1], :, :, :], 1)
|
||||
for i in range(len(self.batch_frame) - 1)
|
||||
]
|
||||
max_list = torch.cat([_tmp[i][0] for i in range(len(_tmp))], 0)
|
||||
arg_max_list = torch.cat([_tmp[i][1] for i in range(len(_tmp))], 0)
|
||||
return max_list, arg_max_list
|
||||
|
||||
def frame_median(self, x):
|
||||
if self.batch_frame is None:
|
||||
return torch.median(x, 1)
|
||||
else:
|
||||
_tmp = [
|
||||
torch.median(x[:, self.batch_frame[i]:self.batch_frame[i + 1], :, :, :], 1)
|
||||
for i in range(len(self.batch_frame) - 1)
|
||||
]
|
||||
median_list = torch.cat([_tmp[i][0] for i in range(len(_tmp))], 0)
|
||||
arg_median_list = torch.cat([_tmp[i][1] for i in range(len(_tmp))], 0)
|
||||
return median_list, arg_median_list
|
||||
|
||||
def forward(self, silho, batch_frame=None):
|
||||
# n: batch_size, s: frame_num, k: keypoints_num, c: channel
|
||||
if batch_frame is not None:
|
||||
batch_frame = batch_frame[0].data.cpu().numpy().tolist()
|
||||
_ = len(batch_frame)
|
||||
for i in range(len(batch_frame)):
|
||||
if batch_frame[-(i + 1)] != 0:
|
||||
break
|
||||
else:
|
||||
_ -= 1
|
||||
batch_frame = batch_frame[:_]
|
||||
frame_sum = np.sum(batch_frame)
|
||||
if frame_sum < silho.size(1):
|
||||
silho = silho[:, :frame_sum, :, :]
|
||||
self.batch_frame = [0] + np.cumsum(batch_frame).tolist()
|
||||
n = silho.size(0)
|
||||
x = silho.unsqueeze(2)
|
||||
del silho
|
||||
|
||||
x = self.set_layer1(x)
|
||||
x = self.set_layer2(x)
|
||||
gl = self.gl_layer1(self.frame_max(x)[0])
|
||||
gl = self.gl_layer2(gl)
|
||||
gl = self.gl_pooling(gl)
|
||||
|
||||
x = self.set_layer3(x)
|
||||
x = self.set_layer4(x)
|
||||
gl = self.gl_layer3(gl + self.frame_max(x)[0])
|
||||
gl = self.gl_layer4(gl)
|
||||
|
||||
x = self.set_layer5(x)
|
||||
x = self.set_layer6(x)
|
||||
x = self.frame_max(x)[0]
|
||||
gl = gl + x
|
||||
|
||||
feature = list()
|
||||
n, c, h, w = gl.size()
|
||||
for num_bin in self.bin_num:
|
||||
z = x.view(n, c, num_bin, -1)
|
||||
z = z.mean(3) + z.max(3)[0]
|
||||
feature.append(z)
|
||||
z = gl.view(n, c, num_bin, -1)
|
||||
z = z.mean(3) + z.max(3)[0]
|
||||
feature.append(z)
|
||||
feature = torch.cat(feature, 2).permute(2, 0, 1).contiguous()
|
||||
|
||||
feature = feature.matmul(self.fc_bin[0])
|
||||
feature = feature.permute(1, 0, 2).contiguous()
|
||||
|
||||
return feature, None
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class TripletLoss(nn.Module):
|
||||
def __init__(self, batch_size, hard_or_full, margin):
|
||||
super(TripletLoss, self).__init__()
|
||||
self.batch_size = batch_size
|
||||
self.margin = margin
|
||||
|
||||
def forward(self, feature, label):
|
||||
# feature: [n, m, d], label: [n, m]
|
||||
n, m, d = feature.size()
|
||||
hp_mask = (label.unsqueeze(1) == label.unsqueeze(2)).byte().view(-1)
|
||||
hn_mask = (label.unsqueeze(1) != label.unsqueeze(2)).byte().view(-1)
|
||||
|
||||
dist = self.batch_dist(feature)
|
||||
mean_dist = dist.mean(1).mean(1)
|
||||
dist = dist.view(-1)
|
||||
# hard
|
||||
hard_hp_dist = torch.max(torch.masked_select(dist, hp_mask).view(n, m, -1), 2)[0]
|
||||
hard_hn_dist = torch.min(torch.masked_select(dist, hn_mask).view(n, m, -1), 2)[0]
|
||||
hard_loss_metric = F.relu(self.margin + hard_hp_dist - hard_hn_dist).view(n, -1)
|
||||
|
||||
hard_loss_metric_mean = torch.mean(hard_loss_metric, 1)
|
||||
|
||||
# non-zero full
|
||||
full_hp_dist = torch.masked_select(dist, hp_mask).view(n, m, -1, 1)
|
||||
full_hn_dist = torch.masked_select(dist, hn_mask).view(n, m, 1, -1)
|
||||
full_loss_metric = F.relu(self.margin + full_hp_dist - full_hn_dist).view(n, -1)
|
||||
|
||||
full_loss_metric_sum = full_loss_metric.sum(1)
|
||||
full_loss_num = (full_loss_metric != 0).sum(1).float()
|
||||
|
||||
full_loss_metric_mean = full_loss_metric_sum / full_loss_num
|
||||
full_loss_metric_mean[full_loss_num == 0] = 0
|
||||
|
||||
return full_loss_metric_mean, hard_loss_metric_mean, mean_dist, full_loss_num
|
||||
|
||||
def batch_dist(self, x):
|
||||
x2 = torch.sum(x ** 2, 2)
|
||||
dist = x2.unsqueeze(2) + x2.unsqueeze(2).transpose(1, 2) - 2 * torch.matmul(x, x.transpose(1, 2))
|
||||
dist = torch.sqrt(F.relu(dist))
|
||||
return dist
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from .data_loader import load_data
|
||||
from .data_set import DataSet
|
||||
from .evaluator import evaluation
|
||||
from .sampler import TripletSampler
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import os
|
||||
import os.path as osp
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .data_set import DataSet
|
||||
|
||||
|
||||
def load_data(dataset_path, resolution, dataset, pid_num, pid_shuffle, cache=True):
|
||||
seq_dir = list()
|
||||
view = list()
|
||||
seq_type = list()
|
||||
label = list()
|
||||
|
||||
for _label in sorted(list(os.listdir(dataset_path))):
|
||||
# In CASIA-B, data of subject #5 is incomplete.
|
||||
# Thus, we ignore it in training.
|
||||
if dataset == 'CASIA-B' and _label == '005':
|
||||
continue
|
||||
label_path = osp.join(dataset_path, _label)
|
||||
for _seq_type in sorted(list(os.listdir(label_path))):
|
||||
seq_type_path = osp.join(label_path, _seq_type)
|
||||
for _view in sorted(list(os.listdir(seq_type_path))):
|
||||
_seq_dir = osp.join(seq_type_path, _view)
|
||||
seqs = os.listdir(_seq_dir)
|
||||
if len(seqs) > 0:
|
||||
seq_dir.append([_seq_dir])
|
||||
label.append(_label)
|
||||
seq_type.append(_seq_type)
|
||||
view.append(_view)
|
||||
|
||||
pid_fname = osp.join('partition', '{}_{}_{}.npy'.format(
|
||||
dataset, pid_num, pid_shuffle))
|
||||
if not osp.exists(pid_fname):
|
||||
pid_list = sorted(list(set(label)))
|
||||
if pid_shuffle:
|
||||
np.random.shuffle(pid_list)
|
||||
pid_list = [pid_list[0:pid_num], pid_list[pid_num:]]
|
||||
os.makedirs('partition', exist_ok=True)
|
||||
np.save(pid_fname, pid_list)
|
||||
|
||||
pid_list = np.load(pid_fname)
|
||||
train_list = pid_list[0]
|
||||
test_list = pid_list[1]
|
||||
train_source = DataSet(
|
||||
[seq_dir[i] for i, l in enumerate(label) if l in train_list],
|
||||
[label[i] for i, l in enumerate(label) if l in train_list],
|
||||
[seq_type[i] for i, l in enumerate(label) if l in train_list],
|
||||
[view[i] for i, l in enumerate(label)
|
||||
if l in train_list],
|
||||
cache, resolution)
|
||||
test_source = DataSet(
|
||||
[seq_dir[i] for i, l in enumerate(label) if l in test_list],
|
||||
[label[i] for i, l in enumerate(label) if l in test_list],
|
||||
[seq_type[i] for i, l in enumerate(label) if l in test_list],
|
||||
[view[i] for i, l in enumerate(label)
|
||||
if l in test_list],
|
||||
cache, resolution)
|
||||
|
||||
return train_source, test_source
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import torch.utils.data as tordata
|
||||
import numpy as np
|
||||
import os.path as osp
|
||||
import os
|
||||
import pickle
|
||||
import cv2
|
||||
import xarray as xr
|
||||
|
||||
|
||||
class DataSet(tordata.Dataset):
|
||||
def __init__(self, seq_dir, label, seq_type, view, cache, resolution):
|
||||
self.seq_dir = seq_dir
|
||||
self.view = view
|
||||
self.seq_type = seq_type
|
||||
self.label = label
|
||||
self.cache = cache
|
||||
self.resolution = int(resolution)
|
||||
self.cut_padding = int(float(resolution)/64*10)
|
||||
self.data_size = len(self.label)
|
||||
self.data = [None] * self.data_size
|
||||
self.frame_set = [None] * self.data_size
|
||||
|
||||
self.label_set = set(self.label)
|
||||
self.seq_type_set = set(self.seq_type)
|
||||
self.view_set = set(self.view)
|
||||
_ = np.zeros((len(self.label_set),
|
||||
len(self.seq_type_set),
|
||||
len(self.view_set))).astype('int')
|
||||
_ -= 1
|
||||
self.index_dict = xr.DataArray(
|
||||
_,
|
||||
coords={'label': sorted(list(self.label_set)),
|
||||
'seq_type': sorted(list(self.seq_type_set)),
|
||||
'view': sorted(list(self.view_set))},
|
||||
dims=['label', 'seq_type', 'view'])
|
||||
|
||||
for i in range(self.data_size):
|
||||
_label = self.label[i]
|
||||
_seq_type = self.seq_type[i]
|
||||
_view = self.view[i]
|
||||
self.index_dict.loc[_label, _seq_type, _view] = i
|
||||
|
||||
def load_all_data(self):
|
||||
for i in range(self.data_size):
|
||||
self.load_data(i)
|
||||
|
||||
def load_data(self, index):
|
||||
return self.__getitem__(index)
|
||||
|
||||
def __loader__(self, path):
|
||||
return self.img2xarray(
|
||||
path)[:, :, self.cut_padding:-self.cut_padding].astype(
|
||||
'float32') / 255.0
|
||||
|
||||
def __getitem__(self, index):
|
||||
# pose sequence sampling
|
||||
if not self.cache:
|
||||
data = [self.__loader__(_path) for _path in self.seq_dir[index]]
|
||||
frame_set = [set(feature.coords['frame'].values.tolist()) for feature in data]
|
||||
frame_set = list(set.intersection(*frame_set))
|
||||
elif self.data[index] is None:
|
||||
data = [self.__loader__(_path) for _path in self.seq_dir[index]]
|
||||
frame_set = [set(feature.coords['frame'].values.tolist()) for feature in data]
|
||||
frame_set = list(set.intersection(*frame_set))
|
||||
self.data[index] = data
|
||||
self.frame_set[index] = frame_set
|
||||
else:
|
||||
data = self.data[index]
|
||||
frame_set = self.frame_set[index]
|
||||
|
||||
return data, frame_set, self.view[
|
||||
index], self.seq_type[index], self.label[index],
|
||||
|
||||
def img2xarray(self, flie_path):
|
||||
imgs = sorted(list(os.listdir(flie_path)))
|
||||
frame_list = [np.reshape(
|
||||
cv2.imread(osp.join(flie_path, _img_path)),
|
||||
[self.resolution, self.resolution, -1])[:, :, 0]
|
||||
for _img_path in imgs
|
||||
if osp.isfile(osp.join(flie_path, _img_path))]
|
||||
num_list = list(range(len(frame_list)))
|
||||
data_dict = xr.DataArray(
|
||||
frame_list,
|
||||
coords={'frame': num_list},
|
||||
dims=['frame', 'img_y', 'img_x'],
|
||||
)
|
||||
return data_dict
|
||||
|
||||
def __len__(self):
|
||||
return len(self.label)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import torch
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
|
||||
|
||||
def cuda_dist(x, y):
|
||||
x = torch.from_numpy(x).cuda()
|
||||
y = torch.from_numpy(y).cuda()
|
||||
dist = torch.sum(x ** 2, 1).unsqueeze(1) + torch.sum(y ** 2, 1).unsqueeze(
|
||||
1).transpose(0, 1) - 2 * torch.matmul(x, y.transpose(0, 1))
|
||||
dist = torch.sqrt(F.relu(dist))
|
||||
return dist
|
||||
|
||||
|
||||
def evaluation(data, config):
|
||||
dataset = config['dataset'].split('-')[0]
|
||||
feature, view, seq_type, label = data
|
||||
label = np.array(label)
|
||||
view_list = list(set(view))
|
||||
view_list.sort()
|
||||
view_num = len(view_list)
|
||||
sample_num = len(feature)
|
||||
|
||||
probe_seq_dict = {'CASIA': [['nm-05', 'nm-06'], ['bg-01', 'bg-02'], ['cl-01', 'cl-02']],
|
||||
'OUMVLP': [['00']]}
|
||||
gallery_seq_dict = {'CASIA': [['nm-01', 'nm-02', 'nm-03', 'nm-04']],
|
||||
'OUMVLP': [['01']]}
|
||||
|
||||
num_rank = 5
|
||||
acc = np.zeros([len(probe_seq_dict[dataset]), view_num, view_num, num_rank])
|
||||
for (p, probe_seq) in enumerate(probe_seq_dict[dataset]):
|
||||
for gallery_seq in gallery_seq_dict[dataset]:
|
||||
for (v1, probe_view) in enumerate(view_list):
|
||||
for (v2, gallery_view) in enumerate(view_list):
|
||||
gseq_mask = np.isin(seq_type, gallery_seq) & np.isin(view, [gallery_view])
|
||||
gallery_x = feature[gseq_mask, :]
|
||||
gallery_y = label[gseq_mask]
|
||||
|
||||
pseq_mask = np.isin(seq_type, probe_seq) & np.isin(view, [probe_view])
|
||||
probe_x = feature[pseq_mask, :]
|
||||
probe_y = label[pseq_mask]
|
||||
|
||||
dist = cuda_dist(probe_x, gallery_x)
|
||||
idx = dist.sort(1)[1].cpu().numpy()
|
||||
acc[p, v1, v2, :] = np.round(
|
||||
np.sum(np.cumsum(np.reshape(probe_y, [-1, 1]) == gallery_y[idx[:, 0:num_rank]], 1) > 0,
|
||||
0) * 100 / dist.shape[0], 2)
|
||||
|
||||
return acc
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import torch.utils.data as tordata
|
||||
import random
|
||||
|
||||
|
||||
class TripletSampler(tordata.sampler.Sampler):
|
||||
def __init__(self, dataset, batch_size):
|
||||
self.dataset = dataset
|
||||
self.batch_size = batch_size
|
||||
|
||||
def __iter__(self):
|
||||
while (True):
|
||||
sample_indices = list()
|
||||
pid_list = random.sample(
|
||||
list(self.dataset.label_set),
|
||||
self.batch_size[0])
|
||||
for pid in pid_list:
|
||||
_index = self.dataset.index_dict.loc[pid, :, :].values
|
||||
_index = _index[_index > 0].flatten().tolist()
|
||||
_index = random.choices(
|
||||
_index,
|
||||
k=self.batch_size[1])
|
||||
sample_indices += _index
|
||||
yield sample_indices
|
||||
|
||||
def __len__(self):
|
||||
return self.dataset.data_size
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
|
||||
import os
|
||||
from scipy import misc as scisc
|
||||
import cv2
|
||||
import numpy as np
|
||||
from warnings import warn
|
||||
from time import sleep
|
||||
import argparse
|
||||
|
||||
from multiprocessing import Pool
|
||||
from multiprocessing import TimeoutError as MP_TimeoutError
|
||||
|
||||
START = "START"
|
||||
FINISH = "FINISH"
|
||||
WARNING = "WARNING"
|
||||
FAIL = "FAIL"
|
||||
|
||||
|
||||
def boolean_string(s):
|
||||
if s.upper() not in {'FALSE', 'TRUE'}:
|
||||
raise ValueError('Not a valid boolean string')
|
||||
return s.upper() == 'TRUE'
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Test')
|
||||
parser.add_argument('--input_path', default='', type=str,
|
||||
help='Root path of raw dataset.')
|
||||
parser.add_argument('--output_path', default='', type=str,
|
||||
help='Root path for output.')
|
||||
parser.add_argument('--log_file', default='./pretreatment.log', type=str,
|
||||
help='Log file path. Default: ./pretreatment.log')
|
||||
parser.add_argument('--log', default=False, type=boolean_string,
|
||||
help='If set as True, all logs will be saved. '
|
||||
'Otherwise, only warnings and errors will be saved.'
|
||||
'Default: False')
|
||||
parser.add_argument('--worker_num', default=1, type=int,
|
||||
help='How many subprocesses to use for data pretreatment. '
|
||||
'Default: 1')
|
||||
opt = parser.parse_args()
|
||||
|
||||
INPUT_PATH = opt.input_path
|
||||
OUTPUT_PATH = opt.output_path
|
||||
IF_LOG = opt.log
|
||||
LOG_PATH = opt.log_file
|
||||
WORKERS = opt.worker_num
|
||||
|
||||
T_H = 64
|
||||
T_W = 64
|
||||
|
||||
|
||||
def log2str(pid, comment, logs):
|
||||
str_log = ''
|
||||
if type(logs) is str:
|
||||
logs = [logs]
|
||||
for log in logs:
|
||||
str_log += "# JOB %d : --%s-- %s\n" % (
|
||||
pid, comment, log)
|
||||
return str_log
|
||||
|
||||
|
||||
def log_print(pid, comment, logs):
|
||||
str_log = log2str(pid, comment, logs)
|
||||
if comment in [WARNING, FAIL]:
|
||||
with open(LOG_PATH, 'a') as log_f:
|
||||
log_f.write(str_log)
|
||||
if comment in [START, FINISH]:
|
||||
if pid % 500 != 0:
|
||||
return
|
||||
print(str_log, end='')
|
||||
|
||||
|
||||
def cut_img(img, seq_info, frame_name, pid):
|
||||
# A silhouette contains too little white pixels
|
||||
# might be not valid for identification.
|
||||
if img.sum() <= 10000:
|
||||
message = 'seq:%s, frame:%s, no data, %d.' % (
|
||||
'-'.join(seq_info), frame_name, img.sum())
|
||||
warn(message)
|
||||
log_print(pid, WARNING, message)
|
||||
return None
|
||||
# Get the top and bottom point
|
||||
y = img.sum(axis=1)
|
||||
y_top = (y != 0).argmax(axis=0)
|
||||
y_btm = (y != 0).cumsum(axis=0).argmax(axis=0)
|
||||
img = img[y_top:y_btm + 1, :]
|
||||
# As the height of a person is larger than the width,
|
||||
# use the height to calculate resize ratio.
|
||||
_r = img.shape[1] / img.shape[0]
|
||||
_t_w = int(T_H * _r)
|
||||
img = cv2.resize(img, (_t_w, T_H), interpolation=cv2.INTER_CUBIC)
|
||||
# Get the median of x axis and regard it as the x center of the person.
|
||||
sum_point = img.sum()
|
||||
sum_column = img.sum(axis=0).cumsum()
|
||||
x_center = -1
|
||||
for i in range(sum_column.size):
|
||||
if sum_column[i] > sum_point / 2:
|
||||
x_center = i
|
||||
break
|
||||
if x_center < 0:
|
||||
message = 'seq:%s, frame:%s, no center.' % (
|
||||
'-'.join(seq_info), frame_name)
|
||||
warn(message)
|
||||
log_print(pid, WARNING, message)
|
||||
return None
|
||||
h_T_W = int(T_W / 2)
|
||||
left = x_center - h_T_W
|
||||
right = x_center + h_T_W
|
||||
if left <= 0 or right >= img.shape[1]:
|
||||
left += h_T_W
|
||||
right += h_T_W
|
||||
_ = np.zeros((img.shape[0], h_T_W))
|
||||
img = np.concatenate([_, img, _], axis=1)
|
||||
img = img[:, left:right]
|
||||
return img.astype('uint8')
|
||||
|
||||
|
||||
def cut_pickle(seq_info, pid):
|
||||
seq_name = '-'.join(seq_info)
|
||||
log_print(pid, START, seq_name)
|
||||
seq_path = os.path.join(INPUT_PATH, *seq_info)
|
||||
out_dir = os.path.join(OUTPUT_PATH, *seq_info)
|
||||
frame_list = os.listdir(seq_path)
|
||||
frame_list.sort()
|
||||
count_frame = 0
|
||||
for _frame_name in frame_list:
|
||||
frame_path = os.path.join(seq_path, _frame_name)
|
||||
img = cv2.imread(frame_path)[:, :, 0]
|
||||
img = cut_img(img, seq_info, _frame_name, pid)
|
||||
if img is not None:
|
||||
# Save the cut img
|
||||
save_path = os.path.join(out_dir, _frame_name)
|
||||
scisc.imsave(save_path, img)
|
||||
count_frame += 1
|
||||
# Warn if the sequence contains less than 5 frames
|
||||
if count_frame < 5:
|
||||
message = 'seq:%s, less than 5 valid data.' % (
|
||||
'-'.join(seq_info))
|
||||
warn(message)
|
||||
log_print(pid, WARNING, message)
|
||||
|
||||
log_print(pid, FINISH,
|
||||
'Contain %d valid frames. Saved to %s.'
|
||||
% (count_frame, out_dir))
|
||||
|
||||
|
||||
pool = Pool(WORKERS)
|
||||
results = list()
|
||||
pid = 0
|
||||
|
||||
print('Pretreatment Start.\n'
|
||||
'Input path: %s\n'
|
||||
'Output path: %s\n'
|
||||
'Log file: %s\n'
|
||||
'Worker num: %d' % (
|
||||
INPUT_PATH, OUTPUT_PATH, LOG_PATH, WORKERS))
|
||||
|
||||
id_list = os.listdir(INPUT_PATH)
|
||||
id_list.sort()
|
||||
# Walk the input path
|
||||
for _id in id_list:
|
||||
seq_type = os.listdir(os.path.join(INPUT_PATH, _id))
|
||||
seq_type.sort()
|
||||
for _seq_type in seq_type:
|
||||
view = os.listdir(os.path.join(INPUT_PATH, _id, _seq_type))
|
||||
view.sort()
|
||||
for _view in view:
|
||||
seq_info = [_id, _seq_type, _view]
|
||||
out_dir = os.path.join(OUTPUT_PATH, *seq_info)
|
||||
os.makedirs(out_dir)
|
||||
results.append(
|
||||
pool.apply_async(
|
||||
cut_pickle,
|
||||
args=(seq_info, pid)))
|
||||
sleep(0.02)
|
||||
pid += 1
|
||||
|
||||
pool.close()
|
||||
unfinish = 1
|
||||
while unfinish > 0:
|
||||
unfinish = 0
|
||||
for i, res in enumerate(results):
|
||||
try:
|
||||
res.get(timeout=0.1)
|
||||
except Exception as e:
|
||||
if type(e) == MP_TimeoutError:
|
||||
unfinish += 1
|
||||
continue
|
||||
else:
|
||||
print('\n\n\nERROR OCCUR: PID ##%d##, ERRORTYPE: %s\n\n\n',
|
||||
i, type(e))
|
||||
raise e
|
||||
pool.join()
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
from datetime import datetime
|
||||
import numpy as np
|
||||
import argparse
|
||||
|
||||
from model.initialization import initialization
|
||||
from model.utils import evaluation
|
||||
from config import conf
|
||||
|
||||
|
||||
def boolean_string(s):
|
||||
if s.upper() not in {'FALSE', 'TRUE'}:
|
||||
raise ValueError('Not a valid boolean string')
|
||||
return s.upper() == 'TRUE'
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Test')
|
||||
parser.add_argument('--iter', default='80000', type=int,
|
||||
help='iter: iteration of the checkpoint to load. Default: 80000')
|
||||
parser.add_argument('--batch_size', default='1', type=int,
|
||||
help='batch_size: batch size for parallel test. Default: 1')
|
||||
parser.add_argument('--cache', default=False, type=boolean_string,
|
||||
help='cache: if set as TRUE all the test data will be loaded at once'
|
||||
' before the transforming start. Default: FALSE')
|
||||
opt = parser.parse_args()
|
||||
|
||||
|
||||
# Exclude identical-view cases
|
||||
def de_diag(acc, each_angle=False):
|
||||
result = np.sum(acc - np.diag(np.diag(acc)), 1) / 10.0
|
||||
if not each_angle:
|
||||
result = np.mean(result)
|
||||
return result
|
||||
|
||||
|
||||
m = initialization(conf, test=opt.cache)[0]
|
||||
|
||||
# load model checkpoint of iteration opt.iter
|
||||
print('Loading the model of iteration %d...' % opt.iter)
|
||||
m.load(opt.iter)
|
||||
print('Transforming...')
|
||||
time = datetime.now()
|
||||
test = m.transform('test', opt.batch_size)
|
||||
print('Evaluating...')
|
||||
acc = evaluation(test, conf['data'])
|
||||
print('Evaluation complete. Cost:', datetime.now() - time)
|
||||
|
||||
# Print rank-1 accuracy of the best model
|
||||
# e.g.
|
||||
# ===Rank-1 (Include identical-view cases)===
|
||||
# NM: 95.405, BG: 88.284, CL: 72.041
|
||||
for i in range(1):
|
||||
print('===Rank-%d (Include identical-view cases)===' % (i + 1))
|
||||
print('NM: %.3f,\tBG: %.3f,\tCL: %.3f' % (
|
||||
np.mean(acc[0, :, :, i]),
|
||||
np.mean(acc[1, :, :, i]),
|
||||
np.mean(acc[2, :, :, i])))
|
||||
|
||||
# Print rank-1 accuracy of the best model,excluding identical-view cases
|
||||
# e.g.
|
||||
# ===Rank-1 (Exclude identical-view cases)===
|
||||
# NM: 94.964, BG: 87.239, CL: 70.355
|
||||
for i in range(1):
|
||||
print('===Rank-%d (Exclude identical-view cases)===' % (i + 1))
|
||||
print('NM: %.3f,\tBG: %.3f,\tCL: %.3f' % (
|
||||
de_diag(acc[0, :, :, i]),
|
||||
de_diag(acc[1, :, :, i]),
|
||||
de_diag(acc[2, :, :, i])))
|
||||
|
||||
# Print rank-1 accuracy of the best model (Each Angle)
|
||||
# e.g.
|
||||
# ===Rank-1 of each angle (Exclude identical-view cases)===
|
||||
# NM: [90.80 97.90 99.40 96.90 93.60 91.70 95.00 97.80 98.90 96.80 85.80]
|
||||
# BG: [83.80 91.20 91.80 88.79 83.30 81.00 84.10 90.00 92.20 94.45 79.00]
|
||||
# CL: [61.40 75.40 80.70 77.30 72.10 70.10 71.50 73.50 73.50 68.40 50.00]
|
||||
np.set_printoptions(precision=2, floatmode='fixed')
|
||||
for i in range(1):
|
||||
print('===Rank-%d of each angle (Exclude identical-view cases)===' % (i + 1))
|
||||
print('NM:', de_diag(acc[0, :, :, i], True))
|
||||
print('BG:', de_diag(acc[1, :, :, i], True))
|
||||
print('CL:', de_diag(acc[2, :, :, i], True))
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
from model.initialization import initialization
|
||||
from config import conf
|
||||
import argparse
|
||||
|
||||
|
||||
def boolean_string(s):
|
||||
if s.upper() not in {'FALSE', 'TRUE'}:
|
||||
raise ValueError('Not a valid boolean string')
|
||||
return s.upper() == 'TRUE'
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Train')
|
||||
parser.add_argument('--cache', default=True, type=boolean_string,
|
||||
help='cache: if set as TRUE all the training data will be loaded at once'
|
||||
' before the training start. Default: TRUE')
|
||||
opt = parser.parse_args()
|
||||
|
||||
m = initialization(conf, train=opt.cache)[0]
|
||||
|
||||
print("Training START")
|
||||
m.fit()
|
||||
print("Training COMPLETE")
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
class BasicConv2d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, **kwargs):
|
||||
super(BasicConv2d, self).__init__()
|
||||
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, bias=False, **kwargs)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
return F.leaky_relu(x, inplace=True)
|
||||
|
||||
|
||||
class SetBlock(nn.Module):
|
||||
def __init__(self, forward_block, pooling=False):
|
||||
super(SetBlock, self).__init__()
|
||||
self.forward_block = forward_block
|
||||
self.pooling = pooling
|
||||
if pooling:
|
||||
self.pool2d = nn.MaxPool2d(2)
|
||||
def forward(self, x):
|
||||
n, s, c, h, w = x.size()
|
||||
x = self.forward_block(x.view(-1,c,h,w))
|
||||
if self.pooling:
|
||||
x = self.pool2d(x)
|
||||
_, c, h, w = x.size()
|
||||
return x.view(n, s, c, h ,w)
|
||||
|
||||
|
||||
class HPM(nn.Module):
|
||||
def __init__(self, in_dim, out_dim, bin_level_num=5):
|
||||
super(HPM, self).__init__()
|
||||
self.bin_num = [2**i for i in range(bin_level_num)]
|
||||
self.fc_bin = nn.ParameterList([
|
||||
nn.Parameter(
|
||||
nn.init.xavier_uniform(
|
||||
torch.zeros(sum(self.bin_num), in_dim, out_dim)))])
|
||||
def forward(self, x):
|
||||
feature = list()
|
||||
n, c, h, w = x.size()
|
||||
for num_bin in self.bin_num:
|
||||
z = x.view(n, c, num_bin, -1)
|
||||
z = z.mean(3)+z.max(3)[0]
|
||||
feature.append(z)
|
||||
feature = torch.cat(feature, 2).permute(2, 0, 1).contiguous()
|
||||
|
||||
feature = feature.matmul(self.fc_bin[0])
|
||||
return feature.permute(1, 0, 2).contiguous()
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
class SetNet(nn.Module):
|
||||
def __init__(self, hidden_dim):
|
||||
super(SetNet, self).__init__()
|
||||
self.hidden_dim = hidden_dim
|
||||
self.batch_frame = None
|
||||
|
||||
_in_channels = 1
|
||||
_channels = [64,128,256]
|
||||
self.set_layer1 = SetBlock(BasicConv2d(_in_channels, _channels[0], 5, padding=2))
|
||||
self.set_layer2 = SetBlock(BasicConv2d(_channels[0], _channels[0], 3, padding=1), True)
|
||||
self.set_layer3 = SetBlock(BasicConv2d(_channels[0], _channels[1], 3, padding=1))
|
||||
self.set_layer4 = SetBlock(BasicConv2d(_channels[1], _channels[1], 3, padding=1), True)
|
||||
self.set_layer5 = SetBlock(BasicConv2d(_channels[1], _channels[2], 3, padding=1))
|
||||
self.set_layer6 = SetBlock(BasicConv2d(_channels[2], _channels[2], 3, padding=1))
|
||||
|
||||
self.gl_layer1 = BasicConv2d(_channels[0], _channels[1], 3, padding=1)
|
||||
self.gl_layer2 = BasicConv2d(_channels[1], _channels[1], 3, padding=1)
|
||||
self.gl_layer3 = BasicConv2d(_channels[1], _channels[2], 3, padding=1)
|
||||
self.gl_layer4 = BasicConv2d(_channels[2], _channels[2], 3, padding=1)
|
||||
self.gl_pooling = nn.MaxPool2d(2)
|
||||
|
||||
self.gl_hpm = HPM(_channels[-1], hidden_dim)
|
||||
self.x_hpm = HPM(_channels[-1], hidden_dim)
|
||||
|
||||
for m in self.modules():
|
||||
if isinstance(m, (nn.Conv2d, nn.Conv1d)):
|
||||
nn.init.xavier_uniform(m.weight.data)
|
||||
elif isinstance(m, nn.Linear):
|
||||
nn.init.xavier_uniform(m.weight.data)
|
||||
nn.init.constant(m.bias.data, 0.0)
|
||||
elif isinstance(m, (nn.BatchNorm2d, nn.BatchNorm1d)):
|
||||
nn.init.normal(m.weight.data, 1.0, 0.02)
|
||||
nn.init.constant(m.bias.data, 0.0)
|
||||
|
||||
|
||||
def frame_max(self, x):
|
||||
if self.batch_frame is None:
|
||||
return torch.max(x, 1)
|
||||
else:
|
||||
_tmp = [
|
||||
torch.max(x[:, self.batch_frame[i]:self.batch_frame[i+1], :, :, :], 1)
|
||||
for i in range(len(self.batch_frame)-1)
|
||||
]
|
||||
max_list = torch.cat([_tmp[i][0] for i in range(len(_tmp))], 0)
|
||||
arg_max_list = torch.cat([_tmp[i][1] for i in range(len(_tmp))], 0)
|
||||
return max_list, arg_max_list
|
||||
|
||||
|
||||
def forward(self, silho, batch_frame=None):
|
||||
silho = silho/255
|
||||
# n: batch_size, s: frame_num, k: keypoints_num, c: channel
|
||||
if batch_frame is not None:
|
||||
batch_frame = batch_frame[0].data.cpu().numpy().tolist()
|
||||
_ = len(batch_frame)
|
||||
for i in range(len(batch_frame)):
|
||||
if batch_frame[-(i+1)] != 0:
|
||||
break
|
||||
else:
|
||||
_ -= 1
|
||||
batch_frame = batch_frame[:_]
|
||||
frame_sum = np.sum(batch_frame)
|
||||
if frame_sum < silho.size(1):
|
||||
silho = silho[:, :frame_sum,:,:]
|
||||
self.batch_frame = [0]+np.cumsum(batch_frame).tolist()
|
||||
n = silho.size(0)
|
||||
x = silho.unsqueeze(2)
|
||||
del silho
|
||||
|
||||
x = self.set_layer1(x)
|
||||
x = self.set_layer2(x)
|
||||
gl = self.gl_layer1(self.frame_max(x)[0])
|
||||
gl = self.gl_layer2(gl)
|
||||
gl = self.gl_pooling(gl)
|
||||
|
||||
x = self.set_layer3(x)
|
||||
x = self.set_layer4(x)
|
||||
gl = self.gl_layer3(gl+self.frame_max(x)[0])
|
||||
gl = self.gl_layer4(gl)
|
||||
|
||||
x = self.set_layer5(x)
|
||||
x = self.set_layer6(x)
|
||||
x = self.frame_max(x)[0]
|
||||
gl = gl+x
|
||||
|
||||
gl_f = self.gl_hpm(gl)
|
||||
x_f = self.x_hpm(x)
|
||||
|
||||
return torch.cat([gl_f, x_f], 1), None
|
||||
|
|
@ -10,6 +10,7 @@ def pop_and_add(l, val, max_length):
|
|||
l.pop(0)
|
||||
l.append(val)
|
||||
|
||||
|
||||
def last_ip(ips):
|
||||
for i, ip in enumerate(reversed(ips)):
|
||||
if ip is not None:
|
||||
|
|
@ -70,4 +71,5 @@ def get_hist(img, bbox, nbins=3):
|
|||
hist = cv2.calcHist([img], [0, 1], mask, [nbins, 2*nbins], [0, 180, 0, 256])
|
||||
cv2.normalize(hist, hist, alpha=1, norm_type=cv2.NORM_L1)
|
||||
|
||||
|
||||
return hist
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
from datetime import datetime
|
||||
import numpy as np
|
||||
import argparse
|
||||
|
||||
from model.initialization import initialization
|
||||
from model.utils import evaluation
|
||||
from config import conf
|
||||
|
||||
|
||||
def boolean_string(s):
|
||||
if s.upper() not in {'FALSE', 'TRUE'}:
|
||||
raise ValueError('Not a valid boolean string')
|
||||
return s.upper() == 'TRUE'
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Test')
|
||||
parser.add_argument('--iter', default='80000', type=int,
|
||||
help='iter: iteration of the checkpoint to load. Default: 80000')
|
||||
parser.add_argument('--batch_size', default='1', type=int,
|
||||
help='batch_size: batch size for parallel test. Default: 1')
|
||||
parser.add_argument('--cache', default=False, type=boolean_string,
|
||||
help='cache: if set as TRUE all the test data will be loaded at once'
|
||||
' before the transforming start. Default: FALSE')
|
||||
opt = parser.parse_args()
|
||||
|
||||
|
||||
# Exclude identical-view cases
|
||||
def de_diag(acc, each_angle=False):
|
||||
result = np.sum(acc - np.diag(np.diag(acc)), 1) / 10.0
|
||||
if not each_angle:
|
||||
result = np.mean(result)
|
||||
return result
|
||||
|
||||
|
||||
m = initialization(conf, test=opt.cache)[0]
|
||||
|
||||
# load model checkpoint of iteration opt.iter
|
||||
print('Loading the model of iteration %d...' % opt.iter)
|
||||
m.load(opt.iter)
|
||||
print('Transforming...')
|
||||
time = datetime.now()
|
||||
test = m.transform('test', opt.batch_size)
|
||||
print('Evaluating...')
|
||||
acc = evaluation(test, conf['data'])
|
||||
print('Evaluation complete. Cost:', datetime.now() - time)
|
||||
|
||||
# Print rank-1 accuracy of the best model
|
||||
# e.g.
|
||||
# ===Rank-1 (Include identical-view cases)===
|
||||
# NM: 95.405, BG: 88.284, CL: 72.041
|
||||
for i in range(1):
|
||||
print('===Rank-%d (Include identical-view cases)===' % (i + 1))
|
||||
print('NM: %.3f,\tBG: %.3f,\tCL: %.3f' % (
|
||||
np.mean(acc[0, :, :, i]),
|
||||
np.mean(acc[1, :, :, i]),
|
||||
np.mean(acc[2, :, :, i])))
|
||||
|
||||
# Print rank-1 accuracy of the best model,excluding identical-view cases
|
||||
# e.g.
|
||||
# ===Rank-1 (Exclude identical-view cases)===
|
||||
# NM: 94.964, BG: 87.239, CL: 70.355
|
||||
for i in range(1):
|
||||
print('===Rank-%d (Exclude identical-view cases)===' % (i + 1))
|
||||
print('NM: %.3f,\tBG: %.3f,\tCL: %.3f' % (
|
||||
de_diag(acc[0, :, :, i]),
|
||||
de_diag(acc[1, :, :, i]),
|
||||
de_diag(acc[2, :, :, i])))
|
||||
|
||||
# Print rank-1 accuracy of the best model (Each Angle)
|
||||
# e.g.
|
||||
# ===Rank-1 of each angle (Exclude identical-view cases)===
|
||||
# NM: [90.80 97.90 99.40 96.90 93.60 91.70 95.00 97.80 98.90 96.80 85.80]
|
||||
# BG: [83.80 91.20 91.80 88.79 83.30 81.00 84.10 90.00 92.20 94.45 79.00]
|
||||
# CL: [61.40 75.40 80.70 77.30 72.10 70.10 71.50 73.50 73.50 68.40 50.00]
|
||||
np.set_printoptions(precision=2, floatmode='fixed')
|
||||
for i in range(1):
|
||||
print('===Rank-%d of each angle (Exclude identical-view cases)===' % (i + 1))
|
||||
print('NM:', de_diag(acc[0, :, :, i], True))
|
||||
print('BG:', de_diag(acc[1, :, :, i], True))
|
||||
print('CL:', de_diag(acc[2, :, :, i], True))
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
from model.initialization import initialization
|
||||
from config import conf
|
||||
import argparse
|
||||
|
||||
|
||||
def boolean_string(s):
|
||||
if s.upper() not in {'FALSE', 'TRUE'}:
|
||||
raise ValueError('Not a valid boolean string')
|
||||
return s.upper() == 'TRUE'
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Train')
|
||||
parser.add_argument('--cache', default=True, type=boolean_string,
|
||||
help='cache: if set as TRUE all the training data will be loaded at once'
|
||||
' before the training start. Default: TRUE')
|
||||
opt = parser.parse_args()
|
||||
|
||||
m = initialization(conf, train=opt.cache)[0]
|
||||
|
||||
print("Training START")
|
||||
m.fit()
|
||||
print("Training COMPLETE")
|
||||
Loading…
Reference in New Issue