├── .gitignore ├── AUTHORS.md ├── LICENSE ├── README.md ├── data ├── haarcascade_frontalface_alt2.xml ├── individuals.pkl ├── individuals │ ├── bill │ │ ├── bill_crop0.jpg │ │ ├── bill_crop1.jpg │ │ ├── bill_crop2.jpg │ │ ├── bill_crop3.jpg │ │ ├── bill_crop4.jpg │ │ ├── bill_crop5.jpg │ │ ├── bill_crop6.jpg │ │ └── bill_crop7.jpg │ ├── dennis │ │ ├── dennis_crop0.jpg │ │ ├── dennis_crop1.jpg │ │ ├── dennis_crop2.jpg │ │ ├── dennis_crop3.jpg │ │ ├── dennis_crop5.JPG │ │ ├── dennis_crop6.jpg │ │ └── dennis_crop7.jpg │ ├── linus │ │ ├── linus_crop1.jpg │ │ ├── linus_crop2.jpg │ │ ├── linus_crop3.jpg │ │ ├── linus_crop4.jpg │ │ ├── linus_crop6.jpg │ │ ├── linus_crop7.jpg │ │ ├── linus_crop8.jpg │ │ └── linus_crop9.jpg │ └── steve │ │ ├── steve_crop0.jpg │ │ ├── steve_crop1.jpg │ │ ├── steve_crop2.jpg │ │ ├── steve_crop3.jpg │ │ ├── steve_crop4.jpg │ │ ├── steve_crop5.jpg │ │ ├── steve_crop6.jpg │ │ └── steve_crop7.jpg └── raw │ ├── b.png │ ├── d.png │ ├── l.png │ └── s.png ├── doc └── images │ ├── ocvf_example_florian.jpg │ └── ocvf_example_norman.png ├── ros_cam_node └── ros_cam_node.launch ├── rsb_video_sender └── rsb_video_sender.sh └── src ├── __init__.py ├── bin ├── __init__.py ├── ocvf_image_publisher_ros.py ├── ocvf_interactive_trainer.py ├── ocvf_recognizer.py ├── ocvf_recognizer_ros.py ├── ocvf_recognizer_rsb.py ├── ocvf_retrain_ros.py └── ocvf_retrain_rsb.py ├── ocvfacerec ├── __init__.py ├── facedet │ ├── __init__.py │ └── detector.py ├── facerec │ ├── __init__.py │ ├── classifier.py │ ├── dataset.py │ ├── distance.py │ ├── feature.py │ ├── lbp.py │ ├── model.py │ ├── normalization.py │ ├── operators.py │ ├── preprocessing.py │ ├── serialization.py │ ├── svm.py │ ├── util.py │ ├── validation.py │ └── visual.py ├── helper │ ├── PersonWrapper.py │ ├── __init__.py │ ├── common.py │ └── video.py ├── mwconnector │ ├── __init__.py │ ├── abtractconnector.py │ ├── rosconnector.py │ └── rsbconnector.py └── trainer │ ├── __init__.py │ └── thetrainer.py ├── setup.py └── tools ├── face_cropper.py └── rosbag2jpg.launch /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | dist 4 | build 5 | *.egg 6 | serverlog.log 7 | *.egg-* 8 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Contributors # 2 | 3 | This file contains the list of people involved in the development 4 | of facerec along its history. The purpose of this file is to 5 | encourage the participation in the project. 6 | 7 | There is nothing like a minimal contribution, so if you have 8 | contributed to the library, then feel free to add your 9 | name to the list: 10 | 11 | * Philipp Wagner [bytefish[at]gmx[dot]de] 12 | * Florian Lier [flier[at]techfak.uni-bielefeld.de] 13 | * Norman Köster [nkoester[at]techfak.uni-bielefeld.de] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, philipp and 2 | Florian Lier and 3 | Norman Köster 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of the organization nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop0.jpg -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop1.jpg -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop2.jpg -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop3.jpg -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop4.jpg -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop5.jpg -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop6.jpg -------------------------------------------------------------------------------- /data/individuals/bill/bill_crop7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/bill/bill_crop7.jpg -------------------------------------------------------------------------------- /data/individuals/dennis/dennis_crop0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/dennis/dennis_crop0.jpg -------------------------------------------------------------------------------- /data/individuals/dennis/dennis_crop1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/dennis/dennis_crop1.jpg -------------------------------------------------------------------------------- /data/individuals/dennis/dennis_crop2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/dennis/dennis_crop2.jpg -------------------------------------------------------------------------------- /data/individuals/dennis/dennis_crop3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/dennis/dennis_crop3.jpg -------------------------------------------------------------------------------- /data/individuals/dennis/dennis_crop5.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/dennis/dennis_crop5.JPG -------------------------------------------------------------------------------- /data/individuals/dennis/dennis_crop6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/dennis/dennis_crop6.jpg -------------------------------------------------------------------------------- /data/individuals/dennis/dennis_crop7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/dennis/dennis_crop7.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop1.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop2.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop3.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop4.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop6.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop7.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop8.jpg -------------------------------------------------------------------------------- /data/individuals/linus/linus_crop9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/linus/linus_crop9.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop0.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop1.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop2.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop3.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop4.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop5.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop6.jpg -------------------------------------------------------------------------------- /data/individuals/steve/steve_crop7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/individuals/steve/steve_crop7.jpg -------------------------------------------------------------------------------- /data/raw/b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/raw/b.png -------------------------------------------------------------------------------- /data/raw/d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/raw/d.png -------------------------------------------------------------------------------- /data/raw/l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/raw/l.png -------------------------------------------------------------------------------- /data/raw/s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/data/raw/s.png -------------------------------------------------------------------------------- /doc/images/ocvf_example_florian.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/doc/images/ocvf_example_florian.jpg -------------------------------------------------------------------------------- /doc/images/ocvf_example_norman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warp1337/opencv_facerecognizer/49563d61c0dc08684ccd09d671487a50f818eec9/doc/images/ocvf_example_norman.png -------------------------------------------------------------------------------- /ros_cam_node/ros_cam_node.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /rsb_video_sender/rsb_video_sender.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -eq 0 ]; then 4 | echo "No arguments supplied: using /rsbopencv/ipl as output scope" 5 | rsb_videosender -o /rsbopencv/ipl 6 | else 7 | rsb_videosender -o $1 8 | fi 9 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'flier' 2 | -------------------------------------------------------------------------------- /src/bin/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'flier' 2 | -------------------------------------------------------------------------------- /src/bin/ocvf_image_publisher_ros.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | 36 | # STD Imports 37 | import os 38 | import cv2 39 | import sys 40 | import time 41 | import glob 42 | from Queue import Queue 43 | 44 | # ROS Imports 45 | import rospy 46 | from sensor_msgs.msg import Image 47 | from cv_bridge import CvBridge, CvBridgeError 48 | 49 | 50 | class StaticImageSender(): 51 | def __init__(self): 52 | self.image_q = None 53 | self.bridge = CvBridge() 54 | self.images = [] 55 | 56 | def glob_images(self, image_path): 57 | img_list = glob.glob(image_path) 58 | if len(img_list) <= 0: 59 | print '>> No Images Found in ' + image_path 60 | return 61 | self.image_q = Queue(len(img_list)) 62 | for img in img_list: 63 | img = cv2.imread(img, 1) 64 | self.image_q.put(img) 65 | self.images.append(img) 66 | 67 | def sender(self): 68 | pub = rospy.Publisher('ocvfacerec/static_image', Image, queue_size=4) 69 | rospy.init_node('ocvf_static_image_sender', anonymous=False) 70 | c = 0 71 | rate = rospy.Rate(0.5) # Every Two Seconds 72 | while not rospy.is_shutdown(): 73 | pub.publish(self.bridge.cv2_to_imgmsg(self.images[c], "bgr8")) 74 | time.sleep(2) 75 | print ">> Image " + str(c) 76 | c += 1 77 | if c > 3: 78 | c = 0 79 | 80 | if __name__ == '__main__': 81 | if len(sys.argv) < 2: 82 | print ">> Please provide a path to images" 83 | sys.exit(1) 84 | try: 85 | sim = StaticImageSender() 86 | sim.glob_images(str(sys.argv[1] + '/*')) 87 | sim.sender() 88 | except rospy.ROSInterruptException: 89 | pass 90 | -------------------------------------------------------------------------------- /src/bin/ocvf_interactive_trainer.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # STD Imports 36 | import os 37 | import sys 38 | import cv 39 | import cv2 40 | import Image 41 | import signal 42 | import optparse 43 | import traceback 44 | import shutil 45 | import glob 46 | import time 47 | 48 | # OCVF Imports 49 | from ocvfacerec.helper.common import * 50 | from ocvfacerec.trainer.thetrainer import TheTrainer 51 | 52 | 53 | class Trainer(object): 54 | 55 | tmp_storage = "/tmp/ocvf_retraining" 56 | 57 | def __init__(self, _options, _middelware_connector): 58 | self.counter = 0 59 | self.middleware = _middelware_connector 60 | self.image_source = _options.image_source 61 | self.mugshot_size = _options.mugshot_size 62 | self.retrain_source = _options.retrain_source 63 | self.restart_target = _options.restart_target 64 | self.restart_recgonizer = _options.restart_target 65 | self.middleware_type = _options.middleware_type 66 | self.training_data_path = _options.training_data_path 67 | self.training_image_number = _options.training_image_number 68 | self.cascade_filename = _options.cascade_filename 69 | try: 70 | self.image_size = (int(_options.image_size.split("x")[0]), int(_options.image_size.split("x")[1])) 71 | except Exception, e: 72 | print ">> Error: Unable to parse the given image size '%s'. Please pass it in the format [width]x[height]!" \ 73 | % _options.image_size 74 | sys.exit(1) 75 | 76 | self.model_path = _options.model_path 77 | self.abort_training = False 78 | self.doRun = True 79 | 80 | def signal_handler(signal, frame): 81 | print ">> Exiting.." 82 | self.doRun = False 83 | self.abort_training = True 84 | 85 | signal.signal(signal.SIGINT, signal_handler) 86 | 87 | def run(self): 88 | print ">> Middleware %s" % self.middleware_type.upper() 89 | print ">> Path to Training Images <-- %s " % self.training_data_path 90 | print ">> Resize Images before Training to %s " % str(self.image_size) 91 | print ">> Path to Model File <-- --> %s" % self.model_path 92 | print ">> Remote Camera Source <-- %s " % self.image_source 93 | print ">> Re-Train Command Scope/Topic <-- %s" % self.retrain_source 94 | print ">> Restart Recognizer Scope/Topic --> %s" % self.restart_recgonizer 95 | 96 | try: 97 | self.middleware.activate(self.image_source, self.retrain_source, self.restart_target) 98 | except Exception, ex: 99 | print ">> Error: Can't Activate Middleware ", ex 100 | traceback.print_exc() 101 | sys.exit(1) 102 | 103 | self.re_train() 104 | 105 | print ">> Ready!" 106 | 107 | while self.doRun: 108 | try: 109 | train_name = self.middleware.wait_for_start_training() 110 | except Exception, e: 111 | # Check every timeout seconds if we are supposed to exit 112 | continue 113 | 114 | try: 115 | # Given name == learn person and then restart classifier 116 | if train_name is not "": 117 | print ">> Training for '%s' (run %d)" % (train_name, self.counter) 118 | if self.record_images(train_name): 119 | self.re_train() 120 | self.restart_classifier() 121 | self.counter += 1 122 | else: 123 | print ">> Unable to Collect Enough Images" 124 | # Empty name == only restart classifier (manual data set change) 125 | else: 126 | print ">> Re-training and restarting only ..." 127 | self.re_train() 128 | self.restart_classifier() 129 | 130 | except Exception, e: 131 | print ">> Error: ", e 132 | traceback.print_exc() 133 | continue 134 | 135 | print ">> Deactivating Middleware" 136 | self.middleware.deactivate() 137 | 138 | def record_images(self, train_name): 139 | print ">> Recording %d Images From %s..." % (self.training_image_number, self.image_source) 140 | tmp_person_image_path = os.path.join(self.tmp_storage, train_name) 141 | cascade = cv.Load(self.cascade_filename) 142 | mkdir_p(tmp_person_image_path) 143 | num_mugshots = 0 144 | abort_threshold = 80 145 | abort_count = 0 146 | switch = False 147 | while num_mugshots < self.training_image_number and not self.abort_training and abort_count < abort_threshold: 148 | 149 | # Take every second frame to add some more variance 150 | switch = not switch 151 | if switch: 152 | input_image = self.middleware.get_image() 153 | else: 154 | continue 155 | 156 | im = Image.fromarray(cv2.cvtColor(input_image, cv2.COLOR_BGR2RGB)) 157 | cropped_image = face_crop_single_image(im, cascade) 158 | 159 | ok_shot = False 160 | if cropped_image: 161 | if cropped_image.size[0] >= self.mugshot_size and cropped_image.size[1] >= self.mugshot_size: 162 | sys.stdout.write("+") 163 | sys.stdout.flush() 164 | cropped_image.save(os.path.join(tmp_person_image_path, "%03d.jpg" % num_mugshots)) 165 | num_mugshots += 1 166 | ok_shot = True 167 | 168 | if ok_shot is False: 169 | abort_count += 1 170 | sys.stdout.write("-") 171 | sys.stdout.flush() 172 | 173 | # Sleep between mug shots 174 | time.sleep(0.01 * 20) 175 | 176 | print "" 177 | if abort_count >= abort_threshold: 178 | return False 179 | else: 180 | person_image_path = os.path.join(self.training_data_path, train_name) 181 | try: 182 | shutil.rmtree(person_image_path) 183 | except Exception as e: 184 | pass 185 | mkdir_p(person_image_path) 186 | for filename in glob.glob(os.path.join(tmp_person_image_path, '*.*')): 187 | shutil.copy(filename, person_image_path) 188 | return True 189 | 190 | def re_train(self): 191 | print ">> Training is running.." 192 | walk_result = [x[0] for x in os.walk(self.training_data_path)][1:] 193 | if len(walk_result) > 0: 194 | print ">> Persons Available for Re-Training ", ", ".join([x.split("/")[-1] for x in walk_result]) 195 | else: 196 | print ">> No Persons Found for Re-Training" 197 | return 198 | 199 | trainer = TheTrainer(self.training_data_path, self.image_size, self.model_path, _numfolds=options.numfolds) 200 | 201 | [images, labels, subject_names] = trainer.read_images(self.training_data_path, self.image_size) 202 | 203 | if len(labels) == 0: 204 | self.doRun = False 205 | raise Exception(">> No Images in Folder %s" % self.training_data_path) 206 | else: 207 | trainer.train() 208 | 209 | def restart_classifier(self): 210 | print ">> Restarting Recognizer" 211 | self.middleware.restart_classifier() 212 | 213 | 214 | if __name__ == '__main__': 215 | usage = ">> Usage: %prog [options] model_filename" 216 | # Add options for training, resizing, validation: 217 | parser = optparse.OptionParser(usage=usage) 218 | group_mw = optparse.OptionGroup(parser, 'Middleware Options') 219 | group_algorithm = optparse.OptionGroup(parser, 'Algorithm Options') 220 | group_io = optparse.OptionGroup(parser, 'IO Options') 221 | 222 | group_mw.add_option("-w", "--middleware", action="store", dest="middleware_type", type="string", default="rsb", 223 | help="Type of middleware to use. Currently supported: 'rsb' and 'ros' (default: %default).") 224 | group_mw.add_option("-s", "--image-source", action="store", 225 | dest="image_source", default="/rsbopencv/ipl", 226 | help="Source Topic [RSB] or Scope [ROS] of video images (default: %default).") 227 | group_mw.add_option("-e", "--re-train-source", action="store", 228 | dest="retrain_source", default="/ocvfacerec/trainer/retrainperson", 229 | help="Source (topic/scope) from which to get a re-train message (String, name of the person) (default: %default).") 230 | group_mw.add_option("-p", "--restart-target", action="store", 231 | dest="restart_target", default="/ocvfacerec/restart", 232 | help="Target (topic/scope) to where a simple restart message is sent (String 'restart') (default: %default).") 233 | 234 | group_io.add_option("-m", "--model-path", action="store", dest="model_path", default="/tmp/model.pkl", 235 | help="Storage path for the model file (default: %default).") 236 | group_io.add_option("-t", "--training-path", action="store", 237 | dest="training_data_path", default="/tmp/training_data", 238 | help="Storage path for the training data files (default: %default).") 239 | 240 | group_algorithm.add_option("-n", "--training-images", action="store", 241 | dest="training_image_number", type="int", default=70, 242 | help="Number of images to use for training of a new person(default: %default).") 243 | group_algorithm.add_option("-r", "--resize", action="store", type="string", dest="image_size", default="70x70", 244 | help="Resizes the given and new dataset(s) to a given size in format [width]x[height] (default: %default).") 245 | group_algorithm.add_option("-v", "--validate", action="store", dest="numfolds", type="int", default=None, 246 | help="Performs a k-fold cross validation on the dataset, if given (default: %default).") 247 | group_algorithm.add_option("-c", "--cascade", action="store", dest="cascade_filename", 248 | help="Sets the path to the HaarCascade file used for the face detection algorithm [haarcascade_frontalface_alt2.xml].") 249 | group_algorithm.add_option("-l", "--mugshot-size", action="store", type="int", dest="mugshot_size", default=100, 250 | help="Sets minimal size (in pixels) required for a mugshot of a person in order to use it for training (default: %default).") 251 | 252 | parser.add_option_group(group_mw) 253 | parser.add_option_group(group_io) 254 | parser.add_option_group(group_algorithm) 255 | (options, args) = parser.parse_args() 256 | 257 | try: 258 | mkdir_p(os.path.dirname(os.path.abspath(options.model_path))) 259 | mkdir_p(os.path.dirname(os.path.abspath(options.training_data_path))) 260 | except Exception as e: 261 | print ">> Error: ", e 262 | sys.exit(1) 263 | 264 | if options.middleware_type == "rsb": 265 | from ocvfacerec.mwconnector.rsbconnector import RSBConnector 266 | 267 | Trainer(options, RSBConnector()).run() 268 | elif options.middleware_type == "ros": 269 | from ocvfacerec.mwconnector.rosconnector import ROSConnector 270 | 271 | Trainer(options, ROSConnector()).run() 272 | else: 273 | print ">> Error: Middleware %s not supported" % options.middleware_type 274 | sys.exit(1) 275 | -------------------------------------------------------------------------------- /src/bin/ocvf_recognizer.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import os 36 | import cv2 37 | import sys 38 | from ocvfacerec.helper.video import * 39 | from ocvfacerec.helper.common import * 40 | from ocvfacerec.trainer.thetrainer import TheTrainer 41 | from ocvfacerec.facerec.serialization import load_model 42 | from ocvfacerec.facedet.detector import CascadedDetector 43 | from ocvfacerec.trainer.thetrainer import ExtendedPredictableModel 44 | 45 | 46 | class Recognizer(object): 47 | def __init__(self, model, camera_id, cascade_filename, run_local, wait=50): 48 | self.model = model 49 | self.wait = wait 50 | self.detector = CascadedDetector(cascade_fn=cascade_filename, minNeighbors=5, scaleFactor=1.1) 51 | if run_local: 52 | self.cam = create_capture(camera_id) 53 | 54 | def run(self): 55 | while True: 56 | ret, frame = self.cam.read() 57 | # Resize the frame to half the original size for speeding up the detection process: 58 | # img = cv2.resize(frame, (frame.shape[1] / 2, frame.shape[0] / 2), interpolation=cv2.INTER_CUBIC) 59 | img = cv2.resize(frame, (320, 240), interpolation=cv2.INTER_CUBIC) 60 | imgout = img.copy() 61 | for i, r in enumerate(self.detector.detect(img)): 62 | x0, y0, x1, y1 = r 63 | # (1) Get face, (2) Convert to grayscale & (3) resize to image_size: 64 | face = img[y0:y1, x0:x1] 65 | face = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) 66 | face = cv2.resize(face, self.model.image_size, interpolation=cv2.INTER_CUBIC) 67 | prediction = self.model.predict(face) 68 | predicted_label = prediction[0] 69 | classifier_output = prediction[1] 70 | # Now let's get the distance from the assuming a 1-Nearest Neighbor. 71 | # Since it's a 1-Nearest Neighbor only look take the zero-th element: 72 | distance = classifier_output['distances'][0] 73 | # Draw the face area in image: 74 | cv2.rectangle(imgout, (x0, y0), (x1, y1), (0, 0, 255), 2) 75 | # Draw the predicted name (folder name...): 76 | draw_str(imgout, (x0 - 20, y0 - 40), "Label " + self.model.subject_names[predicted_label]) 77 | draw_str(imgout, (x0 - 20, y0 - 20), "Feature Distance " + "%1.1f" % distance) 78 | cv2.imshow('OCVFACEREC < LOCAL', imgout) 79 | key = cv2.waitKey(self.wait) 80 | if 'q' == chr(key & 255): 81 | print ">> 'q' Pressed. Exiting." 82 | break 83 | 84 | 85 | if __name__ == '__main__': 86 | from optparse import OptionParser 87 | # model.pkl is a pickled (hopefully trained) PredictableModel, which is 88 | # used to make predictions. You can learn a model yourself by passing the 89 | # parameter -d (or --dataset) to learn the model from a given dataset. 90 | usage = "Usage: %prog [options] model_filename" 91 | # Add options for training, resizing, validation and setting the camera id: 92 | parser = OptionParser(usage=usage) 93 | parser.add_option("-r", "--resize", action="store", type="string", dest="size", default="70x70", 94 | help="Resizes the given dataset to a given size in format [width]x[height] (default: 70x70).") 95 | parser.add_option("-v", "--validate", action="store", dest="numfolds", type="int", default=None, 96 | help="Performs a k-fold cross validation on the dataset, if given (default: None).") 97 | parser.add_option("-t", "--train", action="store", dest="dataset", type="string", default=None, 98 | help="Trains the model on the given dataset.") 99 | parser.add_option("-i", "--id", action="store", dest="camera_id", type="int", default=0, 100 | help="Sets the Camera Id to be used (default: 0).") 101 | parser.add_option("-c", "--cascade", action="store", dest="cascade_filename", 102 | default="haarcascade_frontalface_alt2.xml", 103 | help="Sets the path to the Haar Cascade used for the face detection part (default: haarcascade_frontalface_alt2.xml).") 104 | parser.add_option("-w", "--wait", action="store", dest="wait_time", default=20, type="int", 105 | help="Amount of time (in ms) to sleep between face identifaction runs (frames). Default is 20 ms") 106 | (options, args) = parser.parse_args() 107 | print "\n" 108 | # Check if a model name was passed: 109 | if len(args) == 0: 110 | print ">> Error: No prediction model was given." 111 | sys.exit(1) 112 | # This model will be used (or created if the training parameter (-t, --train) exists: 113 | model_filename = args[0] 114 | # Check if the given model exists, if no dataset was passed: 115 | if (options.dataset is None) and (not os.path.exists(model_filename)): 116 | print ">> Error: No prediction model found at '%s'." % model_filename 117 | sys.exit(1) 118 | # Check if the given (or default) cascade file exists: 119 | if not os.path.exists(options.cascade_filename): 120 | print ">> Error: No Cascade File found at '%s'." % options.cascade_filename 121 | sys.exit(1) 122 | # We are resizing the images to a fixed size, as this is neccessary for some of 123 | # the algorithms, some algorithms like LBPH don't have this requirement. To 124 | # prevent problems from popping up, we resize them with a default value if none 125 | # was given: 126 | try: 127 | image_size = (int(options.size.split("x")[0]), int(options.size.split("x")[1])) 128 | except Exception, e: 129 | print ">> Error: Unable to parse the given image size '%s'. Please pass it in the format [width]x[height]!" % options.size 130 | sys.exit(1) 131 | # We have got a dataset to learn a new model from: 132 | if options.dataset: 133 | # Check if the given dataset exists: 134 | if not os.path.exists(options.dataset): 135 | print ">> Error: No dataset found at '%s'." % options.dataset 136 | sys.exit(1) 137 | # Reads the images, labels and folder_names from a given dataset. Images 138 | 139 | trainer = TheTrainer(options.dataset, image_size, model_filename, _numfolds=options.numfolds) 140 | trainer.train() 141 | 142 | print ">> Loading model " + str(model_filename) 143 | model = load_model(model_filename) 144 | # We operate on an ExtendedPredictableModel. Quit the Recognizerlication if this 145 | # isn't what we expect it to be: 146 | if not isinstance(model, ExtendedPredictableModel): 147 | print ">> Error: The given model is not of type '%s'." % "ExtendedPredictableModel" 148 | sys.exit(1) 149 | # Now it's time to finally start the Recognizerlication! It simply get's the model 150 | # and the image size the incoming webcam or video images are resized to: 151 | print ">> Using Local Camera <-- " + "/dev/video" + str(options.camera_id) 152 | Recognizer(model=model, camera_id=options.camera_id, cascade_filename=options.cascade_filename, run_local=True, 153 | wait=options.wait_time).run() 154 | 155 | -------------------------------------------------------------------------------- /src/bin/ocvf_recognizer_ros.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # STD IMPORTS 36 | import os 37 | import cv2 38 | import sys 39 | import time 40 | import rospy 41 | import roslib 42 | import signal 43 | from optparse import OptionParser 44 | from thread import start_new_thread 45 | 46 | 47 | # ROS IMPORTS 48 | from cv_bridge import CvBridge 49 | from std_msgs.msg import String 50 | from std_msgs.msg import Header 51 | from sensor_msgs.msg import Image 52 | from people_msgs.msg import People 53 | from people_msgs.msg import Person 54 | from geometry_msgs.msg import Point 55 | 56 | # LOCAL IMPORTS 57 | from ocvfacerec.helper.common import * 58 | from ocvfacerec.trainer.thetrainer import TheTrainer 59 | from ocvfacerec.facerec.serialization import load_model 60 | from ocvfacerec.facedet.detector import CascadedDetector 61 | from ocvfacerec.trainer.thetrainer import ExtendedPredictableModel 62 | 63 | 64 | class RosPeople: 65 | def __init__(self): 66 | self.publisher = rospy.Publisher('ocvfacerec/ros/people', People, queue_size=1) 67 | rospy.init_node('ocvfacerec_people_publisher', anonymous=True) 68 | 69 | 70 | def ros_spinning(message="None"): 71 | print ">> ROS is spinning()" 72 | rospy.spin() 73 | 74 | 75 | class Recognizer(object): 76 | def __init__(self, model, cascade_filename, run_local, wait, rp): 77 | self.rp = rp 78 | self.wait = wait 79 | self.doRun = True 80 | self.model = model 81 | self.restart = False 82 | self.ros_restart_request = False 83 | self.detector = CascadedDetector(cascade_fn=cascade_filename, minNeighbors=5, scaleFactor=1.1) 84 | if run_local: 85 | print ">> Error: Run local selected in ROS based Recognizer" 86 | sys.exit(1) 87 | else: 88 | self.bridge = CvBridge() 89 | 90 | def signal_handler(signal, frame): 91 | print ">> ROS Exiting" 92 | self.doRun = False 93 | 94 | signal.signal(signal.SIGINT, signal_handler) 95 | 96 | def image_callback(self, ros_data): 97 | try: 98 | cv_image = self.bridge.imgmsg_to_cv2(ros_data, "bgr8") 99 | except Exception, ex: 100 | print ex 101 | return 102 | # Resize the frame to half the original size for speeding up the detection process. 103 | # In ROS we can control the size, so we are sending a 320*240 image by default. 104 | img = cv2.resize(cv_image, (320, 240), interpolation=cv2.INTER_CUBIC) 105 | # img = cv2.resize(cv_image, (cv_image.shape[1] / 2, cv_image.shape[0] / 2), interpolation=cv2.INTER_CUBIC) 106 | # img = cv_image 107 | imgout = img.copy() 108 | # Remember the Persons found in current image 109 | persons = [] 110 | for _i, r in enumerate(self.detector.detect(img)): 111 | x0, y0, x1, y1 = r 112 | # (1) Get face, (2) Convert to grayscale & (3) resize to image_size: 113 | face = img[y0:y1, x0:x1] 114 | face = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) 115 | face = cv2.resize(face, self.model.image_size, interpolation=cv2.INTER_CUBIC) 116 | prediction = self.model.predict(face) 117 | predicted_label = prediction[0] 118 | classifier_output = prediction[1] 119 | # Now let's get the distance from the assuming a 1-Nearest Neighbor. 120 | # Since it's a 1-Nearest Neighbor only look take the zero-th element: 121 | distance = classifier_output['distances'][0] 122 | # Draw the face area in image: 123 | cv2.rectangle(imgout, (x0, y0), (x1, y1), (0, 0, 255), 2) 124 | # Draw the predicted name (folder name...): 125 | draw_str(imgout, (x0 - 20, y0 - 40), "Label " + self.model.subject_names[predicted_label]) 126 | draw_str(imgout, (x0 - 20, y0 - 20), "Feature Distance " + "%1.1f" % distance) 127 | msg = Person() 128 | point = Point() 129 | # Send the center of the person's bounding box 130 | mid_x = float(x1 + (x1 - x0) * 0.5) 131 | mid_y = float(y1 + (y1 - y0) * 0.5) 132 | point.x = mid_x 133 | point.y = mid_y 134 | # Z is "mis-used" to represent the size of the bounding box 135 | point.z = x1 - x0 136 | msg.position = point 137 | msg.name = str(self.model.subject_names[predicted_label]) 138 | msg.reliability = float(distance) 139 | persons.append(msg) 140 | if len(persons) > 0: 141 | h = Header() 142 | h.stamp = rospy.Time.now() 143 | h.frame_id = '/ros_cam' 144 | msg = People() 145 | msg.header = h 146 | for p in persons: 147 | msg.people.append(p) 148 | self.rp.publisher.publish(msg) 149 | cv2.imshow('OCVFACEREC < ROS STREAM', imgout) 150 | cv2.waitKey(self.wait) 151 | 152 | try: 153 | z = self.ros_restart_request 154 | if z: 155 | self.restart = True 156 | self.doRun = False 157 | except Exception, e: 158 | pass 159 | 160 | def restart_callback(self, ros_data): 161 | print ">> Received Restart Request" 162 | if "restart" in str(ros_data): 163 | self.ros_restart_request = True 164 | 165 | def run_distributed(self, image_topic, restart_topic): 166 | print ">> Activating ROS Subscriber" 167 | image_subscriber = rospy.Subscriber(image_topic, Image, self.image_callback, queue_size=1) 168 | restart_subscriber = rospy.Subscriber(restart_topic, String, self.restart_callback, queue_size=1) 169 | # print ">> Recognizer is running" 170 | while self.doRun: 171 | time.sleep(0.01) 172 | pass 173 | # Important: You need to unregister before restarting! 174 | image_subscriber.unregister() 175 | restart_subscriber.unregister() 176 | print ">> Deactivating ROS Subscriber" 177 | 178 | 179 | if __name__ == '__main__': 180 | # model.pkl is a pickled (hopefully trained) PredictableModel, which is 181 | # used to make predictions. You can learn a model yourself by passing the 182 | # parameter -d (or --dataset) to learn the model from a given dataset. 183 | usage = "Usage: %prog [options] model_filename" 184 | # Add options for training, resizing, validation and setting the camera id: 185 | parser = OptionParser(usage=usage) 186 | parser.add_option("-r", "--resize", action="store", type="string", dest="size", default="70x70", 187 | help="Resizes the given dataset to a given size in format [width]x[height] (default: 70x70).") 188 | parser.add_option("-v", "--validate", action="store", dest="numfolds", type="int", default=None, 189 | help="Performs a k-fold cross validation on the dataset, if given (default: None).") 190 | parser.add_option("-t", "--train", action="store", dest="dataset", type="string", default=None, 191 | help="Trains the model on the given dataset.") 192 | parser.add_option("-c", "--cascade", action="store", dest="cascade_filename", 193 | help="Sets the path to the Haar Cascade used for the face detection part [haarcascade_frontalface_alt2.xml].") 194 | parser.add_option("-n", "--restart-notification", action="store", dest="restart_notification", 195 | default="/ocvfacerec/restart", 196 | help="Target Scope where a simple restart message is received (default: %default).") 197 | parser.add_option("-s", "--ros-source", action="store", dest="ros_source", help="Grab video from ROS Middleware (default: %default).", 198 | default="/usb_cam/image_raw") 199 | parser.add_option("-w", "--wait", action="store", dest="wait_time", default=20, type="int", 200 | help="Amount of time (in ms) to sleep between face identification frames (default: %default).") 201 | (options, args) = parser.parse_args() 202 | # Check if a model name was passed: 203 | if options.ros_source is None: 204 | print ">> Error: No ROS Topic provided use i.e. --ros-source=/usb_cam/image_raw" 205 | sys.exit(1) 206 | if len(args) == 0: 207 | print ">> Error: No prediction model was given." 208 | sys.exit(1) 209 | # This model will be used (or created if the training parameter (-t, --train) exists: 210 | model_filename = args[0] 211 | # Check if the given model exists, if no dataset was passed: 212 | if (options.dataset is None) and (not os.path.exists(model_filename)): 213 | print ">> Error: No prediction model found at '%s'." % model_filename 214 | sys.exit(1) 215 | # Check if the given (or default) cascade file exists: 216 | if not os.path.exists(options.cascade_filename): 217 | print ">> Error: No Cascade File found at '%s'." % options.cascade_filename 218 | sys.exit(1) 219 | # We are resizing the images to a fixed size, as this is neccessary for some of 220 | # the algorithms, some algorithms like LBPH don't have this requirement. To 221 | # prevent problems from popping up, we resize them with a default value if none 222 | # was given: 223 | try: 224 | image_size = (int(options.size.split("x")[0]), int(options.size.split("x")[1])) 225 | except Exception, e: 226 | print ">> Error: Unable to parse the given image size '%s'. Please pass it in the format [width]x[height]!" % options.size 227 | sys.exit(1) 228 | # We got a dataset to learn a new model from: 229 | if options.dataset: 230 | trainer = TheTrainer(options.dataset, image_size, model_filename, _numfolds=options.numfolds) 231 | trainer.train() 232 | 233 | print ">> Loading Model <-- " + str(model_filename) 234 | model = load_model(model_filename) 235 | # We operate on an ExtendedPredictableModel. 236 | if not isinstance(model, ExtendedPredictableModel): 237 | print ">> Error: The given model is not of type '%s'." % "ExtendedPredictableModel" 238 | sys.exit(1) 239 | 240 | # Now it's time to finally start the Recognizerlication! It simply get's the model 241 | # and the image size the incoming webcam or video images are resized to: 242 | print ">> ROS Camera Input Stream <-- " + str(options.ros_source) 243 | print ">> Publishing People Info --> /ocvfacerec/ros/people" 244 | print ">> Restart Recognizer Scope <-- " + str(options.restart_notification) 245 | # Init ROS People Publisher 246 | rp = RosPeople() 247 | start_new_thread(ros_spinning, ("None",)) 248 | x = Recognizer(model=model, cascade_filename=options.cascade_filename, run_local=False, 249 | wait=options.wait_time, rp=rp) 250 | x.run_distributed(str(options.ros_source), str(options.restart_notification)) 251 | while x.restart: 252 | time.sleep(1) 253 | model = load_model(model_filename) 254 | x = Recognizer(model=model, cascade_filename=options.cascade_filename, run_local=False, 255 | wait=options.wait_time, rp=rp) 256 | x.run_distributed(str(options.ros_source), str(options.restart_notification)) -------------------------------------------------------------------------------- /src/bin/ocvf_recognizer_rsb.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # STD Imports 36 | from Queue import Queue 37 | import cv2 38 | from optparse import OptionParser 39 | import os 40 | import signal 41 | import sys 42 | import time 43 | 44 | import rsb 45 | from rstconverters.opencv import IplimageConverter 46 | from rstsandbox.vision.HeadObjects_pb2 import HeadObjects 47 | 48 | import numpy as np 49 | from ocvfacerec.facedet.detector import CascadedDetector 50 | from ocvfacerec.facerec.serialization import load_model 51 | from ocvfacerec.helper.PersonWrapper import PersonWrapper 52 | from ocvfacerec.helper.common import * 53 | from ocvfacerec.trainer.thetrainer import ExtendedPredictableModel 54 | from ocvfacerec.trainer.thetrainer import TheTrainer 55 | from rst.geometry.PointCloud2DInt_pb2 import PointCloud2DInt 56 | 57 | 58 | # OCVF Imports 59 | # RSB Specifics 60 | class Recognizer(object): 61 | 62 | def __init__(self, model, camera_id, cascade_filename, run_local, inscope="/rsbopencv/ipl", 63 | outscope="/ocvfacerec/rsb/people", wait=50, notification="/ocvfacerec/rsb/restart/", 64 | show_gui=False): 65 | self.model = model 66 | self.wait = wait 67 | self.notification_scope = notification 68 | self.show_gui = show_gui 69 | self.detector = CascadedDetector(cascade_fn=cascade_filename, minNeighbors=5, scaleFactor=1.1) 70 | 71 | if run_local: 72 | print ">> Error Run local selected in RSB based Recognizer" 73 | sys.exit(1) 74 | 75 | self.doRun = True 76 | self.restart = False 77 | 78 | def signal_handler(signal, frame): 79 | print "\n>> RSB Exiting" 80 | self.doRun = False 81 | 82 | signal.signal(signal.SIGINT, signal_handler) 83 | 84 | # RSB 85 | try: 86 | rsb.converter.registerGlobalConverter(IplimageConverter()) 87 | except Exception, e: 88 | # If they are already loaded, the lib throws an exception ... 89 | # print ">> [Error] While loading RST converter: ", e 90 | pass 91 | 92 | self.listener = rsb.createListener(inscope) 93 | self.lastImage = Queue(1) 94 | 95 | self.restart_listener = rsb.createListener(self.notification_scope) 96 | self.last_restart_request = Queue(1) 97 | 98 | try: 99 | rsb.converter.registerGlobalConverter(rsb.converter.ProtocolBufferConverter(messageClass=HeadObjects)) 100 | except Exception, e: 101 | # If they are already loaded, the lib throws an exception ... 102 | # print ">> [Error] While loading RST converter: ", e 103 | pass 104 | 105 | self.person_publisher = rsb.createInformer(outscope, dataType=HeadObjects) 106 | 107 | # This must be set at last! 108 | rsb.setDefaultParticipantConfig(rsb.ParticipantConfig.fromDefaultSources()) 109 | 110 | def add_last_image(self, image_event): 111 | try: 112 | self.lastImage.get(False) 113 | except Exception, e: 114 | pass 115 | self.lastImage.put((np.asarray(image_event.data[:, :]), image_event.getId()), False) 116 | 117 | def add_restart_request(self, restart_event): 118 | try: 119 | self.last_restart_request.get(False) 120 | except Exception, e: 121 | pass 122 | self.last_restart_request.put(restart_event.data, False) 123 | 124 | def publish_persons(self, persons, cause_uuid): 125 | # Gather the information of every head 126 | rsb_person_list = HeadObjects() 127 | for a_person in persons: 128 | rsb_person_list.head_objects.extend([a_person.to_rsb_msg()]) 129 | 130 | # Create the event and add the cause. Maybe some day someone will use 131 | # this reference to the cause :) 132 | event = rsb.Event(scope=self.person_publisher.getScope(), 133 | data=rsb_person_list, 134 | type=type(rsb_person_list), 135 | causes=[cause_uuid]) 136 | 137 | # Publish the data 138 | self.person_publisher.publishEvent(event) 139 | 140 | def run_distributed(self): 141 | print ">> Activating RSB Listener" 142 | self.listener.addHandler(self.add_last_image) 143 | self.restart_listener.addHandler(self.add_restart_request) 144 | # TODO # TODO Implement Result Informer (ClassificationResult) 145 | while self.doRun: 146 | # GetLastImage is blocking so we won't get a "None" Image 147 | image, cause_uuid = self.lastImage.get(True) 148 | # This should not be resized with a fixed rate, this should be rather configured by the sender 149 | # i.e. by sending smaller images. Don't fiddle with input data in two places. 150 | # img = cv2.resize(image, (image.shape[1] / 2, image.shape[0] / 2), interpolation=cv2.INTER_CUBIC) 151 | image_size = (320, 240) 152 | img = cv2.resize(image, image_size, interpolation=cv2.INTER_CUBIC) 153 | imgout = img.copy() 154 | 155 | persons = [] 156 | for i, r in enumerate(self.detector.detect(img)): 157 | x0, y0, x1, y1 = r 158 | # (1) Get face, (2) Convert to grayscale & (3) resize to image_size: 159 | face = img[y0:y1, x0:x1] 160 | face = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) 161 | face = cv2.resize(face, self.model.image_size, interpolation=cv2.INTER_CUBIC) 162 | 163 | # The prediction result 164 | prediction = self.model.predict(face) 165 | predicted_label = prediction[0] 166 | classifier_output = prediction[1] 167 | 168 | # Now let's get the distance from the assuming a 1-Nearest Neighbor. 169 | # Since it's a 1-Nearest Neighbor only look take the zero-th element: 170 | distance = float(classifier_output['distances'][0]) 171 | name = str(self.model.subject_names[predicted_label]) 172 | 173 | # Create a PersonWrapper 174 | a_person = PersonWrapper(r, name, distance, image_size) 175 | persons.append(a_person) 176 | 177 | # Draw the face area in image: 178 | cv2.rectangle(imgout, (x0, y0), (x1, y1), (0, 0, 255), 2) 179 | # Draw the predicted name (folder name...): 180 | draw_str(imgout, (x0 - 20, y0 - 40), "Label " + a_person.name) 181 | draw_str(imgout, (x0 - 20, y0 - 20), "Feature Distance " + "%1.1f" % a_person.reliability) 182 | 183 | if self.show_gui: 184 | cv2.imshow('OCVFACEREC < RSB STREAM', imgout) 185 | cv2.waitKey(self.wait) 186 | else: 187 | # Sleep for the desired time, less CPU 188 | time.sleep(self.wait * 0.01) 189 | 190 | if len(persons) > 0: 191 | # Publish the result 192 | self.publish_persons(persons, cause_uuid) 193 | 194 | # Check if external restart requested 195 | try: 196 | z = self.last_restart_request.get(False) 197 | if z: 198 | self.restart = True 199 | self.doRun = False 200 | except Exception, e: 201 | pass 202 | 203 | print ">> Deactivating RSB Listener" 204 | self.listener.deactivate() 205 | self.restart_listener.deactivate() 206 | self.person_publisher.deactivate() 207 | # informer.deactivate() 208 | 209 | 210 | if __name__ == '__main__': 211 | # model.pkl is a pickled (hopefully trained) PredictableModel, which is 212 | # used to make predictions. You can learn a model yourself by passing the 213 | # parameter -d (or --dataset) to learn the model from a given dataset. 214 | usage = "Usage: %prog [options] model_filename" 215 | # Add options for training, resizing, validation and setting the camera id: 216 | parser = OptionParser(usage=usage) 217 | parser.add_option("-r", "--resize", action="store", type="string", dest="size", default="70x70", 218 | help="Resizes the given dataset to a given size in format [width]x[height] (default: 70x70).") 219 | parser.add_option("-v", "--validate", action="store", dest="numfolds", type="int", default=None, 220 | help="Performs a k-fold cross validation on the dataset, if given (default: None).") 221 | parser.add_option("-t", "--train", action="store", dest="dataset", type="string", default=None, 222 | help="Trains the model on the given dataset.") 223 | parser.add_option("-c", "--cascade", action="store", dest="cascade_filename", 224 | help="Sets the path to the Haar Cascade used for the face detection part [haarcascade_frontalface_alt2.xml].") 225 | parser.add_option("-s", "--rsb-source", action="store", dest="rsb_source", default="/rsbopencv/ipl", 226 | help="Grab video from RSB Middleware (default: %default)") 227 | parser.add_option("-d", "--rsb-destination", action="store", dest="rsb_destination", default="/ocvfacerec/rsb/people", 228 | help="Target RSB scope to which persons are published (default: %default).") 229 | parser.add_option("-n", "--restart-notification", action="store", dest="restart_notification", 230 | default="/ocvfacerec/restart", 231 | help="Target Topic where a simple restart message is received (default: %default).") 232 | parser.add_option("-w", "--wait", action="store", dest="wait_time", default=20, type="int", 233 | help="Amount of time (in ms) to sleep between face identification frames (default: %default).") 234 | parser.add_option("-g", "--no-gui", dest="show_gui", action='store_false', default=True, 235 | help="Hides the GUI elements for headless mode (default: show gui).") 236 | (options, args) = parser.parse_args() 237 | print "\n" 238 | # Check if a model name was passed: 239 | if len(args) == 0: 240 | print ">> [Error] No Prediction Model was given." 241 | sys.exit(1) 242 | # This model will be used (or created if the training parameter (-t, --train) exists: 243 | model_filename = args[0] 244 | # Check if the given model exists, if no dataset was passed: 245 | if (options.dataset is None) and (not os.path.exists(model_filename)): 246 | print ">> [Error] No Prediction Model found at '%s'." % model_filename 247 | sys.exit(1) 248 | # Check if the given (or default) cascade file exists: 249 | if not os.path.exists(options.cascade_filename): 250 | print ">> [Error] No Cascade File found at '%s'." % options.cascade_filename 251 | sys.exit(1) 252 | # We are resizing the images to a fixed size, as this is neccessary for some of 253 | # the algorithms, some algorithms like LBPH don't have this requirement. To 254 | # prevent problems from popping up, we resize them with a default value if none 255 | # was given: 256 | try: 257 | image_size = (int(options.size.split("x")[0]), int(options.size.split("x")[1])) 258 | except Exception, e: 259 | print ">> [Error] Unable to parse the given image size '%s'. Please pass it in the format [width]x[height]!" % options.size 260 | sys.exit(1) 261 | 262 | if options.dataset: 263 | trainer = TheTrainer(options.dataset, image_size, model_filename, _numfolds=options.numfolds) 264 | trainer.train() 265 | 266 | print ">> Loading model <-- " + str(model_filename) 267 | model = load_model(model_filename) 268 | print ">> Known Persons --> ", ", ".join(model.subject_names.values()) 269 | if not isinstance(model, ExtendedPredictableModel): 270 | print ">> [Error] The given model is not of type '%s'." % "ExtendedPredictableModel" 271 | sys.exit(1) 272 | print ">> Using Remote RSB Camera Stream <-- " + str(options.rsb_source) 273 | print ">> Publishing regognised people to --> " + str(options.rsb_destination) 274 | print ">> Restart Recognizer Scope <-- " + str(options.restart_notification) 275 | x = Recognizer(model=model, camera_id=None, cascade_filename=options.cascade_filename, run_local=False, 276 | inscope=options.rsb_source, outscope=str(options.rsb_destination), wait=options.wait_time, 277 | notification=options.restart_notification, show_gui=options.show_gui) 278 | x.run_distributed() 279 | while x.restart: 280 | time.sleep(1) 281 | model = load_model(model_filename) 282 | print ">> Known Persons --> ", ", ".join(model.subject_names.values()) 283 | x = Recognizer(model=model, camera_id=None, cascade_filename=options.cascade_filename, run_local=False, 284 | inscope=options.rsb_source, outscope=str(options.rsb_destination), wait=options.wait_time, 285 | notification=options.restart_notification, show_gui=options.show_gui) 286 | x.run_distributed() 287 | -------------------------------------------------------------------------------- /src/bin/ocvf_retrain_ros.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # STD Imports 36 | import time 37 | import rospy 38 | import optparse 39 | 40 | # ROS Imports 41 | from std_msgs.msg import String 42 | 43 | 44 | def restart(topic, name): 45 | pub = rospy.Publisher(topic, String, queue_size=1) 46 | rospy.init_node('ros_restart', anonymous=True) 47 | msg = name 48 | pub.publish(msg) 49 | print ">> Sent %s to Topic %s" % (msg, topic) 50 | 51 | 52 | if __name__ == '__main__': 53 | usage = ">> Usage: %prog [options] " 54 | parser = optparse.OptionParser(usage=usage) 55 | parser.add_option("-t", "--topic", action="store", type="string", dest="topic", 56 | default="/ocvfacerec/trainer/retrainperson", 57 | help="Send a Re-Train Command to this Scope") 58 | (options, args) = parser.parse_args() 59 | if len(args) < 1: 60 | print "[WARNING]: You did not provide a person_label - will only retrain and restart the classifier!" 61 | args.append("") 62 | try: 63 | restart(options.topic, str(args[0])) 64 | time.sleep(1) 65 | except rospy.ROSInterruptException, ex: 66 | print ex 67 | -------------------------------------------------------------------------------- /src/bin/ocvf_retrain_rsb.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import rsb 36 | import logging 37 | import optparse 38 | 39 | if __name__ == '__main__': 40 | usage = "Usage: %prog [options] " 41 | parser = optparse.OptionParser(usage=usage) 42 | parser.add_option("-s", "--scope", action="store", type="string", dest="scope", 43 | default="/ocvfacerec/trainer/retrainperson", 44 | help="Send a Re-Train Command to this Scope") 45 | (options, args) = parser.parse_args() 46 | # Pacify logger. 47 | logging.basicConfig() 48 | if len(args) < 1: 49 | print "Warning: You did not provide a person_label - will only retrain and restart the classifier!" 50 | args.append("") 51 | 52 | with rsb.createInformer(options.scope, dataType=str) as informer: 53 | print "Publishing name '%s' to scope '%s'" % (str(args[0]), options.scope) 54 | informer.publishData(str(args[0])) 55 | -------------------------------------------------------------------------------- /src/ocvfacerec/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/ocvfacerec/facedet/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/ocvfacerec/facedet/detector.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import sys 36 | import os 37 | import cv2 38 | import numpy as np 39 | 40 | 41 | class Detector: 42 | def __init__(self): 43 | pass 44 | 45 | def detect(self, src): 46 | raise NotImplementedError("Every Detector must implement the detect method.") 47 | 48 | 49 | class SkinDetector(Detector): 50 | """ 51 | Implements common color thresholding rules for the RGB, YCrCb and HSV color 52 | space. The values are taken from a paper, which I can't find right now, so 53 | be careful with this detector. 54 | 55 | """ 56 | 57 | def _R1(self, BGR): 58 | # channels 59 | B = BGR[:, :, 0] 60 | G = BGR[:, :, 1] 61 | R = BGR[:, :, 2] 62 | e1 = (R > 95) & (G > 40) & (B > 20) & ( 63 | (np.maximum(R, np.maximum(G, B)) - np.minimum(R, np.minimum(G, B))) > 15) & (np.abs(R - G) > 15) & ( 64 | R > G) & ( 65 | R > B) 66 | e2 = (R > 220) & (G > 210) & (B > 170) & (abs(R - G) <= 15) & (R > B) & (G > B) 67 | return (e1 | e2) 68 | 69 | def _R2(self, YCrCb): 70 | Y = YCrCb[:, :, 0] 71 | Cr = YCrCb[:, :, 1] 72 | Cb = YCrCb[:, :, 2] 73 | e1 = Cr <= (1.5862 * Cb + 20) 74 | e2 = Cr >= (0.3448 * Cb + 76.2069) 75 | e3 = Cr >= (-4.5652 * Cb + 234.5652) 76 | e4 = Cr <= (-1.15 * Cb + 301.75) 77 | e5 = Cr <= (-2.2857 * Cb + 432.85) 78 | return e1 & e2 & e3 & e4 & e5 79 | 80 | def _R3(self, HSV): 81 | H = HSV[:, :, 0] 82 | S = HSV[:, :, 1] 83 | V = HSV[:, :, 2] 84 | return ((H < 25) | (H > 230)) 85 | 86 | def detect(self, src): 87 | if np.ndim(src) < 3: 88 | return np.ones(src.shape, dtype=np.uint8) 89 | if src.dtype != np.uint8: 90 | return np.ones(src.shape, dtype=np.uint8) 91 | srcYCrCb = cv2.cvtColor(src, cv2.COLOR_BGR2YCR_CB) 92 | srcHSV = cv2.cvtColor(src, cv2.COLOR_BGR2HSV) 93 | skinPixels = self._R1(src) & self._R2(srcYCrCb) & self._R3(srcHSV) 94 | return np.asarray(skinPixels, dtype=np.uint8) 95 | 96 | 97 | class CascadedDetector(Detector): 98 | """ 99 | Uses the OpenCV cascades to perform the detection. Returns the Regions of Interest, where 100 | the detector assumes a face. You probably have to play around with the scaleFactor, 101 | minNeighbors and minSize parameters to get good results for your use case. From my 102 | personal experience, all I can say is: there's no parameter combination which *just 103 | works*. 104 | """ 105 | 106 | def __init__(self, cascade_fn="./cascades/haarcascade_frontalface_alt2.xml", scaleFactor=1.2, minNeighbors=5, 107 | minSize=(30, 30)): 108 | if not os.path.exists(cascade_fn): 109 | raise IOError("No valid cascade found for path=%s." % cascade_fn) 110 | self.cascade = cv2.CascadeClassifier(cascade_fn) 111 | self.scaleFactor = scaleFactor 112 | self.minNeighbors = minNeighbors 113 | self.minSize = minSize 114 | 115 | def detect(self, src): 116 | if np.ndim(src) == 3: 117 | src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) 118 | src = cv2.equalizeHist(src) 119 | rects = self.cascade.detectMultiScale(src, scaleFactor=self.scaleFactor, minNeighbors=self.minNeighbors, 120 | minSize=self.minSize) 121 | if len(rects) == 0: 122 | return [] 123 | rects[:, 2:] += rects[:, :2] 124 | return rects 125 | 126 | 127 | class SkinFaceDetector(Detector): 128 | """ 129 | Uses the SkinDetector to accept only faces over a given skin color tone threshold (ignored for 130 | grayscale images). Be careful with skin color tone thresholding, as it won't work in uncontrolled 131 | scenarios (without preprocessing)! 132 | 133 | """ 134 | 135 | def __init__(self, threshold=0.3, cascade_fn="./cascades/haarcascade_frontalface_alt2.xml", scaleFactor=1.2, 136 | minNeighbors=5, minSize=(30, 30)): 137 | self.faceDetector = CascadedDetector(cascade_fn=cascade_fn, scaleFactor=scaleFactor, minNeighbors=minNeighbors, 138 | minSize=minSize) 139 | self.skinDetector = SkinDetector() 140 | self.threshold = threshold 141 | 142 | def detect(self, src): 143 | rects = [] 144 | for i, r in enumerate(self.faceDetector.detect(src)): 145 | x0, y0, x1, y1 = r 146 | face = src[y0:y1, x0:x1] 147 | skinPixels = self.skinDetector.detect(face) 148 | skinPercentage = float(np.sum(skinPixels)) / skinPixels.size 149 | print skinPercentage 150 | if skinPercentage > self.threshold: 151 | rects.append(r) 152 | return rects 153 | 154 | 155 | if __name__ == "__main__": 156 | # script parameters 157 | if len(sys.argv) < 2: 158 | raise Exception("No image given.") 159 | inFileName = sys.argv[1] 160 | outFileName = None 161 | if len(sys.argv) > 2: 162 | outFileName = sys.argv[2] 163 | if outFileName == inFileName: 164 | outFileName = None 165 | # detection begins here 166 | img = np.array(cv2.imread(inFileName), dtype=np.uint8) 167 | imgOut = img.copy() 168 | # set up detectors 169 | # detector = SkinFaceDetector(threshold=0.3, cascade_fn="/home/philipp/projects/opencv2/OpenCV-2.3.1/data/haarcascades/haarcascade_frontalface_alt2.xml") 170 | detector = CascadedDetector( 171 | cascade_fn="/home/philipp/projects/opencv2/OpenCV-2.3.1/data/haarcascades/haarcascade_frontalface_alt2.xml") 172 | eyesDetector = CascadedDetector(scaleFactor=1.1, minNeighbors=5, minSize=(20, 20), 173 | cascade_fn="/home/philipp/projects/opencv2/OpenCV-2.3.1/data/haarcascades/haarcascade_eye.xml") 174 | # detection 175 | for i, r in enumerate(detector.detect(img)): 176 | x0, y0, x1, y1 = r 177 | cv2.rectangle(imgOut, (x0, y0), (x1, y1), (0, 255, 0), 1) 178 | face = img[y0:y1, x0:x1] 179 | for j, r2 in enumerate(eyesDetector.detect(face)): 180 | ex0, ey0, ex1, ey1 = r2 181 | cv2.rectangle(imgOut, (x0 + ex0, y0 + ey0), (x0 + ex1, y0 + ey1), (0, 255, 0), 1) 182 | # display image or write to file 183 | if outFileName is None: 184 | cv2.imshow('faces', imgOut) 185 | cv2.waitKey(0) 186 | cv2.imwrite(outFileName, imgOut) 187 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'flier' 2 | 3 | # Copyright (c) 2015. 4 | # Philipp Wagner and 5 | # Florian Lier and 6 | # Norman Koester 7 | # 8 | # 9 | # Released to public domain under terms of the BSD Simplified license. 10 | # 11 | # Redistribution and use in source and binary forms, with or without 12 | # modification, are permitted provided that the following conditions are met: 13 | # * Redistributions of source code must retain the above copyright 14 | # notice, this list of conditions and the following disclaimer. 15 | # * Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | # * Neither the name of the organization nor the names of its contributors 19 | # may be used to endorse or promote products derived from this software 20 | # without specific prior written permission. 21 | # 22 | # See 23 | 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 | # POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/classifier.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | from ocvfacerec.facerec.distance import EuclideanDistance 36 | from ocvfacerec.facerec.util import as_row_matrix 37 | import logging 38 | import numpy as np 39 | import operator as op 40 | 41 | 42 | class AbstractClassifier(object): 43 | def compute(self, X, y): 44 | raise NotImplementedError("Every AbstractClassifier must implement the compute method.") 45 | 46 | def predict(self, X): 47 | raise NotImplementedError("Every AbstractClassifier must implement the predict method.") 48 | 49 | def update(self, X, y): 50 | raise NotImplementedError("This Classifier is cannot be updated.") 51 | 52 | 53 | class NearestNeighbor(AbstractClassifier): 54 | """ 55 | Implements a k-Nearest Neighbor Model with a generic distance metric. 56 | """ 57 | 58 | def __init__(self, dist_metric=EuclideanDistance(), k=1): 59 | AbstractClassifier.__init__(self) 60 | self.k = k 61 | self.dist_metric = dist_metric 62 | self.X = [] 63 | self.y = np.array([], dtype=np.int32) 64 | 65 | def update(self, X, y): 66 | """ 67 | Updates the classifier. 68 | """ 69 | self.X.append(X) 70 | self.y = np.append(self.y, y) 71 | 72 | def compute(self, X, y): 73 | self.X = X 74 | self.y = np.asarray(y) 75 | 76 | def predict(self, q): 77 | """ 78 | Predicts the k-nearest neighbor for a given query in q. 79 | 80 | Args: 81 | 82 | q: The given query sample, which is an array. 83 | 84 | Returns: 85 | 86 | A list with the classifier output. In this framework it is 87 | assumed, that the predicted class is always returned as first 88 | element. Moreover, this class returns the distances for the 89 | first k-Nearest Neighbors. 90 | 91 | Example: 92 | 93 | [ 0, 94 | { 'labels' : [ 0, 0, 1 ], 95 | 'distances' : [ 10.132, 10.341, 13.314 ] 96 | } 97 | ] 98 | 99 | So if you want to perform a thresholding operation, you could 100 | pick the distances in the second array of the generic classifier 101 | output. 102 | 103 | """ 104 | distances = [] 105 | for xi in self.X: 106 | xi = xi.reshape(-1, 1) 107 | d = self.dist_metric(xi, q) 108 | distances.append(d) 109 | if len(distances) > len(self.y): 110 | raise Exception("More distances than classes. Is your distance metric correct?") 111 | distances = np.asarray(distances) 112 | # Get the indices in an ascending sort order: 113 | idx = np.argsort(distances) 114 | # Sort the labels and distances accordingly: 115 | sorted_y = self.y[idx] 116 | sorted_distances = distances[idx] 117 | # Take only the k first items: 118 | sorted_y = sorted_y[0:self.k] 119 | sorted_distances = sorted_distances[0:self.k] 120 | # Make a histogram of them: 121 | hist = dict((key, val) for key, val in enumerate(np.bincount(sorted_y)) if val) 122 | # And get the bin with the maximum frequency: 123 | predicted_label = max(hist.iteritems(), key=op.itemgetter(1))[0] 124 | # A classifier should output a list with the label as first item and 125 | # generic data behind. The k-nearest neighbor classifier outputs the 126 | # distance of the k first items. So imagine you have a 1-NN and you 127 | # want to perform a threshold against it, you should take the first 128 | # item 129 | return [predicted_label, {'labels': sorted_y, 'distances': sorted_distances}] 130 | 131 | def __repr__(self): 132 | return "NearestNeighbor (k=%s, dist_metric=%s)" % (self.k, repr(self.dist_metric)) 133 | 134 | # libsvm 135 | try: 136 | from svmutil import * 137 | except ImportError: 138 | logger = logging.getLogger("facerec.classifier.SVM") 139 | logger.debug("Import Error: libsvm bindings not available.") 140 | except: 141 | logger = logging.getLogger("facerec.classifier.SVM") 142 | logger.debug("Import Error: libsvm bindings not available.") 143 | 144 | import sys 145 | from StringIO import StringIO 146 | 147 | bkp_stdout = sys.stdout 148 | 149 | 150 | class SVM(AbstractClassifier): 151 | """ 152 | This class is just a simple wrapper to use libsvm in the 153 | CrossValidation module. If you don't use this framework 154 | use the validation methods coming with LibSVM, they are 155 | much easier to access (simply pass the correct class 156 | labels in svm_predict and you are done...). 157 | 158 | The grid search method in this class is somewhat similar 159 | to libsvm grid.py, as it performs a parameter search over 160 | a logarithmic scale. Again if you don't use this framework, 161 | use the libsvm tools as they are much easier to access. 162 | 163 | Please keep in mind to normalize your input data, as expected 164 | for the model. There's no way to assume a generic normalization 165 | step. 166 | """ 167 | 168 | def __init__(self, param=None): 169 | AbstractClassifier.__init__(self) 170 | self.logger = logging.getLogger("facerec.classifier.SVM") 171 | self.param = param 172 | self.svm = svm_model() 173 | self.param = param 174 | if self.param is None: 175 | self.param = svm_parameter("-q") 176 | 177 | def compute(self, X, y): 178 | self.logger.debug("SVM TRAINING (C=%.2f,gamma=%.2f,p=%.2f,nu=%.2f,coef=%.2f,degree=%.2f)" % ( 179 | self.param.C, self.param.gamma, self.param.p, self.param.nu, self.param.coef0, self.param.degree)) 180 | # turn data into a row vector (needed for libsvm) 181 | X = as_row_matrix(X) 182 | y = np.asarray(y) 183 | problem = svm_problem(y, X.tolist()) 184 | self.svm = svm_train(problem, self.param) 185 | self.y = y 186 | 187 | def predict(self, X): 188 | """ 189 | 190 | Args: 191 | 192 | X: The query image, which is an array. 193 | 194 | Returns: 195 | 196 | A list with the classifier output. In this framework it is 197 | assumed, that the predicted class is always returned as first 198 | element. Moreover, this class returns the libsvm output for 199 | p_labels, p_acc and p_vals. The libsvm help states: 200 | 201 | p_labels: a list of predicted labels 202 | p_acc: a tuple including accuracy (for classification), mean-squared 203 | error, and squared correlation coefficient (for regression). 204 | p_vals: a list of decision values or probability estimates (if '-b 1' 205 | is specified). If k is the number of classes, for decision values, 206 | each element includes results of predicting k(k-1)/2 binary-class 207 | SVMs. For probabilities, each element contains k values indicating 208 | the probability that the testing instance is in each class. 209 | Note that the order of classes here is the same as 'model.label' 210 | field in the model structure. 211 | """ 212 | X = np.asarray(X).reshape(1, -1) 213 | sys.stdout = StringIO() 214 | p_lbl, p_acc, p_val = svm_predict([0], X.tolist(), self.svm) 215 | sys.stdout = bkp_stdout 216 | predicted_label = int(p_lbl[0]) 217 | return [predicted_label, {'p_lbl': p_lbl, 'p_acc': p_acc, 'p_val': p_val}] 218 | 219 | def __repr__(self): 220 | return "Support Vector Machine (kernel_type=%s, C=%.2f,gamma=%.2f,p=%.2f,nu=%.2f,coef=%.2f,degree=%.2f)" % ( 221 | KERNEL_TYPE[self.param.kernel_type], self.param.C, self.param.gamma, self.param.p, self.param.nu, 222 | self.param.coef0, self.param.degree) 223 | 224 | 225 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/dataset.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | 36 | # To abstract the dirty things away, we are going to use a 37 | # new class, which we call a NumericDataSet. This NumericDataSet 38 | # allows us to add images and turn them into a facerec compatible 39 | # representation. 40 | # 41 | # This DataSet does not provide a method for removing entities yet, 42 | # because this would render the ordering of the labels useless. 43 | # This is caused by a severe limitation of the framework right now, 44 | # because it would make algorithms like LDA and PCA fail. 45 | 46 | class NumericDataSet(object): 47 | def __init__(self): 48 | self.data = {} 49 | self.str_to_num_mapping = {} 50 | self.num_to_str_mapping = {} 51 | 52 | def add(self, identifier, image): 53 | try: 54 | self.data[identifier].append(image) 55 | except: 56 | self.data[identifier] = [image] 57 | numerical_identifier = len(self.str_to_num_mapping) 58 | # Store in mapping tables: 59 | self.str_to_num_mapping[identifier] = numerical_identifier 60 | self.num_to_str_mapping[numerical_identifier] = identifier 61 | 62 | def get(self): 63 | X = [] 64 | y = [] 65 | for name, num in self.str_to_num_mapping.iteritems(): 66 | for image in self.data[name]: 67 | X.append(image) 68 | y.append(num) 69 | return X, y 70 | 71 | def resolve_by_str(self, identifier): 72 | return self.str_num_mapping[identifier] 73 | 74 | def resolve_by_num(self, numerical_identifier): 75 | return self.num_to_str_mapping[numerical_identifier] 76 | 77 | def length(self): 78 | return len(self.data) 79 | 80 | def __repr__(self): 81 | print "NumericDataSet" 82 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/distance.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import numpy as np 36 | 37 | 38 | class AbstractDistance(object): 39 | def __init__(self, name): 40 | self._name = name 41 | 42 | def __call__(self, p, q): 43 | raise NotImplementedError("Every AbstractDistance must implement the __call__ method.") 44 | 45 | @property 46 | def name(self): 47 | return self._name 48 | 49 | def __repr__(self): 50 | return self._name 51 | 52 | 53 | class EuclideanDistance(AbstractDistance): 54 | def __init__(self): 55 | AbstractDistance.__init__(self, "EuclideanDistance") 56 | 57 | def __call__(self, p, q): 58 | p = np.asarray(p).flatten() 59 | q = np.asarray(q).flatten() 60 | return np.sqrt(np.sum(np.power((p - q), 2))) 61 | 62 | 63 | class CosineDistance(AbstractDistance): 64 | """ 65 | Negated Mahalanobis Cosine Distance. 66 | 67 | Literature: 68 | "Studies on sensitivity of face recognition performance to eye location accuracy.". Master Thesis (2004), Wang 69 | """ 70 | 71 | def __init__(self): 72 | AbstractDistance.__init__(self, "CosineDistance") 73 | 74 | def __call__(self, p, q): 75 | p = np.asarray(p).flatten() 76 | q = np.asarray(q).flatten() 77 | return -np.dot(p.T, q) / (np.sqrt(np.dot(p, p.T) * np.dot(q, q.T))) 78 | 79 | 80 | class NormalizedCorrelation(AbstractDistance): 81 | """ 82 | Calculates the NormalizedCorrelation Coefficient for two vectors. 83 | 84 | Literature: 85 | "Multi-scale Local Binary Pattern Histogram for Face Recognition". PhD (2008). Chi Ho Chan, University Of Surrey. 86 | """ 87 | 88 | def __init__(self): 89 | AbstractDistance.__init__(self, "NormalizedCorrelation") 90 | 91 | def __call__(self, p, q): 92 | p = np.asarray(p).flatten() 93 | q = np.asarray(q).flatten() 94 | pmu = p.mean() 95 | qmu = q.mean() 96 | pm = p - pmu 97 | qm = q - qmu 98 | return 1.0 - (np.dot(pm, qm) / (np.sqrt(np.dot(pm, pm)) * np.sqrt(np.dot(qm, qm)))) 99 | 100 | 101 | class ChiSquareDistance(AbstractDistance): 102 | """ 103 | Negated Mahalanobis Cosine Distance. 104 | 105 | Literature: 106 | "Studies on sensitivity of face recognition performance to eye location accuracy.". Master Thesis (2004), Wang 107 | """ 108 | 109 | def __init__(self): 110 | AbstractDistance.__init__(self, "ChiSquareDistance") 111 | 112 | def __call__(self, p, q): 113 | p = np.asarray(p).flatten() 114 | q = np.asarray(q).flatten() 115 | bin_dists = (p - q) ** 2 / (p + q + np.finfo('float').eps) 116 | return np.sum(bin_dists) 117 | 118 | 119 | class HistogramIntersection(AbstractDistance): 120 | def __init__(self): 121 | AbstractDistance.__init__(self, "HistogramIntersection") 122 | 123 | def __call__(self, p, q): 124 | p = np.asarray(p).flatten() 125 | q = np.asarray(q).flatten() 126 | return np.sum(np.minimum(p, q)) 127 | 128 | 129 | class BinRatioDistance(AbstractDistance): 130 | """ 131 | Calculates the Bin Ratio Dissimilarity. 132 | 133 | Literature: 134 | "Use Bin-Ratio Information for Category and Scene Classification" (2010), Xie et.al. 135 | """ 136 | 137 | def __init__(self): 138 | AbstractDistance.__init__(self, "BinRatioDistance") 139 | 140 | def __call__(self, p, q): 141 | p = np.asarray(p).flatten() 142 | q = np.asarray(q).flatten() 143 | a = np.abs(1 - np.dot(p, q.T)) # NumPy needs np.dot instead of * for reducing to tensor 144 | b = ((p - q) ** 2 + 2 * a * (p * q)) / ((p + q) ** 2 + np.finfo('float').eps) 145 | return np.abs(np.sum(b)) 146 | 147 | 148 | class L1BinRatioDistance(AbstractDistance): 149 | """ 150 | Calculates the L1-Bin Ratio Dissimilarity. 151 | 152 | Literature: 153 | "Use Bin-Ratio Information for Category and Scene Classification" (2010), Xie et.al. 154 | """ 155 | 156 | def __init__(self): 157 | AbstractDistance.__init__(self, "L1-BinRatioDistance") 158 | 159 | def __call__(self, p, q): 160 | p = np.asarray(p, dtype=np.float).flatten() 161 | q = np.asarray(q, dtype=np.float).flatten() 162 | a = np.abs(1 - np.dot(p, q.T)) # NumPy needs np.dot instead of * for reducing to tensor 163 | b = ((p - q) ** 2 + 2 * a * (p * q)) * abs(p - q) / ((p + q) ** 2 + np.finfo('float').eps) 164 | return np.abs(np.sum(b)) 165 | 166 | 167 | class ChiSquareBRD(AbstractDistance): 168 | """ 169 | Calculates the ChiSquare-Bin Ratio Dissimilarity. 170 | 171 | Literature: 172 | "Use Bin-Ratio Information for Category and Scene Classification" (2010), Xie et.al. 173 | """ 174 | 175 | def __init__(self): 176 | AbstractDistance.__init__(self, "ChiSquare-BinRatioDistance") 177 | 178 | def __call__(self, p, q): 179 | p = np.asarray(p, dtype=np.float).flatten() 180 | q = np.asarray(q, dtype=np.float).flatten() 181 | a = np.abs(1 - np.dot(p, q.T)) # NumPy needs np.dot instead of * for reducing to tensor 182 | b = ((p - q) ** 2 + 2 * a * (p * q)) * (p - q) ** 2 / ((p + q) ** 3 + np.finfo('float').eps) 183 | return np.abs(np.sum(b)) 184 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/feature.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import numpy as np 36 | 37 | 38 | class AbstractFeature(object): 39 | def compute(self, X, y): 40 | raise NotImplementedError("Every AbstractFeature must implement the compute method.") 41 | 42 | def extract(self, X): 43 | raise NotImplementedError("Every AbstractFeature must implement the extract method.") 44 | 45 | def save(self): 46 | raise NotImplementedError("Not implemented yet (TODO).") 47 | 48 | def load(self): 49 | raise NotImplementedError("Not implemented yet (TODO).") 50 | 51 | def __repr__(self): 52 | return "AbstractFeature" 53 | 54 | 55 | class Identity(AbstractFeature): 56 | """ 57 | Simplest AbstractFeature you could imagine. It only forwards the data and does not operate on it, 58 | probably useful for learning a Support Vector Machine on raw data for example! 59 | """ 60 | 61 | def __init__(self): 62 | AbstractFeature.__init__(self) 63 | 64 | def compute(self, X, y): 65 | return X 66 | 67 | def extract(self, X): 68 | return X 69 | 70 | def __repr__(self): 71 | return "Identity" 72 | 73 | 74 | from ocvfacerec.facerec.util import as_column_matrix 75 | from ocvfacerec.facerec.operators import ChainOperator, CombineOperator 76 | 77 | 78 | class PCA(AbstractFeature): 79 | def __init__(self, num_components=0): 80 | AbstractFeature.__init__(self) 81 | self._num_components = num_components 82 | 83 | def compute(self, X, y): 84 | # build the column matrix 85 | XC = as_column_matrix(X) 86 | y = np.asarray(y) 87 | # set a valid number of components 88 | if self._num_components <= 0 or (self._num_components > XC.shape[1] - 1): 89 | self._num_components = XC.shape[1] - 1 90 | # center dataset 91 | self._mean = XC.mean(axis=1).reshape(-1, 1) 92 | XC = XC - self._mean 93 | # perform an economy size decomposition (may still allocate too much memory for computation) 94 | self._eigenvectors, self._eigenvalues, variances = np.linalg.svd(XC, full_matrices=False) 95 | # sort eigenvectors by eigenvalues in descending order 96 | idx = np.argsort(-self._eigenvalues) 97 | self._eigenvalues, self._eigenvectors = self._eigenvalues[idx], self._eigenvectors[:, idx] 98 | # use only num_components 99 | self._eigenvectors = self._eigenvectors[0:, 0:self._num_components].copy() 100 | self._eigenvalues = self._eigenvalues[0:self._num_components].copy() 101 | # finally turn singular values into eigenvalues 102 | self._eigenvalues = np.power(self._eigenvalues, 2) / XC.shape[1] 103 | # get the features from the given data 104 | features = [] 105 | for x in X: 106 | xp = self.project(x.reshape(-1, 1)) 107 | features.append(xp) 108 | return features 109 | 110 | def extract(self, X): 111 | X = np.asarray(X).reshape(-1, 1) 112 | return self.project(X) 113 | 114 | def project(self, X): 115 | X = X - self._mean 116 | return np.dot(self._eigenvectors.T, X) 117 | 118 | def reconstruct(self, X): 119 | X = np.dot(self._eigenvectors, X) 120 | return X + self._mean 121 | 122 | @property 123 | def num_components(self): 124 | return self._num_components 125 | 126 | @property 127 | def eigenvalues(self): 128 | return self._eigenvalues 129 | 130 | @property 131 | def eigenvectors(self): 132 | return self._eigenvectors 133 | 134 | @property 135 | def mean(self): 136 | return self._mean 137 | 138 | def __repr__(self): 139 | return "PCA (num_components=%d)" % (self._num_components) 140 | 141 | 142 | class LDA(AbstractFeature): 143 | def __init__(self, num_components=0): 144 | AbstractFeature.__init__(self) 145 | self._num_components = num_components 146 | 147 | def compute(self, X, y): 148 | # build the column matrix 149 | XC = as_column_matrix(X) 150 | y = np.asarray(y) 151 | # calculate dimensions 152 | d = XC.shape[0] 153 | c = len(np.unique(y)) 154 | # set a valid number of components 155 | if self._num_components <= 0: 156 | self._num_components = c - 1 157 | elif self._num_components > (c - 1): 158 | self._num_components = c - 1 159 | # calculate total mean 160 | meanTotal = XC.mean(axis=1).reshape(-1, 1) 161 | # calculate the within and between scatter matrices 162 | Sw = np.zeros((d, d), dtype=np.float32) 163 | Sb = np.zeros((d, d), dtype=np.float32) 164 | for i in range(0, c): 165 | Xi = XC[:, np.where(y == i)[0]] 166 | meanClass = np.mean(Xi, axis=1).reshape(-1, 1) 167 | Sw = Sw + np.dot((Xi - meanClass), (Xi - meanClass).T) 168 | Sb = Sb + Xi.shape[1] * np.dot((meanClass - meanTotal), (meanClass - meanTotal).T) 169 | # solve eigenvalue problem for a general matrix 170 | self._eigenvalues, self._eigenvectors = np.linalg.eig(np.linalg.inv(Sw) * Sb) 171 | # sort eigenvectors by their eigenvalue in descending order 172 | idx = np.argsort(-self._eigenvalues.real) 173 | self._eigenvalues, self._eigenvectors = self._eigenvalues[idx], self._eigenvectors[:, idx] 174 | # only store (c-1) non-zero eigenvalues 175 | self._eigenvalues = np.array(self._eigenvalues[0:self._num_components].real, dtype=np.float32, copy=True) 176 | self._eigenvectors = np.matrix(self._eigenvectors[0:, 0:self._num_components].real, dtype=np.float32, copy=True) 177 | # get the features from the given data 178 | features = [] 179 | for x in X: 180 | xp = self.project(x.reshape(-1, 1)) 181 | features.append(xp) 182 | return features 183 | 184 | def project(self, X): 185 | return np.dot(self._eigenvectors.T, X) 186 | 187 | def reconstruct(self, X): 188 | return np.dot(self._eigenvectors, X) 189 | 190 | @property 191 | def num_components(self): 192 | return self._num_components 193 | 194 | @property 195 | def eigenvectors(self): 196 | return self._eigenvectors 197 | 198 | @property 199 | def eigenvalues(self): 200 | return self._eigenvalues 201 | 202 | def __repr__(self): 203 | return "LDA (num_components=%d)" % (self._num_components) 204 | 205 | 206 | class Fisherfaces(AbstractFeature): 207 | def __init__(self, num_components=0): 208 | AbstractFeature.__init__(self) 209 | self._num_components = num_components 210 | 211 | def compute(self, X, y): 212 | # turn into numpy representation 213 | Xc = as_column_matrix(X) 214 | y = np.asarray(y) 215 | # gather some statistics about the dataset 216 | n = len(y) 217 | c = len(np.unique(y)) 218 | # define features to be extracted 219 | pca = PCA(num_components=(n - c)) 220 | lda = LDA(num_components=self._num_components) 221 | # fisherfaces are a chained feature of PCA followed by LDA 222 | model = ChainOperator(pca, lda) 223 | # computing the chained model then calculates both decompositions 224 | model.compute(X, y) 225 | # store eigenvalues and number of components used 226 | self._eigenvalues = lda.eigenvalues 227 | self._num_components = lda.num_components 228 | # compute the new eigenspace as pca.eigenvectors*lda.eigenvectors 229 | self._eigenvectors = np.dot(pca.eigenvectors, lda.eigenvectors) 230 | # finally compute the features (these are the Fisherfaces) 231 | features = [] 232 | for x in X: 233 | xp = self.project(x.reshape(-1, 1)) 234 | features.append(xp) 235 | return features 236 | 237 | def extract(self, X): 238 | X = np.asarray(X).reshape(-1, 1) 239 | return self.project(X) 240 | 241 | def project(self, X): 242 | return np.dot(self._eigenvectors.T, X) 243 | 244 | def reconstruct(self, X): 245 | return np.dot(self._eigenvectors, X) 246 | 247 | @property 248 | def num_components(self): 249 | return self._num_components 250 | 251 | @property 252 | def eigenvalues(self): 253 | return self._eigenvalues 254 | 255 | @property 256 | def eigenvectors(self): 257 | return self._eigenvectors 258 | 259 | def __repr__(self): 260 | return "Fisherfaces (num_components=%s)" % (self.num_components) 261 | 262 | 263 | from ocvfacerec.facerec.lbp import LocalDescriptor, ExtendedLBP 264 | 265 | 266 | class SpatialHistogram(AbstractFeature): 267 | def __init__(self, lbp_operator=ExtendedLBP(), sz=(8, 8)): 268 | AbstractFeature.__init__(self) 269 | if not isinstance(lbp_operator, LocalDescriptor): 270 | raise TypeError("Only an operator of type facerec.lbp.LocalDescriptor is a valid lbp_operator.") 271 | self.lbp_operator = lbp_operator 272 | self.sz = sz 273 | 274 | def compute(self, X, y): 275 | features = [] 276 | for x in X: 277 | x = np.asarray(x) 278 | h = self.spatially_enhanced_histogram(x) 279 | features.append(h) 280 | return features 281 | 282 | def extract(self, X): 283 | X = np.asarray(X) 284 | return self.spatially_enhanced_histogram(X) 285 | 286 | def spatially_enhanced_histogram(self, X): 287 | # calculate the LBP image 288 | L = self.lbp_operator(X) 289 | # calculate the grid geometry 290 | lbp_height, lbp_width = L.shape 291 | grid_rows, grid_cols = self.sz 292 | py = int(np.floor(lbp_height / grid_rows)) 293 | px = int(np.floor(lbp_width / grid_cols)) 294 | E = [] 295 | for row in range(0, grid_rows): 296 | for col in range(0, grid_cols): 297 | C = L[row * py:(row + 1) * py, col * px:(col + 1) * px] 298 | H = np.histogram(C, bins=2 ** self.lbp_operator.neighbors, range=(0, 2 ** self.lbp_operator.neighbors), 299 | normed=True)[0] 300 | # probably useful to apply a mapping? 301 | E.extend(H) 302 | return np.asarray(E) 303 | 304 | def __repr__(self): 305 | return "SpatialHistogram (operator=%s, grid=%s)" % (repr(self.lbp_operator), str(self.sz)) 306 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/lbp.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # coding: utf-8 36 | import numpy as np 37 | from scipy.signal import convolve2d 38 | 39 | 40 | class LocalDescriptor(object): 41 | def __init__(self, neighbors): 42 | self._neighbors = neighbors 43 | 44 | def __call__(self, X): 45 | raise NotImplementedError("Every LBPOperator must implement the __call__ method.") 46 | 47 | @property 48 | def neighbors(self): 49 | return self._neighbors 50 | 51 | def __repr__(self): 52 | return "LBPOperator (neighbors=%s)" % (self._neighbors) 53 | 54 | 55 | class OriginalLBP(LocalDescriptor): 56 | def __init__(self): 57 | LocalDescriptor.__init__(self, neighbors=8) 58 | 59 | def __call__(self, X): 60 | X = np.asarray(X) 61 | X = (1 << 7) * (X[0:-2, 0:-2] >= X[1:-1, 1:-1]) \ 62 | + (1 << 6) * (X[0:-2, 1:-1] >= X[1:-1, 1:-1]) \ 63 | + (1 << 5) * (X[0:-2, 2:] >= X[1:-1, 1:-1]) \ 64 | + (1 << 4) * (X[1:-1, 2:] >= X[1:-1, 1:-1]) \ 65 | + (1 << 3) * (X[2:, 2:] >= X[1:-1, 1:-1]) \ 66 | + (1 << 2) * (X[2:, 1:-1] >= X[1:-1, 1:-1]) \ 67 | + (1 << 1) * (X[2:, :-2] >= X[1:-1, 1:-1]) \ 68 | + (1 << 0) * (X[1:-1, :-2] >= X[1:-1, 1:-1]) 69 | return X 70 | 71 | def __repr__(self): 72 | return "OriginalLBP (neighbors=%s)" % (self._neighbors) 73 | 74 | 75 | class ExtendedLBP(LocalDescriptor): 76 | def __init__(self, radius=1, neighbors=8): 77 | LocalDescriptor.__init__(self, neighbors=neighbors) 78 | self._radius = radius 79 | 80 | def __call__(self, X): 81 | X = np.asanyarray(X) 82 | ysize, xsize = X.shape 83 | # define circle 84 | angles = 2 * np.pi / self._neighbors 85 | theta = np.arange(0, 2 * np.pi, angles) 86 | # calculate sample points on circle with radius 87 | sample_points = np.array([-np.sin(theta), np.cos(theta)]).T 88 | sample_points *= self._radius 89 | # find boundaries of the sample points 90 | miny = min(sample_points[:, 0]) 91 | maxy = max(sample_points[:, 0]) 92 | minx = min(sample_points[:, 1]) 93 | maxx = max(sample_points[:, 1]) 94 | # calculate block size, each LBP code is computed within a block of size bsizey*bsizex 95 | blocksizey = np.ceil(max(maxy, 0)) - np.floor(min(miny, 0)) + 1 96 | blocksizex = np.ceil(max(maxx, 0)) - np.floor(min(minx, 0)) + 1 97 | # coordinates of origin (0,0) in the block 98 | origy = 0 - np.floor(min(miny, 0)) 99 | origx = 0 - np.floor(min(minx, 0)) 100 | # calculate output image size 101 | dx = xsize - blocksizex + 1 102 | dy = ysize - blocksizey + 1 103 | # get center points 104 | C = np.asarray(X[origy:origy + dy, origx:origx + dx], dtype=np.uint8) 105 | result = np.zeros((dy, dx), dtype=np.uint32) 106 | for i, p in enumerate(sample_points): 107 | # get coordinate in the block 108 | y, x = p + (origy, origx) 109 | # Calculate floors, ceils and rounds for the x and y. 110 | fx = np.floor(x) 111 | fy = np.floor(y) 112 | cx = np.ceil(x) 113 | cy = np.ceil(y) 114 | # calculate fractional part 115 | ty = y - fy 116 | tx = x - fx 117 | # calculate interpolation weights 118 | w1 = (1 - tx) * (1 - ty) 119 | w2 = tx * (1 - ty) 120 | w3 = (1 - tx) * ty 121 | w4 = tx * ty 122 | # calculate interpolated image 123 | N = w1 * X[fy:fy + dy, fx:fx + dx] 124 | N += w2 * X[fy:fy + dy, cx:cx + dx] 125 | N += w3 * X[cy:cy + dy, fx:fx + dx] 126 | N += w4 * X[cy:cy + dy, cx:cx + dx] 127 | # update LBP codes 128 | D = N >= C 129 | result += (1 << i) * D 130 | return result 131 | 132 | @property 133 | def radius(self): 134 | return self._radius 135 | 136 | def __repr__(self): 137 | return "ExtendedLBP (neighbors=%s, radius=%s)" % (self._neighbors, self._radius) 138 | 139 | 140 | class VarLBP(LocalDescriptor): 141 | def __init__(self, radius=1, neighbors=8): 142 | LocalDescriptor.__init__(self, neighbors=neighbors) 143 | self._radius = radius 144 | 145 | def __call__(self, X): 146 | X = np.asanyarray(X) 147 | ysize, xsize = X.shape 148 | # define circle 149 | angles = 2 * np.pi / self._neighbors 150 | theta = np.arange(0, 2 * np.pi, angles) 151 | # calculate sample points on circle with radius 152 | sample_points = np.array([-np.sin(theta), np.cos(theta)]).T 153 | sample_points *= self._radius 154 | # find boundaries of the sample points 155 | miny = min(sample_points[:, 0]) 156 | maxy = max(sample_points[:, 0]) 157 | minx = min(sample_points[:, 1]) 158 | maxx = max(sample_points[:, 1]) 159 | # calculate block size, each LBP code is computed within a block of size bsizey*bsizex 160 | blocksizey = np.ceil(max(maxy, 0)) - np.floor(min(miny, 0)) + 1 161 | blocksizex = np.ceil(max(maxx, 0)) - np.floor(min(minx, 0)) + 1 162 | # coordinates of origin (0,0) in the block 163 | origy = 0 - np.floor(min(miny, 0)) 164 | origx = 0 - np.floor(min(minx, 0)) 165 | # Calculate output image size: 166 | dx = xsize - blocksizex + 1 167 | dy = ysize - blocksizey + 1 168 | # Allocate memory for online variance calculation: 169 | mean = np.zeros((dy, dx), dtype=np.float32) 170 | delta = np.zeros((dy, dx), dtype=np.float32) 171 | m2 = np.zeros((dy, dx), dtype=np.float32) 172 | # Holds the resulting variance matrix: 173 | result = np.zeros((dy, dx), dtype=np.float32) 174 | for i, p in enumerate(sample_points): 175 | # Get coordinate in the block: 176 | y, x = p + (origy, origx) 177 | # Calculate floors, ceils and rounds for the x and y: 178 | fx = np.floor(x) 179 | fy = np.floor(y) 180 | cx = np.ceil(x) 181 | cy = np.ceil(y) 182 | # Calculate fractional part: 183 | ty = y - fy 184 | tx = x - fx 185 | # Calculate interpolation weights: 186 | w1 = (1 - tx) * (1 - ty) 187 | w2 = tx * (1 - ty) 188 | w3 = (1 - tx) * ty 189 | w4 = tx * ty 190 | # Calculate interpolated image: 191 | N = w1 * X[fy:fy + dy, fx:fx + dx] 192 | N += w2 * X[fy:fy + dy, cx:cx + dx] 193 | N += w3 * X[cy:cy + dy, fx:fx + dx] 194 | N += w4 * X[cy:cy + dy, cx:cx + dx] 195 | # Update the matrices for Online Variance calculation (http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm): 196 | delta = N - mean 197 | mean = mean + delta / float(i + 1) 198 | m2 = m2 + delta * (N - mean) 199 | # Optional estimate for variance is m2/self._neighbors: 200 | result = m2 / (self._neighbors - 1) 201 | return result 202 | 203 | @property 204 | def radius(self): 205 | return self._radius 206 | 207 | def __repr__(self): 208 | return "VarLBP (neighbors=%s, radius=%s)" % (self._neighbors, self._radius) 209 | 210 | 211 | class LPQ(LocalDescriptor): 212 | """ This implementation of Local Phase Quantization (LPQ) is a 1:1 adaption of the 213 | original implementation by Ojansivu V & Heikkilae J, which is available at: 214 | 215 | * http://www.cse.oulu.fi/CMV/Downloads/LPQMatlab 216 | 217 | So all credit goes to them. 218 | 219 | Reference: 220 | Ojansivu V & Heikkilae J (2008) Blur insensitive texture classification 221 | using local phase quantization. Proc. Image and Signal Processing 222 | (ICISP 2008), Cherbourg-Octeville, France, 5099:236-243. 223 | 224 | Copyright 2008 by Heikkilae & Ojansivu 225 | """ 226 | 227 | def __init__(self, radius=3): 228 | LocalDescriptor.__init__(self, neighbors=8) 229 | self._radius = radius 230 | 231 | def euc_dist(self, X): 232 | Y = X = X.astype(np.float) 233 | XX = np.sum(X * X, axis=1)[:, np.newaxis] 234 | YY = XX.T 235 | distances = np.dot(X, Y.T) 236 | distances *= -2 237 | distances += XX 238 | distances += YY 239 | np.maximum(distances, 0, distances) 240 | distances.flat[::distances.shape[0] + 1] = 0.0 241 | return np.sqrt(distances) 242 | 243 | def __call__(self, X): 244 | f = 1.0 245 | x = np.arange(-self._radius, self._radius + 1) 246 | n = len(x) 247 | rho = 0.95 248 | [xp, yp] = np.meshgrid(np.arange(1, (n + 1)), np.arange(1, (n + 1))) 249 | pp = np.concatenate((xp, yp)).reshape(2, -1) 250 | dd = self.euc_dist(pp.T) # squareform(pdist(...)) would do the job, too... 251 | C = np.power(rho, dd) 252 | 253 | w0 = (x * 0.0 + 1.0) 254 | w1 = np.exp(-2 * np.pi * 1j * x * f / n) 255 | w2 = np.conj(w1) 256 | 257 | q1 = w0.reshape(-1, 1) * w1 258 | q2 = w1.reshape(-1, 1) * w0 259 | q3 = w1.reshape(-1, 1) * w1 260 | q4 = w1.reshape(-1, 1) * w2 261 | 262 | u1 = np.real(q1) 263 | u2 = np.imag(q1) 264 | u3 = np.real(q2) 265 | u4 = np.imag(q2) 266 | u5 = np.real(q3) 267 | u6 = np.imag(q3) 268 | u7 = np.real(q4) 269 | u8 = np.imag(q4) 270 | 271 | M = np.matrix( 272 | [u1.flatten(1), u2.flatten(1), u3.flatten(1), u4.flatten(1), u5.flatten(1), u6.flatten(1), u7.flatten(1), 273 | u8.flatten(1)]) 274 | 275 | D = np.dot(np.dot(M, C), M.T) 276 | U, S, V = np.linalg.svd(D) 277 | 278 | Qa = convolve2d(convolve2d(X, w0.reshape(-1, 1), mode='same'), w1.reshape(1, -1), mode='same') 279 | Qb = convolve2d(convolve2d(X, w1.reshape(-1, 1), mode='same'), w0.reshape(1, -1), mode='same') 280 | Qc = convolve2d(convolve2d(X, w1.reshape(-1, 1), mode='same'), w1.reshape(1, -1), mode='same') 281 | Qd = convolve2d(convolve2d(X, w1.reshape(-1, 1), mode='same'), w2.reshape(1, -1), mode='same') 282 | 283 | Fa = np.real(Qa) 284 | Ga = np.imag(Qa) 285 | Fb = np.real(Qb) 286 | Gb = np.imag(Qb) 287 | Fc = np.real(Qc) 288 | Gc = np.imag(Qc) 289 | Fd = np.real(Qd) 290 | Gd = np.imag(Qd) 291 | 292 | F = np.array( 293 | [Fa.flatten(1), Ga.flatten(1), Fb.flatten(1), Gb.flatten(1), Fc.flatten(1), Gc.flatten(1), Fd.flatten(1), 294 | Gd.flatten(1)]) 295 | G = np.dot(V.T, F) 296 | 297 | t = 0 298 | 299 | # Calculate the LPQ Patterns: 300 | B = (G[0, :] >= t) * 1 + (G[1, :] >= t) * 2 + (G[2, :] >= t) * 4 + (G[3, :] >= t) * 8 + (G[4, :] >= t) * 16 + ( 301 | G[ 302 | 5, 303 | :] >= t) * 32 + ( 304 | G[ 305 | 6, 306 | :] >= t) * 64 + ( 307 | G[ 308 | 7, 309 | :] >= t) * 128 310 | 311 | return np.reshape(B, np.shape(Fa)) 312 | 313 | @property 314 | def radius(self): 315 | return self._radius 316 | 317 | def __repr__(self): 318 | return "LPQ (neighbors=%s, radius=%s)" % (self._neighbors, self._radius) 319 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/model.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | from ocvfacerec.facerec.feature import AbstractFeature 36 | from ocvfacerec.facerec.classifier import AbstractClassifier 37 | 38 | 39 | class PredictableModel(object): 40 | def __init__(self, feature, classifier): 41 | if not isinstance(feature, AbstractFeature): 42 | raise TypeError("feature must be of type AbstractFeature!") 43 | if not isinstance(classifier, AbstractClassifier): 44 | raise TypeError("classifier must be of type AbstractClassifier!") 45 | 46 | self.feature = feature 47 | self.classifier = classifier 48 | 49 | def compute(self, X, y): 50 | features = self.feature.compute(X, y) 51 | self.classifier.compute(features, y) 52 | 53 | def predict(self, X): 54 | q = self.feature.extract(X) 55 | return self.classifier.predict(q) 56 | 57 | def __repr__(self): 58 | feature_repr = repr(self.feature) 59 | classifier_repr = repr(self.classifier) 60 | return "PredictableModel (feature=%s, classifier=%s)" % (feature_repr, classifier_repr) 61 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/normalization.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import numpy as np 36 | 37 | 38 | def minmax(X, low, high, minX=None, maxX=None, dtype=np.float): 39 | X = np.asarray(X) 40 | if minX is None: 41 | minX = np.min(X) 42 | if maxX is None: 43 | maxX = np.max(X) 44 | # normalize to [0...1]. 45 | X = X - float(minX) 46 | X = X / float((maxX - minX)) 47 | # scale to [low...high]. 48 | X = X * (high - low) 49 | X = X + low 50 | return np.asarray(X, dtype=dtype) 51 | 52 | 53 | def zscore(X, mean=None, std=None): 54 | X = np.asarray(X) 55 | if mean is None: 56 | mean = X.mean() 57 | if std is None: 58 | std = X.std() 59 | X = (X - mean) / std 60 | return X 61 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/operators.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import numpy as np 36 | from ocvfacerec.facerec.feature import AbstractFeature 37 | 38 | 39 | class FeatureOperator(AbstractFeature): 40 | """ 41 | A FeatureOperator operates on two feature models. 42 | 43 | Args: 44 | model1 [AbstractFeature] 45 | model2 [AbstractFeature] 46 | """ 47 | 48 | def __init__(self, model1, model2): 49 | if (not isinstance(model1, AbstractFeature)) or (not isinstance(model2, AbstractFeature)): 50 | raise Exception("A FeatureOperator only works on classes implementing an AbstractFeature!") 51 | self.model1 = model1 52 | self.model2 = model2 53 | 54 | def __repr__(self): 55 | return "FeatureOperator(" + repr(self.model1) + "," + repr(self.model2) + ")" 56 | 57 | 58 | class ChainOperator(FeatureOperator): 59 | """ 60 | The ChainOperator chains two feature extraction modules: 61 | model2.compute(model1.compute(X,y),y) 62 | Where X can be generic input data. 63 | 64 | Args: 65 | model1 [AbstractFeature] 66 | model2 [AbstractFeature] 67 | """ 68 | 69 | def __init__(self, model1, model2): 70 | FeatureOperator.__init__(self, model1, model2) 71 | 72 | def compute(self, X, y): 73 | X = self.model1.compute(X, y) 74 | return self.model2.compute(X, y) 75 | 76 | def extract(self, X): 77 | X = self.model1.extract(X) 78 | return self.model2.extract(X) 79 | 80 | def __repr__(self): 81 | return "ChainOperator(" + repr(self.model1) + "," + repr(self.model2) + ")" 82 | 83 | 84 | class CombineOperator(FeatureOperator): 85 | """ 86 | The CombineOperator combines the output of two feature extraction modules as: 87 | (model1.compute(X,y),model2.compute(X,y)) 88 | , where the output of each feature is a [1xN] or [Nx1] feature vector. 89 | 90 | 91 | Args: 92 | model1 [AbstractFeature] 93 | model2 [AbstractFeature] 94 | 95 | """ 96 | 97 | def __init__(self, model1, model2): 98 | FeatureOperator.__init__(self, model1, model2) 99 | 100 | def compute(self, X, y): 101 | A = self.model1.compute(X, y) 102 | B = self.model2.compute(X, y) 103 | C = [] 104 | for i in range(0, len(A)): 105 | ai = np.asarray(A[i]).reshape(1, -1) 106 | bi = np.asarray(B[i]).reshape(1, -1) 107 | C.append(np.hstack((ai, bi))) 108 | return C 109 | 110 | def extract(self, X): 111 | ai = self.model1.extract(X) 112 | bi = self.model2.extract(X) 113 | ai = np.asarray(ai).reshape(1, -1) 114 | bi = np.asarray(bi).reshape(1, -1) 115 | return np.hstack((ai, bi)) 116 | 117 | def __repr__(self): 118 | return "CombineOperator(" + repr(self.model1) + "," + repr(self.model2) + ")" 119 | 120 | 121 | class CombineOperatorND(FeatureOperator): 122 | """ 123 | The CombineOperator combines the output of two multidimensional feature extraction modules. 124 | (model1.compute(X,y),model2.compute(X,y)) 125 | 126 | Args: 127 | model1 [AbstractFeature] 128 | model2 [AbstractFeature] 129 | hstack [bool] stacks data horizontally if True and vertically if False 130 | 131 | """ 132 | 133 | def __init__(self, model1, model2, hstack=True): 134 | FeatureOperator.__init__(self, model1, model2) 135 | self._hstack = hstack 136 | 137 | def compute(self, X, y): 138 | A = self.model1.compute(X, y) 139 | B = self.model2.compute(X, y) 140 | C = [] 141 | for i in range(0, len(A)): 142 | if self._hstack: 143 | C.append(np.hstack((A[i], B[i]))) 144 | else: 145 | C.append(np.vstack((A[i], B[i]))) 146 | return C 147 | 148 | def extract(self, X): 149 | ai = self.model1.extract(X) 150 | bi = self.model2.extract(X) 151 | if self._hstack: 152 | return np.hstack((ai, bi)) 153 | return np.vstack((ai, bi)) 154 | 155 | def __repr__(self): 156 | return "CombineOperatorND(" + repr(self.model1) + "," + repr(self.model2) + ", hstack=" + str( 157 | self._hstack) + ")" 158 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/preprocessing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import numpy as np 36 | from ocvfacerec.facerec.feature import AbstractFeature 37 | from ocvfacerec.facerec.util import as_column_matrix 38 | from ocvfacerec.facerec.lbp import ExtendedLBP 39 | from cvfacerec.facerec.normalization import zscore, minmax 40 | from scipy import ndimage 41 | from scipy.misc import imresize 42 | 43 | 44 | class Resize(AbstractFeature): 45 | def __init__(self, size): 46 | AbstractFeature.__init__(self) 47 | self._size = size 48 | 49 | def compute(self, X, y): 50 | Xp = [] 51 | for xi in X: 52 | Xp.append(self.extract(xi)) 53 | return Xp 54 | 55 | def extract(self, X): 56 | return imresize(X, self._size) 57 | 58 | def __repr__(self): 59 | return "Resize (size=%s)" % (self._size,) 60 | 61 | 62 | class HistogramEqualization(AbstractFeature): 63 | def __init__(self, num_bins=256): 64 | AbstractFeature.__init__(self) 65 | self._num_bins = num_bins 66 | 67 | def compute(self, X, y): 68 | Xp = [] 69 | for xi in X: 70 | Xp.append(self.extract(xi)) 71 | return Xp 72 | 73 | def extract(self, X): 74 | h, b = np.histogram(X.flatten(), self._num_bins, normed=True) 75 | cdf = h.cumsum() 76 | cdf = 255 * cdf / cdf[-1] 77 | return np.interp(X.flatten(), b[:-1], cdf).reshape(X.shape) 78 | 79 | def __repr__(self): 80 | return "HistogramEqualization (num_bins=%s)" % (self._num_bins) 81 | 82 | 83 | class TanTriggsPreprocessing(AbstractFeature): 84 | def __init__(self, alpha=0.1, tau=10.0, gamma=0.2, sigma0=1.0, sigma1=2.0): 85 | AbstractFeature.__init__(self) 86 | self._alpha = float(alpha) 87 | self._tau = float(tau) 88 | self._gamma = float(gamma) 89 | self._sigma0 = float(sigma0) 90 | self._sigma1 = float(sigma1) 91 | 92 | def compute(self, X, y): 93 | Xp = [] 94 | for xi in X: 95 | Xp.append(self.extract(xi)) 96 | return Xp 97 | 98 | def extract(self, X): 99 | X = np.array(X, dtype=np.float32) 100 | X = np.power(X, self._gamma) 101 | X = np.asarray(ndimage.gaussian_filter(X, self._sigma1) - ndimage.gaussian_filter(X, self._sigma0)) 102 | X = X / np.power(np.mean(np.power(np.abs(X), self._alpha)), 1.0 / self._alpha) 103 | X = X / np.power(np.mean(np.power(np.minimum(np.abs(X), self._tau), self._alpha)), 1.0 / self._alpha) 104 | X = self._tau * np.tanh(X / self._tau) 105 | return X 106 | 107 | def __repr__(self): 108 | return "TanTriggsPreprocessing (alpha=%.3f,tau=%.3f,gamma=%.3f,sigma0=%.3f,sigma1=%.3f)" % ( 109 | self._alpha, self._tau, self._gamma, self._sigma0, self._sigma1) 110 | 111 | 112 | class LBPPreprocessing(AbstractFeature): 113 | def __init__(self, lbp_operator=ExtendedLBP(radius=1, neighbors=8)): 114 | AbstractFeature.__init__(self) 115 | self._lbp_operator = lbp_operator 116 | 117 | def compute(self, X, y): 118 | Xp = [] 119 | for xi in X: 120 | Xp.append(self.extract(xi)) 121 | return Xp 122 | 123 | def extract(self, X): 124 | return self._lbp_operator(X) 125 | 126 | def __repr__(self): 127 | return "LBPPreprocessing (lbp_operator=%s)" % (repr(self._lbp_operator)) 128 | 129 | 130 | class MinMaxNormalizePreprocessing(AbstractFeature): 131 | def __init__(self, low=0, high=1): 132 | AbstractFeature.__init__(self) 133 | self._low = low 134 | self._high = high 135 | 136 | def compute(self, X, y): 137 | Xp = [] 138 | XC = as_column_matrix(X) 139 | self._min = np.min(XC) 140 | self._max = np.max(XC) 141 | for xi in X: 142 | Xp.append(self.extract(xi)) 143 | return Xp 144 | 145 | def extract(self, X): 146 | return minmax(X, self._low, self._high, self._min, self._max) 147 | 148 | def __repr__(self): 149 | return "MinMaxNormalizePreprocessing (low=%s, high=%s)" % (self._low, self._high) 150 | 151 | 152 | class ZScoreNormalizePreprocessing(AbstractFeature): 153 | def __init__(self): 154 | AbstractFeature.__init__(self) 155 | self._mean = 0.0 156 | self._std = 1.0 157 | 158 | def compute(self, X, y): 159 | XC = as_column_matrix(X) 160 | self._mean = XC.mean() 161 | self._std = XC.std() 162 | Xp = [] 163 | for xi in X: 164 | Xp.append(self.extract(xi)) 165 | return Xp 166 | 167 | def extract(self, X): 168 | return zscore(X, self._mean, self._std) 169 | 170 | def __repr__(self): 171 | return "ZScoreNormalizePreprocessing (mean=%s, std=%s)" % (self._mean, self._std) 172 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/serialization.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import cPickle 36 | 37 | 38 | def save_model(filename, model): 39 | output = open(filename, 'wb') 40 | cPickle.dump(model, output) 41 | output.close() 42 | 43 | 44 | def load_model(filename): 45 | pkl_file = open(filename, 'rb') 46 | res = cPickle.load(pkl_file) 47 | pkl_file.close() 48 | print ">> New Model Loaded" 49 | return res 50 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/svm.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | from classifier import SVM 36 | from ocvfacerec.facerec.validation import KFoldCrossValidation 37 | from ocvfacerec.facerec.model import PredictableModel 38 | from ocvfacerec.facerec.svmutil import * 39 | from itertools import product 40 | import numpy as np 41 | import logging 42 | 43 | 44 | def range_f(begin, end, step): 45 | seq = [] 46 | while True: 47 | if step == 0: break 48 | if step > 0 and begin > end: break 49 | if step < 0 and begin < end: break 50 | seq.append(begin) 51 | begin = begin + step 52 | return seq 53 | 54 | 55 | def grid(grid_parameters): 56 | grid = [] 57 | for parameter in grid_parameters: 58 | begin, end, step = parameter 59 | grid.append(range_f(begin, end, step)) 60 | return product(*grid) 61 | 62 | 63 | def grid_search(model, X, y, C_range=(-5, 15, 2), gamma_range=(3, -15, -2), k=5, num_cores=1): 64 | if not isinstance(model, PredictableModel): 65 | raise TypeError( 66 | "GridSearch expects a PredictableModel. If you want to perform optimization on raw data use facerec.feature.Identity to pass unpreprocessed data!") 67 | if not isinstance(model.classifier, SVM): 68 | raise TypeError("GridSearch expects a SVM as classifier. Please use a facerec.classifier.SVM!") 69 | 70 | logger = logging.getLogger("facerec.svm.gridsearch") 71 | logger.info("Performing a Grid Search.") 72 | 73 | # best parameter combination to return 74 | best_parameter = svm_parameter("-q") 75 | best_parameter.kernel_type = model.classifier.param.kernel_type 76 | best_parameter.nu = model.classifier.param.nu 77 | best_parameter.coef0 = model.classifier.param.coef0 78 | # either no gamma given or kernel is linear (only C to optimize) 79 | if (gamma_range is None) or (model.classifier.param.kernel_type == LINEAR): 80 | gamma_range = (0, 0, 1) 81 | 82 | # best validation error so far 83 | best_accuracy = np.finfo('float').min 84 | 85 | # create grid (cartesian product of ranges) 86 | g = grid([C_range, gamma_range]) 87 | results = [] 88 | for p in g: 89 | C, gamma = p 90 | C, gamma = 2 ** C, 2 ** gamma 91 | model.classifier.param.C, model.classifier.param.gamma = C, gamma 92 | 93 | # perform a k-fold cross validation 94 | cv = KFoldCrossValidation(model=model, k=k) 95 | cv.validate(X, y) 96 | 97 | # append parameter into list with accuracies for all parameter combinations 98 | results.append([C, gamma, cv.accuracy]) 99 | 100 | # store best parameter combination 101 | if cv.accuracy > best_accuracy: 102 | logger.info("best_accuracy=%s" % (cv.accuracy)) 103 | best_accuracy = cv.accuracy 104 | best_parameter.C, best_parameter.gamma = C, gamma 105 | 106 | logger.info("%d-CV Result = %.2f." % (k, cv.accuracy)) 107 | 108 | # set best parameter combination to best found 109 | return best_parameter, results 110 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import numpy as np 36 | import random 37 | from scipy import ndimage 38 | 39 | 40 | def read_image(filename): 41 | imarr = np.array([]) 42 | try: 43 | im = Image.open(os.path.join(filename)) 44 | im = im.convert("L") # convert to greyscale 45 | imarr = np.array(im, dtype=np.uint8) 46 | except IOError as (errno, strerror): 47 | print "I/O error({0}): {1}".format(errno, strerror) 48 | except: 49 | print "Cannot open image." 50 | return imarr 51 | 52 | 53 | def as_row_matrix(X): 54 | """ 55 | Creates a row-matrix from multi-dimensional data items in list l. 56 | 57 | X [list] List with multi-dimensional data. 58 | """ 59 | if len(X) == 0: 60 | return np.array([]) 61 | total = 1 62 | for i in range(0, np.ndim(X[0])): 63 | total = total * X[0].shape[i] 64 | mat = np.empty([0, total], dtype=X[0].dtype) 65 | for row in X: 66 | mat = np.append(mat, row.reshape(1, -1), axis=0) # same as vstack 67 | return np.asmatrix(mat) 68 | 69 | 70 | def as_column_matrix(X): 71 | """ 72 | Creates a column-matrix from multi-dimensional data items in list l. 73 | 74 | X [list] List with multi-dimensional data. 75 | """ 76 | if len(X) == 0: 77 | return np.array([]) 78 | total = 1 79 | for i in range(0, np.ndim(X[0])): 80 | total = total * X[0].shape[i] 81 | mat = np.empty([total, 0], dtype=X[0].dtype) 82 | for col in X: 83 | mat = np.append(mat, col.reshape(-1, 1), axis=1) # same as hstack 84 | return np.asmatrix(mat) 85 | 86 | 87 | def minmax_normalize(X, low, high, minX=None, maxX=None, dtype=np.float): 88 | """ min-max normalize a given matrix to given range [low,high]. 89 | 90 | Args: 91 | X [rows x columns] input data 92 | low [numeric] lower bound 93 | high [numeric] upper bound 94 | """ 95 | if minX is None: 96 | minX = np.min(X) 97 | if maxX is None: 98 | maxX = np.max(X) 99 | minX = float(minX) 100 | maxX = float(maxX) 101 | # Normalize to [0...1]. 102 | X = X - minX 103 | X = X / (maxX - minX) 104 | # Scale to [low...high]. 105 | X = X * (high - low) 106 | X = X + low 107 | return np.asarray(X, dtype=dtype) 108 | 109 | 110 | def zscore(X): 111 | X = np.asanyarray(X) 112 | mean = X.mean() 113 | std = X.std() 114 | X = (X - mean) / std 115 | return X, mean, std 116 | 117 | 118 | def shuffle(X, y): 119 | idx = np.argsort([random.random() for i in xrange(y.shape[0])]) 120 | return X[:, idx], y[idx] 121 | 122 | 123 | def shuffle_array(X, y): 124 | """ Shuffles two arrays! 125 | """ 126 | idx = np.argsort([random.random() for i in xrange(len(y))]) 127 | X = [X[i] for i in idx] 128 | y = [y[i] for i in idx] 129 | return (X, y) 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /src/ocvfacerec/facerec/visual.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | import os as os 36 | from ocvfacerec.facerec.normalization import minmax 37 | import numpy as np 38 | import matplotlib.pyplot as plt 39 | import matplotlib.cm as cm 40 | # try to import the PIL Image module 41 | try: 42 | from PIL import Image 43 | except ImportError: 44 | import Image 45 | import math as math 46 | 47 | 48 | def create_font(fontname='Tahoma', fontsize=10): 49 | return {'fontname': fontname, 'fontsize': fontsize} 50 | 51 | 52 | def plot_gray(X, sz=None, filename=None): 53 | if not sz is None: 54 | X = X.reshape(sz) 55 | X = minmax(I, 0, 255) 56 | fig = plt.figure() 57 | implot = plt.imshow(np.asarray(Ig), cmap=cm.gray) 58 | if filename is None: 59 | plt.show() 60 | else: 61 | fig.savefig(filename, format="png", transparent=False) 62 | 63 | 64 | def plot_eigenvectors(eigenvectors, num_components, sz, filename=None, start_component=0, rows=None, cols=None, 65 | title="Subplot", color=True): 66 | if (rows is None) or (cols is None): 67 | rows = cols = int(math.ceil(np.sqrt(num_components))) 68 | num_components = np.min(num_components, eigenvectors.shape[1]) 69 | fig = plt.figure() 70 | for i in range(start_component, num_components): 71 | vi = eigenvectors[0:, i].copy() 72 | vi = minmax(np.asarray(vi), 0, 255, dtype=np.uint8) 73 | vi = vi.reshape(sz) 74 | 75 | ax0 = fig.add_subplot(rows, cols, (i - start_component) + 1) 76 | 77 | plt.setp(ax0.get_xticklabels(), visible=False) 78 | plt.setp(ax0.get_yticklabels(), visible=False) 79 | plt.title("%s #%d" % (title, i), create_font('Tahoma', 10)) 80 | if color: 81 | implot = plt.imshow(np.asarray(vi)) 82 | else: 83 | implot = plt.imshow(np.asarray(vi), cmap=cm.grey) 84 | if filename is None: 85 | fig.show() 86 | else: 87 | fig.savefig(filename, format="png", transparent=False) 88 | 89 | 90 | def subplot(title, images, rows, cols, sptitle="subplot", sptitles=[], colormap=cm.gray, ticks_visible=True, 91 | filename=None): 92 | fig = plt.figure() 93 | # main title 94 | fig.text(.5, .95, title, horizontalalignment='center') 95 | for i in xrange(len(images)): 96 | ax0 = fig.add_subplot(rows, cols, (i + 1)) 97 | plt.setp(ax0.get_xticklabels(), visible=False) 98 | plt.setp(ax0.get_yticklabels(), visible=False) 99 | if len(sptitles) == len(images): 100 | plt.title("%s #%s" % (sptitle, str(sptitles[i])), create_font('Tahoma', 10)) 101 | else: 102 | plt.title("%s #%d" % (sptitle, (i + 1)), create_font('Tahoma', 10)) 103 | plt.imshow(np.asarray(images[i]), cmap=colormap) 104 | if filename is None: 105 | plt.show() 106 | else: 107 | fig.savefig(filename) 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/ocvfacerec/helper/PersonWrapper.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Feb 9, 2015 3 | 4 | @author: nkoester 5 | ''' 6 | 7 | class PersonWrapper(object): 8 | ''' 9 | Simple class representing a person 10 | ''' 11 | 12 | position = None 13 | name = None 14 | reliability = None 15 | 16 | def __init__(self, _position, _name, _reliability, _image_size): 17 | ''' 18 | Constructor. 19 | ''' 20 | #bounding box of the person 21 | self.x0, self.y0, self.x1, self.y1 = _position 22 | self.position = self._person_center() 23 | self.name = _name 24 | self.reliability = _reliability 25 | self.image_width = _image_size[0] 26 | self.image_height = _image_size[1] 27 | self._import_done = False 28 | 29 | def _person_center(self): 30 | mid_x = float(self.x1 + (self.x1 - self.x0) * 0.5) 31 | mid_y = float(self.y1 + (self.y1 - self.y0) * 0.5) 32 | mid_z = float(self.x1 - self.x0) 33 | self.position = mid_x, mid_y, mid_z 34 | return (mid_x, mid_y, mid_z) 35 | 36 | def to_ros_msg(self): 37 | # TODO: export the middleware knowledge to this class -> further 38 | # encapsulation of middleware && less duplicated code. 39 | raise Exception("Not implemented yet.") 40 | 41 | def to_rsb_msg(self): 42 | # Creates a RST representation of this person 43 | # The RST datatype used here: 44 | # [* HeadObjects] 45 | # ** (repeated) HeadObject 46 | # *** LabledFace 47 | # **** Name 48 | # **** Face 49 | # ***** Confidence 50 | # ***** BoundingBox 51 | 52 | from rstsandbox.vision.HeadObject_pb2 import HeadObject 53 | from rst.vision.Face_pb2 import Face 54 | from rst.geometry.BoundingBox_pb2 import BoundingBox 55 | from rst.math.Vec2DInt_pb2 import Vec2DInt 56 | 57 | new_head = HeadObject() 58 | new_labled_face = new_head.faces.add() 59 | new_labled_face.label = self.name 60 | new_labled_face.face.confidence = self.reliability 61 | new_labled_face.face.region.top_left.x = int(self.x0) 62 | new_labled_face.face.region.top_left.y = int(self.y0) 63 | new_labled_face.face.region.width = int(self.x1 - self.x0) 64 | new_labled_face.face.region.height = int(self.y1 - self.y0) 65 | new_labled_face.face.region.image_width = int(self.image_width) 66 | new_labled_face.face.region.image_height = int(self.image_height) 67 | 68 | return new_head 69 | -------------------------------------------------------------------------------- /src/ocvfacerec/helper/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/ocvfacerec/helper/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | 36 | import os 37 | import cv 38 | import cv2 39 | import errno 40 | import numpy as np 41 | import itertools as it 42 | from contextlib import contextmanager 43 | 44 | image_extensions = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff', '.pbm', '.pgm', '.ppm'] 45 | 46 | 47 | class Bunch(object): 48 | def __init__(self, **kw): 49 | self.__dict__.update(kw) 50 | 51 | def __str__(self): 52 | return str(self.__dict__) 53 | 54 | 55 | def splitfn(fn): 56 | path, fn = os.path.split(fn) 57 | name, ext = os.path.splitext(fn) 58 | return path, name, ext 59 | 60 | 61 | def anorm2(a): 62 | return (a * a).sum(-1) 63 | 64 | 65 | def anorm(a): 66 | return np.sqrt(anorm2(a)) 67 | 68 | 69 | def homotrans(H, x, y): 70 | xs = H[0, 0] * x + H[0, 1] * y + H[0, 2] 71 | ys = H[1, 0] * x + H[1, 1] * y + H[1, 2] 72 | s = H[2, 0] * x + H[2, 1] * y + H[2, 2] 73 | return xs / s, ys / s 74 | 75 | 76 | def to_rect(a): 77 | a = np.ravel(a) 78 | if len(a) == 2: 79 | a = (0, 0, a[0], a[1]) 80 | return np.array(a, np.float64).reshape(2, 2) 81 | 82 | 83 | def rect2rect_mtx(src, dst): 84 | src, dst = to_rect(src), to_rect(dst) 85 | cx, cy = (dst[1] - dst[0]) / (src[1] - src[0]) 86 | tx, ty = dst[0] - src[0] * (cx, cy) 87 | M = np.float64([[cx, 0, tx], 88 | [0, cy, ty], 89 | [0, 0, 1]]) 90 | return M 91 | 92 | 93 | def lookat(eye, target, up=(0, 0, 1)): 94 | fwd = np.asarray(target, np.float64) - eye 95 | fwd /= anorm(fwd) 96 | right = np.cross(fwd, up) 97 | right /= anorm(right) 98 | down = np.cross(fwd, right) 99 | R = np.float64([right, down, fwd]) 100 | tvec = -np.dot(R, eye) 101 | return R, tvec 102 | 103 | 104 | def mtx2rvec(R): 105 | w, u, vt = cv2.SVDecomp(R - np.eye(3)) 106 | p = vt[0] + u[:, 0] * w[0] # same as np.dot(R, vt[0]) 107 | c = np.dot(vt[0], p) 108 | s = np.dot(vt[1], p) 109 | axis = np.cross(vt[0], vt[1]) 110 | return axis * np.arctan2(s, c) 111 | 112 | 113 | def draw_str(dst, (x, y), s): 114 | cv2.putText(dst, s, (x + 1, y + 1), cv2.FONT_HERSHEY_PLAIN, 1.0, (0, 0, 0), thickness=2, lineType=cv2.CV_AA) 115 | cv2.putText(dst, s, (x, y), cv2.FONT_HERSHEY_PLAIN, 1.0, (255, 255, 255), lineType=cv2.CV_AA) 116 | 117 | 118 | class Sketcher: 119 | def __init__(self, windowname, dests, colors_func): 120 | self.prev_pt = None 121 | self.windowname = windowname 122 | self.dests = dests 123 | self.colors_func = colors_func 124 | self.dirty = False 125 | self.show() 126 | cv2.setMouseCallback(self.windowname, self.on_mouse) 127 | 128 | def show(self): 129 | cv2.imshow(self.windowname, self.dests[0]) 130 | 131 | def on_mouse(self, event, x, y, flags, param): 132 | pt = (x, y) 133 | if event == cv2.EVENT_LBUTTONDOWN: 134 | self.prev_pt = pt 135 | if self.prev_pt and flags & cv2.EVENT_FLAG_LBUTTON: 136 | for dst, color in zip(self.dests, self.colors_func()): 137 | cv2.line(dst, self.prev_pt, pt, color, 5) 138 | self.dirty = True 139 | self.prev_pt = pt 140 | self.show() 141 | else: 142 | self.prev_pt = None 143 | 144 | 145 | # palette data from matplotlib/_cm.py 146 | _jet_data = {'red': ((0., 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89, 1, 1), 147 | (1, 0.5, 0.5)), 148 | 'green': ((0., 0, 0), (0.125, 0, 0), (0.375, 1, 1), (0.64, 1, 1), 149 | (0.91, 0, 0), (1, 0, 0)), 150 | 'blue': ((0., 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1), (0.65, 0, 0), 151 | (1, 0, 0))} 152 | 153 | cmap_data = {'jet': _jet_data} 154 | 155 | 156 | def make_cmap(name, n=256): 157 | data = cmap_data[name] 158 | xs = np.linspace(0.0, 1.0, n) 159 | channels = [] 160 | eps = 1e-6 161 | for ch_name in ['blue', 'green', 'red']: 162 | ch_data = data[ch_name] 163 | xp, yp = [], [] 164 | for x, y1, y2 in ch_data: 165 | xp += [x, x + eps] 166 | yp += [y1, y2] 167 | ch = np.interp(xs, xp, yp) 168 | channels.append(ch) 169 | return np.uint8(np.array(channels).T * 255) 170 | 171 | 172 | def nothing(*arg, **kw): 173 | pass 174 | 175 | 176 | def clock(): 177 | return cv2.getTickCount() / cv2.getTickFrequency() 178 | 179 | 180 | @contextmanager 181 | def Timer(msg): 182 | print msg, '...', 183 | start = clock() 184 | try: 185 | yield 186 | finally: 187 | print "%.2f ms" % ((clock() - start) * 1000) 188 | 189 | 190 | class StatValue: 191 | def __init__(self, smooth_coef=0.5): 192 | self.value = None 193 | self.smooth_coef = smooth_coef 194 | 195 | def update(self, v): 196 | if self.value is None: 197 | self.value = v 198 | else: 199 | c = self.smooth_coef 200 | self.value = c * self.value + (1.0 - c) * v 201 | 202 | 203 | class RectSelector: 204 | def __init__(self, win, callback): 205 | self.win = win 206 | self.callback = callback 207 | cv2.setMouseCallback(win, self.onmouse) 208 | self.drag_start = None 209 | self.drag_rect = None 210 | 211 | def onmouse(self, event, x, y, flags, param): 212 | x, y = np.int16([x, y]) # BUG 213 | if event == cv2.EVENT_LBUTTONDOWN: 214 | self.drag_start = (x, y) 215 | if self.drag_start: 216 | if flags & cv2.EVENT_FLAG_LBUTTON: 217 | xo, yo = self.drag_start 218 | x0, y0 = np.minimum([xo, yo], [x, y]) 219 | x1, y1 = np.maximum([xo, yo], [x, y]) 220 | self.drag_rect = None 221 | if x1 - x0 > 0 and y1 - y0 > 0: 222 | self.drag_rect = (x0, y0, x1, y1) 223 | else: 224 | rect = self.drag_rect 225 | self.drag_start = None 226 | self.drag_rect = None 227 | if rect: 228 | self.callback(rect) 229 | 230 | def draw(self, vis): 231 | if not self.drag_rect: 232 | return False 233 | x0, y0, x1, y1 = self.drag_rect 234 | cv2.rectangle(vis, (x0, y0), (x1, y1), (0, 255, 0), 2) 235 | return True 236 | 237 | @property 238 | def dragging(self): 239 | return self.drag_rect is not None 240 | 241 | 242 | def grouper(n, iterable, fillvalue=None): 243 | '''grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx''' 244 | args = [iter(iterable)] * n 245 | return it.izip_longest(fillvalue=fillvalue, *args) 246 | 247 | 248 | def mosaic(w, imgs): 249 | '''Make a grid from images. 250 | 251 | w -- number of grid columns 252 | imgs -- images (must have same size and format) 253 | ''' 254 | imgs = iter(imgs) 255 | img0 = imgs.next() 256 | pad = np.zeros_like(img0) 257 | imgs = it.chain([img0], imgs) 258 | rows = grouper(w, imgs, pad) 259 | return np.vstack(map(np.hstack, rows)) 260 | 261 | 262 | def getsize(img): 263 | h, w = img.shape[:2] 264 | return w, h 265 | 266 | 267 | def mdot(*args): 268 | return reduce(np.dot, args) 269 | 270 | 271 | def draw_keypoints(vis, keypoints, color=(0, 255, 255)): 272 | for kp in keypoints: 273 | x, y = kp.pt 274 | cv2.circle(vis, (int(x), int(y)), 2, color) 275 | 276 | 277 | def detect_face(image, face_cascade, return_image=False): 278 | # This function takes a grey scale cv image and finds 279 | # the patterns defined in the haarcascade function 280 | min_size = (20, 20) 281 | haar_scale = 1.1 282 | min_neighbors = 5 283 | haar_flags = 0 284 | 285 | # Equalize histogram 286 | cv.EqualizeHist(image, image) 287 | 288 | # Detect faces 289 | faces = cv.HaarDetectObjects(image, face_cascade, cv.CreateMemStorage(0), haar_scale, min_neighbors, haar_flags, 290 | min_size) 291 | 292 | # If faces are found 293 | if faces and return_image: 294 | for ((x, y, w, h), n) in faces: 295 | # Convert bounding box to two CvPoints 296 | pt1 = (int(x), int(y)) 297 | pt2 = (int(x + w), int(y + h)) 298 | cv.Rectangle(image, pt1, pt2, cv.RGB(255, 0, 0), 5, 8, 0) 299 | if return_image: 300 | return image 301 | else: 302 | return faces 303 | 304 | 305 | def pil2_cvgrey(pil_im): 306 | # Convert a PIL image to a greyscale cv image 307 | # from: http://pythonpath.wordpress.com/2012/05/08/pil-to-opencv-image/ 308 | pil_im = pil_im.convert('L') 309 | cv_im = cv.CreateImageHeader(pil_im.size, cv.IPL_DEPTH_8U, 1) 310 | cv.SetData(cv_im, pil_im.tostring(), pil_im.size[0]) 311 | return cv_im 312 | 313 | 314 | def img_crop(image, crop_box, box_scale=1): 315 | # Crop a PIL image with the provided box [x(left), y(upper), w(width), h(height)] 316 | # Calculate scale factors 317 | x_delta = max(crop_box[2] * (box_scale - 1), 0) 318 | y_delta = max(crop_box[3] * (box_scale - 1), 0) 319 | # Convert cv box to PIL box [left, upper, right, lower] 320 | pil_box = [crop_box[0] - x_delta, crop_box[1] - y_delta, crop_box[0] + crop_box[2] + x_delta, 321 | crop_box[1] + crop_box[3] + y_delta] 322 | 323 | return image.crop(pil_box) 324 | 325 | 326 | def face_crop_single_image(pil_image, face_cascade, box_scale=1): 327 | 328 | cv_im = pil2_cvgrey(pil_image) 329 | faces = detect_face(cv_im, face_cascade) 330 | face_list = [] 331 | cropped_image = None 332 | if faces: 333 | for face in faces: 334 | cropped_image = img_crop(pil_image, face[0], box_scale=box_scale) 335 | face_list.append(cropped_image) 336 | return cropped_image 337 | 338 | 339 | def mkdir_p(path): 340 | try: 341 | os.makedirs(path) 342 | except OSError as exc: 343 | if exc.errno == errno.EEXIST and os.path.isdir(path): 344 | pass 345 | else: 346 | raise -------------------------------------------------------------------------------- /src/ocvfacerec/helper/video.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | 36 | import cv2 37 | import numpy as np 38 | import ocvfacerec.helper.common 39 | from numpy import pi, sin, cos 40 | 41 | 42 | class VideoSynthBase(object): 43 | def __init__(self, size=None, noise=0.0, bg=None, **params): 44 | self.bg = None 45 | self.frame_size = (640, 480) 46 | if bg is not None: 47 | self.bg = cv2.imread(bg, 1) 48 | h, w = self.bg.shape[:2] 49 | self.frame_size = (w, h) 50 | 51 | if size is not None: 52 | w, h = map(int, size.split('x')) 53 | self.frame_size = (w, h) 54 | self.bg = cv2.resize(self.bg, self.frame_size) 55 | 56 | self.noise = float(noise) 57 | 58 | def render(self, dst): 59 | pass 60 | 61 | def read(self, dst=None): 62 | w, h = self.frame_size 63 | 64 | if self.bg is None: 65 | buf = np.zeros((h, w, 3), np.uint8) 66 | else: 67 | buf = self.bg.copy() 68 | 69 | self.render(buf) 70 | 71 | if self.noise > 0.0: 72 | noise = np.zeros((h, w, 3), np.int8) 73 | cv2.randn(noise, np.zeros(3), np.ones(3) * 255 * self.noise) 74 | buf = cv2.add(buf, noise, dtype=cv2.CV_8UC3) 75 | return True, buf 76 | 77 | 78 | class Chess(VideoSynthBase): 79 | def __init__(self, **kw): 80 | super(Chess, self).__init__(**kw) 81 | 82 | w, h = self.frame_size 83 | 84 | self.grid_size = sx, sy = 10, 7 85 | white_quads = [] 86 | black_quads = [] 87 | for i, j in np.ndindex(sy, sx): 88 | q = [[j, i, 0], [j + 1, i, 0], [j + 1, i + 1, 0], [j, i + 1, 0]] 89 | [white_quads, black_quads][(i + j) % 2].append(q) 90 | self.white_quads = np.float32(white_quads) 91 | self.black_quads = np.float32(black_quads) 92 | 93 | fx = 0.9 94 | self.K = np.float64([[fx * w, 0, 0.5 * (w - 1)], 95 | [0, fx * w, 0.5 * (h - 1)], 96 | [0.0, 0.0, 1.0]]) 97 | 98 | self.dist_coef = np.float64([-0.2, 0.1, 0, 0]) 99 | self.t = 0 100 | 101 | def draw_quads(self, img, quads, color=(0, 255, 0)): 102 | img_quads = cv2.projectPoints(quads.reshape(-1, 3), self.rvec, self.tvec, self.K, self.dist_coef)[0] 103 | img_quads.shape = quads.shape[:2] + (2,) 104 | for q in img_quads: 105 | cv2.fillConvexPoly(img, np.int32(q * 4), color, cv2.CV_AA, shift=2) 106 | 107 | def render(self, dst): 108 | t = self.t 109 | self.t += 1.0 / 30.0 110 | 111 | sx, sy = self.grid_size 112 | center = np.array([0.5 * sx, 0.5 * sy, 0.0]) 113 | phi = pi / 3 + sin(t * 3) * pi / 8 114 | c, s = cos(phi), sin(phi) 115 | ofs = np.array([sin(1.2 * t), cos(1.8 * t), 0]) * sx * 0.2 116 | eye_pos = center + np.array([cos(t) * c, sin(t) * c, s]) * 15.0 + ofs 117 | target_pos = center + ofs 118 | 119 | R, self.tvec = common.lookat(eye_pos, target_pos) 120 | self.rvec = common.mtx2rvec(R) 121 | 122 | self.draw_quads(dst, self.white_quads, (245, 245, 245)) 123 | self.draw_quads(dst, self.black_quads, (10, 10, 10)) 124 | 125 | 126 | classes = dict(chess=Chess) 127 | 128 | 129 | def create_capture(source): 130 | ''' 131 | source: or '' or '' or 'synth:' 132 | ''' 133 | try: 134 | source = int(source) 135 | except ValueError: 136 | pass 137 | else: 138 | return cv2.VideoCapture(source) 139 | source = str(source).strip() 140 | if source.startswith('synth'): 141 | ss = filter(None, source.split(':')) 142 | params = dict(s.split('=') for s in ss[1:]) 143 | try: 144 | Class = classes[params['class']] 145 | except: 146 | Class = VideoSynthBase 147 | 148 | return Class(**params) 149 | return cv2.VideoCapture(source) 150 | 151 | 152 | presets = dict( 153 | empty='synth:', 154 | lena='synth:bg=../cpp/lena.jpg:noise=0.1', 155 | chess='synth:class=chess:bg=../cpp/lena.jpg:noise=0.1:size=640x480' 156 | ) 157 | 158 | if __name__ == '__main__': 159 | import sys 160 | import getopt 161 | 162 | print 'USAGE: video.py [--shotdir ] [source0] [source1] ...' 163 | print "source: '' or '' or 'synth:'" 164 | print 165 | 166 | args, sources = getopt.getopt(sys.argv[1:], '', 'shotdir=') 167 | args = dict(args) 168 | shotdir = args.get('--shotdir', '.') 169 | if len(sources) == 0: 170 | sources = [presets['chess']] 171 | 172 | print 'Press SPACE to save current frame' 173 | 174 | caps = map(create_capture, sources) 175 | shot_idx = 0 176 | while True: 177 | imgs = [] 178 | for i, cap in enumerate(caps): 179 | ret, img = cap.read() 180 | imgs.append(img) 181 | cv2.imshow('capture %d' % i, img) 182 | ch = cv2.waitKey(1) 183 | if ch == 27: 184 | break 185 | if ch == ord(' '): 186 | for i, img in enumerate(imgs): 187 | fn = '%s/shot_%d_%03d.bmp' % (shotdir, i, shot_idx) 188 | cv2.imwrite(fn, img) 189 | print fn, 'saved' 190 | shot_idx += 1 191 | -------------------------------------------------------------------------------- /src/ocvfacerec/mwconnector/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/ocvfacerec/mwconnector/abtractconnector.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | 36 | class MiddlewareConnector(object): 37 | # TODO: USE ABC for Abstraction?! 38 | pass -------------------------------------------------------------------------------- /src/ocvfacerec/mwconnector/rosconnector.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # STD Imports 36 | import numpy as np 37 | from Queue import Queue 38 | 39 | # ROS IMPORTS 40 | import rospy 41 | from cv_bridge import CvBridge 42 | from std_msgs.msg import String 43 | from std_msgs.msg import Header 44 | from sensor_msgs.msg import Image 45 | from people_msgs.msg import People 46 | from people_msgs.msg import Person 47 | from geometry_msgs.msg import Point 48 | 49 | # OCVF Imports 50 | from ocvfacerec.mwconnector.abtractconnector import MiddlewareConnector 51 | 52 | 53 | class ROSConnector(MiddlewareConnector): 54 | # TODO Implement 55 | def __init__(self): 56 | self.bridge = CvBridge() 57 | self.restart_subscriber = None 58 | self.restart_publisher = None 59 | self.image_subscriber = None 60 | self.last_image = None 61 | self.last_train = None 62 | 63 | def add_last_image(self, image_data): 64 | try: 65 | self.last_image.get(False) 66 | except Exception, e: 67 | pass 68 | try: 69 | cv_image = self.bridge.imgmsg_to_cv2(image_data, "bgr8") 70 | # self.last_image.put(cv_image, False) 71 | self.last_image.put(np.asarray(cv_image[:, :]), False) 72 | except Exception, e: 73 | pass 74 | 75 | def add_last_train(self, msg): 76 | try: 77 | self.last_train.get(False) 78 | except Exception, e: 79 | pass 80 | self.last_train.put(str(msg.data), False) 81 | 82 | def activate(self, image_source, retrain_source, restart_target): 83 | self.image_subscriber = rospy.Subscriber(image_source, Image, self.add_last_image, queue_size=1) 84 | self.last_image = Queue(1) 85 | 86 | self.restart_subscriber = rospy.Subscriber(retrain_source, String, self.add_last_train, queue_size=1) 87 | self.last_train = Queue(1) 88 | 89 | self.restart_publisher = rospy.Publisher(restart_target, String, queue_size=1) 90 | rospy.init_node('ros_connector_trainer', anonymous=False) 91 | 92 | def deactivate(self): 93 | self.restart_subscriber.unregister() 94 | self.image_subscriber.unregister() 95 | 96 | def restart_classifier(self): 97 | # Send a short "restart" event to the recognizer 98 | msg = "restart" 99 | self.restart_publisher.publish(msg) 100 | 101 | def wait_for_start_training(self): 102 | return self.last_train.get(True, timeout=1) 103 | 104 | def get_image(self): 105 | return self.last_image.get(True, timeout=10) -------------------------------------------------------------------------------- /src/ocvfacerec/mwconnector/rsbconnector.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # STD Imports 36 | import numpy as np 37 | from Queue import Queue 38 | 39 | # RSB Specifics 40 | import rsb 41 | import rstsandbox 42 | from rsb.converter import ProtocolBufferConverter 43 | from rsb.converter import registerGlobalConverter 44 | from rstconverters.opencv import IplimageConverter 45 | 46 | # OCVF Imports 47 | from ocvfacerec.mwconnector.abtractconnector import MiddlewareConnector 48 | 49 | 50 | class RSBConnector(MiddlewareConnector): 51 | def __init__(self): 52 | pass 53 | 54 | def add_last_image(self, image_event): 55 | try: 56 | self.lastImage.get(False) 57 | except Exception, e: 58 | pass 59 | self.lastImage.put(np.asarray(image_event.data[:, :]), False) 60 | 61 | def add_last_train(self, retrain_event): 62 | try: 63 | self.last_train.get(False) 64 | except Exception, e: 65 | pass 66 | self.last_train.put(retrain_event.data, False) 67 | 68 | def activate(self, image_source, retrain_source, restart_target): 69 | registerGlobalConverter(IplimageConverter()) 70 | rsb.setDefaultParticipantConfig(rsb.ParticipantConfig.fromDefaultSources()) 71 | # Listen to Image Events 72 | self.image_listener = rsb.createListener(image_source) 73 | self.lastImage = Queue(1) 74 | self.image_listener.addHandler(self.add_last_image) 75 | # Listen to Re-Train events with a Person Label 76 | self.training_start = rsb.createListener(retrain_source) 77 | self.last_train = Queue(10) 78 | self.training_start.addHandler(self.add_last_train) 79 | # Publisher to Restart Recognizer 80 | self.restart_publisher = rsb.createInformer(restart_target, dataType=str) 81 | 82 | def deactivate(self): 83 | self.image_listener.deactivate() 84 | self.training_start.deactivate() 85 | self.restart_publisher.deactivate() 86 | 87 | def restart_classifier(self): 88 | # Send a short "restart" event to the recognizer 89 | self.restart_publisher.publishData("restart") 90 | 91 | def wait_for_start_training(self): 92 | return self.last_train.get(True, timeout=1) 93 | 94 | def get_image(self): 95 | return self.lastImage.get(True, timeout=10) -------------------------------------------------------------------------------- /src/ocvfacerec/trainer/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/ocvfacerec/trainer/thetrainer.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # STD Imports 36 | import os 37 | import cv2 38 | import sys 39 | import logging 40 | import numpy as np 41 | 42 | # OCVF imports 43 | from ocvfacerec.facerec.feature import Fisherfaces 44 | from ocvfacerec.facerec.model import PredictableModel 45 | from ocvfacerec.facerec.distance import EuclideanDistance 46 | from ocvfacerec.facerec.classifier import NearestNeighbor 47 | from ocvfacerec.facerec.validation import KFoldCrossValidation 48 | from ocvfacerec.facerec.serialization import save_model 49 | 50 | 51 | class ExtendedPredictableModel(PredictableModel): 52 | 53 | """ Subclasses the PredictableModel to store some more 54 | information, so we don't need to pass the dataset 55 | on each program call... 56 | """ 57 | 58 | def __init__(self, feature, classifier, image_size, subject_names): 59 | PredictableModel.__init__(self, feature=feature, classifier=classifier) 60 | self.image_size = image_size 61 | self.subject_names = subject_names 62 | 63 | 64 | class TheTrainer(): 65 | 66 | def __init__(self, _data_set, _image_size, _model_filename, _numfolds=None): 67 | self.dataset = _data_set 68 | self.image_size = _image_size 69 | self.model_filename = _model_filename 70 | self.numfolds = _numfolds 71 | 72 | @staticmethod 73 | def read_images(path, image_size=None): 74 | """Reads the images in a given folder, resizes images on the fly if size is given. 75 | 76 | Args: 77 | path: Path to a folder with subfolders representing the subjects (persons). 78 | sz: A tuple with the size Resizes 79 | 80 | Returns: 81 | A list [X, y, folder_names] 82 | 83 | X: The images, which is a Python list of numpy arrays. 84 | y: The corresponding labels (the unique number of the subject, person) in a Python list. 85 | folder_names: The names of the folder, so you can display it in a prediction. 86 | """ 87 | c = 0 88 | X = [] 89 | y = [] 90 | folder_names = [] 91 | for dirname, dirnames, filenames in os.walk(path): 92 | for subdirname in dirnames: 93 | # only if there are images in the folder 94 | if os.listdir(os.path.join(dirname, subdirname)): 95 | folder_names.append(subdirname) 96 | subject_path = os.path.join(dirname, subdirname) 97 | for filename in os.listdir(subject_path): 98 | try: 99 | im = cv2.imread(os.path.join(subject_path, filename), cv2.IMREAD_GRAYSCALE) 100 | # Resize to given size (if given) 101 | if image_size is not None: 102 | im = cv2.resize(im, image_size) 103 | X.append(np.asarray(im, dtype=np.uint8)) 104 | y.append(c) 105 | except IOError, (errno, strerror): 106 | print ">> I/O error({0}): {1}".format(errno, strerror) 107 | except: 108 | print ">> Unexpected error:", sys.exc_info()[0] 109 | raise 110 | c = c + 1 111 | return [X, y, folder_names] 112 | 113 | @staticmethod 114 | def get_model(image_size, subject_names): 115 | """ This method returns the PredictableModel which is used to learn a model 116 | for possible further usage. If you want to define your own model, this 117 | is the method to return it from! 118 | """ 119 | # Define the Fisherfaces Method as Feature Extraction method: 120 | feature = Fisherfaces() 121 | # Define a 1-NN classifier with Euclidean Distance: 122 | classifier = NearestNeighbor(dist_metric=EuclideanDistance(), k=1) 123 | # Return the model as the combination: 124 | return ExtendedPredictableModel(feature=feature, classifier=classifier, image_size=image_size, subject_names=subject_names) 125 | 126 | def read_subject_names(path): 127 | """Reads the folders of a given directory, which are used to display some 128 | meaningful name instead of simply displaying a number. 129 | 130 | Args: 131 | path: Path to a folder with subfolders representing the subjects (persons). 132 | 133 | Returns: 134 | folder_names: The names of the folder, so you can display it in a prediction. 135 | """ 136 | folder_names = [] 137 | for dirname, dirnames, filenames in os.walk(path): 138 | for subdirname in dirnames: 139 | folder_names.append(subdirname) 140 | return folder_names 141 | 142 | def train(self): 143 | # Check if the given dataset exists: 144 | if not os.path.exists(self.dataset): 145 | print ">> [Error] No Dataset Found at '%s'." % self.dataset 146 | sys.exit(1) 147 | # Reads the images, labels and folder_names from a given dataset. Images 148 | # are resized to given size on the fly: 149 | print ">> Loading Dataset <-- " + self.dataset 150 | [images, labels, subject_names] = self.read_images(self.dataset, self.image_size) 151 | # Zip us a {label, name} dict from the given data: 152 | list_of_labels = list(xrange(max(labels) + 1)) 153 | subject_dictionary = dict(zip(list_of_labels, subject_names)) 154 | # Get the model we want to compute: 155 | model = self.get_model(image_size=self.image_size, subject_names=subject_dictionary) 156 | # Sometimes you want to know how good the model may perform on the data 157 | # given, the script allows you to perform a k-fold Cross Validation before 158 | # the Detection & Recognition part starts: 159 | if self.numfolds is not None: 160 | print ">> Validating Model With %s Folds.." % self.numfolds 161 | # We want to have some log output, so set up a new logging handler 162 | # and point it to stdout: 163 | handler = logging.StreamHandler(sys.stdout) 164 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 165 | handler.setFormatter(formatter) 166 | # Add a handler to facerec modules, so we see what's going on inside: 167 | logger = logging.getLogger("facerec") 168 | logger.addHandler(handler) 169 | logger.setLevel(logging.DEBUG) 170 | # Perform the validation & print results: 171 | crossval = KFoldCrossValidation(model, k=self.numfolds) 172 | crossval.validate(images, labels) 173 | crossval.print_results() 174 | # Compute the model: 175 | print ">> Computing Model.." 176 | model.compute(images, labels) 177 | # And save the model, which uses Pythons pickle module: 178 | print ">> Saving Model.." 179 | save_model(self.model_filename, model) 180 | -------------------------------------------------------------------------------- /src/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012. 2 | # Philipp Wagner and 3 | # Florian Lier 4 | # Released to public domain under terms of the BSD Simplified license. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # * Neither the name of the organization nor the names of its contributors 14 | # may be used to endorse or promote products derived from this software 15 | # without specific prior written permission. 16 | # 17 | # See 18 | 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import os 33 | import sys 34 | from distutils.dir_util import copy_tree 35 | from sys import platform as _platform 36 | from setuptools import setup 37 | 38 | setup( 39 | name='ocvfacerec', 40 | version='0.1', 41 | description='OpenCV Facerecognizer', 42 | long_description='''OpenCV Facerecognizer (OCVFACEREC) is a simple 43 | application for face detection and recognition. It provides a set of 44 | classifiers, features, algorithms, operators and examples including 45 | distributed processing via ROS and RSB Middleware. It is _heavily_ based on 46 | Philipp Wagner's facerec Framework [https://github.com/bytefish/facerec]''', 47 | author='Philipp Wagner, Florian Lier, Norman Koester', 48 | author_email='flier[at]techfak.uni-bielefeld.de', 49 | url='https://github.com/warp1337/opencv_facerecognizer.git', 50 | license='BSD', 51 | classifiers=[ 52 | 'Intended Audience :: Science/Research', 53 | 'Intended Audience :: Developers', 54 | 'License :: OSI Approved', 55 | 'Operating System :: MacOS :: MacOS X', 56 | 'Operating System :: Microsoft :: Windows', 57 | 'Operating System :: POSIX', 58 | 'Programming Language :: Python', 59 | 'Topic :: Software Development', 60 | 'Topic :: Scientific/Engineering', 61 | 'Operating System :: Microsoft :: Windows', 62 | 'Operating System :: POSIX', 63 | 'Operating System :: Unix', 64 | 'Operating System :: MacOS', 65 | ], 66 | packages=['ocvfacerec', 'ocvfacerec/facedet', 'ocvfacerec/facerec', 'ocvfacerec/helper', 67 | 'ocvfacerec/trainer', 'ocvfacerec/mwconnector'], 68 | scripts=["bin/ocvf_recognizer.py", "bin/ocvf_recognizer_ros.py", "bin/ocvf_recognizer_rsb.py", 69 | "bin/ocvf_interactive_trainer.py", "bin/ocvf_retrain_rsb.py", "bin/ocvf_retrain_ros.py", 70 | "bin/ocvf_image_publisher_ros.py"], 71 | # Due to heavy dependencies (liblas, ATLAS, etc..) it is easier to install 'SciPy >= 0.14.0' 72 | # and PIL >= 1.1.7 using your Package Manager, i.e., sudo apt-get install python-scipy python-imaging-* 73 | install_requires=['NumPy >=1.8.1', 'matplotlib >= 1.2.0'], 74 | ) 75 | 76 | if _platform == "linux" or _platform == "linux2": 77 | if os.path.isdir("../data"): 78 | home = os.getenv("HOME") 79 | copy_tree('../data', str(home) + "/ocvf_data/") 80 | else: 81 | pass 82 | -------------------------------------------------------------------------------- /src/tools/face_cropper.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015. 2 | # Philipp Wagner and 3 | # Florian Lier and 4 | # Norman Koester 5 | # 6 | # 7 | # Released to public domain under terms of the BSD Simplified license. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the organization nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # See 21 | 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | # POSSIBILITY OF SUCH DAMAGE. 34 | 35 | # Source: http://www.lucaamore.com/?p=638 36 | 37 | # Adaption: Florian Lier | flier[at]techfak.uni-bielefeld. 38 | # + Name Prefix for images 39 | # + More Pythonic Syntax 40 | # + Cropped Images Folder 41 | # + Bounding Box Margin for Cropped Images 42 | 43 | import shutil 44 | import Image 45 | import glob 46 | import sys 47 | import os 48 | import cv 49 | 50 | 51 | def detect_face(image, face_cascade, return_image=False): 52 | 53 | # This function takes a grey scale cv image and finds 54 | # the patterns defined in the haarcascade function 55 | 56 | min_size = (20, 20) 57 | haar_scale = 1.1 58 | min_neighbors = 5 59 | haar_flags = 0 60 | 61 | # Equalize the histogram 62 | cv.EqualizeHist(image, image) 63 | 64 | # Detect the faces 65 | faces = cv.HaarDetectObjects( 66 | image, face_cascade, cv.CreateMemStorage(0), 67 | haar_scale, min_neighbors, haar_flags, min_size 68 | ) 69 | 70 | # If faces are found 71 | if faces and return_image: 72 | for ((x, y, w, h), n) in faces: 73 | # Convert bounding box to two CvPoints 74 | pt1 = (int(x), int(y)) 75 | pt2 = (int(x + w), int(y + h)) 76 | cv.Rectangle(image, pt1, pt2, cv.RGB(255, 0, 0), 5, 8, 0) 77 | 78 | if return_image: 79 | return image 80 | else: 81 | return faces 82 | 83 | 84 | def pil2_cvgrey(pil_im): 85 | # Convert a PIL image to a greyscale cv image 86 | # from: http://pythonpath.wordpress.com/2012/05/08/pil-to-opencv-image/ 87 | pil_im = pil_im.convert('L') 88 | cv_im = cv.CreateImageHeader(pil_im.size, cv.IPL_DEPTH_8U, 1) 89 | cv.SetData(cv_im, pil_im.tostring(), pil_im.size[0]) 90 | return cv_im 91 | 92 | 93 | def cv2_pil(cv_im): 94 | # Convert the cv image to a PIL image 95 | return Image.frombytes("L", cv.GetSize(cv_im), cv_im.tostring()) 96 | 97 | 98 | def img_crop(image, crop_box, box_scale=1): 99 | # Crop a PIL image with the provided box [x(left), y(upper), w(width), h(height)] 100 | 101 | # Calculate scale factors 102 | x_delta = max(crop_box[2] * (box_scale - 1), 0) 103 | y_delta = max(crop_box[3] * (box_scale - 1), 0) 104 | 105 | # Convert cv box to PIL box [left, upper, right, lower] 106 | pil_box = [crop_box[0] - x_delta, crop_box[1] - y_delta, crop_box[0] + crop_box[2] + x_delta, 107 | crop_box[1] + crop_box[3] + y_delta] 108 | 109 | return image.crop(pil_box) 110 | 111 | 112 | def face_crop(image_pattern, prefix, haar, box_scale=1): 113 | # Select one of the haarcascade files: 114 | # haarcascade_frontalface_alt.xml 115 | # haarcascade_frontalface_alt2.xml 116 | # haarcascade_frontalface_alt_tree.xml 117 | # haarcascade_frontalface_default.xml 118 | # haarcascade_profileface.xml 119 | face_cascade = cv.Load(haar) 120 | 121 | img_list = glob.glob(image_pattern) 122 | if len(img_list) <= 0: 123 | print '>> No Images Found' 124 | return 125 | 126 | img_count = 0 127 | 128 | for img in img_list: 129 | pil_im = Image.open(img) 130 | cv_im = pil2_cvgrey(pil_im) 131 | faces = detect_face(cv_im, face_cascade) 132 | if faces: 133 | n = 0 134 | for face in faces: 135 | cropped_image = img_crop(pil_im, face[0], box_scale=box_scale) 136 | f_name, ext = os.path.splitext(img) 137 | cropped_image_name = f_name + '_crop' + str(n) + ext 138 | cropped_image.save(cropped_image_name) 139 | 140 | dir_name = os.path.dirname(cropped_image_name) 141 | rename_name = dir_name + "/" + prefix + "_crop" + str(img_count) + ext 142 | os.rename(cropped_image_name, rename_name) 143 | 144 | cropped_folder = dir_name + '/cropped' 145 | 146 | if not os.path.exists(cropped_folder): 147 | os.mkdir(dir_name + '/cropped') 148 | 149 | shutil.move(rename_name, cropped_folder) 150 | 151 | print ">> Saving cropped image " + cropped_folder + '/' + prefix + "_crop" + str(img_count) + ext 152 | n += 1 153 | img_count += 1 154 | else: 155 | print '>> No faces found in image ', img 156 | 157 | 158 | # Crop all jpegs in a folder. Note: the code uses glob which follows unix shell rules. 159 | # Use the box_scale to scale the cropping area. 1=opencv box, 2=2x the width and height 160 | if __name__ == '__main__': 161 | if not len(sys.argv) > 3: 162 | print ">> USAGE: face_cropper.py " 163 | sys.exit(1) 164 | else: 165 | prefix = sys.argv[1] 166 | folder = sys.argv[2] 167 | haar = sys.argv[3] 168 | path = folder + '/*' 169 | print ">> Loading all images in path " + path 170 | face_crop(path, prefix, haar, box_scale=1) -------------------------------------------------------------------------------- /src/tools/rosbag2jpg.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------