├── .gitignore ├── Docs ├── README.docx ├── README.pdf └── images │ ├── dataset_LBP.png │ ├── dataset_illumination_normalization.png │ ├── dataset_original.png │ ├── local_binary_pattern.jpg │ ├── main_script.png │ ├── occlusion-example-2.png │ ├── occlusion-example.png │ ├── realtime_face_recognition.png │ └── trasformation.png ├── LICENSE ├── README.md ├── RealTime ├── create_data.py ├── face_recognize.py ├── haarcascade_frontalface_default.xml └── utils │ ├── __init__.py │ └── utils.py ├── algorithms ├── LBP.py └── __init__.py ├── main.py ├── rotate.py └── utils ├── __init__.py ├── dataset.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pgm 2 | *.pyc 3 | *.pkl 4 | *.jar 5 | 6 | /Matlab 7 | /datasets -------------------------------------------------------------------------------- /Docs/README.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/README.docx -------------------------------------------------------------------------------- /Docs/README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/README.pdf -------------------------------------------------------------------------------- /Docs/images/dataset_LBP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/dataset_LBP.png -------------------------------------------------------------------------------- /Docs/images/dataset_illumination_normalization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/dataset_illumination_normalization.png -------------------------------------------------------------------------------- /Docs/images/dataset_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/dataset_original.png -------------------------------------------------------------------------------- /Docs/images/local_binary_pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/local_binary_pattern.jpg -------------------------------------------------------------------------------- /Docs/images/main_script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/main_script.png -------------------------------------------------------------------------------- /Docs/images/occlusion-example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/occlusion-example-2.png -------------------------------------------------------------------------------- /Docs/images/occlusion-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/occlusion-example.png -------------------------------------------------------------------------------- /Docs/images/realtime_face_recognition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/realtime_face_recognition.png -------------------------------------------------------------------------------- /Docs/images/trasformation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/0f336403026fe280ca40104330ccef46968808f1/Docs/images/trasformation.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andrea Corriga 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Computer-Vision-Project 2 | 3 | - [Computer-Vision-Project](#computer-vision-project) 4 | * [1.1 Introduction](#11-introduction) 5 | * [1.2 Used tools](#12-used-tools) 6 | * [1.3 Face Recognition problem](#13-face-recognition-problem) 7 | * [1.4 Local Binary Pattern](#14-local-binary-pattern) 8 | - [2. Project structure](#2-project-structure) 9 | - [3. Run the project](#3-run-the-project) 10 | * [3.1 Install dependencies](#31-install-dependencies) 11 | * [3.2 Run Main project](#32-run-main-project) 12 | - [Usage example](#usage-example) 13 | * [3.3 Run Real Time project](#33-run-real-time-project) 14 | * [3.4 Normalization Illumination](#34-normalization-illumination) 15 | - [4. Experiments](#4-experiments) 16 | * [4.1 Main project](#41-main-project) 17 | * [4.2 Real time project](#42-real-time-project) 18 | - [Conclusion](#conclusion) 19 | - [References](#references) 20 | 21 | 22 | ## 1.1 Introduction 23 | 24 | The goal of this project was to develop a Face Recognition application using a **Local Binary Pattern** approach and, using the same approach, develop a real time Face Recognition application. 25 | 26 | At high level the system is able, with a set of familiar faces, to recognize with a certain accuracy a new face. To do this, different methods were used in order to compare the results in terms of accuracy. 27 | 28 | This project was developed for [Computer Vision](http://people.unica.it/giovannipuglisi/didattica/insegnamenti/?mu=Guide/PaginaADErogata.do;jsessionid=CBB39621933B1A5C549359BBEFDCA119.jvm1?ad_er_id=2017*N0*N0*S2*26520*20168&ANNO_ACCADEMICO=2017&mostra_percorsi=S&step=1&jsid=CBB39621933B1A5C549359BBEFDCA119.jvm1&nsc=ffffffff0909189545525d5f4f58455e445a4a42378b) course at [University of Cagliari](http://corsi.unica.it/informatica), supervised by prof. [Giovanni Puglisi](http://people.unica.it/giovannipuglisi/). 29 | 30 | ## 1.2 Used tools 31 | 32 | Most of the project has been developed using Python as programming language, open source libraries and open source datasets. In particular it's been used: 33 | 34 | - Python 2.7.14 35 | - OpenCV 36 | - Scikit-learn 37 | - Extended Yale B Faces Dataset 38 | - Pil 39 | 40 | [Extended Yale B Faces Dataset](http://vision.ucsd.edu/~leekc/ExtYaleDatabase/ExtYaleB.html) includes 2414 frontal pictures of faces belonging to 38 different subjects. For each subject there are 64 photos taken in different light conditions. 41 | 42 | Non open source tools: 43 | 44 | - Matlab 45 | 46 | ![YaleFacesDataset](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/dataset_original.png) 47 | 48 | *Picture 1: the Yale Faces Dataset B* 49 | 50 | ## 1.3 Face Recognition problem 51 | 52 | Over the last ten years or so, face recognition has become a popular area of research in computer vision and one of the most successful applications of image analysis and understanding. A facial recognition system is a technology capable of identifying or verifying a person from a digital image or a video frame from a video source. 53 | For a human is very easy to perform the face recognition process but the same process is not easy for a computer. Computers have to deal with numerous interfering factors related to treatment of images such as: wide chromatic variations and different angles of view. 54 | Beyond this there are other factors that can be affect the face recognition process like: occlusion, hair style, glasses, facial expression etc. 55 | 56 | There are multiples methods in which facial recognition systems work, but in general, they work by comparing selected facial features from given image with faces within a database. 57 | 58 | ## 1.4 Local Binary Pattern 59 | 60 | **Local binary patterns (LBP)** is a type of visual descriptor used for classification in computer vision. In this project **LBP** operator was used to filter the features of facial textures. 61 | 62 | LBP transforms image blocks into an array of labels. Such labels (or their statistics, for example histograms) are used as features. Various versions have been developed in the state of the art. 63 | 64 | In the basic version of the LBP we consider the values of a 3x3 pixel neighborhood. For each pixel in a cell, compare the pixel to each of its 8 neighbors (on its left-top, left-middle, left-bottom, right-top, etc.). Follow the pixels along a circle, i.e. clockwise or counter-clockwise. Where the center pixel's value is greater than the neighbor's value, write "0". Otherwise, write "1". This gives an 8-digit binary number (which is usually converted to decimal for convenience). 65 | 66 | In its generalized version, each pixel is considered in a circular neighborhood of **P** points with radius **r**. If the elements do not fall on a single pixel, their values are obtained by interpolation. Generally the number of neighboring pixels chosen is 8. 67 | 68 | In this project a multi-blocks LBP were used. 69 | 70 | ![LocalBinaryPattern](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/local_binary_pattern.jpg) 71 | 72 | *Picture 2: an example of multi block Local Binary Pattern* 73 | 74 | # 2. Project structure 75 | 76 | In this chapter we'll be discuss the project structure and his general function. 77 | 78 | ``` 79 | Computer-Vision-Project 80 | │ LICENSE 81 | │ README.md 82 | │ main.py 83 | │ rotate.py 84 | │ 85 | └─── algorithms 86 | │ LBP.py 87 | | 88 | └─── model 89 | │ (here will be knn/naivebayes/svm.pkl) 90 | | 91 | └─── utils 92 | │ dataset.py 93 | │ utils.py 94 | | 95 | └─── RealTime (subproject) 96 | | utils 97 | │ │ utils.py 98 | | ------------------ 99 | │ create_data.py 100 | │ face_recognize.py 101 | │ haarcascade_frontalface_default.xml 102 | ``` 103 | 104 | The folder that starting with a capital letter represents a sub-project, the other folders are used to store the different libraries. 105 | 106 | In the **root** folder there are the `algorithms` folder, where there is the basic Local Binary Pattern implementation made by myself. The `datasets` folder contain the zips of the datasets used to test the project. In the `model` folder will be saved training data values, in order to perform the prediction without training the classifier again. In the `utils` folder there are some helper function. 107 | 108 | The **Realtime** folder contain the sub-project that perform a real time face recognition using the pc camera. It contain two main scripts: `create_data.py` that generate the dataset shooting some photos using the camera. `face_recognize.py` train the classifier and perform the real time face recognition. More details will be given in the next chapters. 109 | 110 | # 3. Run the project 111 | 112 | ## 3.1 Install dependencies 113 | 114 | In order to run, test and modify the source code must be installed the following packages. 115 | 116 | ```shell 117 | #CV2 118 | sudo apt-get install python-opencv 119 | 120 | #PIP 121 | sudo apt-get install python-pip 122 | pip install Pillow 123 | 124 | # SKLEARN 125 | pip install -U scikit-learn 126 | 127 | sudo apt-get install build-essential python3-dev python3-setuptools 128 | sudo apt-get install python3-numpy python3-scipy 129 | sudo apt-get install libopenblas-dev 130 | 131 | # DLIB 132 | sudo apt-get install build-essential cmake 133 | sudo apt-get install libgtk-3-dev 134 | sudo apt-get install libboost-all-dev 135 | 136 | # OTHER 137 | sudo pip install scikit-image 138 | sudo pip install dlib 139 | ``` 140 | 141 | ## 3.2 Run Main project 142 | 143 | First of all you have to unzip the YaleFaces.zip and YaleFaces_small. 144 | 145 | Launch the `python main.py` with the following parameters: 146 | 147 | ```shell 148 | --dataset [datasetName] 149 | --classifier [svm, knn, naivebayes] 150 | --training 151 | --histEq 152 | --output 153 | 154 | ``` 155 | 156 | By default launching only `python main.py` the script use YaleFaces dataset without training (loading from model folder). If there are not model in your model folder you must use `--training`. 157 | `--histEq` is helpful because perform an histogram equalization before calculating the LBP. 158 | If `--output` is setted the script will produce inside `./datasets/your_chosen_algorithm_/your_chosen_dataset/` the PNG of the LBP calculated for each image. 159 | 160 | ![LBP](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/dataset_LBP.png) 161 | 162 | *Picture 3: the LBP result on Yale Faces Dataset* 163 | 164 | With `--classifier` you can choose which classifier to use between SVM, KNN and NaiveBayes (obviously for each classifier you must perform the training of the model). 165 | 166 | By default LBP is performed splitting the images in 12x12 blocks. 167 | 168 | #### Usage example 169 | 170 | `python main.py --dataset YaleFaces --classifier svm` 171 | 172 | `python main.py --training --output --histEq` 173 | 174 | ![MainProject](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/main_script.png) 175 | 176 | *Picture 4: screenshot of main.py script* 177 | 178 | ## 3.3 Run Real Time project 179 | 180 | This section basically work in two steps: 181 | 182 | - Creation of the dataset 183 | - Training and real time classification 184 | 185 | First of all launch the `python create_data.py` with the following parameters: 186 | 187 | ```shell 188 | --name 189 | ``` 190 | 191 | Launching the script it will create inside `./datasets/CHOOSEN_NAME/` some photos shooted using the PC camera. The script use `CascadeClassifier`in order to perform Face Detection and cut out the face rettangle. Currently I choose to save 30 photos per subject. 192 | 193 | Now is it possible to perform Face Recognition in real time launching `python face_recognize.py` with the following parameters: 194 | 195 | ```shell 196 | --algorithm [lbp, fisherface] 197 | ``` 198 | 199 | The script, first of all, use the previously created dataset to train the classifier. If LBP is chosen it will be used SVM as classifier, else is used a general default CV2 classifier. 200 | 201 | After the training, `CascadeClassifier` is used to identify the face inside the scene and, when a face is identified, the classifier try to recognize it. 202 | 203 | ![RealTimeProject](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/realtime_face_recognition.png) 204 | 205 | *Picture 5: a screenshot of real time application* 206 | 207 | ## 3.4 Normalization Illumination 208 | 209 | Starting by this paper: [Enhanced Local Texture Feature Sets for Face Recognition under Difficult Lighting Conditions](http://parnec.nuaa.edu.cn/xtan/paper/TIP-05069-2009.R1-double.pdf) (authors page: [Xiaoyang Tan's Publications](http://parnec.nuaa.edu.cn/xtan/Publication.htm)) and his open source matlab code I perfomed a normalization of lighting as you can see in the picture. 210 | 211 | ![IlluminationNormalization](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/trasformation.png) 212 | 213 | *Picture 6: the result of Normalization Illumination* 214 | 215 | # 4. Experiments 216 | 217 | ## 4.1 Main project 218 | 219 | The features extraction methods and the accuracy was tested changing: 220 | 221 | - Classifier 222 | - The blocks size of LBP 223 | - Performing the normalization of illumination 224 | 225 | The tests were done splitting the dataset 77% for training and 33% for tests. The final values is the result of the average value of 5 tests with different training/test set. 226 | 227 | Below there are my results obtained with YaleFaces dataset using this PC: *Dell XPS 15 9550, with Intel i7 6700HQ Skylake @ 2.60GHz*. 228 | 229 | **Test with 3x3 blocks using LBP** 230 | 231 | Average time for calculating the LBP: 14 seconds. 232 | 233 | | Classifier | Accuracy without histogram equalization | Accuracy with histogram equalization | Accuracy with Normalization Illumination | 234 | | ---------- | --------------------------------------- | ------------------------------------ | ---------------------------------------- | 235 | | LinearSVM | 0.05 | 0.07 | 0.05 | 236 | | KNN | 0.48 | 0.48 | 0.69 | 237 | | NaiveBayes | 0.35 | 0.37 | 0.66 | 238 | 239 | All classifiers get poor result without the Matlab scripts. With this blocks size LinearSVM always get the worst results. 240 | 241 | **Test with 6x6 blocks using LBP** 242 | 243 | Average time for calculating the LBP: 18 seconds. 244 | 245 | | Classifier | Accuracy without histogram equalization | Accuracy with histogram equalization | Accuracy with Normalization Illumination | 246 | | ---------- | --------------------------------------- | ------------------------------------ | ---------------------------------------- | 247 | | LinearSVM | 0.40 | 0.55 | 0.40 | 248 | | KNN | 0.65 | 0.66 | 0.90 | 249 | | NaiveBayes | 0.70 | 0.70 | 0.93 | 250 | 251 | With normalization illumination the accuracy using SVM is very variable. It get an accuracy starting by 0.33 to 0.49. Also the other classifier is very variable, but not so much. In general NaiveBayes get the best result in all conditions. 252 | 253 | **Test with 12x12 blocks using LBP** 254 | 255 | Average time for calculating the LBP: 32 seconds. 256 | 257 | | Classifier | Accuracy without histogram equalization | Accuracy with histogram equalization | Accuracy with Normalization Illumination | 258 | | ---------- | --------------------------------------- | ------------------------------------ | ---------------------------------------- | 259 | | LinearSVM | 0.94 | 0.95 | 0.99 | 260 | | KNN | 0.81 | 0.81 | 0.97 | 261 | | NaiveBayes | 0.82 | 0.83 | 0.96 | 262 | 263 | In this case linear SVM get the best results and using the normalization script we come close to the 100%. 264 | 265 | ------ 266 | 267 | In general the first thing that can be notice is that splitting the images into blocks in order to calculate the LBP is essential to obtain satisfactory results. 268 | 269 | Splitting the images into 12*12 get the best results. 270 | 271 | Normalize the lighting improve the classifier score in the most of the case, except for the LinearSVM with larger block size (3x3 and 6x6). 272 | 273 | KNN get average score, but we must specify that this classifier is instance based, so it is not good in a realistic case. Some faces can't be classified if there are not a very similar element in the training set. 274 | 275 | NaiveBayes at least, get a good result in many different case. 276 | Splitting the images in 12x12 blocks it get worse result than LinearSVM (without Normalization Illumination) yes, but still get good result with smaller blocks. 277 | 278 | ## 4.2 Real time project 279 | 280 | Several tests have been done in order to test the performance of Local Binary Pattern approach with Linear SVM classifier in a real-time scenario. 281 | 282 | In particular, have been tested these situations: 283 | 284 | - With only two persons stored in the dataset: 285 | - recognize one subject in normal light condition 286 | - recognize one subject in normal light condition but adding some occlusions (glasses, scarves, etc.) 287 | - the previous situations but changing the light condition 288 | - With more than two persons stored in the dataset: 289 | - recognize one subject in normal light condition 290 | - recognize one subject in normal light condition but adding some occlusions (glasses, scarves, etc.) 291 | - recognize two subjects in the same scene in normal light condition 292 | - recognize two subjects in the same scene but adding some occlusions (glasses, scarves, etc.) 293 | 294 | In summary we can say that the LBP work very good in the both case with normal light condition. If the light is good as the training set photos, the algorithm recognizes the faces even if the subject moves his head a few degrees. 295 | 296 | Also adding some occlusions the algorithm seem work properly, in fact it is notable one thing: if the occlusion isn't very big the classifier recognizes the face; if the occlusion is too intrusive the face detection algorithm doesn't find the face so it's not even launched the classifier. 297 | 298 | ![OcclusionTest](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/occlusion-example.png) 299 | 300 | *Picture 7: test with occlusion* 301 | 302 | The classification becomes less accurate when other subjects are added into the dataset and into the scene. 303 | 304 | Adding more faces in the dataset leads a more complex training process and more complex classification. In this case is it possible to see the limits of LBP in the real time case. 305 | 306 | A person can't be perfectly still, so even a small movement that produce a little light variation can produce significant changes at low level processing and obtain a not accurate classification. 307 | 308 | But adding more subjects into the scene with a small dataset produce anyway a very good result. 309 | 310 | ![OcclusionTest-2](https://raw.githubusercontent.com/AsoStrife/Computer-Vision-Project/master/Docs/images/occlusion-example-2.png) 311 | 312 | *Picture 8: occlusion test with 2 subjects into the scene* 313 | 314 | As you can see at Picture 8, even adding an occlusion the classifier work very good with no flickering or bad classification (in most of the case). 315 | 316 | The same case visible at Picture 8 with more classes into the dataset produce a very variant and inaccurate results confirming the thesis that the small movement in the scene produce a little light variation in the faces that can lead a bad classification. 317 | 318 | # Conclusion 319 | 320 | The goal of this project was to develop a Face Recognition application using a **Local Binary Pattern** approach and after that, using the same approach, develop a real time Face Recognition application. 321 | 322 | The goal can be considered achieved with excellent results. 323 | 324 | In general we can say that LinearSVM perform the best results but need to split the images in very small blocks (at least 12x12 blocks) and the scene must be under control. These compromises are burdensome in terms of computational load. 325 | 326 | With different and smaller blocks size LinearSVM get the worse result if compared with KNN and NaiveBayes. 327 | 328 | NaiveBayes work good in the most of the case, even if we split the images with large blocks (3x3). 329 | 330 | # References 331 | 332 | - [[1] X.Tan and B.Triggs, Enhanced Local Texture Feature Sets for Face Recognition under Difficult Lighting Conditions, IEEE Transactions on Image Processing, 19(6), 1635-1650,2010.](http://parnec.nuaa.edu.cn/xtan/paper/TIP-05069-2009.R1-double.pdf) 333 | - [[2] T. Ojala, M. Pietikainen and T. Maenpaa, "Multiresolution gray-scale and rotation invariant texture classification with local binary patterns," in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 24, no. 7, pp. 971-987, Jul 2002.](https://ieeexplore.ieee.org/document/1017623/authors) 334 | - [[3] Timo Ahonen, Student Member, IEEE, Abdenour Hadid, and Matti Pietika ̈inen, Senior Member, IEEE: Face Description with Local Binary Patterns: Application to Face Recognition](https://ieeexplore.ieee.org/document/1717463/) 335 | 336 | 337 | -------------------------------------------------------------------------------- /RealTime/create_data.py: -------------------------------------------------------------------------------- 1 | #creating database 2 | import cv2, sys, numpy, os, argparse 3 | 4 | def main(): 5 | parser = argparse.ArgumentParser(description='Run the local binary patterns algorithm using either a single process or multiple processes.') 6 | parser.add_argument('--name', dest='name', type=str, default="", help='The name of the person you want to store the face') 7 | 8 | arguments = parser.parse_args() 9 | 10 | if(arguments.name == ""): 11 | print("Name can't be null. Please insert a name") 12 | return 13 | 14 | haar_file = 'haarcascade_frontalface_default.xml' 15 | 16 | #All the faces data will be present this folder 17 | datasets = 'datasets' 18 | #These are sub data sets of folder, for my faces I've used my name 19 | sub_data = arguments.name 20 | 21 | # If not exist the datasets folder 22 | if not os.path.isdir(datasets): 23 | os.mkdir(datasets) 24 | 25 | # Create the name folder 26 | path = os.path.join(datasets, sub_data) 27 | if not os.path.isdir(path): 28 | os.mkdir(path) 29 | 30 | # defining the size of images 31 | (width, height) = (130, 100) 32 | 33 | 34 | face_cascade = cv2.CascadeClassifier(haar_file) 35 | webcam = cv2.VideoCapture(0) 36 | 37 | # The program loops until it has 30 images of the face. 38 | count = 1 39 | 40 | # Keep open the webcam until click ESC 41 | while True: 42 | (_, im) = webcam.read() 43 | gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 44 | faces = face_cascade.detectMultiScale(gray, 1.3, 4) 45 | 46 | # Store 30 pic of the face 47 | for (x,y,w,h) in faces: 48 | cv2.rectangle(im,(x,y),(x+w,y+h),(255,0,0),2) 49 | face = gray[y:y + h, x:x + w] 50 | if count < 31: 51 | face_resize = cv2.resize(face, (width, height)) 52 | cv2.imwrite('%s/%s.png' % (path,count), face_resize) 53 | count += 1 54 | 55 | cv2.imshow('OpenCV', im) 56 | key = cv2.waitKey(10) 57 | if key == 27: 58 | break 59 | 60 | if __name__ == "__main__": 61 | main() -------------------------------------------------------------------------------- /RealTime/face_recognize.py: -------------------------------------------------------------------------------- 1 | # facerec.py 2 | import cv2, sys, numpy, os, argparse 3 | from utils.utils import * 4 | from sklearn import svm 5 | 6 | 7 | def main(): 8 | 9 | parser = argparse.ArgumentParser(description='Run the local binary patterns algorithm using either a single process or multiple processes.') 10 | parser.add_argument('--algorithm', dest='algorithm', type=str, default='lbp', help='Algorithm to use: "lbp" or "fisherface"') 11 | 12 | arguments = parser.parse_args() 13 | 14 | # Security check about the algorithm 15 | if(arguments.algorithm != "lbp" and arguments.algorithm != "fisherface"): 16 | print("Algorithm not valid. Choose between lbp or fisherface") 17 | return 18 | 19 | size = 4 20 | haar_file = 'haarcascade_frontalface_default.xml' 21 | datasets = 'datasets' 22 | # Part 1: Create fisherRecognizer 23 | 24 | # Create a list of images and a list of corresponding names 25 | (images, lables, names, id) = getDatasets(datasets) 26 | # Size of images 27 | (width, height) = (130, 100) 28 | 29 | # Create a Numpy array from the two lists above 30 | (images, lables) = [numpy.array(lis) for lis in [images, lables]] 31 | 32 | print('Training dataset using ' + arguments.algorithm) 33 | if arguments.algorithm == "fisherface": 34 | # OpenCV trains a model from the images 35 | model = cv2.face.createFisherFaceRecognizer() 36 | model.train(images, lables) 37 | if arguments.algorithm == "lbp": 38 | x = []; 39 | 40 | for img in images: 41 | lbp = LBP(img) 42 | # Concatenate the various histogram, the resulting histogram is append into feature vector 43 | x.append(lbp) 44 | 45 | model = svm.LinearSVC() 46 | model.fit(x, lables) 47 | print('Training dataset using ' + arguments.algorithm + ' done') 48 | 49 | 50 | # Part 2: Use fisherRecognizer on camera stream 51 | face_cascade = cv2.CascadeClassifier(haar_file) 52 | webcam = cv2.VideoCapture(0) 53 | 54 | while True: 55 | (_, im) = webcam.read() 56 | gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 57 | faces = face_cascade.detectMultiScale(gray, 1.3, 5) 58 | for (x,y,w,h) in faces: 59 | cv2.rectangle(im,(x,y),(x+w,y+h),(255,0,0),2) 60 | face = gray[y:y + h, x:x + w] 61 | face_resize = cv2.resize(face, (width, height)) 62 | 63 | if arguments.algorithm == "fisherface": 64 | # Try to recognize the face using fisherface 65 | prediction = model.predict(face_resize) #fisherface get the label as 0 66 | #print("[Debug]: " + names[prediction] + "'s face found") 67 | if arguments.algorithm == "lbp": 68 | lbp = []; 69 | lbp.append(LBP(face_resize)) 70 | prediction = model.predict(lbp) 71 | prediction = prediction[0] # SVM get and array with the class [0] 72 | #print("[Debug]: " + names[prediction] + "'s face found") 73 | 74 | cv2.rectangle(im, (x, y), (x + w, y + h), (0, 255, 0), 3) 75 | 76 | if prediction < 500: 77 | cv2.putText(im,'%s - %.0f' % (names[prediction],prediction),(x-10, y-10), cv2.FONT_HERSHEY_PLAIN,1,(0, 255, 0)) 78 | else: 79 | cv2.putText(im,'not recognized',(x-10, y-10), cv2.FONT_HERSHEY_PLAIN,1,(0, 255, 0)) 80 | 81 | 82 | cv2.imshow('OpenCV', im) 83 | key = cv2.waitKey(10) 84 | if key == 27: 85 | break 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /RealTime/utils/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["dataset", "utils"] 2 | -------------------------------------------------------------------------------- /RealTime/utils/utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Andrea Corriga 3 | @contact me@andreacorriga.com 4 | @date 2018 5 | @version 1.0 6 | ''' 7 | 8 | import cv2, sys, numpy, os, argparse 9 | from skimage.feature import local_binary_pattern 10 | 11 | """ 12 | Return an array of shape (n, nrows, ncols) where 13 | n * nrows * ncols = arr.size 14 | 15 | If arr is a 2D array, the returned array should look like n subblocks with 16 | each subblock preserving the "physical" layout of arr. 17 | """ 18 | def blockshaped(arr, nrows, ncols): 19 | 20 | h, w = arr.shape 21 | return (arr.reshape(h//nrows, nrows, -1, ncols) 22 | .swapaxes(1,2) 23 | .reshape(-1, nrows, ncols)) 24 | 25 | # https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.histogram.html 26 | def getHistogram(imgArray): 27 | hist, bin_edges = numpy.histogram(imgArray, density=True) 28 | return hist 29 | 30 | 31 | # Perform LBP with multiblock 32 | def LBP(img): 33 | lbp_value = local_binary_pattern(img, 8, 1) 34 | 35 | # Split img into 10*10 blocks 36 | shaped = blockshaped(lbp_value, 10, 13) 37 | 38 | # Calculate the histogram for each block 39 | xBlocks = [] 40 | for s in shaped: 41 | xBlocks.append(getHistogram(s)) 42 | 43 | return numpy.concatenate(xBlocks) 44 | 45 | # Get datasets photos 46 | def getDatasets(datasets): 47 | (images, lables, names, id) = ([], [], {}, 0) 48 | 49 | for (subdirs, dirs, files) in os.walk(datasets): 50 | for subdir in dirs: 51 | names[id] = subdir 52 | subjectpath = os.path.join(datasets, subdir) 53 | for filename in os.listdir(subjectpath): 54 | path = subjectpath + '/' + filename 55 | lable = id 56 | images.append(cv2.imread(path, 0)) 57 | lables.append(int(lable)) 58 | id += 1 59 | 60 | return images, lables, names, id -------------------------------------------------------------------------------- /algorithms/LBP.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Andrea Corriga 3 | @contact me@andreacorriga.com 4 | @date 2018 5 | @version 1.0 6 | ''' 7 | 8 | from PIL import Image 9 | import numpy 10 | 11 | class LBP: 12 | def __init__(self, img): 13 | # Convert the image to grayscale 14 | self.image = img 15 | # Size of original image 16 | self.width = self.image.size[0] 17 | self.height = self.image.size[1] 18 | # Future value of LBP image 19 | self.patterns = [] 20 | 21 | 22 | # Starting by the image, calculate the LBP values and store into self.patterns variable 23 | def execute(self): 24 | pixels = list(self.image.getdata()) 25 | pixels = [pixels[i * self.width:(i + 1) * self.width] for i in xrange(self.height)] 26 | 27 | # Calculate LBP for each non-edge pixel 28 | for i in xrange(1, self.height - 1): 29 | # Cache only the rows we need (within the neighborhood) 30 | previous_row = pixels[i - 1] 31 | current_row = pixels[i] 32 | next_row = pixels[i + 1] 33 | 34 | for j in xrange(1, self.width - 1): 35 | # Compare this pixel to its neighbors, starting at the top-left pixel and moving 36 | # clockwise, and use bit operations to efficiently update the feature vector 37 | pixel = current_row[j] 38 | pattern = 0 39 | pattern = pattern | (1 << 0) if pixel < previous_row[j-1] else pattern 40 | pattern = pattern | (1 << 1) if pixel < previous_row[j] else pattern 41 | pattern = pattern | (1 << 2) if pixel < previous_row[j+1] else pattern 42 | pattern = pattern | (1 << 3) if pixel < current_row[j+1] else pattern 43 | pattern = pattern | (1 << 4) if pixel < next_row[j+1] else pattern 44 | pattern = pattern | (1 << 5) if pixel < next_row[j] else pattern 45 | pattern = pattern | (1 << 6) if pixel < next_row[j-1] else pattern 46 | pattern = pattern | (1 << 7) if pixel < current_row[j-1] else pattern 47 | 48 | self.patterns.append(pattern) 49 | 50 | # This method return the LBP image generated (Image object) 51 | def getImage(self): 52 | result_image = Image.new(self.image.mode, (self.width - 2, self.height - 2)) 53 | result_image.putdata(self.patterns) 54 | return result_image 55 | 56 | # Return the LBP patters as array 57 | def getImageArray(self): 58 | return self.patterns 59 | 60 | # Return the histogram of the image 61 | def getHistogram(self): 62 | hist, bin_edges = numpy.histogram(self.patterns, density=True) 63 | return hist 64 | -------------------------------------------------------------------------------- /algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["LBP"] -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Andrea Corriga 3 | @contact me@andreacorriga.com 4 | @date 2018 5 | @version 1.0 6 | ''' 7 | import time 8 | import argparse 9 | import cv2 10 | from PIL import Image 11 | 12 | # Import my alghorithms 13 | from algorithms.LBP import LBP 14 | # Import my utils method 15 | from utils.utils import * 16 | from utils.dataset import * 17 | 18 | from sklearn.metrics import accuracy_score #c alculate accuracy 19 | from sklearn.externals import joblib # save and load model 20 | from sklearn.model_selection import train_test_split # in order to split training and test 21 | import numpy 22 | from skimage.feature import local_binary_pattern 23 | 24 | # Classifier 25 | from sklearn.naive_bayes import GaussianNB 26 | from sklearn.neighbors import KNeighborsClassifier 27 | from sklearn import svm 28 | 29 | def main(): 30 | 31 | parser = argparse.ArgumentParser(description='') 32 | parser.add_argument('--dataset', dest='dataset', type=str, default='YaleFaces', help='Main folder of the dataset') 33 | parser.add_argument('--classifier', dest='classifier', type=str, default='svm', help='Classifier to use: "svm" or "naivebayes" or "knn"') 34 | parser.add_argument('--training', dest='training', action='store_true', default=False, help='whether or not an output image should be produced') 35 | parser.add_argument('--histEq', dest='histEq', action='store_true', default=False, help='if you want to equialize the histogram before calculating LBP') 36 | parser.add_argument('--output', dest='output', action='store_true', default=False, help='if you want to save the png of LBP image') 37 | 38 | arguments = parser.parse_args() 39 | datasetMainFolder = os.getcwd() + "/datasets/" 40 | 41 | # Security check about the classifier 42 | if(arguments.classifier != "svm" and arguments.classifier != "naivebayes" and arguments.classifier != "knn"): 43 | print("Classifier not valid. Choose between svm, naivebayes or knn") 44 | return 45 | 46 | # Security check about the dataset 47 | if os.path.isdir(datasetMainFolder + arguments.dataset) == False: 48 | print('The Dataset "' + arguments.dataset + '" doesn\'t exist') 49 | return 50 | 51 | # Helpful instad of write datasetMainFolder + arguments.dataset + "/" 52 | datasetFolder = datasetMainFolder + arguments.dataset + "/" 53 | 54 | # Get Dataset information 55 | classes, filenames, xFilepaths, y = getDataset(arguments.dataset) 56 | x = [] 57 | 58 | print("Launching LBP on the " + arguments.dataset + " dataset...") 59 | startTime = time.time() 60 | 61 | # This counter is used to store the png 62 | counter = 0 63 | 64 | # if --output is passed as parameter 65 | if arguments.output == True: 66 | createFolderLBP(arguments.dataset, "LBP" ) 67 | 68 | 69 | for xfp in xFilepaths: 70 | img = imgRead(datasetFolder + xfp) 71 | 72 | #imgShow(numpy.matrix(img)) 73 | 74 | # Check if img exist (security check) 75 | if img: 76 | # if --histEq is passed as parameter, perform an histogram equalization 77 | if(arguments.histEq == True): 78 | img = histogramEqualization(img) 79 | 80 | lbp_value = local_binary_pattern(img, 8, 1) 81 | 82 | # Split img into 12*12 blocks (image size: 168 * 192) 83 | shaped = blockshaped(lbp_value, 16, 14) 84 | 85 | # Split img into 6*6 blocks (image size: 168 * 192) 86 | #shaped = blockshaped(lbp_value, 32, 28) 87 | 88 | # Split img into 3*3 blocks (image size: 168 * 192) 89 | #shaped = blockshaped(lbp_value, 64, 56) 90 | 91 | # Calculate the histogram for each block 92 | xBlocks = [] 93 | for s in shaped: 94 | xBlocks.append(getHistogram(s)) 95 | # Concatenate the various histogram, the resulting histogram is append into feature vector 96 | x.append(numpy.concatenate(xBlocks)) 97 | 98 | # if --output is passed as parameter 99 | if arguments.output == True: 100 | saveImgLBP(getImgObjFromArray(lbp_value), arguments.dataset, filenames[counter], "LBP" ) 101 | 102 | # If the image doens't exist 103 | else: 104 | print("The image: " + datasetFolder + xfp + " doesn't exist") 105 | # Add counter for new image 106 | counter = counter + 1 107 | 108 | 109 | print("--- LBP done in %s seconds ---" % (time.time() - startTime)) 110 | 111 | print("Split dataset into training and test set [0.77] [0.33]") 112 | 113 | # Split dataset x (feature vector) and y (label) into training and test set 114 | xTrain, xTest, yTrain, yTest = train_test_split(x, y, test_size=0.33) 115 | 116 | print("Launching " + arguments.classifier.upper() + "...") 117 | 118 | startTime = time.time() 119 | 120 | filename = arguments.classifier + ".pkl" 121 | 122 | # if --training is passet as parameter, perform the training of model 123 | if arguments.training == True: 124 | trainingTime = time.time() 125 | 126 | if arguments.classifier == "svm": 127 | clf = svm.LinearSVC() 128 | if arguments.classifier == "naivebayes": 129 | clf = GaussianNB() 130 | if arguments.classifier == "knn": 131 | clf = KNeighborsClassifier(n_neighbors=3) 132 | 133 | print("Start training...") 134 | clf.fit(xTrain, yTrain) 135 | joblib.dump(clf, 'model/' + filename) 136 | print("--- Training done in %s seconds ---" % (time.time() - trainingTime)) 137 | 138 | # Test the model 139 | clf = joblib.load('model/' + filename) 140 | print("Start testing...") 141 | predicted = clf.predict(xTest) 142 | 143 | print("--- " + arguments.classifier.upper() + " done in %s seconds ---" % (time.time() - startTime)) 144 | 145 | print("Accuracy: " + str(accuracy_score(yTest, predicted))) 146 | 147 | if __name__ == "__main__": 148 | main() -------------------------------------------------------------------------------- /rotate.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Andrea Corriga 3 | @contact me@andreacorriga.com 4 | @date 2018 5 | @version 1.0 6 | ''' 7 | import time 8 | import argparse 9 | import cv2 10 | from PIL import Image 11 | from sklearn import svm 12 | 13 | # Import my alghorithms 14 | from algorithms.LBP import LBP 15 | # Import my utils method 16 | from utils.utils import * 17 | from utils.dataset import * 18 | 19 | from sklearn.metrics import accuracy_score #c alculate accuracy 20 | from sklearn.externals import joblib # save and load model 21 | from sklearn.model_selection import train_test_split # in order to split training and test 22 | 23 | def main(): 24 | 25 | parser = argparse.ArgumentParser(description='Run the local binary patterns algorithm using either a single process or multiple processes.') 26 | parser.add_argument('--dataset', dest='dataset', type=str, default='YaleFaces', help='Main folder of the dataset') 27 | parser.add_argument('--histEq', dest='histEq', action='store_true', default=False, help='if you want to equialize the histogram before calculating LBP or ELBP') 28 | 29 | arguments = parser.parse_args() 30 | datasetMainFolder = os.getcwd() + "/datasets/" 31 | 32 | # Security check about the dataset 33 | if os.path.isdir(datasetMainFolder + arguments.dataset) == False: 34 | print('The Dataset "' + arguments.dataset + '" doesn\'t exist') 35 | return 36 | 37 | # Helpful instad of write datasetMainFolder + arguments.dataset + "/" 38 | datasetFolder = datasetMainFolder + arguments.dataset + "/" 39 | 40 | # Get Dataset information 41 | classes, filenames, xFilepaths, y = getDataset(arguments.dataset) 42 | 43 | print("Launching rotate algorithm algorithm for the " + arguments.dataset + " dataset...") 44 | startTime = time.time() 45 | 46 | # This counter is used to store the png 47 | counter = 0 48 | 49 | createFolder(arguments.dataset + "_rotate") 50 | 51 | for yfolder in y: 52 | createFolder(arguments.dataset + "_rotate/" + yfolder) 53 | 54 | for xfp in xFilepaths: 55 | img = imgRead(datasetFolder + xfp) 56 | # Check if img exist (security check) 57 | if img: 58 | # if --histEq is passed as parameter, perform an histogram equalization 59 | if(arguments.histEq == True): 60 | img = histogramEqualization(img) 61 | 62 | imgRotate = rotateImg(getImgArray(img), 90) 63 | 64 | rotateObj = getImgObjFromArray(imgRotate) 65 | 66 | saveImgFromArray(rotateObj, arguments.dataset + "_rotate/" + y[counter], filenames[counter] ) 67 | # If the image doens't exist 68 | else: 69 | print("The image: " + datasetFolder + xfp + " doesn't exist") 70 | 71 | counter = counter + 1 72 | 73 | print("--- Rotate done in %s seconds ---" % (time.time() - startTime)) 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["dataset", "utils"] 2 | -------------------------------------------------------------------------------- /utils/dataset.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Andrea Corriga 3 | @contact me@andreacorriga.com 4 | @date 2018 5 | @version 1.0 6 | ''' 7 | 8 | import os 9 | 10 | ''' 11 | Starting by a dataset name, this function return 12 | classes => an array with all classes (subfolders inside dataset folder) 13 | filename => an array with all images filenames 14 | xFilepath => an array with all images name with path /dataset/classes_folder/filename.pgm 15 | y => an array with the relative label of filename||xFilepath 16 | ''' 17 | def getDataset(dataset): 18 | directory = os.getcwd() + "/datasets/" + dataset +"/" 19 | 20 | classes = [] 21 | filename = [] 22 | xFilepath = [] 23 | y = [] 24 | 25 | for root, dirs, files in os.walk(directory): 26 | for dir in dirs: 27 | classes.append(dir) 28 | 29 | for imgClass in classes: 30 | for file in os.listdir(directory + imgClass): 31 | y.append(imgClass) 32 | xFilepath.append(imgClass + "/" + file) 33 | filename.append(file) 34 | 35 | return classes, filename, xFilepath, y 36 | 37 | 38 | def getPersonalDataset(): 39 | directory = os.getcwd() + "/datasets/personal/" 40 | 41 | x = [] 42 | 43 | for root, dirs, files in os.walk(directory): 44 | for file in files: 45 | x.append("datasets/personal/" + file) 46 | 47 | return x -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | @author Andrea Corriga 3 | @contact me@andreacorriga.com 4 | @date 2018 5 | @version 1.0 6 | ''' 7 | 8 | import os.path 9 | import numpy 10 | import dlib 11 | import cv2 12 | from PIL import Image 13 | import shutil #remove folder with all files 14 | 15 | # Other method to read an image starting by filepath 16 | #img = cv2.imread(filepath) 17 | 18 | # Read an image starting by the file path and and return the Image object converted in grayscale 19 | def imgRead(filepath): 20 | if os.path.isfile(filepath): 21 | return Image.open(filepath).convert("L") 22 | else: 23 | return 24 | 25 | # Return the size of an image 26 | def getImgSize(imgObj): 27 | return imgObj.size 28 | 29 | # Passing an Image Object return the equivalent numpy array values 30 | def getImgArray(imgObj): 31 | return numpy.matrix(imgObj) 32 | 33 | # Check if an Image contain one or more valid faces. 34 | def faceDetect(imgArray): 35 | detector = dlib.get_frontal_face_detector() 36 | # Run the face detector, upsampling the image 1 time to find smaller faces. 37 | dets = detector(imgArray, 1) 38 | 39 | if len(dets) >= 1: 40 | return True 41 | else: 42 | return False 43 | 44 | '''DEBUG SHOW FACE RETTANGLE 45 | print "number of faces detected: ", len(dets) 46 | win = dlib.image_window() 47 | win.set_image(img) 48 | win.add_overlay(dets) 49 | raw_input("Hit enter to continue") 50 | ''' 51 | 52 | # Usefull to debug, show in a new Windows the image passed as a parameters 53 | def imgShow(imgArray): 54 | win = dlib.image_window() 55 | win.set_image(imgArray) 56 | raw_input("Hit enter to continue...") 57 | 58 | # Convert an img array into Image object 59 | def getImgObjFromArray(imgArray): 60 | return Image.fromarray(imgArray.astype('uint8'), 'L') 61 | 62 | # Create the folder LBP inside the dataset folder, in order to save the LBP image 63 | def createFolderLBP(dataset, algorithm): 64 | path = "datasets/" + algorithm + "/" + dataset +"/" 65 | if not os.path.exists(path): 66 | os.makedirs(path) 67 | else: 68 | shutil.rmtree(path, ignore_errors=True) 69 | os.makedirs(path) 70 | 71 | # Create the folder LBP inside the dataset folder, in order to save the LBP image 72 | def createFolder(dataset): 73 | path = "datasets/" + dataset +"/" 74 | if not os.path.exists(path): 75 | os.makedirs(path) 76 | else: 77 | shutil.rmtree(path, ignore_errors=True) 78 | os.makedirs(path) 79 | 80 | # Passing an image, a dataset name and file name, store the image in png format 81 | def saveImgLBP(img, dataset, filename, algorithm): 82 | path = "datasets/" + algorithm + "/" + dataset +"/" 83 | img.save(path + filename) 84 | 85 | # Passing an image, a dataset name and file name, store the image in png format 86 | def saveImgFromArray(img, dataset, filename): 87 | path = "datasets/" + "/" + dataset +"/" 88 | img.save(path + filename) 89 | 90 | 91 | # Equalize the img histogram passed as a parameter 92 | def histogramEqualization(imgObj): 93 | imgArray = getImgArray(imgObj) 94 | eqImg = cv2.equalizeHist(imgArray) 95 | return getImgObjFromArray(eqImg) 96 | 97 | 98 | """ 99 | Return an array of shape (n, nrows, ncols) where 100 | n * nrows * ncols = arr.size 101 | 102 | If arr is a 2D array, the returned array should look like n subblocks with 103 | each subblock preserving the "physical" layout of arr. 104 | """ 105 | def blockshaped(arr, nrows, ncols): 106 | 107 | h, w = arr.shape 108 | return (arr.reshape(h//nrows, nrows, -1, ncols) 109 | .swapaxes(1,2) 110 | .reshape(-1, nrows, ncols)) 111 | 112 | # Rotate an img 113 | def rotateImg(imgArray, angle): 114 | rows,cols = imgArray.shape 115 | M = cv2.getRotationMatrix2D((cols/2,rows/2), angle, 1) 116 | dst = cv2.warpAffine(imgArray,M,(cols,rows)) 117 | return dst 118 | 119 | # https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.histogram.html 120 | def getHistogram(imgArray): 121 | hist, bin_edges = numpy.histogram(imgArray, density=True) 122 | return hist --------------------------------------------------------------------------------