facetracker.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. #! /usr/bin/python3
  2. from picamera.array import PiRGBArray
  3. from picamera import PiCamera
  4. from datetime import datetime
  5. import time
  6. import cv2
  7. import sys
  8. import imutils
  9. import np
  10. import math # for sqrt distance formula
  11. # Create the haar cascade
  12. frontalfaceCascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
  13. profilefaceCascade = cv2.CascadeClassifier("haarcascade_profileface.xml")
  14. face = [0,0,0,0] # This will hold the array that OpenCV returns when it finds a face: (makes a rectangle)
  15. center = [0,0] # Center of the face: a point calculated from the above variable
  16. lastface = 0 # int 1-3 used to speed up detection. The script is looking for a right profile face,-
  17. # a left profile face, or a frontal face; rather than searching for all three every time,-
  18. # it uses this variable to remember which is last saw: and looks for that again. If it-
  19. # doesn't find it, it's set back to zero and on the next loop it will search for all three.-
  20. # This basically tripples the detect time so long as the face hasn't moved much.
  21. # initialize the camera and grab a reference to the raw camera capture
  22. camera = PiCamera()
  23. #camera.resolution = (160, 120)
  24. #camera.resolution = (640,480)
  25. camera.resolution = (1024,768)
  26. cameracenter = (camera.resolution[0]/2, camera.resolution[1]/2)
  27. camera.framerate = 32
  28. rawCapture = PiRGBArray(camera, camera.resolution)
  29. # Points to the last place we sawa a face
  30. target = ( camera.resolution[0]/2, camera.resolution[1]/2 )
  31. # Fisheye corrections. See https://medium.com/@kennethjiang/calibrate-fisheye-lens-using-opencv-333b05afa0b0
  32. # 640x480:
  33. #correct_fisheye = False
  34. #DIM=(640, 480)
  35. #K=np.array([[363.787052141742, 0.0, 332.09761373599576], [0.0, 362.23769923959975, 238.35982850966641], [0.0, 0.0, 1.0]])
  36. #D=np.array([[-0.019982864934848042], [-0.10107557279423625], [0.20401597940960342], [-0.1406464201639892]])
  37. # 1024x768:
  38. correct_fisheye = True
  39. DIM=(1024, 768)
  40. K=np.array([[583.6639649321671, 0.0, 518.0139106134624], [0.0, 580.8039721094127, 384.32095600935503], [0.0, 0.0, 1.0]])
  41. D=np.array([[0.0028045742945672475], [-0.14423839478882694], [0.23715105072799644], [-0.1400677375634837]])
  42. def distance(p0, p1):
  43. return math.sqrt((p0[0] - p1[0])**2 + (p0[1] - p1[1])**2)
  44. def search_rightprofile(i):
  45. # return profilefaceCascade.detectMultiScale(i,1.3,4,(cv2.CV_HAAR_DO_CANNY_PRUNING + cv2.CV_HAAR_FIND_BIGGEST_OBJECT + cv2.CV_HAAR_DO_ROUGH_SEARCH),(30,30))
  46. return profilefaceCascade.detectMultiScale(i)
  47. def search_leftprofile(i):
  48. revimage = cv2.flip(i, 1) # Flip the image
  49. # return profilefaceCascade.detectMultiScale(i,1.3,4,(cv2.CV_HAAR_DO_CANNY_PRUNING + cv2.CV_HAAR_FIND_BIGGEST_OBJECT + cv2.CV_HAAR_DO_ROUGH_SEARCH),(30,30))
  50. return profilefaceCascade.detectMultiScale(i)
  51. def search_frontface(i):
  52. # return frontalfaceCascade.detectMultiScale(i,1.3,4,(cv2.CV_HAAR_DO_CANNY_PRUNING + cv2.CV_HAAR_FIND_BIGGEST_OBJECT + cv2.CV_HAAR_DO_ROUGH_SEARCH),(30,30))
  53. return frontalfaceCascade.detectMultiScale(i)
  54. def undistort(i, balance=0.0, dim2=None, dim3=None):
  55. # Sanity Check the source dimensions
  56. dim1 = i.shape[:2][::-1] #dim1 is the dimension of input image to un-distort
  57. assert dim1[0]/dim1[1] == DIM[0]/DIM[1], "Image to undistort needs to have same aspect ratio as the ones used in calibration"
  58. if not dim2:
  59. dim2 = dim1
  60. if not dim3:
  61. dim3 = dim1
  62. scaled_K = K * dim1[0] / DIM[0] # The values of K is to scale with image dimension.
  63. scaled_K[2][2] = 1.0 # Except that K[2][2] is always 1.0
  64. # This is how scaled_K, dim2 and balance are used to determine the final K used to un-distort image. OpenCV document failed to make this clear!
  65. new_K = cv2.fisheye.estimateNewCameraMatrixForUndistortRectify(scaled_K, D, dim2, np.eye(3), balance=balance)
  66. map1, map2 = cv2.fisheye.initUndistortRectifyMap(scaled_K, D, np.eye(3), new_K, dim3, cv2.CV_16SC2)
  67. return cv2.remap(i, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
  68. # allow the camera to warmup
  69. time.sleep(0.1)
  70. lastTime = time.time()*1000.0
  71. # capture frames from the camera
  72. for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
  73. # grab the raw NumPy array representing the image, then initialize the timestamp
  74. # and occupied/unoccupied text
  75. image = frame.array
  76. image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # convert to greyscale
  77. if correct_fisheye:
  78. image = undistort(image, 0.8)
  79. faces = ();
  80. faceFound = False # This variable is set to true if, on THIS loop a face has already been found
  81. # We search for a face three diffrent ways, and if we have found one already-
  82. # there is no reason to keep looking.
  83. # First Scan
  84. if lastface == 1:
  85. faces = search_rightprofile(image)
  86. if faces != ():
  87. faceFound=True
  88. lastface = 1
  89. elif lastface == 2:
  90. faces = search_leftprofile(image)
  91. if faces != ():
  92. faceFound=True
  93. lastface = 2
  94. else:
  95. faces = search_frontface(image)
  96. if faces != ():
  97. lastface = 3
  98. faceFound=True
  99. # Second scan
  100. if not faceFound:
  101. if lastface == 1:
  102. faces = search_frontface(image)
  103. if faces != ():
  104. lastface = 3
  105. faceFound=True
  106. elif lastface == 2:
  107. faces = search_rightprofile(image)
  108. if faces != ():
  109. faceFound=True
  110. lastface = 1
  111. else:
  112. faces = search_leftprofile(image)
  113. if faces != ():
  114. faceFound=True
  115. lastface = 2
  116. # Third scan
  117. if not faceFound:
  118. if lastface == 1:
  119. faces = search_leftprofile(image)
  120. if faces != ():
  121. faceFound=True
  122. lastface = 2
  123. elif lastface == 2:
  124. faces = search_frontface(image)
  125. if faces != ():
  126. lastface = 3
  127. faceFound=True
  128. else:
  129. faces = search_rightprofile(image)
  130. if faces != ():
  131. faceFound=True
  132. lastface = 1
  133. if faceFound:
  134. print("{}: {} faces found. Type: {}".format(time.time()*1000.0-lastTime, len(faces), lastface))
  135. # Draw a rectangle around the faces
  136. for (x, y, w, h) in faces:
  137. cv2.circle(image, (int(x+w/2), int(y+h/2)), int((w+h)/3), (255, 255, 255), 1)
  138. # Temporary, save the image
  139. cv2.imwrite("tmp/img.{}.facetype{}.png".format(datetime.now().strftime("%Y%m%d.%H%M%S.%f"), lastface), image)
  140. # Find the centermost face
  141. curdistance = 1000000 # Outside the dimensions of the picture
  142. for f in faces:
  143. x,y,w,h = f
  144. tmpcenter = [(w/2+x),(h/2+y)] # we are given an x,y corner point and a width and height, we need the center
  145. tmpdistance = distance(tmpcenter, cameracenter)
  146. if(tmpdistance < curdistance):
  147. print("Face closer to center detected. New target location: ({}, {}) - distance: {}".format(tmpcenter[0],tmpcenter[1],tmpdistance))
  148. center = tmpcenter;
  149. target = center
  150. else: # No face found
  151. print("{}: no faces found. Continuing with existing target ({}, {})".format(time.time()*1000.0-lastTime, target[0], target[1]))
  152. # clear the stream in preparation for the next frame
  153. rawCapture.truncate(0)
  154. lastTime = time.time()*1000.0
  155. # Determine directions and distance
  156. travel = [ (cameracenter[0] - target[0]) / camera.resolution[0], (cameracenter[1] - target[1]) /camera.resolution[1] ]
  157. print("To move horizontal: {}, vertical: {}".format(travel[0], travel[1]))