├── tests └── __init__.py ├── s_pycharm.jpg ├── data ├── boss │ └── dummy.jpg └── other │ └── dummy.jpg ├── resource_for_readme ├── editor.jpg ├── standup.jpg └── approach.jpg ├── requirements.txt ├── image_show.py ├── LICENSE ├── README.md ├── camera_reader.py ├── boss_input.py ├── .gitignore └── boss_train.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /s_pycharm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/BossSensor/master/s_pycharm.jpg -------------------------------------------------------------------------------- /data/boss/dummy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/BossSensor/master/data/boss/dummy.jpg -------------------------------------------------------------------------------- /data/other/dummy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/BossSensor/master/data/other/dummy.jpg -------------------------------------------------------------------------------- /resource_for_readme/editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/BossSensor/master/resource_for_readme/editor.jpg -------------------------------------------------------------------------------- /resource_for_readme/standup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/BossSensor/master/resource_for_readme/standup.jpg -------------------------------------------------------------------------------- /resource_for_readme/approach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujunze/BossSensor/master/resource_for_readme/approach.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | h5py==2.6.0 2 | Keras==1.1.0 3 | mock==2.0.0 4 | numpy==1.11.1 5 | pbr==1.10.0 6 | protobuf==3.0.0b2 7 | PyYAML==3.12 8 | scikit-learn==0.17.1 9 | scipy==0.18.1 10 | six==1.10.0 11 | sklearn==0.0 12 | tensorflow==0.10.0 13 | Theano==0.8.2 14 | -------------------------------------------------------------------------------- /image_show.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | 4 | from PyQt4 import QtGui 5 | 6 | 7 | def show_image(image_path='s_pycharm.jpg'): 8 | app = QtGui.QApplication(sys.argv) 9 | pixmap = QtGui.QPixmap(image_path) 10 | screen = QtGui.QLabel() 11 | screen.setPixmap(pixmap) 12 | screen.showFullScreen() 13 | sys.exit(app.exec_()) 14 | 15 | 16 | if __name__ == '__main__': 17 | show_image() 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hiroki Nakayama 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BossSensor 2 | Hide screen when boss is approaching. 3 | 4 | ## Demo 5 | Boss stands up. He is approaching. 6 | 7 | ![standup](https://github.com/Hironsan/BossSensor/blob/master/resource_for_readme/standup.jpg) 8 | 9 | When he is approaching, fetch face images and classify image. 10 | 11 | ![approaching](https://github.com/Hironsan/BossSensor/blob/master/resource_for_readme/approach.jpg) 12 | 13 | If image is classified as the Boss, monitor changes. 14 | 15 | ![editor](https://github.com/Hironsan/BossSensor/blob/master/resource_for_readme/editor.jpg) 16 | 17 | ## Requirements 18 | 19 | * WebCamera 20 | * Python3.5 21 | * OSX 22 | * Anaconda 23 | * Many boss image and other person image 24 | 25 | Put images into [data/boss](https://github.com/Hironsan/BossSensor/tree/master/data/boss) and [data/other](https://github.com/Hironsan/BossSensor/tree/master/data/other). 26 | 27 | ## Usage 28 | First, Train boss image. 29 | 30 | ``` 31 | $ python boss_train.py 32 | ``` 33 | 34 | 35 | Second, start BossSensor. 36 | 37 | ``` 38 | $ python camera_reader.py 39 | ``` 40 | 41 | ## Install 42 | Install OpenCV, PyQt4, Anaconda. 43 | 44 | ``` 45 | conda create -n venv python=3.5 46 | source activate venv 47 | conda install -c https://conda.anaconda.org/menpo opencv3 48 | conda install -c conda-forge tensorflow 49 | pip install -r requirements.txt 50 | ``` 51 | 52 | Change Keras backend from Theano to TensorFlow. 53 | 54 | ## Licence 55 | 56 | [MIT](https://github.com/Hironsan/BossSensor/blob/master/LICENSE) 57 | 58 | ## Author 59 | 60 | [Hironsan](https://github.com/Hironsan) -------------------------------------------------------------------------------- /camera_reader.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import cv2 3 | 4 | from boss_train import Model 5 | from image_show import show_image 6 | 7 | 8 | if __name__ == '__main__': 9 | cap = cv2.VideoCapture(0) 10 | cascade_path = "/usr/local/opt/opencv/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml" 11 | model = Model() 12 | model.load() 13 | while True: 14 | _, frame = cap.read() 15 | 16 | # グレースケール変換 17 | frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 18 | 19 | # カスケード分類器の特徴量を取得する 20 | cascade = cv2.CascadeClassifier(cascade_path) 21 | 22 | # 物体認識(顔認識)の実行 23 | facerect = cascade.detectMultiScale(frame_gray, scaleFactor=1.2, minNeighbors=3, minSize=(10, 10)) 24 | #facerect = cascade.detectMultiScale(frame_gray, scaleFactor=1.01, minNeighbors=3, minSize=(3, 3)) 25 | if len(facerect) > 0: 26 | print('face detected') 27 | color = (255, 255, 255) # 白 28 | for rect in facerect: 29 | # 検出した顔を囲む矩形の作成 30 | #cv2.rectangle(frame, tuple(rect[0:2]), tuple(rect[0:2] + rect[2:4]), color, thickness=2) 31 | 32 | x, y = rect[0:2] 33 | width, height = rect[2:4] 34 | image = frame[y - 10: y + height, x: x + width] 35 | 36 | result = model.predict(image) 37 | if result == 0: # boss 38 | print('Boss is approaching') 39 | show_image() 40 | else: 41 | print('Not boss') 42 | 43 | #10msecキー入力待ち 44 | k = cv2.waitKey(100) 45 | #Escキーを押されたら終了 46 | if k == 27: 47 | break 48 | 49 | #キャプチャを終了 50 | cap.release() 51 | cv2.destroyAllWindows() 52 | -------------------------------------------------------------------------------- /boss_input.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | import numpy as np 5 | import cv2 6 | 7 | IMAGE_SIZE = 64 8 | 9 | 10 | def resize_with_pad(image, height=IMAGE_SIZE, width=IMAGE_SIZE): 11 | 12 | def get_padding_size(image): 13 | h, w, _ = image.shape 14 | longest_edge = max(h, w) 15 | top, bottom, left, right = (0, 0, 0, 0) 16 | if h < longest_edge: 17 | dh = longest_edge - h 18 | top = dh // 2 19 | bottom = dh - top 20 | elif w < longest_edge: 21 | dw = longest_edge - w 22 | left = dw // 2 23 | right = dw - left 24 | else: 25 | pass 26 | return top, bottom, left, right 27 | 28 | top, bottom, left, right = get_padding_size(image) 29 | BLACK = [0, 0, 0] 30 | constant = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value=BLACK) 31 | 32 | resized_image = cv2.resize(constant, (height, width)) 33 | 34 | return resized_image 35 | 36 | 37 | images = [] 38 | labels = [] 39 | def traverse_dir(path): 40 | for file_or_dir in os.listdir(path): 41 | abs_path = os.path.abspath(os.path.join(path, file_or_dir)) 42 | print(abs_path) 43 | if os.path.isdir(abs_path): # dir 44 | traverse_dir(abs_path) 45 | else: # file 46 | if file_or_dir.endswith('.jpg'): 47 | image = read_image(abs_path) 48 | images.append(image) 49 | labels.append(path) 50 | 51 | return images, labels 52 | 53 | 54 | def read_image(file_path): 55 | image = cv2.imread(file_path) 56 | image = resize_with_pad(image, IMAGE_SIZE, IMAGE_SIZE) 57 | 58 | return image 59 | 60 | 61 | def extract_data(path): 62 | images, labels = traverse_dir(path) 63 | images = np.array(images) 64 | labels = np.array([0 if label.endswith('boss') else 1 for label in labels]) 65 | 66 | return images, labels 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | ### VirtualEnv template 62 | # Virtualenv 63 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 64 | .Python 65 | [Bb]in 66 | [Ii]nclude 67 | [Ll]ib 68 | [Ss]cripts 69 | pyvenv.cfg 70 | pip-selfcheck.json 71 | ### JetBrains template 72 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 73 | 74 | *.iml 75 | 76 | ## Directory-based project format: 77 | .idea/ 78 | # if you remove the above rule, at least ignore the following: 79 | 80 | # User-specific stuff: 81 | # .idea/workspace.xml 82 | # .idea/tasks.xml 83 | # .idea/dictionaries 84 | 85 | # Sensitive or high-churn files: 86 | # .idea/dataSources.ids 87 | # .idea/dataSources.xml 88 | # .idea/sqlDataSources.xml 89 | # .idea/dynamic.xml 90 | # .idea/uiDesigner.xml 91 | 92 | # Gradle: 93 | # .idea/gradle.xml 94 | # .idea/libraries 95 | 96 | # Mongo Explorer plugin: 97 | # .idea/mongoSettings.xml 98 | 99 | ## File-based project format: 100 | *.ipr 101 | *.iws 102 | 103 | ## Plugin-specific files: 104 | 105 | # IntelliJ 106 | /out/ 107 | 108 | # mpeltonen/sbt-idea plugin 109 | .idea_modules/ 110 | 111 | # JIRA plugin 112 | atlassian-ide-plugin.xml 113 | 114 | # Crashlytics plugin (for Android Studio and IntelliJ) 115 | com_crashlytics_export_strings.xml 116 | crashlytics.properties 117 | crashlytics-build.properties 118 | ### OSX template 119 | .DS_Store 120 | .AppleDouble 121 | .LSOverride 122 | 123 | # Icon must end with two \r 124 | Icon 125 | 126 | # Thumbnails 127 | ._* 128 | 129 | # Files that might appear in the root of a volume 130 | .DocumentRevisions-V100 131 | .fseventsd 132 | .Spotlight-V100 133 | .TemporaryItems 134 | .Trashes 135 | .VolumeIcon.icns 136 | 137 | # Directories potentially created on remote AFP share 138 | .AppleDB 139 | .AppleDesktop 140 | Network Trash Folder 141 | Temporary Items 142 | .apdisk 143 | 144 | -------------------------------------------------------------------------------- /boss_train.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function 3 | import random 4 | 5 | import numpy as np 6 | from sklearn.cross_validation import train_test_split 7 | from keras.preprocessing.image import ImageDataGenerator 8 | from keras.models import Sequential 9 | from keras.layers import Dense, Dropout, Activation, Flatten 10 | from keras.layers import Convolution2D, MaxPooling2D 11 | from keras.optimizers import SGD 12 | from keras.utils import np_utils 13 | from keras.models import load_model 14 | from keras import backend as K 15 | 16 | from boss_input import extract_data, resize_with_pad, IMAGE_SIZE 17 | 18 | 19 | class Dataset(object): 20 | 21 | def __init__(self): 22 | self.X_train = None 23 | self.X_valid = None 24 | self.X_test = None 25 | self.Y_train = None 26 | self.Y_valid = None 27 | self.Y_test = None 28 | 29 | def read(self, img_rows=IMAGE_SIZE, img_cols=IMAGE_SIZE, img_channels=3, nb_classes=2): 30 | images, labels = extract_data('./data/') 31 | labels = np.reshape(labels, [-1]) 32 | # numpy.reshape 33 | X_train, X_test, y_train, y_test = train_test_split(images, labels, test_size=0.3, random_state=random.randint(0, 100)) 34 | X_valid, X_test, y_valid, y_test = train_test_split(images, labels, test_size=0.5, random_state=random.randint(0, 100)) 35 | if K.image_dim_ordering() == 'th': 36 | X_train = X_train.reshape(X_train.shape[0], 3, img_rows, img_cols) 37 | X_valid = X_valid.reshape(X_valid.shape[0], 3, img_rows, img_cols) 38 | X_test = X_test.reshape(X_test.shape[0], 3, img_rows, img_cols) 39 | input_shape = (3, img_rows, img_cols) 40 | else: 41 | X_train = X_train.reshape(X_train.shape[0], img_rows, img_cols, 3) 42 | X_valid = X_valid.reshape(X_valid.shape[0], img_rows, img_cols, 3) 43 | X_test = X_test.reshape(X_test.shape[0], img_rows, img_cols, 3) 44 | input_shape = (img_rows, img_cols, 3) 45 | 46 | # the data, shuffled and split between train and test sets 47 | print('X_train shape:', X_train.shape) 48 | print(X_train.shape[0], 'train samples') 49 | print(X_valid.shape[0], 'valid samples') 50 | print(X_test.shape[0], 'test samples') 51 | 52 | # convert class vectors to binary class matrices 53 | Y_train = np_utils.to_categorical(y_train, nb_classes) 54 | Y_valid = np_utils.to_categorical(y_valid, nb_classes) 55 | Y_test = np_utils.to_categorical(y_test, nb_classes) 56 | 57 | X_train = X_train.astype('float32') 58 | X_valid = X_valid.astype('float32') 59 | X_test = X_test.astype('float32') 60 | X_train /= 255 61 | X_valid /= 255 62 | X_test /= 255 63 | 64 | self.X_train = X_train 65 | self.X_valid = X_valid 66 | self.X_test = X_test 67 | self.Y_train = Y_train 68 | self.Y_valid = Y_valid 69 | self.Y_test = Y_test 70 | 71 | 72 | class Model(object): 73 | 74 | FILE_PATH = './store/model.h5' 75 | 76 | def __init__(self): 77 | self.model = None 78 | 79 | def build_model(self, dataset, nb_classes=2): 80 | self.model = Sequential() 81 | 82 | self.model.add(Convolution2D(32, 3, 3, border_mode='same', input_shape=dataset.X_train.shape[1:])) 83 | self.model.add(Activation('relu')) 84 | self.model.add(Convolution2D(32, 3, 3)) 85 | self.model.add(Activation('relu')) 86 | self.model.add(MaxPooling2D(pool_size=(2, 2))) 87 | self.model.add(Dropout(0.25)) 88 | 89 | self.model.add(Convolution2D(64, 3, 3, border_mode='same')) 90 | self.model.add(Activation('relu')) 91 | self.model.add(Convolution2D(64, 3, 3)) 92 | self.model.add(Activation('relu')) 93 | self.model.add(MaxPooling2D(pool_size=(2, 2))) 94 | self.model.add(Dropout(0.25)) 95 | 96 | self.model.add(Flatten()) 97 | self.model.add(Dense(512)) 98 | self.model.add(Activation('relu')) 99 | self.model.add(Dropout(0.5)) 100 | self.model.add(Dense(nb_classes)) 101 | self.model.add(Activation('softmax')) 102 | 103 | self.model.summary() 104 | 105 | def train(self, dataset, batch_size=32, nb_epoch=40, data_augmentation=True): 106 | # let's train the model using SGD + momentum (how original). 107 | sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) 108 | self.model.compile(loss='categorical_crossentropy', 109 | optimizer=sgd, 110 | metrics=['accuracy']) 111 | if not data_augmentation: 112 | print('Not using data augmentation.') 113 | self.model.fit(dataset.X_train, dataset.Y_train, 114 | batch_size=batch_size, 115 | nb_epoch=nb_epoch, 116 | validation_data=(dataset.X_valid, dataset.Y_valid), 117 | shuffle=True) 118 | else: 119 | print('Using real-time data augmentation.') 120 | 121 | # this will do preprocessing and realtime data augmentation 122 | datagen = ImageDataGenerator( 123 | featurewise_center=False, # set input mean to 0 over the dataset 124 | samplewise_center=False, # set each sample mean to 0 125 | featurewise_std_normalization=False, # divide inputs by std of the dataset 126 | samplewise_std_normalization=False, # divide each input by its std 127 | zca_whitening=False, # apply ZCA whitening 128 | rotation_range=20, # randomly rotate images in the range (degrees, 0 to 180) 129 | width_shift_range=0.2, # randomly shift images horizontally (fraction of total width) 130 | height_shift_range=0.2, # randomly shift images vertically (fraction of total height) 131 | horizontal_flip=True, # randomly flip images 132 | vertical_flip=False) # randomly flip images 133 | 134 | # compute quantities required for featurewise normalization 135 | # (std, mean, and principal components if ZCA whitening is applied) 136 | datagen.fit(dataset.X_train) 137 | 138 | # fit the model on the batches generated by datagen.flow() 139 | self.model.fit_generator(datagen.flow(dataset.X_train, dataset.Y_train, 140 | batch_size=batch_size), 141 | samples_per_epoch=dataset.X_train.shape[0], 142 | nb_epoch=nb_epoch, 143 | validation_data=(dataset.X_valid, dataset.Y_valid)) 144 | 145 | def save(self, file_path=FILE_PATH): 146 | print('Model Saved.') 147 | self.model.save(file_path) 148 | 149 | def load(self, file_path=FILE_PATH): 150 | print('Model Loaded.') 151 | self.model = load_model(file_path) 152 | 153 | def predict(self, image): 154 | if K.image_dim_ordering() == 'th' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE): 155 | image = resize_with_pad(image) 156 | image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) 157 | elif K.image_dim_ordering() == 'tf' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3): 158 | image = resize_with_pad(image) 159 | image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3)) 160 | image = image.astype('float32') 161 | image /= 255 162 | result = self.model.predict_proba(image) 163 | print(result) 164 | result = self.model.predict_classes(image) 165 | 166 | return result[0] 167 | 168 | def evaluate(self, dataset): 169 | score = self.model.evaluate(dataset.X_test, dataset.Y_test, verbose=0) 170 | print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100)) 171 | 172 | if __name__ == '__main__': 173 | dataset = Dataset() 174 | dataset.read() 175 | 176 | model = Model() 177 | model.build_model(dataset) 178 | model.train(dataset, nb_epoch=10) 179 | model.save() 180 | 181 | model = Model() 182 | model.load() 183 | model.evaluate(dataset) 184 | --------------------------------------------------------------------------------