comparison trafficintelligence/cvutils.py @ 1028:cc5cb04b04b0

major update using the trafficintelligence package name and install through pip
author Nicolas Saunier <nicolas.saunier@polymtl.ca>
date Fri, 15 Jun 2018 11:19:10 -0400
parents python/cvutils.py@16932cefabc1
children c6cf75a2ed08
comparison
equal deleted inserted replaced
1027:6129296848d3 1028:cc5cb04b04b0
1 #! /usr/bin/env python
2 '''Image/Video utilities'''
3
4 from trafficintelligence import utils, moving
5
6 try:
7 import cv2
8 opencvAvailable = True
9 except ImportError:
10 print('OpenCV library could not be loaded (video replay functions will not be available)') # TODO change to logging module
11 opencvAvailable = False
12 try:
13 import skimage
14 skimageAvailable = True
15 except ImportError:
16 print('Scikit-image library could not be loaded (HoG-based classification methods will not be available)')
17 skimageAvailable = False
18
19 from sys import stdout
20 from os import listdir
21 from subprocess import run
22 from math import floor, log10, ceil
23
24 from numpy import dot, array, append, float32, loadtxt, savetxt, append, zeros, ones, identity, abs as npabs, logical_and, unravel_index, sum as npsum, isnan, mgrid, median, floor as npfloor, ceil as npceil
25 from numpy.linalg import inv
26 from matplotlib.mlab import find
27 from matplotlib.pyplot import imread, imsave
28
29 videoFilenameExtensions = ['mov', 'avi', 'mp4', 'MOV', 'AVI', 'MP4']
30 trackerExe = 'feature-based-tracking'
31 #importaggdraw # agg on top of PIL (antialiased drawing)
32
33 cvRed = {'default': (0,0,255),
34 'colorblind': (0,114,178)}
35 cvGreen = {'default': (0,255,0),
36 'colorblind': (0,158,115)}
37 cvBlue = {'default': (255,0,0),
38 'colorblind': (213,94,0)}
39 cvCyan = {'default': (255, 255, 0),
40 'colorblind': (240,228,66)}
41 cvYellow = {'default': (0, 255, 255),
42 'colorblind': (86,180,233)}
43 cvMagenta = {'default': (255, 0, 255),
44 'colorblind': (204,121,167)}
45 cvWhite = {k: (255, 255, 255) for k in ['default', 'colorblind']}
46 cvBlack = {k: (0,0,0) for k in ['default', 'colorblind']}
47
48 cvColors3 = {k: utils.PlottingPropertyValues([cvRed[k], cvGreen[k], cvBlue[k]]) for k in ['default', 'colorblind']}
49 cvColors = {k: utils.PlottingPropertyValues([cvRed[k], cvGreen[k], cvBlue[k], cvCyan[k], cvYellow[k], cvMagenta[k], cvWhite[k], cvBlack[k]]) for k in ['default', 'colorblind']}
50
51 def quitKey(key):
52 return chr(key&255)== 'q' or chr(key&255) == 'Q'
53
54 def saveKey(key):
55 return chr(key&255) == 's'
56
57 def int2FOURCC(x):
58 fourcc = ''
59 for i in range(4):
60 fourcc += chr((x >> 8*i)&255)
61 return fourcc
62
63 def rgb2gray(rgb):
64 return dot(rgb[...,:3], [0.299, 0.587, 0.144])
65
66 def matlab2PointCorrespondences(filename):
67 '''Loads and converts the point correspondences saved
68 by the matlab camera calibration tool'''
69 points = loadtxt(filename, delimiter=',')
70 savetxt(utils.removeExtension(filename)+'-point-correspondences.txt',append(points[:,:2].T, points[:,3:].T, axis=0))
71
72 def loadPointCorrespondences(filename):
73 '''Loads and returns the corresponding points in world (first 2 lines) and image spaces (last 2 lines)'''
74 points = loadtxt(filename, dtype=float32)
75 return (points[:2,:].T, points[2:,:].T) # (world points, image points)
76
77 def cvMatToArray(cvmat):
78 '''Converts an OpenCV CvMat to numpy array.'''
79 print('Deprecated, use new interface')
80 a = zeros((cvmat.rows, cvmat.cols))#array([[0.0]*cvmat.width]*cvmat.height)
81 for i in range(cvmat.rows):
82 for j in range(cvmat.cols):
83 a[i,j] = cvmat[i,j]
84 return a
85
86 def createWhiteImage(height, width, filename):
87 img = ones((height, width, 3), uint8)*255
88 imsave(filename, img)
89
90 if opencvAvailable:
91 def computeHomography(srcPoints, dstPoints, method=0, ransacReprojThreshold=3.0):
92 '''Returns the homography matrix mapping from srcPoints to dstPoints (dimension Nx2)'''
93 H, mask = cv2.findHomography(srcPoints, dstPoints, method, ransacReprojThreshold)
94 return H
95
96 def cvPlot(img, positions, color, lastCoordinate = None, **kwargs):
97 if lastCoordinate is None:
98 last = positions.length()-1
99 elif lastCoordinate >=0:
100 last = min(positions.length()-1, lastCoordinate)
101 for i in range(0, last):
102 cv2.line(img, positions[i].asint().astuple(), positions[i+1].asint().astuple(), color, **kwargs)
103
104 def cvImshow(windowName, img, rescale = 1.0):
105 'Rescales the image (in particular if too large)'
106 from cv2 import resize
107 if rescale != 1.:
108 size = (int(round(img.shape[1]*rescale)), int(round(img.shape[0]*rescale)))
109 resizedImg = resize(img, size)
110 cv2.imshow(windowName, resizedImg)
111 else:
112 cv2.imshow(windowName, img)
113
114 def computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients):
115 newImgSize = (int(round(width*undistortedImageMultiplication)), int(round(height*undistortedImageMultiplication)))
116 newCameraMatrix = cv2.getDefaultNewCameraMatrix(intrinsicCameraMatrix, newImgSize, True)
117 return cv2.initUndistortRectifyMap(intrinsicCameraMatrix, array(distortionCoefficients), None, newCameraMatrix, newImgSize, cv2.CV_32FC1), newCameraMatrix
118
119 def playVideo(filenames, windowNames = None, firstFrameNums = None, frameRate = -1, interactive = False, printFrames = True, text = None, rescale = 1., step = 1, colorBlind = False):
120 '''Plays the video(s)'''
121 if colorBlind:
122 colorType = 'colorblind'
123 else:
124 colorType = 'default'
125 if len(filenames) == 0:
126 print('Empty filename list')
127 return
128 if windowNames is None:
129 windowNames = ['frame{}'.format(i) for i in range(len(filenames))]
130 wait = 5
131 if rescale == 1.:
132 for windowName in windowNames:
133 cv2.namedWindow(windowName, cv2.WINDOW_NORMAL)
134 if frameRate > 0:
135 wait = int(round(1000./frameRate))
136 if interactive:
137 wait = 0
138 captures = [cv2.VideoCapture(fn) for fn in filenames]
139 if array([cap.isOpened() for cap in captures]).all():
140 key = -1
141 ret = True
142 nFramesShown = 0
143 if firstFrameNums is not None:
144 for i in range(len(captures)):
145 captures[i].set(cv2.CAP_PROP_POS_FRAMES, firstFrameNums[i])
146 while ret and not quitKey(key):
147 rets = []
148 images = []
149 for cap in captures:
150 ret, img = cap.read()
151 rets.append(ret)
152 images.append(img)
153 ret = array(rets).all()
154 if ret:
155 if printFrames:
156 print('frame shown {0}'.format(nFramesShown))
157 for i in range(len(filenames)):
158 if text is not None:
159 cv2.putText(images[i], text, (10,50), cv2.FONT_HERSHEY_PLAIN, 1, cvRed[colorType])
160 cvImshow(windowNames[i], images[i], rescale) # cv2.imshow('frame', img)
161 key = cv2.waitKey(wait)
162 if saveKey(key):
163 cv2.imwrite('image-{}.png'.format(frameNum), img)
164 nFramesShown += step
165 if step > 1:
166 for i in range(len(captures)):
167 captures[i].set(cv2.CAP_PROP_POS_FRAMES, firstFrameNums[i]+nFramesShown)
168 cv2.destroyAllWindows()
169 else:
170 print('Video captures for {} failed'.format(filenames))
171
172 def infoVideo(filename):
173 '''Provides all available info on video '''
174 cvPropertyNames = {cv2.CAP_PROP_FORMAT: "format",
175 cv2.CAP_PROP_FOURCC: "codec (fourcc)",
176 cv2.CAP_PROP_FPS: "fps",
177 cv2.CAP_PROP_FRAME_COUNT: "number of frames",
178 cv2.CAP_PROP_FRAME_HEIGHT: "heigh",
179 cv2.CAP_PROP_FRAME_WIDTH: "width",
180 cv2.CAP_PROP_RECTIFICATION: "rectification",
181 cv2.CAP_PROP_SATURATION: "saturation"}
182 capture = cv2.VideoCapture(filename)
183 videoProperties = {}
184 if capture.isOpened():
185 for cvprop in [#cv2.CAP_PROP_BRIGHTNESS
186 #cv2.CAP_PROP_CONTRAST
187 #cv2.CAP_PROP_CONVERT_RGB
188 #cv2.CAP_PROP_EXPOSURE
189 cv2.CAP_PROP_FORMAT,
190 cv2.CAP_PROP_FOURCC,
191 cv2.CAP_PROP_FPS,
192 cv2.CAP_PROP_FRAME_COUNT,
193 cv2.CAP_PROP_FRAME_HEIGHT,
194 cv2.CAP_PROP_FRAME_WIDTH,
195 #cv2.CAP_PROP_GAIN,
196 #cv2.CAP_PROP_HUE
197 #cv2.CAP_PROP_MODE
198 #cv2.CAP_PROP_POS_AVI_RATIO
199 #cv2.CAP_PROP_POS_FRAMES
200 #cv2.CAP_PROP_POS_MSEC
201 #cv2.CAP_PROP_RECTIFICATION,
202 #cv2.CAP_PROP_SATURATION
203 ]:
204 prop = capture.get(cvprop)
205 if cvprop == cv2.CAP_PROP_FOURCC and prop > 0:
206 prop = int2FOURCC(int(prop))
207 videoProperties[cvPropertyNames[cvprop]] = prop
208 else:
209 print('Video capture for {} failed'.format(filename))
210 return videoProperties
211
212 def getImagesFromVideo(videoFilename, firstFrameNum = 0, lastFrameNum = 1, step = 1, saveImage = False, outputPrefix = 'image'):
213 '''Returns nFrames images from the video sequence'''
214 images = []
215 capture = cv2.VideoCapture(videoFilename)
216 if capture.isOpened():
217 rawCount = capture.get(cv2.CAP_PROP_FRAME_COUNT)
218 if rawCount < 0:
219 rawCount = lastFrameNum+1
220 nDigits = int(floor(log10(rawCount)))+1
221 ret = False
222 capture.set(cv2.CAP_PROP_POS_FRAMES, firstFrameNum)
223 frameNum = firstFrameNum
224 while frameNum<lastFrameNum and frameNum<rawCount:
225 ret, img = capture.read()
226 i = 0
227 while not ret and i<10:
228 ret, img = capture.read()
229 i += 1
230 if img is not None and img.size>0:
231 if saveImage:
232 frameNumStr = format(frameNum, '0{}d'.format(nDigits))
233 cv2.imwrite(outputPrefix+frameNumStr+'.png', img)
234 else:
235 images.append(img)
236 frameNum +=step
237 if step > 1:
238 capture.set(cv2.CAP_PROP_POS_FRAMES, frameNum)
239 capture.release()
240 else:
241 print('Video capture for {} failed'.format(videoFilename))
242 return images
243
244 def getFPS(videoFilename):
245 capture = cv2.VideoCapture(videoFilename)
246 if capture.isOpened():
247 fps = capture.get(cv2.CAP_PROP_FPS)
248 capture.release()
249 return fps
250 else:
251 print('Video capture for {} failed'.format(videoFilename))
252 return None
253
254 def imageBoxSize(obj, frameNum, width, height, px = 0.2, py = 0.2):
255 'Computes the bounding box size of object at frameNum'
256 x = []
257 y = []
258 if obj.hasFeatures():
259 for f in obj.getFeatures():
260 if f.existsAtInstant(frameNum):
261 p = f.getPositionAtInstant(frameNum)
262 x.append(p.x)
263 y.append(p.y)
264 xmin = min(x)
265 xmax = max(x)
266 ymin = min(y)
267 ymax = max(y)
268 xMm = px * (xmax - xmin)
269 yMm = py * (ymax - ymin)
270 a = max(ymax - ymin + (2 * yMm), xmax - (xmin + 2 * xMm))
271 yCropMin = int(max(0, .5 * (ymin + ymax - a)))
272 yCropMax = int(min(height - 1, .5 * (ymin + ymax + a)))
273 xCropMin = int(max(0, .5 * (xmin + xmax - a)))
274 xCropMax = int(min(width - 1, .5 * (xmin + xmax + a)))
275 return yCropMin, yCropMax, xCropMin, xCropMax
276
277 def imageBox(img, obj, frameNum, width, height, px = 0.2, py = 0.2, minNPixels = 800):
278 'Computes the bounding box of object at frameNum'
279 yCropMin, yCropMax, xCropMin, xCropMax = imageBoxSize(obj, frameNum, width, height, px, py)
280 if yCropMax != yCropMin and xCropMax != xCropMin and (yCropMax - yCropMin) * (xCropMax - xCropMin) > minNPixels:
281 return img[yCropMin : yCropMax, xCropMin : xCropMax]
282 else:
283 return None
284
285 def tracking(configFilename, grouping, videoFilename = None, dbFilename = None, homographyFilename = None, maskFilename = None, undistort = False, intrinsicCameraMatrix = None, distortionCoefficients = None, dryRun = False):
286 '''Runs the tracker in a subprocess
287 if grouping is True, it is feature grouping
288 otherwise it is feature tracking'''
289 if grouping:
290 trackingMode = '--gf'
291 else:
292 trackingMode = '--tf'
293 cmd = [trackerExe, configFilename, trackingMode, '--quiet']
294
295 if videoFilename is not None:
296 cmd += ['--video-filename', videoFilename]
297 if dbFilename is not None:
298 cmd += ['--database-filename', dbFilename]
299 if homographyFilename is not None:
300 cmd += ['--homography-filename', homographyFilename]
301 if maskFilename is not None:
302 cmd += ['--mask-filename', maskFilename]
303 if undistort:
304 cmd += ['--undistort', 'true']
305 if intrinsicCameraMatrix is not None: # we currently have to save a file
306 from time import time
307 intrinsicCameraFilename = '/tmp/intrinsic-{}.txt'.format(time())
308 savetxt(intrinsicCameraFilename, intrinsicCameraMatrix)
309 cmd += ['--intrinsic-camera-filename', intrinsicCameraFilename]
310 if distortionCoefficients is not None:
311 cmd += ['--distortion-coefficients '+' '.join([str(x) for x in distortionCoefficients])]
312 if dryRun:
313 print(cmd)
314 else:
315 run(cmd)
316
317 def displayTrajectories(videoFilename, objects, boundingBoxes = {}, homography = None, firstFrameNum = 0, lastFrameNumArg = None, printFrames = True, rescale = 1., nFramesStep = 1, saveAllImages = False, nZerosFilenameArg = None, undistort = False, intrinsicCameraMatrix = None, distortionCoefficients = None, undistortedImageMultiplication = 1., annotations = [], gtMatches = {}, toMatches = {}, colorBlind = False):
318 '''Displays the objects overlaid frame by frame over the video '''
319 if colorBlind:
320 colorType = 'colorblind'
321 else:
322 colorType = 'default'
323
324 capture = cv2.VideoCapture(videoFilename)
325 width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
326 height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
327
328 windowName = 'frame'
329 if rescale == 1.:
330 cv2.namedWindow(windowName, cv2.WINDOW_NORMAL)
331
332 if undistort: # setup undistortion
333 [map1, map2], newCameraMatrix = computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients)
334 if capture.isOpened():
335 key = -1
336 ret = True
337 frameNum = firstFrameNum
338 capture.set(cv2.CAP_PROP_POS_FRAMES, firstFrameNum)
339 if lastFrameNumArg is None:
340 lastFrameNum = float("inf")
341 else:
342 lastFrameNum = lastFrameNumArg
343 if nZerosFilenameArg is None:
344 if lastFrameNumArg is None:
345 nZerosFilename = int(ceil(log10(objects[-1].getLastInstant())))
346 else:
347 nZerosFilename = int(ceil(log10(lastFrameNum)))
348 else:
349 nZerosFilename = nZerosFilenameArg
350 while ret and not quitKey(key) and frameNum <= lastFrameNum:
351 ret, img = capture.read()
352 if ret:
353 if undistort:
354 img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR)
355 if printFrames:
356 print('frame {0}'.format(frameNum))
357 # plot objects
358 for obj in objects[:]:
359 if obj.existsAtInstant(frameNum):
360 if not hasattr(obj, 'projectedPositions'):
361 obj.projectedPositions = obj.getPositions().homographyProject(homography)
362 if undistort:
363 obj.projectedPositions = obj.projectedPositions.newCameraProject(newCameraMatrix)
364 cvPlot(img, obj.projectedPositions, cvColors[colorType][obj.getNum()], frameNum-obj.getFirstInstant())
365 if frameNum not in boundingBoxes and obj.hasFeatures():
366 yCropMin, yCropMax, xCropMin, xCropMax = imageBoxSize(obj, frameNum, homography, width, height)
367 cv2.rectangle(img, (xCropMin, yCropMin), (xCropMax, yCropMax), cvBlue[colorType], 1)
368 objDescription = '{} '.format(obj.num)
369 if moving.userTypeNames[obj.userType] != 'unknown':
370 objDescription += moving.userTypeNames[obj.userType][0].upper()
371 if len(annotations) > 0: # if we loaded annotations, but there is no match
372 if frameNum not in toMatches[obj.getNum()]:
373 objDescription += " FA"
374 cv2.putText(img, objDescription, obj.projectedPositions[frameNum-obj.getFirstInstant()].asint().astuple(), cv2.FONT_HERSHEY_PLAIN, 1, cvColors[colorType][obj.getNum()])
375 if obj.getLastInstant() == frameNum:
376 objects.remove(obj)
377 # plot object bounding boxes
378 if frameNum in boundingBoxes:
379 for rect in boundingBoxes[frameNum]:
380 cv2.rectangle(img, rect[0].asint().astuple(), rect[1].asint().astuple(), cvColors[colorType][obj.getNum()])
381 # plot ground truth
382 if len(annotations) > 0:
383 for gt in annotations:
384 if gt.existsAtInstant(frameNum):
385 if frameNum in gtMatches[gt.getNum()]:
386 color = cvColors[colorType][gtMatches[gt.getNum()][frameNum]] # same color as object
387 else:
388 color = cvRed[colorType]
389 cv2.putText(img, 'Miss', gt.topLeftPositions[frameNum-gt.getFirstInstant()].asint().astuple(), cv2.FONT_HERSHEY_PLAIN, 1, color)
390 cv2.rectangle(img, gt.topLeftPositions[frameNum-gt.getFirstInstant()].asint().astuple(), gt.bottomRightPositions[frameNum-gt.getFirstInstant()].asint().astuple(), color)
391 # saving images and going to next
392 if not saveAllImages:
393 cvImshow(windowName, img, rescale)
394 key = cv2.waitKey()
395 if saveAllImages or saveKey(key):
396 cv2.imwrite('image-{{:0{}}}.png'.format(nZerosFilename).format(frameNum), img)
397 frameNum += nFramesStep
398 if nFramesStep > 1:
399 capture.set(cv2.CAP_PROP_POS_FRAMES, frameNum)
400 cv2.destroyAllWindows()
401 else:
402 print('Cannot load file ' + videoFilename)
403
404 def computeHomographyFromPDTV(camera):
405 '''Returns the homography matrix at ground level from PDTV camera
406 https://bitbucket.org/hakanardo/pdtv'''
407 # camera = pdtv.load(cameraFilename)
408 srcPoints = [[x,y] for x, y in zip([1.,2.,2.,1.],[1.,1.,2.,2.])] # need floats!!
409 dstPoints = []
410 for srcPoint in srcPoints:
411 projected = camera.image_to_world(tuple(srcPoint))
412 dstPoints.append([projected[0], projected[1]])
413 H, mask = cv2.findHomography(array(srcPoints), array(dstPoints), method = 0) # No need for different methods for finding homography
414 return H
415
416 def getIntrinsicCameraMatrix(cameraData):
417 return array([[cameraData['f']*cameraData['Sx']/cameraData['dx'], 0, cameraData['Cx']],
418 [0, cameraData['f']/cameraData['dy'], cameraData['Cy']],
419 [0, 0, 1.]])
420
421 def getDistortionCoefficients(cameraData):
422 return array([cameraData['k']]+4*[0])
423
424 def undistortedCoordinates(map1, map2, x, y, maxDistance = 1.):
425 '''Returns the coordinates of a point in undistorted image
426 map1 and map2 are the mapping functions from undistorted image
427 to distorted (original image)
428 map1(x,y) = originalx, originaly'''
429 distx = npabs(map1-x)
430 disty = npabs(map2-y)
431 indices = logical_and(distx<maxDistance, disty<maxDistance)
432 closeCoordinates = unravel_index(find(indices), distx.shape) # returns i,j, ie y,x
433 xWeights = 1-distx[indices]
434 yWeights = 1-disty[indices]
435 return dot(xWeights, closeCoordinates[1])/npsum(xWeights), dot(yWeights, closeCoordinates[0])/npsum(yWeights)
436
437 def undistortTrajectoryFromCVMapping(map1, map2, t):
438 '''test 'perfect' inversion'''
439 undistortedTrajectory = moving.Trajectory()
440 for i,p in enumerate(t):
441 res = undistortedCoordinates(map1, map2, p.x,p.y)
442 if not isnan(res).any():
443 undistortedTrajectory.addPositionXY(res[0], res[1])
444 else:
445 print('{} {} {}'.format(i,p,res))
446 return undistortedTrajectory
447
448 def computeInverseMapping(originalImageSize, map1, map2):
449 'Computes inverse mapping from maps provided by cv2.initUndistortRectifyMap'
450 invMap1 = -ones(originalImageSize)
451 invMap2 = -ones(originalImageSize)
452 for x in range(0,originalImageSize[1]):
453 for y in range(0,originalImageSize[0]):
454 res = undistortedCoordinates(x,y, map1, map2)
455 if not isnan(res).any():
456 invMap1[y,x] = res[0]
457 invMap2[y,x] = res[1]
458 return invMap1, invMap2
459
460 def intrinsicCameraCalibration(path, checkerBoardSize=[6,7], secondPassSearch=False, display=False, fixK2 = True, fixK3 = True, zeroTangent = True):
461 ''' Camera calibration searches through all the images (jpg or png) located
462 in _path_ for matches to a checkerboard pattern of size checkboardSize.
463 These images should all be of the same camera with the same resolution.
464
465 For best results, use an asymetric board and ensure that the image has
466 very high contrast, including the background.
467
468 cherckerBoardSize is the number of internal corners (7x10 squares have 6x9 internal corners)
469
470 The code below is based off of:
471 https://opencv-python-tutroals.readthedocs.org/en/latest/py_tutorials/py_calib3d/py_calibration/py_calibration.html
472 Modified by Paul St-Aubin
473 '''
474 import glob, os
475
476 # termination criteria
477 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
478
479 # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
480 objp = zeros((checkerBoardSize[0]*checkerBoardSize[1],3), float32)
481 objp[:,:2] = mgrid[0:checkerBoardSize[1],0:checkerBoardSize[0]].T.reshape(-1,2)
482
483 # Arrays to store object points and image points from all the images.
484 objpoints = [] # 3d point in real world space
485 imgpoints = [] # 2d points in image plane.
486
487 ## Loop throuhg all images in _path_
488 images = glob.glob(os.path.join(path,'*.[jJ][pP][gG]'))+glob.glob(os.path.join(path,'*.[jJ][pP][eE][gG]'))+glob.glob(os.path.join(path,'*.[pP][nN][gG]'))
489 for fname in images:
490 img = cv2.imread(fname)
491 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
492
493 # Find the chess board corners
494 ret, corners = cv2.findChessboardCorners(gray, (checkerBoardSize[1],checkerBoardSize[0]), None)
495
496 # If found, add object points, image points (after refining them)
497 if ret:
498 print('Found pattern in '+fname)
499
500 if secondPassSearch:
501 corners = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
502
503 objpoints.append(objp)
504 imgpoints.append(corners)
505
506 # Draw and display the corners
507 if display:
508 cv2.drawChessboardCorners(img, (checkerBoardSize[1],checkerBoardSize[0]), corners, ret)
509 if img is not None:
510 cv2.imshow('img',img)
511 cv2.waitKey(0)
512 else:
513 print('Pattern not found in '+fname)
514 ## Close up image loading and calibrate
515 cv2.destroyAllWindows()
516 if len(objpoints) == 0 or len(imgpoints) == 0:
517 return None
518 try:
519 flags = 0
520 if fixK2:
521 flags += cv2.CALIB_FIX_K2
522 if fixK3:
523 flags += cv2.CALIB_FIX_K3
524 if zeroTangent:
525 flags += cv2.CALIB_ZERO_TANGENT_DIST
526 ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None, flags = flags)
527 except NameError:
528 return None
529 savetxt('intrinsic-camera.txt', camera_matrix)
530 print('error: {}'.format(ret))
531 return camera_matrix, dist_coeffs
532
533 def undistortImage(img, intrinsicCameraMatrix = None, distortionCoefficients = None, undistortedImageMultiplication = 1., interpolation=cv2.INTER_LINEAR):
534 '''Undistorts the image passed in argument'''
535 width = img.shape[1]
536 height = img.shape[0]
537 [map1, map2] = computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients)
538 return cv2.remap(img, map1, map2, interpolation=interpolation)
539
540 def homographyProject(points, homography, output3D = False):
541 '''Returns the coordinates of the points (2xN array) projected through homography'''
542 if points.shape[0] != 2:
543 raise Exception('points of dimension {}'.format(points.shape))
544
545 if homography is not None and homography.size>0:
546 if output3D:
547 outputDim = 3
548 else:
549 outputDim = 2
550 augmentedPoints = append(points,[[1]*points.shape[1]], 0) # 3xN
551 prod = dot(homography, augmentedPoints)
552 return prod[:outputDim,:]/prod[2]
553 elif output3D:
554 return append(points,[[1]*points.shape[1]], 0) # 3xN
555 else:
556 return points
557
558 def imageToWorldProject(points, intrinsicCameraMatrix = None, distortionCoefficients = None, homography = None):
559 '''Projects points (2xN array) from image (video) space to world space
560 1. through undistorting if provided by intrinsic camera matrix and distortion coefficients
561 2. through homograph projection (from ideal point (no camera) to world)'''
562 if points.shape[0] != 2:
563 raise Exception('points of dimension {}'.format(points.shape))
564
565 if intrinsicCameraMatrix is not None and distortionCoefficients is not None:
566 undistortedPoints = cv2.undistortPoints(points.T.reshape(1,points.shape[1], 2), intrinsicCameraMatrix, distortionCoefficients).reshape(-1,2)
567 return homographyProject(undistortedPoints.T, homography)
568 else:
569 return homographyProject(points, homography)
570
571 def worldToImageProject(points, intrinsicCameraMatrix = None, distortionCoefficients = None, homography = None):
572 '''Projects points (2xN array) from image (video) space to world space
573 1. through undistorting if provided by intrinsic camera matrix and distortion coefficients
574 2. through homograph projection (from ideal point (no camera) to world)'''
575 if points.shape[0] != 2:
576 raise Exception('points of dimension {}'.format(points.shape))
577
578 if intrinsicCameraMatrix is not None and distortionCoefficients is not None:
579 projected3D = homographyProject(points, homography, True)
580 projected, jacobian = cv2.projectPoints(projected3D.T, (0.,0.,0.), (0.,0.,0.), intrinsicCameraMatrix, distortionCoefficients) # in: 3xN, out: 2x1xN
581 return projected.reshape(-1,2).T
582 else:
583 return homographyProject(points, homography)
584
585 def newCameraProject(points, newCameraMatrix):
586 '''Projects points (2xN array) as if seen by camera
587 (or reverse by inverting the camera matrix)'''
588 if points.shape[0] != 2:
589 raise Exception('points of dimension {}'.format(points.shape))
590
591 if newCameraMatrix is not None:
592 augmentedPoints = append(points,[[1]*points.shape[1]], 0) # 3xN
593 projected = dot(newCameraMatrix, augmentedPoints)
594 return projected[:2,:]
595 else:
596 return points
597
598 if opencvAvailable:
599 def computeTranslation(img1, img2, img1Points, maxTranslation2, minNMatches, windowSize = (5,5), level = 5, criteria = (cv2.TERM_CRITERIA_EPS, 0, 0.01)):
600 '''Computes the translation of img2 with respect to img1
601 (loaded using OpenCV as numpy arrays)
602 img1Points are used to compute the translation
603
604 TODO add diagnostic if data is all over the place, and it most likely is not a translation (eg zoom, other non linear distortion)'''
605
606 nextPoints = array([])
607 (img2Points, status, track_error) = cv2.calcOpticalFlowPyrLK(img1, img2, img1Points, nextPoints, winSize=windowSize, maxLevel=level, criteria=criteria)
608 # calcOpticalFlowPyrLK(prevImg, nextImg, prevPts[, nextPts[, status[, err[, winSize[, maxLevel[, criteria[, derivLambda[, flags]]]]]]]]) -> nextPts, status, err
609 delta = []
610 for (k, (p1,p2)) in enumerate(zip(img1Points, img2Points)):
611 if status[k] == 1:
612 dp = p2-p1
613 d = npsum(dp**2)
614 if d < maxTranslation2:
615 delta.append(dp)
616 if len(delta) >= minNMatches:
617 return median(delta, axis=0)
618 else:
619 print(dp)
620 return None
621
622 if skimageAvailable:
623 from skimage.feature import hog
624 from skimage import color, transform
625
626 def HOG(image, rescaleSize = (64, 64), orientations = 9, pixelsPerCell = (8,8), cellsPerBlock = (2,2), blockNorm = 'L1', visualize = False, transformSqrt = False):
627 bwImg = color.rgb2gray(image)
628 inputImg = transform.resize(bwImg, rescaleSize)
629 features = hog(inputImg, orientations, pixelsPerCell, cellsPerBlock, blockNorm, visualize, transformSqrt, True)
630 if visualize:
631 from matplotlib.pyplot import imshow, figure, subplot
632 hogViz = features[1]
633 features = features[0]
634 figure()
635 subplot(1,2,1)
636 imshow(inputImg)
637 subplot(1,2,2)
638 imshow(hogViz)
639 return float32(features)
640
641 def createHOGTrainingSet(imageDirectory, classLabel, rescaleSize = (64,64), orientations = 9, pixelsPerCell = (8,8), blockNorm = 'L1', cellsPerBlock = (2, 2), visualize = False, transformSqrt = False):
642 inputData = []
643 for filename in listdir(imageDirectory):
644 img = imread(imageDirectory+filename)
645 features = HOG(img, rescaleSize, orientations, pixelsPerCell, cellsPerBlock, blockNorm, visualize, transformSqrt)
646 inputData.append(features)
647
648 nImages = len(inputData)
649 return array(inputData, dtype = float32), array([classLabel]*nImages)
650
651
652 #########################
653 # running tests
654 #########################
655
656 if __name__ == "__main__":
657 import doctest
658 import unittest
659 suite = doctest.DocFileSuite('tests/cvutils.txt')
660 #suite = doctest.DocTestSuite()
661 unittest.TextTestRunner().run(suite)
662 #doctest.testmod()
663 #doctest.testfile("example.txt")