Mercurial Hosting > traffic-intelligence
diff python/cvutils.py @ 614:5e09583275a4
Merged Nicolas/trafficintelligence into default
author | Mohamed Gomaa <eng.m.gom3a@gmail.com> |
---|---|
date | Fri, 05 Dec 2014 12:13:53 -0500 |
parents | b5525249eda1 |
children | 9202628a4130 |
line wrap: on
line diff
--- a/python/cvutils.py Thu Apr 18 15:29:33 2013 -0400 +++ b/python/cvutils.py Fri Dec 05 12:13:53 2014 -0500 @@ -1,13 +1,19 @@ #! /usr/bin/env python '''Image/Video utilities''' -import Image, ImageDraw # PIL try: import cv2 - opencvExists = True + opencvAvailable = True except ImportError: - print('OpenCV library could not be loaded') - opencvExists = False + print('OpenCV library could not be loaded (video replay functions will not be available)') # TODO change to logging module + opencvAvailable = False +try: + import skimage + skimageAvailable = True +except ImportError: + print('Scikit-image library could not be loaded (HoG-based classification methods will not be available)') + skimageAvailable = False + from sys import stdout import utils @@ -30,8 +36,9 @@ def saveKey(key): return chr(key&255) == 's' -def drawLines(filename, origins, destinations, w = 1, resultFilename='image.png'): +def plotLines(filename, origins, destinations, w = 1, resultFilename='image.png'): '''Draws lines over the image ''' + import Image, ImageDraw # PIL img = Image.open(filename) @@ -41,7 +48,7 @@ for p1, p2 in zip(origins, destinations): draw.line([p1.x, p1.y, p2.x, p2.y], width = w, fill = (256,0,0)) #draw.line([p1.x, p1.y, p2.x, p2.y], pen) - del draw + del draw #out = utils.openCheck(resultFilename) img.save(resultFilename) @@ -63,6 +70,7 @@ def cvMatToArray(cvmat): '''Converts an OpenCV CvMat to numpy array.''' + print('Deprecated, use new interface') from numpy.core.multiarray import zeros a = zeros((cvmat.rows, cvmat.cols))#array([[0.0]*cvmat.width]*cvmat.height) for i in xrange(cvmat.rows): @@ -70,32 +78,56 @@ a[i,j] = cvmat[i,j] return a -if opencvExists: - def computeHomography(srcPoints, dstPoints, method=0, ransacReprojThreshold=0.0): +if opencvAvailable: + def computeHomography(srcPoints, dstPoints, method=0, ransacReprojThreshold=3.0): '''Returns the homography matrix mapping from srcPoints to dstPoints (dimension Nx2)''' H, mask = cv2.findHomography(srcPoints, dstPoints, method, ransacReprojThreshold) return H - def arrayToCvMat(a, t = cv2.cv.CV_64FC1): + def arrayToCvMat(a, t = cv2.CV_64FC1): '''Converts a numpy array to an OpenCV CvMat, with default type CV_64FC1.''' + print('Deprecated, use new interface') cvmat = cv2.cv.CreateMat(a.shape[0], a.shape[1], t) for i in range(cvmat.rows): for j in range(cvmat.cols): cvmat[i,j] = a[i,j] return cvmat - def draw(img, positions, color, lastCoordinate = None): + def cvPlot(img, positions, color, lastCoordinate = None): last = lastCoordinate+1 if lastCoordinate != None and lastCoordinate >=0: last = min(positions.length()-1, lastCoordinate) for i in range(0, last-1): cv2.line(img, positions[i].asint().astuple(), positions[i+1].asint().astuple(), color) - def playVideo(filename, firstFrameNum = 0, frameRate = -1): + def cvImshow(windowName, img, rescale = 1.0): + 'Rescales the image (in particular if too large)' + from cv2 import resize + if rescale != 1.: + size = (int(round(img.shape[1]*rescale)), int(round(img.shape[0]*rescale))) + resizedImg = resize(img, size) + cv2.imshow(windowName, resizedImg) + else: + cv2.imshow(windowName, img) + + def computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients): + from copy import deepcopy + from numpy import identity, array + newImgSize = (int(round(width*undistortedImageMultiplication)), int(round(height*undistortedImageMultiplication))) + newCameraMatrix = deepcopy(intrinsicCameraMatrix) + newCameraMatrix[0,2] = newImgSize[0]/2. + newCameraMatrix[1,2] = newImgSize[1]/2. + return cv2.initUndistortRectifyMap(intrinsicCameraMatrix, array(distortionCoefficients), identity(3), newCameraMatrix, newImgSize, cv2.CV_32FC1) + + def playVideo(filename, firstFrameNum = 0, frameRate = -1, interactive = False, printFrames = True, text = None, rescale = 1.): '''Plays the video''' + windowName = 'frame' + cv2.namedWindow(windowName, cv2.WINDOW_NORMAL) wait = 5 if frameRate > 0: wait = int(round(1000./frameRate)) + if interactive: + wait = 0 capture = cv2.VideoCapture(filename) if capture.isOpened(): key = -1 @@ -105,49 +137,114 @@ while ret and not quitKey(key): ret, img = capture.read() if ret: - print('frame {0}'.format(frameNum)) + if printFrames: + print('frame {0}'.format(frameNum)) frameNum+=1 - cv2.imshow('frame', img) + if text != None: + cv2.putText(img, text, (10,50), cv2.cv.CV_FONT_HERSHEY_PLAIN, 1, cvRed) + cvImshow(windowName, img, rescale) key = cv2.waitKey(wait) + cv2.destroyAllWindows() + else: + print('Video capture for {} failed'.format(filename)) - def getImagesFromVideo(filename, nImages = 1, saveImage = False): - '''Returns nImages images from the video sequence''' + def getImagesFromVideo(videoFilename, firstFrameNum = 0, nFrames = 1, saveImage = False, outputPrefix = 'image'): + '''Returns nFrames images from the video sequence''' + from math import floor, log10 images = [] - capture = cv2.VideoCapture(filename) - if capture.isOpened(): + capture = cv2.VideoCapture(videoFilename) + if capture.isOpened(): + nDigits = int(floor(log10(capture.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT))))+1 ret = False - numImg = 0 - while numImg<nImages: + capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNum) + imgNum = 0 + while imgNum<nFrames: ret, img = capture.read() i = 0 while not ret and i<10: ret, img = capture.read() i += 1 if img.size>0: - numImg +=1 if saveImage: - cv2.imwrite('image{0:04d}.png'.format(numImg), img) + imgNumStr = format(firstFrameNum+imgNum, '0{}d'.format(nDigits)) + cv2.imwrite(outputPrefix+imgNumStr+'.png', img) else: images.append(img) + imgNum +=1 + capture.release() + else: + print('Video capture for {} failed'.format(videoFilename)) return images + + def getFPS(videoFilename): + capture = cv2.VideoCapture(videoFilename) + if capture.isOpened(): + fps = capture.get(cv2.cv.CV_CAP_PROP_FPS) + capture.release() + return fps + else: + print('Video capture for {} failed'.format(videoFilename)) + return None - def displayTrajectories(videoFilename, objects, homography = None, firstFrameNum = 0, lastFrameNumArg = None): + def imageBox(img, obj, frameNum, homography, width, height, px = 0.2, py = 0.2, pixelThreshold = 800): + 'Computes the bounding box of object at frameNum' + x = [] + y = [] + for f in obj.features: + if f.existsAtInstant(frameNum): + projectedPosition = f.getPositionAtInstant(frameNum).project(homography) + x.append(projectedPosition.x) + y.append(projectedPosition.y) + xmin = min(x) + xmax = max(x) + ymin = min(y) + ymax = max(y) + xMm = px * (xmax - xmin) + yMm = py * (ymax - ymin) + a = max(ymax - ymin + (2 * yMm), xmax - (xmin + 2 * xMm)) + yCropMin = int(max(0, .5 * (ymin + ymax - a))) + yCropMax = int(min(height - 1, .5 * (ymin + ymax + a))) + xCropMin = int(max(0, .5 * (xmin + xmax - a))) + xCropMax = int(min(width - 1, .5 * (xmin + xmax + a))) + if yCropMax != yCropMin and xCropMax != xCropMin and (yCropMax - yCropMin) * (xCropMax - xCropMin) > pixelThreshold: + croppedImg = img[yCropMin : yCropMax, xCropMin : xCropMax] + else: + croppedImg = [] + return croppedImg, yCropMin, yCropMax, xCropMin, xCropMax + + + def displayTrajectories(videoFilename, objects, boundingBoxes = {}, homography = None, firstFrameNum = 0, lastFrameNumArg = None, printFrames = True, rescale = 1., nFramesStep = 1, saveAllImages = False, undistort = False, intrinsicCameraMatrix = None, distortionCoefficients = None, undistortedImageMultiplication = 1.): '''Displays the objects overlaid frame by frame over the video ''' + from moving import userTypeNames + from math import ceil, log10 + capture = cv2.VideoCapture(videoFilename) + width = int(capture.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) + height = int(capture.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)) + + windowName = 'frame' + #cv2.namedWindow(windowName, cv2.WINDOW_NORMAL) + + if undistort: # setup undistortion + [map1, map2] = computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients) if capture.isOpened(): key = -1 ret = True frameNum = firstFrameNum capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, firstFrameNum) - if not lastFrameNumArg: + if lastFrameNumArg == None: from sys import maxint lastFrameNum = maxint else: lastFrameNum = lastFrameNumArg + nZerosFilename = int(ceil(log10(lastFrameNum))) while ret and not quitKey(key) and frameNum < lastFrameNum: ret, img = capture.read() if ret: - print('frame {0}'.format(frameNum)) + if undistort: + img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR) + if printFrames: + print('frame {0}'.format(frameNum)) for obj in objects: if obj.existsAtInstant(frameNum): if not hasattr(obj, 'projectedPositions'): @@ -155,25 +252,168 @@ obj.projectedPositions = obj.positions.project(homography) else: obj.projectedPositions = obj.positions - draw(img, obj.projectedPositions, cvRed, frameNum-obj.getFirstInstant()) - cv2.putText(img, '{0}'.format(obj.num), obj.projectedPositions[frameNum-obj.getFirstInstant()].asint().astuple(), cv2.FONT_HERSHEY_PLAIN, 1, cvRed) - cv2.imshow('frame', img) - key = cv2.waitKey() - if saveKey(key): - cv2.imwrite('image.png', img) - frameNum += 1 - + cvPlot(img, obj.projectedPositions, cvRed, frameNum-obj.getFirstInstant()) + if frameNum in boundingBoxes.keys(): + for rect in boundingBoxes[frameNum]: + cv2.rectangle(img, rect[0].asint().astuple(), rect[1].asint().astuple(), cvRed) + elif len(obj.features) != 0: + imgcrop, yCropMin, yCropMax, xCropMin, xCropMax = imageBox(img, obj, frameNum, homography, width, height) + cv2.rectangle(img, (xCropMin, yCropMin), (xCropMax, yCropMax), cvBlue, 1) + objDescription = '{} '.format(obj.num) + if userTypeNames[obj.userType] != 'unknown': + objDescription += userTypeNames[obj.userType][0].upper() + cv2.putText(img, objDescription, obj.projectedPositions[frameNum-obj.getFirstInstant()].asint().astuple(), cv2.cv.CV_FONT_HERSHEY_PLAIN, 1, cvRed) + if not saveAllImages: + cvImshow(windowName, img, rescale) + key = cv2.waitKey() + if saveAllImages or saveKey(key): + cv2.imwrite('image-{{:0{}}}.png'.format(nZerosFilename).format(frameNum), img) + frameNum += nFramesStep + if nFramesStep > 1: + capture.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, frameNum) + cv2.destroyAllWindows() + else: + print 'Cannot load file ' + videoFilename + + def computeHomographyFromPDTV(cameraFilename, method=0, ransacReprojThreshold=3.0): + '''Returns the homography matrix at ground level from PDTV format + https://bitbucket.org/hakanardo/pdtv''' + import pdtv + from numpy import array + camera = pdtv.load(cameraFilename) + srcPoints = [[x,y] for x, y in zip([1.,2.,2.,1.],[1.,1.,2.,2.])] # need floats!! + dstPoints = [] + for srcPoint in srcPoints: + projected = camera.image_to_world(tuple(srcPoint)) + dstPoints.append([projected[0], projected[1]]) + H, mask = cv2.findHomography(array(srcPoints), array(dstPoints), method, ransacReprojThreshold) + return H + + def undistortedCoordinates(map1, map2, x, y, maxDistance = 1.): + '''Returns the coordinates of a point in undistorted image + map1 and map2 are the mapping functions from undistorted image + to distorted (original image) + map1(x,y) = originalx, originaly''' + from numpy import abs, logical_and, unravel_index, dot, sum + from matplotlib.mlab import find + distx = abs(map1-x) + disty = abs(map2-y) + indices = logical_and(distx<maxDistance, disty<maxDistance) + closeCoordinates = unravel_index(find(indices), distx.shape) # returns i,j, ie y,x + xWeights = 1-distx[indices] + yWeights = 1-disty[indices] + return dot(xWeights, closeCoordinates[1])/sum(xWeights), dot(yWeights, closeCoordinates[0])/sum(yWeights) + + def undistortTrajectoryFromCVMapping(map1, map2, t): + '''test 'perfect' inversion''' + from moving import Trajectory + from numpy import isnan + undistortedTrajectory = Trajectory() + for i,p in enumerate(t): + res = undistortedCoordinates(map1, map2, p.x,p.y) + if not isnan(res).any(): + undistortedTrajectory.addPositionXY(res[0], res[1]) + else: + print i,p,res + return undistortedTrajectory + + def computeInverseMapping(originalImageSize, map1, map2): + 'Computes inverse mapping from maps provided by cv2.initUndistortRectifyMap' + from numpy import ones, isnan + invMap1 = -ones(originalImageSize) + invMap2 = -ones(originalImageSize) + for x in range(0,originalImageSize[1]): + for y in range(0,originalImageSize[0]): + res = undistortedCoordinates(x,y, map1, map2) + if not isnan(res).any(): + invMap1[y,x] = res[0] + invMap2[y,x] = res[1] + return invMap1, invMap2 + + def cameraIntrinsicCalibration(path, checkerBoardSize=[6,7], secondPassSearch=False, display=False): + ''' Camera calibration searches through all the images (jpg or png) located + in _path_ for matches to a checkerboard pattern of size checkboardSize. + These images should all be of the same camera with the same resolution. + + For best results, use an asymetric board and ensure that the image has + very high contrast, including the background. Suitable checkerboard: + http://ftp.isr.ist.utl.pt/pub/roswiki/attachments/camera_calibration(2f)Tutorials(2f)StereoCalibration/check-108.png + + The code below is based off of: + https://opencv-python-tutroals.readthedocs.org/en/latest/py_tutorials/py_calib3d/py_calibration/py_calibration.html + Modified by Paul St-Aubin + ''' + from numpy import zeros, mgrid, float32, savetxt + import glob, os + + # termination criteria + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) + + # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0) + objp = zeros((checkerBoardSize[0]*checkerBoardSize[1],3), float32) + objp[:,:2] = mgrid[0:checkerBoardSize[1],0:checkerBoardSize[0]].T.reshape(-1,2) + + # Arrays to store object points and image points from all the images. + objpoints = [] # 3d point in real world space + imgpoints = [] # 2d points in image plane. + + ## Loop throuhg all images in _path_ + 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]')) + for fname in images: + img = cv2.imread(fname) + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + # Find the chess board corners + ret, corners = cv2.findChessboardCorners(gray, (checkerBoardSize[1],checkerBoardSize[0]), None) + + # If found, add object points, image points (after refining them) + if ret: + print 'Found pattern in '+fname + + if(secondPassSearch): + corners = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria) + + objpoints.append(objp) + imgpoints.append(corners) + + # Draw and display the corners + if(display): + img = cv2.drawChessboardCorners(img, (checkerBoardSize[1],checkerBoardSize[0]), corners, ret) + if(img): + cv2.imshow('img',img) + cv2.waitKey(0) + + ## Close up image loading and calibrate + cv2.destroyAllWindows() + if len(objpoints) == 0 or len(imgpoints) == 0: + return False + try: + ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) + except NameError: + return False + savetxt('intrinsic-camera.txt', camera_matrix) + return camera_matrix, dist_coeffs + + def undistortImage(img, intrinsicCameraMatrix = None, distortionCoefficients = None, undistortedImageMultiplication = 1., interpolation=cv2.INTER_LINEAR): + '''Undistorts the image passed in argument''' + width = img.shape[1] + height = img.shape[0] + [map1, map2] = computeUndistortMaps(width, height, undistortedImageMultiplication, intrinsicCameraMatrix, distortionCoefficients) + return cv2.remap(img, map1, map2, interpolation=interpolation) + + def printCvMat(cvmat, out = stdout): '''Prints the cvmat to out''' + print('Deprecated, use new interface') for i in xrange(cvmat.rows): for j in xrange(cvmat.cols): out.write('{0} '.format(cvmat[i,j])) out.write('\n') def projectArray(homography, points): - '''Returns the coordinates of the projected points (format 2xN points) - through homography''' - from numpy.core._dotblas import dot + '''Returns the coordinates of the projected points through homography + (format: array 2xN points)''' + from numpy.core import dot from numpy.core.multiarray import array from numpy.lib.function_base import append @@ -185,10 +425,10 @@ prod = dot(homography, augmentedPoints) return prod[0:2]/prod[2] else: - return p + return points def project(homography, p): - '''Returns the coordinates of the projection of the point p + '''Returns the coordinates of the projection of the point p with coordinates p[0], p[1] through homography''' from numpy import array return projectArray(homography, array([[p[0]],[p[1]]])) @@ -201,13 +441,39 @@ return projectArray(homography, array(trajectory)) def invertHomography(homography): - 'Returns an inverted homography' + '''Returns an inverted homography + Unnecessary for reprojection over camera image''' from numpy.linalg.linalg import inv invH = inv(homography) invH /= invH[2,2] return invH -if opencvExists: +def undistortTrajectory(invMap1, invMap2, positions): + from numpy import floor, ceil + floorPositions = floor(positions) + #ceilPositions = ceil(positions) + undistortedTrajectory = [[],[]] + for i in xrange(len(positions[0])): + x,y = None, None + if positions[0][i]+1 < invMap1.shape[1] and positions[1][i]+1 < invMap1.shape[0]: + floorX = invMap1[floorPositions[1][i], floorPositions[0][i]] + floorY = invMap2[floorPositions[1][i], floorPositions[0][i]] + ceilX = invMap1[floorPositions[1][i]+1, floorPositions[0][i]+1] + ceilY = invMap2[floorPositions[1][i]+1, floorPositions[0][i]+1] + #ceilX = invMap1[ceilPositions[1][i], ceilPositions[0][i]] + #ceilY = invMap2[ceilPositions[1][i], ceilPositions[0][i]] + if floorX >=0 and floorY >=0 and ceilX >=0 and ceilY >=0: + x = floorX+(positions[0][i]-floorPositions[0][i])*(ceilX-floorX) + y = floorY+(positions[1][i]-floorPositions[1][i])*(ceilY-floorY) + undistortedTrajectory[0].append(x) + undistortedTrajectory[1].append(y) + return undistortedTrajectory + +def projectGInputPoints(homography, points): + from numpy import array + return projectTrajectory(homography, array(points+[points[0]]).T) + +if opencvAvailable: def computeTranslation(img1, img2, img1Points, maxTranslation2, minNMatches, windowSize = (5,5), level = 5, criteria = (cv2.TERM_CRITERIA_EPS, 0, 0.01)): '''Computes the translation of img2 with respect to img1 (loaded using OpenCV as numpy arrays) @@ -233,3 +499,38 @@ else: print(dp) return None + +if skimageAvailable: + def HOG(image, rescaleSize = (64, 64), orientations=9, pixelsPerCell=(8, 8), cellsPerBlock=(2, 2), visualize=False, normalize=False): + from skimage.feature import hog + from skimage import color, transform + + bwImg = color.rgb2gray(image) + inputImg = transform.resize(bwImg, rescaleSize) + features = hog(inputImg, orientations, pixelsPerCell, cellsPerBlock, visualize, normalize) + if visualize: + from matplotlib.pyplot import imshow, figure, subplot + hogViz = features[1] + features = features[0] + figure() + subplot(1,2,1) + imshow(img) + subplot(1,2,2) + imshow(hogViz) + return features + + def createHOGTrainingSet(imageDirectory, classLabel, rescaleSize = (64, 64), orientations=9, pixelsPerCell=(8, 8), cellsPerBlock=(2, 2), visualize=False, normalize=False): + from os import listdir + from numpy import array, float32 + from matplotlib.pyplot import imread + + inputData = [] + for filename in listdir(imageDirectory): + img = imread(imageDirectory+filename) + features = HOG(img, rescaleSize, orientations, pixelsPerCell, cellsPerBlock, visualize, normalize) + inputData.append(features) + + nImages = len(inputData) + return array(inputData, dtype = float32), array([classLabel]*nImages, dtype = float32) + +