├── README.md ├── opr.py ├── comm_ard.py ├── Camera_tracking.ino ├── tracking.ui └── Camera_tracker_main.py /README.md: -------------------------------------------------------------------------------- 1 | # face_tracking_camera 2 | 3 | program for face tracking camera project : 4 | littlefrenchkev.com/projects/face-tracking-camera 5 | 6 | python version = 3.6 7 | required library : 8 | -pyserial 9 | -pyqt5 10 | -------------------------------------------------------------------------------- /opr.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import math 3 | 4 | face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') 5 | 6 | def remap(value_to_map, new_range_min, new_range_max, old_range_min, old_range_max): 7 | 8 | remapped_val = (value_to_map - old_range_min) * (new_range_max - new_range_min) / (old_range_max - old_range_min) + new_range_min 9 | if(remapped_val>new_range_max): 10 | remapped_val = new_range_max 11 | elif (remapped_val < new_range_min): 12 | remapped_val = new_range_min 13 | 14 | return remapped_val 15 | 16 | def find_face(image_to_check, max_target_distance): 17 | gray = cv2.cvtColor(image_to_check, cv2.COLOR_BGR2GRAY) #convert image to black and white 18 | faces = face_cascade.detectMultiScale(gray, 1.2, 5) #look for faces 19 | 20 | 21 | if len(faces) >= 1: #if face(s) detected 22 | faces = list(faces)[0] #if several faces found use the first one 23 | 24 | x = faces[0] 25 | y = faces[1] 26 | w = faces[2] 27 | h = faces[3] 28 | 29 | center_face_X = int(x + w / 2) 30 | center_face_Y = int(y + h / 2) 31 | height, width, channels = image_to_check.shape 32 | 33 | distance_from_center_X = (center_face_X - width/2)/220 # why? can't remember why I did this 34 | distance_from_center_Y = (center_face_Y - height/2)/195 # why? 35 | 36 | target_distance = math.sqrt((distance_from_center_X*220)**2 + (distance_from_center_Y*195)**2) # calculate distance between image center and face center 37 | 38 | if target_distance < max_target_distance :#set added geometry colour 39 | locked = True 40 | color = (0, 255, 0) 41 | else: 42 | locked = False 43 | color = (0, 0, 255) 44 | 45 | 46 | cv2.rectangle(image_to_check,(center_face_X-10, center_face_Y), (center_face_X+10, center_face_Y), #draw first line of the cross 47 | color, 2) 48 | cv2.rectangle(image_to_check,(center_face_X, center_face_Y-10), (center_face_X, center_face_Y+10), #draw second line of the cross 49 | color,2) 50 | 51 | cv2.circle(image_to_check, (int(width/2), int(height/2)), int(max_target_distance) , color, 2) #draw circle 52 | 53 | return [True, image_to_check, distance_from_center_X, distance_from_center_Y, locked] 54 | 55 | else: 56 | return [False] 57 | -------------------------------------------------------------------------------- /comm_ard.py: -------------------------------------------------------------------------------- 1 | import serial 2 | 3 | class ard_connect(): 4 | def __init__(self, parent): 5 | self.parent = parent 6 | self.startMarker = 60 #utf-8 for '<' 7 | self.endMarker = 62 #utf-8 for '>' 8 | #self.ser = serial.Serial('COM3', 9600) 9 | print("ard created") 10 | 11 | 12 | def connect(self, port): 13 | try: 14 | self.ser = serial.Serial(port, 115200) 15 | self.waitForArduino() 16 | self.parent.is_connected = True 17 | return True 18 | except: 19 | print("Not able to connect on this port") 20 | return False 21 | 22 | def waitForArduino(self): 23 | 24 | msg = "" 25 | while msg.find("Hasta la vista baby") == -1: #string.find() return -1 if value not found 26 | 27 | while self.ser.inWaiting() == 0: #inWaiting() return number of bytes in buffer, equivalent of Serial.available in arduino 28 | pass 29 | 30 | msg = self.recvFromArduino() #return decoded serial data 31 | print(msg) # python3 requires parenthesis 32 | #print() 33 | 34 | def recvFromArduino(self): 35 | 36 | message_received = "" #message received start as an empty string 37 | x = "z" #next char read from serial . need to start as any value that is not an end- or startMarker 38 | byteCount = -1 #to allow for the fact that the last increment will be one too many 39 | 40 | # wait for the start character 41 | while ord(x) != self.startMarker: # ord() return utf-8 for a char(1 length string) ex: return 60 for char < 42 | x = self.ser.read() # loop until start marker found 43 | 44 | # save data until the end marker is found 45 | while ord(x) != self.endMarker: # loop until end marker found 46 | if ord(x) != self.startMarker: # if not start marker 47 | message_received = message_received + x.decode("utf-8") # add decoded char to string 48 | byteCount += 1 # WHY IS BYTECOUNT FOR? 49 | x = self.ser.read() # read next char 50 | 51 | return (message_received) 52 | 53 | def sendToArduino(self, sendStr): 54 | #print("sent to arduino func : ", sendStr) 55 | self.ser.write(sendStr.encode('utf-8')) # change for Python3 56 | #self.ser.write(sendStr) #send data as it is if only dealing with int 57 | 58 | def runTest(self, message_to_send): 59 | 60 | waitingForReply = False 61 | 62 | if waitingForReply == False: 63 | self.sendToArduino(message_to_send) # write message to serial 64 | waitingForReply = True 65 | 66 | if waitingForReply == True: #waitfor datafrom arduino 67 | while self.ser.inWaiting() == 0: # equivalent of Serial.available in arduino 68 | pass # loop until data available 69 | 70 | dataRecvd = self.recvFromArduino() # SPLIT STRING INTO INT TO HAVE USABLE DATA 71 | 72 | 73 | split_data = dataRecvd.split(",") 74 | #self.parent.update_LCD_display(split_data[0], split_data[1]) 75 | try: 76 | self.parent.current_pan = split_data[0] 77 | self.parent.current_tilt = split_data[1] 78 | self.parent.update_LCD_display() 79 | except: 80 | print("error split data : ", len(split_data)) 81 | 82 | #print("Reply Received " + dataRecvd) 83 | 84 | waitingForReply = False 85 | 86 | #print("===========") -------------------------------------------------------------------------------- /Camera_tracking.ino: -------------------------------------------------------------------------------- 1 | //Sketch based on work done by Robin2 on the arduino forum 2 | //more info here 3 | //https://forum.arduino.cc/index.php?topic=225329.msg1810764#msg1810764 4 | 5 | 6 | #include 7 | 8 | Servo panServo; 9 | Servo tiltServo; 10 | 11 | byte redledPin = 2; 12 | byte yellowledPin = 3; 13 | byte greenledPin = 4; 14 | 15 | const byte buffSize = 40; 16 | char inputBuffer[buffSize]; 17 | const char startMarker = '<'; 18 | const char endMarker = '>'; 19 | byte bytesRecvd = 0; 20 | boolean readInProgress = false; 21 | boolean newDataFromPC = false; 22 | 23 | float panServoAngle = 90.0; 24 | float tiltServoAngle = 90.0; 25 | int LED_state = 0; 26 | 27 | //8=============D 28 | 29 | void setup() { 30 | Serial.begin(115200); 31 | 32 | panServo.attach(8); 33 | tiltServo.attach(9); 34 | 35 | pinMode(redledPin, OUTPUT); 36 | pinMode(yellowledPin, OUTPUT); 37 | pinMode(greenledPin, OUTPUT); 38 | 39 | //moveServo(); 40 | start_sequence(); 41 | 42 | delay(200); 43 | 44 | Serial.println(""); // send message to computer 45 | } 46 | 47 | //8=============D 48 | 49 | void loop() { 50 | getDataFromPC(); 51 | replyToPC(); 52 | moveServo(); 53 | setLED(); 54 | } 55 | 56 | //8=============D 57 | 58 | void getDataFromPC() { 59 | 60 | // receive data from PC and save it into inputBuffer 61 | 62 | if(Serial.available() > 0) { 63 | 64 | char x = Serial.read(); //read char from serial 65 | 66 | if (x == endMarker) { //look for end marker 67 | readInProgress = false; //if found, set read in progress true (will stop adding new byte to buffer) 68 | newDataFromPC = true; //let arduino know that new data is available 69 | inputBuffer[bytesRecvd] = 0; //clear input buffer 70 | processData(); // process data in buffer 71 | } 72 | 73 | if(readInProgress) { 74 | inputBuffer[bytesRecvd] = x; //populate input buffer with bytes 75 | bytesRecvd ++; //increment index 76 | if (bytesRecvd == buffSize) { //when buffer is full 77 | bytesRecvd = buffSize - 1; //keep space for end marker 78 | } 79 | } 80 | 81 | if (x == startMarker) { // look for start maker 82 | bytesRecvd = 0; // if found, set byte received to 0 83 | readInProgress = true; // set read in progress true 84 | } 85 | } 86 | } 87 | 88 | //8=============D 89 | 90 | void processData() // for data type "" 91 | { 92 | char * strtokIndx; // this is used by strtok() as an index 93 | 94 | strtokIndx = strtok(inputBuffer,","); // get the first part 95 | panServoAngle = atof(strtokIndx); // convert this part to a float 96 | 97 | strtokIndx = strtok(NULL,","); // get the second part(this continues where the previous call left off) 98 | tiltServoAngle = atof(strtokIndx); // convert this part to a float 99 | 100 | strtokIndx = strtok(NULL, ","); // get the last part 101 | LED_state = atoi(strtokIndx); // convert this part to an integer (string to int) 102 | } 103 | 104 | //8=============D 105 | 106 | void replyToPC() { 107 | 108 | if (newDataFromPC) { 109 | newDataFromPC = false; 110 | Serial.print("<"); 111 | Serial.print(panServo.read()); 112 | Serial.print(","); 113 | Serial.print(tiltServo.read()); 114 | Serial.println(">"); 115 | } 116 | } 117 | 118 | //8=============D 119 | 120 | void moveServo() 121 | { 122 | panServo.write(panServoAngle); 123 | tiltServo.write(tiltServoAngle); 124 | } 125 | 126 | void setLED() 127 | { 128 | if(LED_state == 2){ 129 | digitalWrite(redledPin, LOW); 130 | digitalWrite(yellowledPin, HIGH); 131 | digitalWrite(greenledPin, LOW); 132 | } 133 | else if(LED_state == 1){ 134 | digitalWrite(redledPin, LOW); 135 | digitalWrite(yellowledPin, LOW); 136 | digitalWrite(greenledPin, HIGH); 137 | } 138 | else if(LED_state == 0){ 139 | digitalWrite(redledPin, HIGH); 140 | digitalWrite(yellowledPin, LOW); 141 | digitalWrite(greenledPin, LOW); 142 | } 143 | else if(LED_state == 3){ 144 | digitalWrite(redledPin, HIGH); 145 | digitalWrite(yellowledPin, HIGH); 146 | digitalWrite(greenledPin, HIGH); 147 | } 148 | else{ 149 | digitalWrite(redledPin, LOW); 150 | digitalWrite(yellowledPin, LOW); 151 | digitalWrite(greenledPin, LOW); 152 | } 153 | 154 | } 155 | 156 | //8=============D 157 | 158 | void start_sequence() 159 | { 160 | panServo.write(90); 161 | tiltServo.write(90); 162 | delay(300); 163 | 164 | 165 | digitalWrite(redledPin, HIGH); 166 | delay(100); 167 | digitalWrite(redledPin, LOW); 168 | digitalWrite(yellowledPin, HIGH); 169 | delay(100); 170 | digitalWrite(yellowledPin, LOW); 171 | digitalWrite(greenledPin, HIGH); 172 | delay(100); 173 | 174 | digitalWrite(redledPin, LOW); 175 | digitalWrite(yellowledPin, LOW); 176 | digitalWrite(greenledPin, LOW); 177 | delay(100); 178 | digitalWrite(redledPin, HIGH); 179 | digitalWrite(yellowledPin, HIGH); 180 | digitalWrite(greenledPin, HIGH); 181 | delay(100); 182 | digitalWrite(redledPin, LOW); 183 | digitalWrite(yellowledPin, LOW); 184 | digitalWrite(greenledPin, LOW); 185 | } 186 | -------------------------------------------------------------------------------- /tracking.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 980 10 | 700 11 | 12 | 13 | 14 | 15 | 980 16 | 700 17 | 18 | 19 | 20 | Form 21 | 22 | 23 | 24 | 25 | 10 26 | 10 27 | 960 28 | 540 29 | 30 | 31 | 32 | 33 | 960 34 | 540 35 | 36 | 37 | 38 | 39 | 960 40 | 540 41 | 42 | 43 | 44 | true 45 | 46 | 47 | QFrame::Panel 48 | 49 | 50 | QFrame::Sunken 51 | 52 | 53 | TextLabel 54 | 55 | 56 | 57 | 58 | 59 | 850 60 | 560 61 | 121 62 | 31 63 | 64 | 65 | 66 | Manual control 67 | 68 | 69 | 70 | 71 | 72 | 740 73 | 510 74 | 221 75 | 31 76 | 77 | 78 | 79 | 80 | 81 | 82 | false 83 | 84 | 85 | QFrame::NoFrame 86 | 87 | 88 | 1 89 | 90 | 91 | Pan 92 | 93 | 94 | Qt::AutoText 95 | 96 | 97 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Tilt 108 | 109 | 110 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 30 123 | 560 124 | 181 125 | 31 126 | 127 | 128 | 129 | 130 | 131 | 132 | COM port 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 110 141 | 16777215 142 | 143 | 144 | 145 | COM3 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 30 155 | 610 156 | 121 157 | 31 158 | 159 | 160 | 161 | 162 | 163 | 164 | Min Tilt : 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 55 173 | 16777215 174 | 175 | 176 | 177 | 0 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 170 187 | 610 188 | 121 189 | 31 190 | 191 | 192 | 193 | 194 | 195 | 196 | Max Tilt : 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 55 205 | 16777215 206 | 207 | 208 | 209 | 90 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 30 219 | 650 220 | 121 221 | 31 222 | 223 | 224 | 225 | 226 | 227 | 228 | Min Pan : 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 55 237 | 16777215 238 | 239 | 240 | 241 | 0 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 170 251 | 650 252 | 121 253 | 31 254 | 255 | 256 | 257 | 258 | 259 | 260 | Max Pan : 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 55 269 | 16777215 270 | 271 | 272 | 273 | 180 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 870 283 | 650 284 | 91 285 | 31 286 | 287 | 288 | 289 | Quit 290 | 291 | 292 | 293 | 294 | 295 | 870 296 | 610 297 | 91 298 | 31 299 | 300 | 301 | 302 | Pause 303 | 304 | 305 | 306 | 307 | 308 | 590 309 | 560 310 | 211 311 | 31 312 | 313 | 314 | 315 | Connect 316 | 317 | 318 | 319 | 320 | 321 | 710 322 | 610 323 | 91 324 | 71 325 | 326 | 327 | 328 | Update 329 | 330 | 331 | 332 | 333 | 334 | 230 335 | 560 336 | 351 337 | 31 338 | 339 | 340 | 341 | ................................ Not connected .............................. 342 | 343 | 344 | 345 | 346 | 347 | 480 348 | 610 349 | 81 350 | 31 351 | 352 | 353 | 354 | Invert tilt 355 | 356 | 357 | 358 | 359 | 360 | 480 361 | 650 362 | 81 363 | 31 364 | 365 | 366 | 367 | Invert pan 368 | 369 | 370 | 371 | 372 | 373 | 310 374 | 610 375 | 151 376 | 31 377 | 378 | 379 | 380 | 381 | 382 | 383 | Tilt sensivity : 384 | 385 | 386 | Qt::PlainText 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 55 395 | 16777215 396 | 397 | 398 | 399 | 3 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 310 409 | 650 410 | 151 411 | 31 412 | 413 | 414 | 415 | 416 | 417 | 418 | Pan sensivity : 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 55 427 | 16777215 428 | 429 | 430 | 431 | 5 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 580 441 | 610 442 | 91 443 | 31 444 | 445 | 446 | 447 | LED's ON 448 | 449 | 450 | true 451 | 452 | 453 | 454 | 455 | 456 | 580 457 | 650 458 | 101 459 | 31 460 | 461 | 462 | 463 | 464 | 465 | 466 | Camera ID : 467 | 468 | 469 | Qt::PlainText 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 20 478 | 16777215 479 | 480 | 481 | 482 | 0 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | -------------------------------------------------------------------------------- /Camera_tracker_main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt5.QtWidgets import QApplication, QWidget 3 | from PyQt5.QtGui import QIcon, QPixmap, QImage 4 | from PyQt5.uic import loadUi 5 | import opr 6 | import comm_ard 7 | import random 8 | import pickle 9 | 10 | import cv2 11 | 12 | 13 | class App(QWidget): 14 | 15 | def __init__(self): 16 | super().__init__() 17 | 18 | self.ui = loadUi('tracking.ui', self) #Load .ui file that define the window layout(buttons, tick boxoex...) 19 | 20 | self.setMouseTracking(True) #allow mouse tracking(use during manual mode) 21 | self.manual_mode = False #set manual mode to false to start in tracking face mode 22 | self.LED_ON = True #set LED on mode 23 | self.CameraID = 0 #define camera ID, first camera = ID 0, second ID 1 and so on 24 | 25 | self.rec = True #allows to start camera recording 26 | self.cap = cv2.VideoCapture(self.CameraID) #define open CV camera recording 27 | self.cap.set(3, 960) #set capture width 28 | self.cap.set(4, 540) #set capture height 29 | #The bigger the image is, the longer the processing is goint to take to process it 30 | ## My computer is a bit shit so I kept it quite small . 31 | 32 | self.min_tilt = 22 #minimum tilt angle in degree (up/down angle) 33 | self.max_tilt = 80 #maximum tilt angle in degree 34 | self.current_tilt = 0 #current tilt (info received from arduino and displayed in LCD numbers) 35 | self.target_tilt = 90 #the tilt angle you need to reach 36 | 37 | self.min_pan = 80 #minimum pan angle in degree(left/ right angle) 38 | self.max_pan = 100 #maximum pan angle in degree 39 | self.current_pan = 80 #current pan (info received from arduino and displayed in LCD numbers) 40 | self.target_pan = 90 #the pan angle you need to reach 41 | 42 | self.roam_target_pan = 90 43 | self.roam_target_tilt = 90 44 | self.roam_pause = 40 #amount of frame the camera is going to pause for when roam tilt or pan target reached 45 | self.roam_pause_count = self.roam_pause #current pause frame count 46 | 47 | 48 | self.is_connected = False #boolean defining if arduino is connected 49 | 50 | self.InvertPan = False #allows pan to be inverted 51 | self.InvertTilt = False #allows tilt to be inverted 52 | 53 | self.face_detected = False #define if a face is detected 54 | self.target_locked = False #define if detected face is close enough to the center of the captured image 55 | self.max_target_distance = 40 #minimum distance between face/center of image for setting target locked 56 | self.max_empty_frame = 50 #number of empty frame (no face detected) detected before starting roaming 57 | self.empty_frame_number = self.max_empty_frame #current empty frame count 58 | 59 | self.ard = comm_ard.ard_connect(self) #create object allowing communicationn with arduino 60 | self.initUI() #set up UI( see below ) 61 | 62 | def initUI(self): #UI related stuff 63 | self.setWindowTitle('LFK Tracker') #set window title 64 | self.label = self.ui.label #set label (it will be used to display the captured images) 65 | self.QuitButton = self.ui.QuitButton #set quit button 66 | self.PauseButton = self.ui.PauseButton #set pause button 67 | self.Pan_LCD = self.ui.Pan_LCD #and so on... 68 | self.Tilt_LCD = self.ui.Tilt_LCD #... 69 | self.Manual_checkbox = self.ui.Manual_checkbox #... 70 | self.ConnectButton = self.ui.ConnectButton 71 | self.COMlineEdit = self.ui.COMlineEdit 72 | self.COMConnectLabel = self.ui.COMConnectLabel 73 | self.UpdateButton = self.ui.UpdateButton 74 | self.MinTiltlineEdit = self.ui.MinTiltlineEdit 75 | self.MaxTiltlineEdit = self.ui.MaxTiltlineEdit 76 | self.InvertTilt_checkbox = self.ui.InvertTilt_checkbox 77 | self.InvertTilt = self.InvertTilt_checkbox.isChecked() 78 | self.MinPanlineEdit = self.ui.MinPanlineEdit 79 | self.MaxPanlineEdit = self.ui.MaxPanlineEdit 80 | self.InvertPan_checkbox = self.ui.InvertPan_checkbox 81 | self.InvertPan = self.InvertPan_checkbox.isChecked() 82 | self.TiltSensivityEdit = self.ui.TiltSensivityEdit 83 | self.TiltSensivity = 1 84 | self.PanSensivityEdit = self.ui.PanSensivityEdit 85 | self.PanSensivity = 1 86 | self.LED_checkbox = self.ui.LED_checkbox 87 | self.CameraIDEdit = self.ui.CameraIDEdit 88 | 89 | self.QuitButton.clicked.connect(self.quit) #bind quit button to quit method 90 | self.PauseButton.clicked.connect(self.toggle_recording) #bind pause button to pause method 91 | self.Manual_checkbox.stateChanged.connect(self.set_manual_mode) #bla bla bla 92 | self.ConnectButton.clicked.connect(self.connect) 93 | self.UpdateButton.clicked.connect(self.update_angles) 94 | 95 | self.load_init_file() 96 | self.update_angles() #update angle method 97 | 98 | self.record() #start recording 99 | 100 | def load_init_file(self): 101 | #this method will allow to reload the latest values entered in text boxes even after closing the software 102 | try: #try to open init file if existing 103 | with open('init.pkl', 'rb') as init_file: 104 | var = pickle.load(init_file) #load all variable and update text boxes 105 | self.COMlineEdit.setText(var[0]) 106 | if(var[4]): 107 | self.MinTiltlineEdit.setText(str(var[2])) 108 | self.MaxTiltlineEdit.setText(str(var[1])) 109 | else: 110 | self.MinTiltlineEdit.setText(str(var[1])) 111 | self.MaxTiltlineEdit.setText(str(var[2])) 112 | self.TiltSensivityEdit.setText(str(var[3])) 113 | self.InvertTilt_checkbox.setChecked(var[4]) 114 | if (var[8]): 115 | self.MinPanlineEdit.setText(str(var[6])) 116 | self.MaxPanlineEdit.setText(str(var[5])) 117 | else: 118 | self.MinPanlineEdit.setText(str(var[5])) 119 | self.MaxPanlineEdit.setText(str(var[6])) 120 | self.PanSensivityEdit.setText(str(var[7])) 121 | self.InvertPan_checkbox.setChecked(var[8]) 122 | #self.CameraIDEdit.setText(str(var[9])) 123 | self.LED_checkbox.setChecked(var[10]) 124 | print(var) 125 | #set variables 126 | except: 127 | pass 128 | 129 | def save_init_file(self): 130 | init_settings = [self.COMlineEdit.text(), 131 | self.min_tilt, self.max_tilt, self.TiltSensivity, self.InvertTilt, 132 | self.min_pan, self.max_pan, self.PanSensivity, self.InvertPan, 133 | self.CameraID, self.LED_ON] 134 | with open('init.pkl', 'wb') as init_file: 135 | pickle.dump(init_settings, init_file) 136 | 137 | 138 | def connect(self): #set COM port from text box if arduino not already connected 139 | if(not self.is_connected): 140 | port = self.COMlineEdit.text() 141 | if (self.ard.connect(port)): #set port label message 142 | self.COMConnectLabel.setText("..................... Connected to port : " + port + " ......................") 143 | else: 144 | self.COMConnectLabel.setText(".................... Cant connect to port : " + port + " .....................") 145 | 146 | def update_angles(self): #update variables from text boxes 147 | try: 148 | self.InvertTilt = self.InvertTilt_checkbox.isChecked() 149 | self.InvertPan = self.InvertPan_checkbox.isChecked() 150 | self.TiltSensivity = float(self.TiltSensivityEdit.text()) 151 | self.PanSensivity = float(self.PanSensivityEdit.text()) 152 | self.LED_ON = self.LED_checkbox.isChecked() 153 | 154 | self.cap.release() #camera need to be released in order to update the camera ID (if changed) 155 | self.CameraID = int(self.CameraIDEdit.text()) 156 | self.cap = cv2.VideoCapture(self.CameraID) 157 | 158 | if(self.InvertPan): 159 | self.max_pan = int(self.MinPanlineEdit.text()) 160 | self.min_pan = int(self.MaxPanlineEdit.text()) 161 | else: 162 | self.min_pan = int(self.MinPanlineEdit.text()) 163 | self.max_pan = int(self.MaxPanlineEdit.text()) 164 | 165 | if(self.InvertTilt): 166 | self.max_tilt = int(self.MinTiltlineEdit.text()) 167 | self.min_tilt = int(self.MaxTiltlineEdit.text()) 168 | else: 169 | self.min_tilt = int(self.MinTiltlineEdit.text()) 170 | self.max_tilt = int(self.MaxTiltlineEdit.text()) 171 | 172 | self.save_init_file() 173 | print("values updated") 174 | except: 175 | print("can't update values") 176 | 177 | def mouseMoveEvent(self, event): 178 | 179 | #the position of the mouse is tracked avec the window and converted to a pan and tilt amount 180 | #for example if mouse completely to the left-> pan_target = 0(or whatever minimum pan_target value is) 181 | # if completely to the right-> pan_target = 180(or whatever maximum pan_target value is) 182 | #same principal for tilt 183 | 184 | if(self.manual_mode): #if manual mode selected 185 | if(35" 288 | self.ard.runTest(data_to_send) 289 | #the data sent to the arduino will look something like the this (<154, 23, 0>) 290 | #the arduino will look for the start character "<" 291 | #then save everything following until it finds the end character ">" 292 | #at that point the arduino will have saved a message looking like this "154, 23, 0" 293 | #it will then split the message at every coma and use the pieces of data to move the servos acordingly 294 | 295 | #And yes I should have used bytes or something a bit better 296 | #I wrote this a while back and at that time I had even more to learn than I do now! 297 | #I still lazy and can't be asked to change it now as it works . 298 | 299 | 300 | def roam(self): 301 | if(self.roam_pause_count < 0 ): #if roam count inferior to 0 302 | 303 | self.roam_pause_count = self.roam_pause #reset roam count 304 | self.roam_target_pan = int(random.uniform(self.min_pan, self.max_pan)) 305 | self.roam_target_tilt = int(random.uniform(self.min_tilt, self.max_tilt)) 306 | 307 | else: #if roam count > 1 308 | #increment pan target toward roam target 309 | if (int(self.target_pan) > self.roam_target_pan): 310 | self.target_pan -= 1 311 | elif (int(self.target_pan) < self.roam_target_pan): 312 | self.target_pan += 1 313 | else: #if roam target reached decrease roam pause count 314 | self.roam_pause_count -= 1 315 | 316 | if (int(self.target_tilt) > self.roam_target_tilt): 317 | self.target_tilt -= 1 318 | elif (int(self.target_tilt) < self.roam_target_tilt): 319 | self.target_tilt += 1 320 | else: 321 | self.roam_pause_count -= 1 322 | 323 | 324 | def image_process(self, img): #handle the image processing 325 | #to add later : introduce frame scipping (check only 1 every nframe) 326 | 327 | processed_img = opr.find_face(img, self.max_target_distance) # try to find face and return processed image 328 | # if face found during processing , the data return will be as following : 329 | #[True, image_to_check, distance_from_center_X, distance_from_center_Y, locked] 330 | #if not it will just retun False 331 | 332 | if(processed_img[0]): #if face found 333 | self.face_detected = True 334 | self.empty_frame_number = self.max_empty_frame #reset empty frame count 335 | self.target_locked = processed_img[4] 336 | self.calculate_camera_move(processed_img[2], processed_img[3]) # calculate new targets depending on distance between face and image center 337 | return processed_img[1] 338 | else: 339 | self.face_detected = False 340 | self.target_locked = False 341 | if(self.empty_frame_number> 0): 342 | self.empty_frame_number -= 1 #decrease frame count until it equal 0 343 | else: 344 | self.roam() #then roam 345 | return img 346 | 347 | 348 | def calculate_camera_move(self, distance_X, distance_Y): 349 | 350 | #self.target_pan += distance_X * self.PanSensivity 351 | 352 | if(self.InvertPan): #handle inverted pan 353 | self.target_pan -= distance_X * self.PanSensivity 354 | if(self.target_pan>self.min_pan): 355 | self.target_pan = self.min_pan 356 | elif (self.target_pan < self.max_pan): 357 | self.target_pan = self.max_pan 358 | 359 | else: 360 | self.target_pan += distance_X * self.PanSensivity 361 | if(self.target_pan>self.max_pan): 362 | self.target_pan = self.max_pan 363 | elif (self.target_pan < self.min_pan): 364 | self.target_pan = self.min_pan 365 | 366 | 367 | #self.target_tilt += distance_Y * self.TiltSensivity 368 | 369 | if(self.InvertTilt): #handle inverted tilt 370 | self.target_tilt -= distance_Y * self.TiltSensivity 371 | if(self.target_tilt>self.min_tilt): 372 | self.target_tilt = self.min_tilt 373 | elif (self.target_tilt < self.max_tilt): 374 | self.target_tilt = self.max_tilt 375 | else: 376 | self.target_tilt += distance_Y * self.TiltSensivity 377 | if(self.target_tilt>self.max_tilt): 378 | self.target_tilt = self.max_tilt 379 | elif (self.target_tilt < self.min_tilt): 380 | self.target_tilt = self.min_tilt 381 | 382 | if __name__ == '__main__': 383 | app = QApplication(sys.argv) 384 | ex = App() 385 | app.exec_() 386 | #sys.exit(app.exec_()) 387 | --------------------------------------------------------------------------------