Compare commits

..

37 Commits

Author SHA1 Message Date
pvf8p2eo4 82f34834dc ADD file via upload 2021-11-20 08:56:06 +08:00
pvf8p2eo4 8dbf077cac ADD file via upload 2021-11-20 08:55:55 +08:00
pvf8p2eo4 d061e57824 ADD file via upload 2021-11-20 08:55:44 +08:00
pvf8p2eo4 d1f8646456 ADD file via upload 2021-11-20 08:55:31 +08:00
pvf8p2eo4 95a60ecc3d ADD file via upload 2021-11-20 08:55:15 +08:00
pvf8p2eo4 f60fce3855 ADD file via upload 2021-11-20 08:55:07 +08:00
pvf8p2eo4 9cd19143b4 ADD file via upload 2021-11-20 08:54:48 +08:00
pvf8p2eo4 c7ecef9037 ADD file via upload 2021-11-20 08:54:38 +08:00
pvf8p2eo4 e4f18db0fa ADD file via upload 2021-11-20 08:54:27 +08:00
pvf8p2eo4 55318d0132 ADD file via upload 2021-11-20 08:54:07 +08:00
pvf8p2eo4 394eb23827 ADD file via upload 2021-11-20 08:53:47 +08:00
pvf8p2eo4 6a81371e41 ADD file via upload 2021-10-24 13:27:30 +08:00
pvf8p2eo4 5005870cd4 ADD file via upload 2021-10-24 13:27:14 +08:00
pvf8p2eo4 76bc24a398 ADD file via upload 2021-10-24 13:26:28 +08:00
pvf8p2eo4 f34ed24551 ADD file via upload 2021-10-24 13:26:13 +08:00
pvf8p2eo4 73d27b012d ADD file via upload 2021-10-24 13:24:47 +08:00
pvf8p2eo4 e44c1810ad ADD file via upload 2021-10-24 13:24:23 +08:00
pvf8p2eo4 3e8d9d777a ADD file via upload 2021-10-24 13:24:09 +08:00
pvf8p2eo4 d16fba841b ADD file via upload 2021-10-24 13:23:17 +08:00
pvf8p2eo4 6d5d1122dd ADD file via upload 2021-10-24 13:23:01 +08:00
pvf8p2eo4 2f59e2a465 ADD file via upload 2021-10-24 13:22:47 +08:00
pvf8p2eo4 ab16cf1cff ADD file via upload 2021-10-24 13:22:34 +08:00
pvf8p2eo4 b23f1a7516 ADD file via upload 2021-10-24 13:21:54 +08:00
pvf8p2eo4 e5cd9ddb1e ADD file via upload 2021-10-24 13:21:40 +08:00
pvf8p2eo4 a4996d8f6a ADD file via upload 2021-10-24 13:21:18 +08:00
pvf8p2eo4 34694b5274 ADD file via upload 2021-10-24 13:00:56 +08:00
pvf8p2eo4 0fc7a6d3cf ADD file via upload 2021-10-24 13:00:30 +08:00
pvf8p2eo4 b48374c7e9 ADD file via upload 2021-10-24 13:00:14 +08:00
pvf8p2eo4 efd5ae9051 ADD file via upload 2021-10-24 12:59:52 +08:00
pvf8p2eo4 ddf66c7131 ADD file via upload 2021-10-24 12:59:41 +08:00
pvf8p2eo4 a3c8f9ff45 ADD file via upload 2021-10-24 12:59:24 +08:00
pvf8p2eo4 5d249046f3 ADD file via upload 2021-10-24 12:53:06 +08:00
pvf8p2eo4 37ac2fdc76 ADD file via upload 2021-10-24 12:52:13 +08:00
pvf8p2eo4 5d3f734944 ADD file via upload 2021-10-24 12:51:54 +08:00
pvf8p2eo4 d71ed681c7 ADD file via upload 2021-10-24 12:51:34 +08:00
pvf8p2eo4 7afe0ef597 ADD file via upload 2021-10-24 12:37:32 +08:00
pvf8p2eo4 f569f04fca ADD file via upload 2021-10-24 12:32:12 +08:00
49 changed files with 4135 additions and 134 deletions

0
GaitSet Normal file
View File

192
Gaitset Normal file
View File

@ -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()

BIN
LSTM.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

157
READ ME.md Normal file
View File

@ -0,0 +1,157 @@
# GaitSet
[![LICENSE](https://img.shields.io/badge/license-NPL%20(The%20996%20Prohibited%20License)-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
[![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](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
View File

@ -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

View File

@ -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"]

27
config.py Normal file
View File

@ -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.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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')

View File

@ -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')
#

View File

@ -0,0 +1,2 @@
LOGDIR_GAIT_PATH = 'logdir_gait'
LOGDIR_PATH = 'logdir'

View File

@ -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()

View File

@ -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, :]

157
gaitset/READ ME.md Normal file
View File

@ -0,0 +1,157 @@
# GaitSet
[![LICENSE](https://img.shields.io/badge/license-NPL%20(The%20996%20Prohibited%20License)-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
[![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](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/).

49
gaitset/basic_blocks.py Normal file
View File

@ -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()

27
gaitset/config.py Normal file
View File

@ -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',
},
}

4
gaitset/model/_init_.py Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# @Author : admin
# @Time : 2018/11/16

View File

@ -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)

272
gaitset/model/model.py Normal file
View File

@ -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))))

View File

@ -0,0 +1,2 @@
from .triplet import TripletLoss
from .gaitset import SetNet

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
from .data_loader import load_data
from .data_set import DataSet
from .evaluator import evaluation
from .sampler import TripletSampler

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

192
gaitset/pretreatment.py Normal file
View File

@ -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()

80
gaitset/test.py Normal file
View File

@ -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 modelexcluding 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))

22
gaitset/train.py Normal file
View File

@ -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")

View File

@ -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()

88
gaitset/work/gaitset.py Normal file
View File

@ -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

View File

@ -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

80
test.py Normal file
View File

@ -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 modelexcluding 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))

22
train.py Normal file
View File

@ -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")