├── .gitignore ├── 001_down_data.py ├── 002_data-to-pascal-xml.py ├── 003_xml-to-csv.py ├── 004_generate_tfrecord.py ├── 004_generate_tfrecord.sh ├── 005__train.sh ├── 006_train-to-proto.sh ├── 007_evaluate.sh ├── data ├── .gitkeep └── wider_face_split │ ├── readme.txt │ ├── wider_face_test_filelist.txt │ ├── wider_face_train_bbx_gt.txt │ └── wider_face_val_bbx_gt.txt ├── face_label.pbtxt ├── model ├── checkpoint ├── frozen_inference_graph.pb ├── model.ckpt.data-00000-of-00001 ├── model.ckpt.index ├── model.ckpt.meta └── saved_model │ └── saved_model.pb ├── readme.md ├── requirements.txt └── ssd_mobilenet_v1_face.config /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | 104 | data/train.zip 105 | data/val.zip 106 | data/WIDER_train/* 107 | data/WIDER_val/* 108 | data/tf_wider_val/* 109 | data/tf_wider_train/* 110 | data/train.record 111 | model_output/* 112 | data/val.record 113 | data/ssd_mobilenet_v1_coco_11_06_2017/* 114 | data/ssd_mobilenet_v1_coco_11_06_2017.tar* 115 | -------------------------------------------------------------------------------- /001_down_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import os 5 | import shutil 6 | 7 | """ 8 | Script to download 9 | Wider Face Training Images 10 | Wider Face Validation Images 11 | from Google Drive using Python3.6 12 | 13 | http://mmlab.ie.cuhk.edu.hk/projects/WIDERFace/ 14 | 15 | Wider_face_split is included in repo 16 | 17 | """ 18 | 19 | # credits: https://stackoverflow.com/a/16664766 20 | # https://stackoverflow.com/questions/16664526/howto-download-file-from-drive-api-using-python-script 21 | 22 | def download_file_from_google_drive(id, destination): 23 | def get_confirm_token(response): 24 | for key, value in response.cookies.items(): 25 | if key.startswith('download_warning'): 26 | return value 27 | 28 | return None 29 | 30 | def save_response_content(response, destination): 31 | CHUNK_SIZE = 32768 32 | 33 | with open(destination, "wb") as f: 34 | for chunk in response.iter_content(CHUNK_SIZE): 35 | if chunk: # filter out keep-alive new chunks 36 | f.write(chunk) 37 | 38 | URL = "https://docs.google.com/uc?export=download" 39 | 40 | session = requests.Session() 41 | 42 | response = session.get(URL, params = { 'id' : id }, stream = True) 43 | token = get_confirm_token(response) 44 | 45 | if token: 46 | params = { 'id' : id, 'confirm' : token } 47 | response = session.get(URL, params = params, stream = True) 48 | 49 | save_response_content(response, destination) 50 | 51 | 52 | # The script 53 | curr_path = os.getcwd() 54 | models_path = os.path.join(curr_path,"data") 55 | 56 | # make dir => wider_data in folder 57 | try: 58 | os.makedirs(models_path) 59 | except Exception as e: 60 | pass 61 | 62 | if os.path.exists(os.path.join(models_path,"train.zip")) == False: 63 | print("downloading.. train.zip -- 1.47GB") 64 | download_file_from_google_drive("0B6eKvaijfFUDQUUwd21EckhUbWs", os.path.join(models_path,"train.zip")) 65 | 66 | if os.path.exists(os.path.join(models_path,"val.zip")) == False: 67 | print("downloading.. val.zip -- 362.8MB") 68 | download_file_from_google_drive("0B6eKvaijfFUDd3dIRmpvSk8tLUk", os.path.join(models_path,"val.zip")) 69 | 70 | print("files downloaded") 71 | 72 | # unzip the files 73 | import zipfile 74 | 75 | if os.path.exists(os.path.join(models_path,"WIDER_train")) == False: 76 | with zipfile.ZipFile(os.path.join(models_path,"train.zip"),"r") as zip_ref: 77 | zip_ref.extractall(models_path) 78 | 79 | if os.path.exists(os.path.join(models_path,"WIDER_val")) == False: 80 | with zipfile.ZipFile(os.path.join(models_path,"val.zip"),"r") as zip_ref: 81 | zip_ref.extractall(models_path) 82 | 83 | print("files unziped") 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | # downloading from: https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md 98 | url = 'http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_11_06_2017.tar.gz' 99 | 100 | if os.path.exists(os.path.join(models_path,"ssd_mobilenet_v1_coco_11_06_2017.tar.gz")) == False: 101 | response = requests.get(url, stream=True) 102 | with open(os.path.join(models_path,"ssd_mobilenet_v1_coco_11_06_2017.tar.gz"), 'wb') as out_file: 103 | shutil.copyfileobj(response.raw, out_file) 104 | del response 105 | 106 | 107 | import tarfile 108 | filePath = os.path.join(models_path,"ssd_mobilenet_v1_coco_11_06_2017.tar.gz") 109 | os.chdir(models_path) 110 | 111 | 112 | if (filePath.endswith("tar.gz")): 113 | tar = tarfile.open(filePath, "r:gz") 114 | tar.extractall() 115 | tar.close() 116 | elif (filePath.endswith("tar")): 117 | tar = tarfile.open(filePath, "r:") 118 | tar.extractall() 119 | tar.close() 120 | 121 | 122 | print("done") 123 | -------------------------------------------------------------------------------- /002_data-to-pascal-xml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | """ 5 | This script crawls over 9263 training images and 1873 items 6 | On my Macbook pro this takes: 4 minutes 7 | 8 | """ 9 | import cv2 10 | import os 11 | import numpy as np 12 | from glob import iglob # python 3.5 or newer 13 | from shutil import copyfile 14 | 15 | 16 | # The script 17 | curr_path = os.getcwd() 18 | 19 | import xml.etree.cElementTree as ET 20 | 21 | # settings 22 | cnt = 0 23 | hog = cv2.HOGDescriptor((80, 80), (16, 16), (8,8), (8,8), 9) 24 | # data = [] 25 | # label = [] 26 | 27 | 28 | def newXMLPASCALfile(imageheight, imagewidth, path, basename): 29 | # print(filename) 30 | annotation = ET.Element("annotation", verified="yes") 31 | ET.SubElement(annotation, "folder").text = "images" 32 | ET.SubElement(annotation, "filename").text = basename 33 | ET.SubElement(annotation, "path").text = path 34 | 35 | source = ET.SubElement(annotation, "source") 36 | ET.SubElement(source, "database").text = "test" 37 | 38 | size = ET.SubElement(annotation, "size") 39 | ET.SubElement(size, "width").text = str(imagewidth) 40 | ET.SubElement(size, "height").text = str(imageheight) 41 | ET.SubElement(size, "depth").text = "3" 42 | 43 | ET.SubElement(annotation, "segmented").text = "0" 44 | 45 | tree = ET.ElementTree(annotation) 46 | # tree.write("filename.xml") 47 | return tree 48 | 49 | def appendXMLPASCAL(curr_et_object,x1, y1, w, h, filename): 50 | et_object = ET.SubElement(curr_et_object.getroot(), "object") 51 | ET.SubElement(et_object, "name").text = "face" 52 | ET.SubElement(et_object, "pose").text = "Unspecified" 53 | ET.SubElement(et_object, "truncated").text = "0" 54 | ET.SubElement(et_object, "difficult").text = "0" 55 | bndbox = ET.SubElement(et_object, "bndbox") 56 | ET.SubElement(bndbox, "xmin").text = str(x1) 57 | ET.SubElement(bndbox, "ymin").text = str(y1) 58 | ET.SubElement(bndbox, "xmax").text = str(x1+w) 59 | ET.SubElement(bndbox, "ymax").text = str(y1+h) 60 | filename = filename.strip().replace(".jpg",".xml") 61 | curr_et_object.write(filename) 62 | return curr_et_object 63 | 64 | 65 | 66 | 67 | def readAndWrite(bbx_gttxtPath): 68 | cnt = 0 69 | with open(bbx_gttxtPath, 'r') as f: 70 | curr_img = '' 71 | 72 | curr_filename = "" 73 | curr_path = "" 74 | 75 | curr_et_object = ET.ElementTree() 76 | 77 | 78 | img = np.zeros((80, 80)) 79 | for line in f: 80 | inp = line.split(' ') 81 | 82 | # if line.find("--") != -1: 83 | # curr_filename = line.split('--')[1] 84 | # # reset elements 85 | # # emptyEl = ET.Element("") 86 | # curr_et_object = ET.ElementTree() 87 | 88 | if len(inp)==1: 89 | img_path = inp[0] 90 | img_path = img_path[:-1] 91 | curr_img = img_path 92 | if curr_img.isdigit(): 93 | continue 94 | # print(Train_path+'/'+curr_img) 95 | img = cv2.imread(Train_path + '/' + curr_img, 2) # POSIX only 96 | # print( len(list(curr_et_object.getroot()) ) ) 97 | curr_filename = curr_img.split("/")[1].strip() 98 | curr_path = os.path.join(Train_path, os.path.dirname(curr_img)) 99 | curr_et_object = newXMLPASCALfile(img.shape[0],img.shape[1],curr_path, curr_filename ) 100 | # print( curr_et_object ) 101 | 102 | else: 103 | # print(img) 104 | inp = [int(i) for i in inp[:-1]] 105 | x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose = inp 106 | n = max(w,h) 107 | if invalid == 1 or blur > 0 or n < 50: 108 | continue 109 | img2 = img[y1:y1+n, x1:x1+n] 110 | img3 = cv2.resize(img2, (80, 80)) 111 | vec = hog.compute(img3) 112 | # data.append(vec) 113 | # label.append(1) 114 | cnt += 1 115 | 116 | fileNow = os.path.join(curr_path,curr_filename) 117 | print("{}: {} {} {} {}".format(len(vec),x1, y1, w, h) + " " + fileNow) 118 | 119 | curr_et_object = appendXMLPASCAL(curr_et_object,x1, y1, w, h, fileNow ) 120 | 121 | 122 | # ################################ TRAINING DATA 9263 ITEMS ################################## 123 | # # # Run Script for Training data 124 | Train_path = os.path.join(curr_path, "data", "WIDER_train", "images" ) 125 | ## comment this out 126 | bbx_gttxtPath = os.path.join(curr_path, "data", "wider_face_split", "wider_face_train_bbx_gt.txt" ) 127 | readAndWrite(bbx_gttxtPath) 128 | 129 | 130 | # To folders: 131 | to_xml_folder = os.path.join(curr_path, "data", "tf_wider_train", "annotations", "xmls" ) 132 | to_image_folder = os.path.join(curr_path, "data", "tf_wider_train", "images" ) 133 | 134 | # make dir => wider_data in folder 135 | try: 136 | os.makedirs(to_xml_folder) 137 | os.makedirs(to_image_folder) 138 | except Exception as e: 139 | pass 140 | 141 | rootdir_glob = Train_path + '/**/*' # Note the added asterisks # This will return absolute paths 142 | file_list = [f for f in iglob(rootdir_glob, recursive=True) if os.path.isfile(f)] 143 | 144 | train_annotations_index = os.path.join(curr_path, "data", "tf_wider_train", "annotations", "train.txt" ) 145 | 146 | with open(train_annotations_index, "a") as indexFile: 147 | for f in file_list: 148 | if ".xml" in f: 149 | print(f) 150 | copyfile(f, os.path.join(to_xml_folder, os.path.basename(f) )) 151 | img = f.replace(".xml",".jpg") 152 | copyfile(img, os.path.join(to_image_folder, os.path.basename(img) )) 153 | indexFile.write(os.path.basename(f.replace(".xml","")) + "\n") 154 | 155 | 156 | ################################ VALIDATION DATA 1873 ITEMS ################################## 157 | 158 | # Run Script for Validation data 159 | Train_path = os.path.join(curr_path, "data", "WIDER_val", "images" ) 160 | bbx_gttxtPath = os.path.join(curr_path, "data", "wider_face_split", "wider_face_val_bbx_gt.txt" ) 161 | readAndWrite(bbx_gttxtPath) 162 | 163 | 164 | # To folders: 165 | to_xml_folder = os.path.join(curr_path, "data", "tf_wider_val", "annotations", "xmls" ) 166 | to_image_folder = os.path.join(curr_path, "data", "tf_wider_val", "images" ) 167 | 168 | # make dir => wider_data in folder 169 | try: 170 | os.makedirs(to_xml_folder) 171 | os.makedirs(to_image_folder) 172 | except Exception as e: 173 | pass 174 | 175 | 176 | rootdir_glob = Train_path + '/**/*' # Note the added asterisks # This will return absolute paths 177 | file_list = [f for f in iglob(rootdir_glob, recursive=True) if os.path.isfile(f)] 178 | 179 | train_annotations_index = os.path.join(curr_path, "data", "tf_wider_val", "annotations", "val.txt" ) 180 | 181 | with open(train_annotations_index, "a") as indexFile: 182 | for f in file_list: 183 | if ".xml" in f: 184 | print(f) 185 | copyfile(f, os.path.join(to_xml_folder, os.path.basename(f) )) 186 | img = f.replace(".xml",".jpg") 187 | copyfile(img, os.path.join(to_image_folder, os.path.basename(img) )) 188 | indexFile.write(os.path.basename(f.replace(".xml","")) + "\n") 189 | -------------------------------------------------------------------------------- /003_xml-to-csv.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import pandas as pd 4 | import xml.etree.ElementTree as ET 5 | 6 | # source and credits: 7 | # https://raw.githubusercontent.com/datitran/raccoon_dataset/master/xml_to_csv.py 8 | 9 | def xml_to_csv(path): 10 | xml_list = [] 11 | for xml_file in glob.glob(path + '/*.xml'): 12 | tree = ET.parse(xml_file) 13 | root = tree.getroot() 14 | for member in root.findall('object'): 15 | value = (root.find('filename').text, 16 | int(root.find('size')[0].text), 17 | int(root.find('size')[1].text), 18 | member[0].text, 19 | int(member[4][0].text), 20 | int(member[4][1].text), 21 | int(member[4][2].text), 22 | int(member[4][3].text) 23 | ) 24 | xml_list.append(value) 25 | column_name = ['filename', 'width', 'height', 'class', 'xmin', 'ymin', 'xmax', 'ymax'] 26 | xml_df = pd.DataFrame(xml_list, columns=column_name) 27 | return xml_df 28 | 29 | 30 | def train(): 31 | image_path = os.path.join(os.getcwd(), 'data', 'tf_wider_train', 'annotations','xmls') 32 | xml_df = xml_to_csv(image_path) 33 | labels_path = os.path.join(os.getcwd(), 'data', 'tf_wider_train','train.csv') 34 | xml_df.to_csv(labels_path, index=None) 35 | print('> tf_wider_train - Successfully converted xml to csv.') 36 | 37 | def val(): 38 | image_path = os.path.join(os.getcwd(), 'data', 'tf_wider_val', 'annotations','xmls') 39 | xml_df = xml_to_csv(image_path) 40 | labels_path = os.path.join(os.getcwd(), 'data', 'tf_wider_val', 'val.csv') 41 | xml_df.to_csv(labels_path, index=None) 42 | print('> tf_wider_val - Successfully converted xml to csv.') 43 | 44 | train() 45 | val() 46 | -------------------------------------------------------------------------------- /004_generate_tfrecord.py: -------------------------------------------------------------------------------- 1 | """ 2 | Usage: 3 | # From tensorflow/models/ 4 | # Create train data: 5 | python3 004_generate_tfrecord.py --images_path=data/tf_wider_train/images --csv_input=data/tf_wider_train/train.csv --output_path=data/train.record 6 | # creates 847.6MB train.record 7 | 8 | # Create test/validation data: 9 | python3 004_generate_tfrecord.py --images_path=data/tf_wider_val/images --csv_input=data/tf_wider_val/val.csv --output_path=data/val.record 10 | # creates 213.1MB val.record 11 | 12 | source without adjustments: https://raw.githubusercontent.com/datitran/raccoon_dataset/master/generate_tfrecord.py 13 | """ 14 | 15 | from __future__ import division 16 | from __future__ import print_function 17 | from __future__ import absolute_import 18 | 19 | import os 20 | import io 21 | import pandas as pd 22 | import tensorflow as tf 23 | 24 | from PIL import Image 25 | from object_detection.utils import dataset_util # from path 26 | from collections import namedtuple, OrderedDict # tf slim 27 | 28 | flags = tf.app.flags 29 | flags.DEFINE_string('csv_input', '', 'Path to the CSV input') 30 | flags.DEFINE_string('output_path', '', 'Path to output TFRecord') 31 | flags.DEFINE_string('images_path', '', 'Path to images_folder') 32 | 33 | FLAGS = flags.FLAGS 34 | 35 | 36 | # TO-DO replace this with label map 37 | def class_text_to_int(row_label): 38 | if row_label == 'face': 39 | return 1 40 | else: 41 | None 42 | 43 | 44 | def split(df, group): 45 | data = namedtuple('data', ['filename', 'object']) 46 | gb = df.groupby(group) 47 | return [data(filename, gb.get_group(x)) for filename, x in zip(gb.groups.keys(), gb.groups)] 48 | 49 | 50 | def create_tf_example(group, path): 51 | with tf.gfile.GFile(os.path.join(path, '{}'.format(group.filename)), 'rb') as fid: 52 | encoded_jpg = fid.read() 53 | encoded_jpg_io = io.BytesIO(encoded_jpg) 54 | image = Image.open(encoded_jpg_io) 55 | width, height = image.size 56 | 57 | filename = group.filename.encode('utf8') 58 | image_format = b'jpg' 59 | xmins = [] 60 | xmaxs = [] 61 | ymins = [] 62 | ymaxs = [] 63 | classes_text = [] 64 | classes = [] 65 | 66 | for index, row in group.object.iterrows(): 67 | xmins.append(row['xmin'] / width) 68 | xmaxs.append(row['xmax'] / width) 69 | ymins.append(row['ymin'] / height) 70 | ymaxs.append(row['ymax'] / height) 71 | classes_text.append(row['class'].encode('utf8')) 72 | classes.append(class_text_to_int(row['class'])) 73 | 74 | tf_example = tf.train.Example(features=tf.train.Features(feature={ 75 | 'image/height': dataset_util.int64_feature(height), 76 | 'image/width': dataset_util.int64_feature(width), 77 | 'image/filename': dataset_util.bytes_feature(filename), 78 | 'image/source_id': dataset_util.bytes_feature(filename), 79 | 'image/encoded': dataset_util.bytes_feature(encoded_jpg), 80 | 'image/format': dataset_util.bytes_feature(image_format), 81 | 'image/object/bbox/xmin': dataset_util.float_list_feature(xmins), 82 | 'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs), 83 | 'image/object/bbox/ymin': dataset_util.float_list_feature(ymins), 84 | 'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs), 85 | 'image/object/class/text': dataset_util.bytes_list_feature(classes_text), 86 | 'image/object/class/label': dataset_util.int64_list_feature(classes), 87 | })) 88 | return tf_example 89 | 90 | 91 | def main(_): 92 | writer = tf.python_io.TFRecordWriter(FLAGS.output_path) 93 | path = os.path.join(os.getcwd(), FLAGS.images_path) 94 | examples = pd.read_csv(FLAGS.csv_input) 95 | grouped = split(examples, 'filename') 96 | for group in grouped: 97 | tf_example = create_tf_example(group, path) 98 | writer.write(tf_example.SerializeToString()) 99 | 100 | writer.close() 101 | output_path = os.path.join(os.getcwd(), FLAGS.output_path) 102 | print('Successfully created the TFRecords: {}'.format(output_path)) 103 | 104 | 105 | if __name__ == '__main__': 106 | tf.app.run() 107 | -------------------------------------------------------------------------------- /004_generate_tfrecord.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 3 | 4 | python3 004_generate_tfrecord.py --images_path=data/tf_wider_train/images --csv_input=data/tf_wider_train/train.csv --output_path=data/train.record 5 | python3 004_generate_tfrecord.py --images_path=data/tf_wider_val/images --csv_input=data/tf_wider_val/val.csv --output_path=data/val.record 6 | -------------------------------------------------------------------------------- /005__train.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | echo "Did you update the paths ssd_mobilenet_v1_face.config?" 6 | echo "and tensorflow_models in this script" 7 | read -rsp $'Press any key to continue...\n' -n 1 key 8 | echo ">>>>>" 9 | 10 | python3 ~/tensorflow_models/object_detection/train.py --logtostderr --pipeline_config_path=ssd_mobilenet_v1_face.config --train_dir=model_output 11 | 12 | # or using a full path 13 | # python3 /Users/dion/tensorflow_models/object_detection/train.py --logtostderr --pipeline_config_path=/data/git/tensorflow-face-object-detector-tutorial/ssd_mobilenet_v1_face.config --train_dir=/data/git/tensorflow-face-object-detector-tutorial/model_output 14 | 15 | # First output: 16 | # INFO:tensorflow:Summary name Learning Rate is illegal; using Learning_Rate instead. 17 | # INFO:tensorflow:Summary name /clone_loss is illegal; using clone_loss instead. 18 | # INFO:tensorflow:Restoring parameters from /data/git/tensorflow-face-object-detector-tutorial/data/ssd_mobilenet_v1_coco_11_06_2017/model.ckpt 19 | # INFO:tensorflow:Starting Session. 20 | # INFO:tensorflow:Saving checkpoint to path /data/git/tensorflow-face-object-detector-tutorial/model/model.ckpt 21 | # INFO:tensorflow:Starting Queues. 22 | # INFO:tensorflow:global_step/sec: 0 23 | # INFO:tensorflow:Recording summary at step 0. 24 | # INFO:tensorflow:global step 1: loss = 15.5201 (17.574 sec/step) 25 | # INFO:tensorflow:global step 2: loss = 13.0839 (16.703 sec/step) 26 | # INFO:tensorflow:global step 3: loss = 12.7708 (10.608 sec/step) 27 | # INFO:tensorflow:global step 4: loss = 11.6702 (7.782 sec/step) 28 | -------------------------------------------------------------------------------- /006_train-to-proto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | echo "Did you update the 'tensorflow_models' path in this script?" 6 | read -rsp $'Press any key to continue...\n' -n 1 key 7 | echo ">>>>>" 8 | 9 | python3 ~/tensorflow_models/object_detection/export_inference_graph.py \ 10 | --input_type image_tensor \ 11 | --pipeline_config_path ssd_mobilenet_v1_face.config \ 12 | --trained_checkpoint_prefix model_output/model.ckpt-14337 \ 13 | --output_directory model/ 14 | 15 | # Output: 16 | # Converted 199 variables to const ops. 17 | -------------------------------------------------------------------------------- /007_evaluate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | echo "Did you update the 'tensorflow_models' path in this script?" 6 | read -rsp $'Press any key to continue...\n' -n 1 key 7 | echo ">>>>>" 8 | 9 | python3 ~/tensorflow_models/object_detection/eval.py --logtostderr --pipeline_config_path=ssd_mobilenet_v1_face.config --checkpoint_dir=model_output --eval_dir=eval 10 | 11 | # Output: 12 | # INFO:tensorflow:Restoring parameters from /Users/dionvanvelde/Desktop/models/wider/train/model.ckpt-14378 13 | # INFO:tensorflow:Restoring parameters from /Users/dionvanvelde/Desktop/models/wider/train/model.ckpt-14378 14 | # WARNING:root:The following classes have no ground truth examples: 0 15 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdraw/tensorflow-face-object-detector-tutorial/c87e80d737af2a30016f53d2f49997c3362d5c29/data/.gitkeep -------------------------------------------------------------------------------- /data/wider_face_split/readme.txt: -------------------------------------------------------------------------------- 1 | Attached the mappings between attribute names and label values. 2 | 3 | blur: 4 | clear->0 5 | normal blur->1 6 | heavy blur->2 7 | 8 | expression: 9 | typical expression->0 10 | exaggerate expression->1 11 | 12 | illumination: 13 | normal illumination->0 14 | extreme illumination->1 15 | 16 | occlusion: 17 | no occlusion->0 18 | partial occlusion->1 19 | heavy occlusion->2 20 | 21 | pose: 22 | typical pose->0 23 | atypical pose->1 24 | 25 | invalid: 26 | false->0(valid image) 27 | true->1(invalid image) 28 | 29 | The format of txt ground truth. 30 | File name 31 | Number of bounding box 32 | x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose -------------------------------------------------------------------------------- /face_label.pbtxt: -------------------------------------------------------------------------------- 1 | item { 2 | id: 1 3 | name: 'face' 4 | } 5 | -------------------------------------------------------------------------------- /model/checkpoint: -------------------------------------------------------------------------------- 1 | model_checkpoint_path: "model.ckpt" 2 | all_model_checkpoint_paths: "model.ckpt" 3 | -------------------------------------------------------------------------------- /model/frozen_inference_graph.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdraw/tensorflow-face-object-detector-tutorial/c87e80d737af2a30016f53d2f49997c3362d5c29/model/frozen_inference_graph.pb -------------------------------------------------------------------------------- /model/model.ckpt.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdraw/tensorflow-face-object-detector-tutorial/c87e80d737af2a30016f53d2f49997c3362d5c29/model/model.ckpt.data-00000-of-00001 -------------------------------------------------------------------------------- /model/model.ckpt.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdraw/tensorflow-face-object-detector-tutorial/c87e80d737af2a30016f53d2f49997c3362d5c29/model/model.ckpt.index -------------------------------------------------------------------------------- /model/model.ckpt.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdraw/tensorflow-face-object-detector-tutorial/c87e80d737af2a30016f53d2f49997c3362d5c29/model/model.ckpt.meta -------------------------------------------------------------------------------- /model/saved_model/saved_model.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdraw/tensorflow-face-object-detector-tutorial/c87e80d737af2a30016f53d2f49997c3362d5c29/model/saved_model/saved_model.pb -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## Donate 3 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/qdrawmedia) 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.13.1 2 | setuptools==36.2.0 3 | tensorflow==1.3.0 4 | six==1.10.0 5 | Pillow==4.2.1 6 | protobuf==3.3.0 7 | requests==2.14.2 # for downloading 8 | pandas==0.20.3 9 | Pillow==4.2.1 10 | # and opencv => needs to compiled 11 | -------------------------------------------------------------------------------- /ssd_mobilenet_v1_face.config: -------------------------------------------------------------------------------- 1 | # SSD with Mobilenet v1, configured for the Raccoon/Face dataset. 2 | # Users should configure the fine_tune_checkpoint field in the train config as 3 | # well as the label_map_path and input_path fields in the train_input_reader and 4 | # eval_input_reader. Search for /data/git/tensorflow-face-object-detector-tutorial/ to find the fields that 5 | # should be configured. 6 | # orginal source: https://github.com/datitran/raccoon_dataset/blob/master/training/ssd_mobilenet_v1_pets.config 7 | 8 | 9 | 10 | # WARNING SEARCH FIRST FOR: /data/git/tensorflow-face-object-detector-tutorial/ 11 | 12 | 13 | 14 | 15 | model { 16 | ssd { 17 | num_classes: 1 18 | box_coder { 19 | faster_rcnn_box_coder { 20 | y_scale: 10.0 21 | x_scale: 10.0 22 | height_scale: 5.0 23 | width_scale: 5.0 24 | } 25 | } 26 | matcher { 27 | argmax_matcher { 28 | matched_threshold: 0.5 29 | unmatched_threshold: 0.5 30 | ignore_thresholds: false 31 | negatives_lower_than_unmatched: true 32 | force_match_for_each_row: true 33 | } 34 | } 35 | similarity_calculator { 36 | iou_similarity { 37 | } 38 | } 39 | anchor_generator { 40 | ssd_anchor_generator { 41 | num_layers: 6 42 | min_scale: 0.2 43 | max_scale: 0.95 44 | aspect_ratios: 1.0 45 | aspect_ratios: 2.0 46 | aspect_ratios: 0.5 47 | aspect_ratios: 3.0 48 | aspect_ratios: 0.3333 49 | } 50 | } 51 | image_resizer { 52 | fixed_shape_resizer { 53 | height: 300 54 | width: 300 55 | } 56 | } 57 | box_predictor { 58 | convolutional_box_predictor { 59 | min_depth: 0 60 | max_depth: 0 61 | num_layers_before_predictor: 0 62 | use_dropout: false 63 | dropout_keep_probability: 0.8 64 | kernel_size: 1 65 | box_code_size: 4 66 | apply_sigmoid_to_scores: false 67 | conv_hyperparams { 68 | activation: RELU_6, 69 | regularizer { 70 | l2_regularizer { 71 | weight: 0.00004 72 | } 73 | } 74 | initializer { 75 | truncated_normal_initializer { 76 | stddev: 0.03 77 | mean: 0.0 78 | } 79 | } 80 | batch_norm { 81 | train: true, 82 | scale: true, 83 | center: true, 84 | decay: 0.9997, 85 | epsilon: 0.001, 86 | } 87 | } 88 | } 89 | } 90 | feature_extractor { 91 | type: 'ssd_mobilenet_v1' 92 | min_depth: 16 93 | depth_multiplier: 1.0 94 | conv_hyperparams { 95 | activation: RELU_6, 96 | regularizer { 97 | l2_regularizer { 98 | weight: 0.00004 99 | } 100 | } 101 | initializer { 102 | truncated_normal_initializer { 103 | stddev: 0.03 104 | mean: 0.0 105 | } 106 | } 107 | batch_norm { 108 | train: true, 109 | scale: true, 110 | center: true, 111 | decay: 0.9997, 112 | epsilon: 0.001, 113 | } 114 | } 115 | } 116 | loss { 117 | classification_loss { 118 | weighted_sigmoid { 119 | anchorwise_output: true 120 | } 121 | } 122 | localization_loss { 123 | weighted_smooth_l1 { 124 | anchorwise_output: true 125 | } 126 | } 127 | hard_example_miner { 128 | num_hard_examples: 3000 129 | iou_threshold: 0.99 130 | loss_type: CLASSIFICATION 131 | max_negatives_per_positive: 3 132 | min_negatives_per_image: 0 133 | } 134 | classification_weight: 1.0 135 | localization_weight: 1.0 136 | } 137 | normalize_loss_by_num_matches: true 138 | post_processing { 139 | batch_non_max_suppression { 140 | score_threshold: 1e-8 141 | iou_threshold: 0.6 142 | max_detections_per_class: 100 143 | max_total_detections: 100 144 | } 145 | score_converter: SIGMOID 146 | } 147 | } 148 | } 149 | 150 | train_config: { 151 | batch_size: 24 152 | optimizer { 153 | rms_prop_optimizer: { 154 | learning_rate: { 155 | exponential_decay_learning_rate { 156 | initial_learning_rate: 0.004 157 | decay_steps: 800720 158 | decay_factor: 0.95 159 | } 160 | } 161 | momentum_optimizer_value: 0.9 162 | decay: 0.9 163 | epsilon: 1.0 164 | } 165 | } 166 | fine_tune_checkpoint: "/data/git/tensorflow-face-object-detector-tutorial/data/ssd_mobilenet_v1_coco_11_06_2017/model.ckpt" 167 | from_detection_checkpoint: true 168 | data_augmentation_options { 169 | random_horizontal_flip { 170 | } 171 | } 172 | data_augmentation_options { 173 | ssd_random_crop { 174 | } 175 | } 176 | } 177 | 178 | train_input_reader: { 179 | tf_record_input_reader { 180 | input_path: "/data/git/tensorflow-face-object-detector-tutorial/data/train.record" 181 | } 182 | label_map_path: "/data/git/tensorflow-face-object-detector-tutorial/face_label.pbtxt" 183 | } 184 | 185 | eval_config: { 186 | num_examples: 40 187 | } 188 | 189 | eval_input_reader: { 190 | tf_record_input_reader { 191 | input_path: "/data/git/tensorflow-face-object-detector-tutorial/data/val.record" 192 | } 193 | label_map_path: "/data/git/tensorflow-face-object-detector-tutorial/face_label.pbtxt" 194 | shuffle: false 195 | num_readers: 1 196 | } 197 | --------------------------------------------------------------------------------