Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
WangyiNTU authored Sep 15, 2021
1 parent 7017507 commit d8d1b0a
Show file tree
Hide file tree
Showing 6 changed files with 1,111 additions and 28 deletions.
135 changes: 135 additions & 0 deletions LUDA_generate_SH.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import os
import cv2
import cPickle
import numpy as np
import scipy
import scipy.spatial
#used to import .mat file
import scipy.io

############ locally-uniform distribution assumption (LUDA)##########
# change to your ShanghaiTech dataset folder ##########################
### set input dataset path for training or val--part A or B
part = 'part_A' # part_A or part_B
split = 'train' # train or train_val or test
root_dir = os.path.join('/home/wangyi/data', part)

if part == 'part_A':
res_dataset = 'ShanghaiTechA'
range_split = 10.
ratio = 1.
max_sp = None
elif part == 'part_B':
res_dataset = 'ShanghaiTechB'
range_split = 8.
ratio = 0.5
max_sp = 50

#train:
if split == 'train':
#directory of images for training
img_path = os.path.join(root_dir, 'train_data/images')
#directory of annotation info -use .mat
anno_path = os.path.join(root_dir, 'train_data/ground-truth','')
# anno_path = os.path.join(root_dir, 'wider_face_split','wider_face_test_filelist.txt')
#train_val:
if split == 'train_val':
#directory of images for training
img_path = os.path.join(root_dir, 'val_data/images')
#directory of annotation info -use .mat
anno_path = os.path.join(root_dir, 'val_data/ground-truth','')

elif split == 'test':
img_path = os.path.join(root_dir, 'test_data/images')
anno_path = os.path.join(root_dir, 'test_data/ground-truth','')

if not os.path.exists(os.path.join('data/cache', res_dataset)):
os.makedirs(os.path.join('data/cache', res_dataset))

res_path = os.path.join('data/cache', res_dataset, split)


img_names=[x for x in os.listdir(img_path)] #read all filenames in image folder
image_data = [] #will be used at the end--to write files
weights_list = []
scales = []
valid_count = 0 #number of valid images--with at least 1 person
img_count = 0 #number of all images
box_count = 0 #number of all heads in one image
for img_name in img_names: #read all images
img_count += 1 #record the total number of imgs
if img_count % 20 == 0:
print img_count
full_img_path = os.path.join(img_path,img_name) #full image path
img=cv2.imread(full_img_path) #read imgs
img_height, img_width = img.shape[:2] #img-height=row of the img;img_width=colums of the img
mat_path = img_name.replace('jpg','mat')
data=scipy.io.loadmat(os.path.join(anno_path,'GT_'+mat_path)) #find path of .mat file
centers=data['image_info'][0][0][0][0][0].astype(np.float32) #read all the centers in the image

box_count_image = centers.shape[0] #number of boxes(targets) in each img
box_count += box_count_image #number of boxes in the whole dataset

if box_count_image>0: #judge wether it is a valid img:if no less than 1 box->valid
valid_count += 1
annotation = {} #to restore annotation info
annotation['filepath'] = full_img_path #to restore full imgpath into the annotation[]
# add scale in bboxes height
#KD tree
tree = scipy.spatial.KDTree(centers.copy(), leafsize=1024) #intialization of the tree
k = min(box_count_image,3) #for each box, find the nearest k-1 boxes around it(it itself is included in k->so k-1)
if k <= 2: #need parameters-img_height,centers,len(boxes)
scale = max(img_height / (4. + k), 12)
scale = np.ones(k)* scale
scale_weight = 0.1 if k==1 else 1.
scale_weight = np.ones(k)* scale_weight
else:
crowd_range = np.max(centers[:,1]) - np.min(centers[:,1]) # range: y_max - y_min;for the whole img,find the gap of y between the highest box and the lowest box
circle_scale = crowd_range / range_split #initialization of scale of local window
distances, distances_idx = tree.query(centers, k=box_count_image//2) #distances:Euclinear distance between box X and others(by order from near to far)
#distances_idx is the coresponding index of these distances
distances_mean = ratio * np.mean(distances[:,1:k],axis=1) #mean of distances
places = np.where(distances <= circle_scale) #how many boxes within circle_scale
unique, counts = np.unique(places[0], return_counts=True) # places[0]: row index
#counts:how many boxes within circle_scale
take_d_places = dict(zip(unique, counts)) #zip:make (unique,counts)
scale = []
scale_weight = []
for key,value in take_d_places.iteritems():
idx_in_circle = distances_idx[key, :value]
s_p = np.mean(distances_mean[idx_in_circle])
s_p = np.clip(s_p,2,max_sp) #set the number of s_P to be 2-infinite
scale.append(s_p) #scale->s_P
scale_weight.append(value)
scale = np.array(scale)
scale_weight = np.array(scale_weight)

weights_list.extend(list(scale_weight))
scales.extend(list(scale))
boxes_with_scale = np.zeros((box_count_image,4),dtype=np.float32) #initialization of boxes_with_scale,datatype:int64
boxes_with_scale[:, 0], boxes_with_scale[:, 2] = centers[:, 0] - scale / 2., centers[:, 0] + scale / 2. #x1, x2
boxes_with_scale[:, 1], boxes_with_scale[:, 3] = centers[:,1] - scale/2., centers[:,1] + scale/2. #y1, y2
boxes_with_scale[:, 0:4:2] = np.clip(boxes_with_scale[:, 0:4:2], 0, img_width - 1)
boxes_with_scale[:, 1:4:2] = np.clip(boxes_with_scale[:, 1:4:2], 0, img_height - 1)
annotation['bboxes'] = boxes_with_scale #store results in annotation[]
annotation['confs'] = 0.6 * np.ones((boxes_with_scale.shape[0]))
annotation['w_bboxes'] = scale_weight
image_data.append(annotation)

weights = np.array(weights_list,dtype='float') #for testing:record weights_max,weights_mean
print('weights_max: {}'.format(weights.max()))
print('weights_mean: {}'.format(weights.mean()))
print('weights_std: {}'.format(weights.std()))

scales = np.array(scales,dtype='float')
print('scales_max: {}'.format(scales.max()))
print('scales_min: {}'.format(scales.min()))
print('scales_mean: {}'.format(scales.mean()))
print('scales_std: {}'.format(scales.std()))

for image_data_i in image_data:
image_data_i['w_bboxes'] = np.clip(image_data_i['w_bboxes'], None, 50)

print '{} images and {} valid images and {} boxes'.format(img_count, valid_count,box_count)
with open(res_path, 'wb') as fid: #to write a file in the res_path
cPickle.dump(image_data, fid, cPickle.HIGHEST_PROTOCOL)
104 changes: 76 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ This work is accepted by IEEE TIP 2021.

## Progress
- [x] Training and Testing Code of WiderFace
- [ ] Training and Testing Code of ShanghaiTech
- [x] Training and Testing Code of ShanghaiTech
- [ ] Pytorch implementation (Expect coming in Nov.)


## Introduction
Expand Down Expand Up @@ -42,33 +43,54 @@ pip install -r requirements.txt
## Preparation
1. Dataset downloading.

For the WiderFace dataset, we download the training and validation images and annotations at [WiderFace](http://shuoyang1213.me/WIDERFACE/) and put it in `./data/WIDER` with the folder tree:
1.1 For the WiderFace dataset, we download the training and validation images and annotations at [WiderFace](http://shuoyang1213.me/WIDERFACE/) and put it in `./data/WIDER` with the folder tree:

```
-- WIDER
|-- WIDER_train
|-- images
|-- 0--Parade
|-- 1--Handshaking
1.2 For the ShanghaiTech dataset, we download the training and validation images and annotations at [ShanghaiTech](https://entuedu-my.sharepoint.com/:u:/g/personal/wang1241_e_ntu_edu_sg/EQcAhGPavgdEiEnSZY29FjwB9UEt37K_9wJKmOpgAU7-OA?e=wJtlgr) and put it in `./data/part_A` or `./data/part_B` with the folder tree:

```
-- WIDER
|-- WIDER_train
|-- images
|-- 0--Parade
|-- 1--Handshaking
|-- ...
|--WIDER_val
|-- images
|-- 0--Parade
|-- 1--Handshaking
|-- ...
|--wider_face_split
|-- wider_face_train_bbx_gt.txt
|-- wider_face_val_bbx_gt.txt
|-- ...
|--WIDER_val
--part_A or part_B
|-- train_data
|-- images
|-- 0--Parade
|-- 1--Handshaking
|-- ...
|--wider_face_split
|-- wider_face_train_bbx_gt.txt
|-- wider_face_val_bbx_gt.txt
|-- ...
```
|-- IMG_1.jpg
|-- ...
|-- ground-truth
|-- GT_IMG_1.mat
|-- ...
|-- val_data
|-- images
|-- ground-truth
|-- test_data
|-- images
|-- ground-truth
```
2. Dataset preparation.
Prepare an annotation-cache file by the locally-uniform distribution assumption (LUDA) method, which generates the pseudo object sizes. (Change WIDER Face dataset folder and split (train or val) in the code)
Prepare an annotation-cache file by the locally-uniform distribution assumption (LUDA) method, which generates the pseudo object sizes. (Change WiderFace/ShanghaiTech dataset folder and split (train or val) in the following code.)
By default, we assume the cache files is stored in `./data/cache/`.
```
python LUDA_generate_wider.py
```
```
python LUDA_generate_wider.py
```
or (Change Line 13-15 to generate train, train_val, and val cache, respectively),
```
python LUDA_generate_SH.py
```
3. Initialized models preparation.
Expand All @@ -81,6 +103,12 @@ python LUDA_generate_wider.py
Optionally, you should set the training parameters in [./keras_csp/config.py](./keras_csp/config.py) or replace some parameters in train_wider.py.
2. Train on ShanghaiTech.
Follow the [./train_ShanghaiTechA.py](./train_ShanghaiTechA.py) or [./train_ShanghaiTechB.py](./train_ShanghaiTechB.py) to start training. The weight files of all epochs will be saved in `./output/valmodels/`.
Since the codes are slightly different from the paper version with fine-tuned parameters and random seeds, the results may be better or worse. Re-implementation of paper's results can refer to the trained model [Models](#models).
## Test
1. Test on WiderFace.
Expand All @@ -91,8 +119,13 @@ python LUDA_generate_wider.py
Since using multi-scale testing and there is no optimization of soft nms (cpu python version), it will take several hours to run the val set.
Some scales like [0.75, 1.25, 1.75] can be removed to increase inference speed with a little performance decrease.
2. Test on ShanghaiTech.
Follow the [./test_ShanghaiTechA.py](./test_ShanghaiTechA.py) or [./test_ShanghaiTechB.py](./test_ShanghaiTechB.py) to get the detection results. The results will be saved in `./output/valresults/`.
Without [Training](#training), the test code can be performed with pretrained model from [Models](#models).
## Evaluation
1. Calculate AP scores.
1. Calculate AP scores for WiderFace dataset.
Download [Evaluation tool](http://shuoyang1213.me/WIDERFACE/support/eval_script/eval_tools.zip) for the official website of WiderFace.
Expand All @@ -101,19 +134,25 @@ python LUDA_generate_wider.py
2. Counting by detection.
Get the MAE, MSE, NAE on the WiderFace val set by running
Get the MAE, MSE, NAE on the WiderFace val set / ShanghaiTech test set by running
```
python tools/counting_by_detection.py
```
Specify 'part' to the dataset name (wider) and 'split' to val.
Specify 'part' to the dataset name (wider, part_A, part_B) in Line 101.
3. Plot predicted detections of WiderFace.
3. Plot predicted detections.
Plot detections on the image files by running
```
python tools/plot_wider_detections.py
```
Change thr and epoch of Line 13-14 for your trained model. or,
```
python tools/plot_ShanghaiTech_detections.py
```
Change part, thr and epoch of Line 12-23 for your trained model.


Qualitative results on (a) WiderFace, (b) SHA, (c) SHB, (d) PUCPR+, and (e) CARPK. The top row shows the ground-truth boxes or points, and counts. The bottom row shows the bounding boxes and counts predicted by our approach.
![Qualitative results](./docs/Fig3-1.png)
Expand All @@ -126,11 +165,17 @@ To reproduce the results in our paper, we have provided the models. You can down

1. For WiderFace

Download it from [output/valmodels/wider/h/off/](https://entuedu-my.sharepoint.com/:f:/g/personal/wang1241_e_ntu_edu_sg/EtUAOGz_tbJAk4HvucUIqUIBvfvscIbJ94gtzB_f8ILKtg?e=NdpvUf) and put it in the `./output/valmodels/wider/h/off/`
Download [wider.zip](https://entuedu-my.sharepoint.com/:u:/g/personal/wang1241_e_ntu_edu_sg/EWTfU2cop9pMv5wlBGWtzPMBdZ_jtV616G5z-KAKUrqnQw?e=UgvVUq), put it in the `./output/valmodels`, and unzip to `./output/valmodels/wider/h/off/`

2. For ShanghaiTech

Download part_A model [ShanghaiTechA.zip](https://entuedu-my.sharepoint.com/:u:/g/personal/wang1241_e_ntu_edu_sg/EftBSABp1qxNooFHllcIciQB1ZtY-rlwMihJO6YOQpzGig?e=wScHz7), put it in the `./output/valmodels`, and unzip to `./output/valmodels/ShanghaiTechA/h/nooff/`

Download part_B model [ShanghaiTechB.zip](https://entuedu-my.sharepoint.com/:u:/g/personal/wang1241_e_ntu_edu_sg/EdjHag4mRCxBs3u1K7xJ7OQB5RR6VzRRUixn8v2Qvzy6mw?e=5QztWq), put it in the `./output/valmodels`, and unzip to `./output/valmodels/ShanghaiTechB/h/nooff/`


# Citation
If you find this project is useful for your research, please cite:
If you find this research project is useful for your research, please cite:
```
@article{wang2021self_training,
title={A Self-Training Approach for Point-Supervised Object Detection and Counting in Crowds},
Expand All @@ -142,3 +187,6 @@ If you find this project is useful for your research, please cite:
publisher={IEEE}
}
```



Loading

0 comments on commit d8d1b0a

Please sign in to comment.