''' Created on 14/03/2016 @author: Jose A. GarcĂ­a del Arco @organization: Universitat Oberta de Catlunya (UOC) / Institute of Marine Sciences (CSIC) @contact: jagarcia@icm.csic.es @license: Free to use and modify as you desired To launch script in Ubuntu Linux 16.04 LTS: 1. Open a linux terminal 2. Write: python /path_to_the_analysis_script/nephrops_tracking_v2.py /path_to_video_file/ experiment_n video_n /path_to_data_save/ start_date start_time end_date end_time Script input arguments (8): 1. Path to video file to be analyzed 2. Experiment number 3. Video number of a list of videos from an experiment 4. Path to save the data from the video analysis 5. Start date of the video. Format: DD/MM/YYYY 6. Start UTC time of the video. Format: HH:MM:SS 7. End date of the video. Format: DD/MM/YYYY 8. End UTC time of the video. Format: HH:MM:SS ''' from datetime import datetime, timedelta import os import sys import cv2 import numpy as np #Read command line arguments (see script heat) if len(sys.argv) < 8: print "Too few params, try something like: python /path_to_the_analysis_script/nephrops_tracking_v2.py /path_to_video_file/ experiment_n video_n /path_to_data_save/ start_date start_time end_date end_time" exit() video_path = sys.argv[1] experiment = sys.argv[2] video_number = sys.argv[3] data_path = sys.argv[4] start_date = sys.argv[5] start_time = sys.argv[6] end_date = sys.argv[7] end_time = sys.argv[8] #end_experiment_date = sys.argv[9] #end_experiment_time = sys.argv[10] #date and time of the end of an experiment, is equal for an experiment not change between video files #end_experiment_datetime=end_experiment_date + " " + end_experiment_time end_experiment_datetime=datetime.strptime("02/07/2014 9:00:00", "%d/%m/%Y %H:%M:%S") #date and time of the end of an experiment, is equal for an experiment not change between video files ''' if video_number==1: np.save(data_path, datetime.strptime(end_experiment_datetime, "%d/%m/%Y %H:%M:%S")) else: end_experiment_datetime=np.load(data_path+'end_experiment_date_time.npy',) ''' # Create a CLAHE object (Arguments are optional). clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(16,16)) #fourcc = cv2.cv.CV_FOURCC(*'MPEG') # Uncomment for write video directly #out = cv2.VideoWriter('/home/jose/PFC/valerio_video_prova.avi',fourcc, 20.0,(1280,1024)) # Uncomment for write video directly cap = cv2.VideoCapture(video_path) # Uncomment next line if you desire read video directly from a USB video camera and comment the last line, #it needed modify input arguments number #cap = cv2.VideoCapture(0) #cap.set(cv2.cv.CV_CAP_PROP_POS_FRAMES, 60805) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5)) fgbg = cv2.BackgroundSubtractorMOG2(100,256.0,False) # name of all the folders together #folders=['triangle','tri_forat','cercle','cercle_forat'] #folder_number=-1 frames_number = cap.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT) # Path to our shape models shape_models=np.load(data_path+'/training/shape_models.npy',mmap_mode=None) #print len(shape_models) # Open or create csv file with the experiment data f = open(data_path+'experiment'+str(experiment)+'/data/ritfim_poblacional_exp'+str(experiment)+'_video'+str(video_number)+'.csv','a') video_start_datetime = datetime.strptime(start_date + " " + start_time, "%d/%m/%Y %H:%M:%S") #print video_start_datetime video_end_datetime = datetime.strptime(end_date + " " + end_time, "%d/%m/%Y %H:%M:%S") #print video_end_datetime video_duration = video_end_datetime - video_start_datetime #print video_duration seconds = video_duration.days*86400 + video_duration.seconds #print seconds, frames_number seconds_by_frame = round(seconds / frames_number,4) img_num = 0 img_time = video_start_datetime # get the list of all jpg images from the path provided to use as shape or tag models def get_imlist(path): return [[os.path.join(path,f) for f in os.listdir(path) if (f.endswith('.jpg') or f.endswith('.png'))]] #check shape matches def shape_match(contour): matches = [] for shape in shape_models: match_value=cv2.matchShapes(contour, shape[0],1, 0.0) value = [match_value, shape[1]] matches.append(value) matches.sort() #for m in matches: #print m return matches[0] #check vertex from a approximate polygon to a shape def check_figure(lenght): if lenght==4: figure = 'circle' elif lenght==3: figure = 'triangle' else: figure = 'error' return figure def assign_shape(shape,x,y,black_pixels,triangle,triangle_hole,circle,circle_hole): if shape == 'triangle' and black_pixels == 0: triangle = ['triangle',x,y] #print 'assign shape 0' elif shape == 'triangle' and black_pixels > 0: triangle_hole = ['triangle_hole',x,y] #print 'assign shape 1' elif shape == 'circle' and black_pixels == 0: circle = ['circle',x,y] #print 'assign shape 2' elif shape == 'circle' and black_pixels > 0: circle_hole = ['circle_hole',x,y] #print 'assign shape 3' else: print 'assign shape error' return triangle,triangle_hole,circle,circle_hole def time_calculation(frame_number): time_transcurred = timedelta(seconds=(frame_number * seconds_by_frame)) frame_time = video_start_datetime + time_transcurred return frame_time def check_experiment_init(): circle = ['circle',-1,-1] triangle = ['triangle',-1,-1] triangle_hole = ['triangle_hole',-1,-1] circle_hole = ['circle_hole',-1,-1] fgmask = [] if video_number != 1: # 5 minutes of record without present objects in a video, saved for each start of an experiment cap_background = cv2.VideoCapture(data_path + '/Backgrounds/background_exp'+str(experiment)+'.avi')#test count_back = -1 while (cap_background.isOpened()): _, frame_back = cap_background.read() if frame_back is None: break count_back = count_back + 1 output = "Background frame number " + str(count_back) + " applied." sys.stdout.write('%s\r' % output) sys.stdout.flush() gray_img = cv2.cvtColor(frame_back, cv2.COLOR_BGR2GRAY) gray_img = clahe.apply(gray_img) fgmask = fgbg.apply(gray_img) # Load last values of animals positions and start with these if not is the first video of a experiment path_circle=data_path + 'experiment'+str(experiment)+'/data/last_values/circle'+str(video_number-1)+'.npy' path_circle_hole=data_path + 'experiment'+str(experiment)+'/data/last_values/circle_hole'+str(video_number-1)+'.npy' path_triangle=data_path + 'experiment'+str(experiment)+'/data/last_values/triangle'+str(video_number-1)+'.npy' path_triangle_hole=data_path + 'experiment'+str(experiment)+'/data/last_values/triangle_hole'+str(video_number-1)+'.npy' circle=np.load(path_circle,mmap_mode=None) circle_hole=np.load(path_circle_hole,mmap_mode=None) triangle=np.load(path_triangle,mmap_mode=None) triangle_hole=np.load(path_triangle_hole,mmap_mode=None) return circle, circle_hole, triangle, triangle_hole, fgmask ''' circle=['circle',1122,747] circle_hole=['circle_hole',78,598] triangle=['triangle',491,828] triangle_hole=['triangle_hole',107,500] #img_num=60806 ''' circle, circle_hole, triangle, triangle_hole, fgmask = check_experiment_init() #Start read video stream (file or USB cam) while(cap.isOpened()): ret, frame = cap.read() if frame is None or img_time > end_experiment_datetime: break gray_img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #gray_img = cv2.equalizeHist(gray_img) gray_img = clahe.apply(gray_img) fgmask = fgbg.apply(gray_img) fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel) fgmask = cv2.dilate(fgmask,kernel,iterations = 5) # ROI = Arena area (eliminate not necessary parts of a frame. if you use other water tank measures change polygon # Each pair represents a point (x_pixel, y_pixel) change of your convenience pts = np.array([[16,358],[33,872],[332,905],[609,915],[874,908],[1202,882],[1210,337],[887,331],[620,334],[332,343],[16,358]], np.int32) pts = pts.reshape((-1,1,2)) poly = cv2.polylines(frame,[pts],True,(0,255,255),3) mask1 = np.zeros(gray_img.shape, np.uint8) white = (255, 255, 255) cv2.fillConvexPoly(mask1, pts, white, lineType=8, shift=0) masked_image = cv2.bitwise_and(fgmask, mask1) otsu_out = np.zeros(gray_img.shape, np.uint8) contours, hierarchy = cv2.findContours(masked_image,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) for i, cnt in enumerate(contours): (x,y),radius = cv2.minEnclosingCircle(cnt) center = (int(x),int(y)) radius = int(radius) area = cv2.contourArea(cnt, True) hull = cv2.convexHull(cnt, True) hull_area = cv2.contourArea(hull) #print hull_area, radius, 'level 1' if radius > 40 and hull_area >= 500.0 and hull_area <= 100000.0: solidity = float(area)/hull_area x,y,w,h = cv2.boundingRect(cnt) aspect_ratio = float(w)/float(h) #print solidity, aspect_ratio, 'level 2' if solidity > -4.0 and aspect_ratio >= 0.15 and aspect_ratio <= 4.0: mask2 = np.zeros(gray_img.shape, np.uint8) rect = cv2.rectangle(frame,(x,y),(x+w,y+h),255,3) mask2[y:y+h, x:x+w] = 255 otsu_img = cv2.bitwise_and(gray_img,mask2) #hist, bins = np.histogram(otsu_img.ravel(), 256,[0,256]) #gray_level_mean = np.mean(otsu_img[y:y+h, x:x+w]) #gray_level_median = np.median(otsu_img[y:y+h, x:x+w]) gray_level_max = np.max(otsu_img[y:y+h, x:x+w]) roi_ret, otsu_th = cv2.threshold(otsu_img,int(float(gray_level_max)/1.2),255,cv2.THRESH_BINARY) #print 'level 3: ', gray_level_mean, gray_level_median, gray_level_max, roi_ret, aspect_ratio, radius, hull_area otsu_out = otsu_out + otsu_th #Canny edges #edges_img = cv2.bitwise_and(gray_img,mask2) #edges = cv2.Canny(otsu_img,roi_ret,gray_level_max,3,L2gradient=True) roi_contours, roi_hierarchy = cv2.findContours(otsu_th,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) for roi_i, roi_cnt in enumerate(roi_contours): roi_hull = cv2.convexHull(roi_cnt, True) roi_hull_area = cv2.contourArea(roi_hull) roi_x,roi_y,roi_w,roi_h = cv2.boundingRect(roi_cnt) roi_aspect_ratio = float (roi_w)/roi_h peri = cv2.arcLength(roi_cnt, True) approx = cv2.approxPolyDP(roi_cnt, 0.09 * peri, True) shape_value = shape_match(roi_cnt) M = cv2.moments(roi_cnt) if M['m00'] > 0.0 and shape_value[0] < 0.3 and roi_hull_area > 150.0 and roi_hull_area < 500.0 and roi_aspect_ratio > 0.5 and roi_aspect_ratio < 1.5 and peri > 45.0 and peri < 90.0: closed = cv2.isContourConvex(roi_cnt) shape_mask =np.zeros(gray_img.shape,np.uint8) cv2.drawContours(shape_mask,[roi_cnt],0,255,-1) shape_image = cv2.bitwise_and(otsu_th, shape_mask) figure = check_figure(len(approx)) center_mask = np.zeros(gray_img.shape,np.uint8) center = (int(M['m10']/M['m00']),int(M['m01']/M['m00'])) cv2.circle(center_mask,center, 4, 255, -1) pixels_cercle = np.transpose(np.nonzero(center_mask)) count_bp=0 for pixel in pixels_cercle: px_value = shape_image[pixel[0],pixel[1]] #print value if px_value==0: count_bp=count_bp+1 cv2.rectangle(frame,(roi_x,roi_y),(roi_x+roi_w,roi_y+roi_h),255,3) triangle,triangle_hole,circle,circle_hole = assign_shape(figure,center[0],center[1],count_bp,triangle,triangle_hole,circle,circle_hole) res_frame = cv2.resize(frame,(0,0),fx=0.5,fy=0.5) res_gray = cv2.resize(gray_img,(0,0),fx=0.5,fy=0.5) res_otsu_out = cv2.resize(otsu_out,(0,0),fx=0.5,fy=0.5) res_fgmask = cv2.resize(fgmask,(0,0),fx=0.5,fy=0.5) cv2.imshow('Gray Image',res_gray) cv2.imshow('Background Subtractor',res_otsu_out) cv2.imshow('Original Frame',res_frame) cv2.imshow('Frame',res_fgmask) # Define the codec and create VideoWriter object, uncomment if directly catch video from USB #out.write(frame) value0 = ('experiment_'+str(experiment)+'\t'+'video_number_'+str(video_number)+'\t'+str(img_num)+'\t'+str(circle[0])+'\t'+str(circle[1])+'\t'+str(circle[2])+'\t'+str(img_time)+'\n') value1 = ('experiment_'+str(experiment)+'\t'+'video_number_'+str(video_number)+'\t'+str(img_num)+'\t'+str(circle_hole[0])+'\t'+str(circle_hole[1])+'\t'+str(circle_hole[2])+'\t'+str(img_time)+'\n') value2 = ('experiment_'+str(experiment)+'\t'+'video_number_'+str(video_number)+'\t'+str(img_num)+'\t'+str(triangle[0])+'\t'+str(triangle[1])+'\t'+str(triangle[2])+'\t'+str(img_time)+'\n') value3 = ('experiment_'+str(experiment)+'\t'+'video_number_'+str(video_number)+'\t'+str(img_num)+'\t'+str(triangle_hole[0])+'\t'+str(triangle_hole[1])+'\t'+str(triangle_hole[2])+'\t'+str(img_time)+'\n') print 'experiment',experiment,'\t','video number',video_number,'\t',img_num,'\t',circle[0],'\t',circle[1],'\t',circle[2],'\t',img_time print 'experiment',experiment,'\t','video number',video_number,'\t',img_num,'\t',circle_hole[0],'\t',circle_hole[1],'\t',circle_hole[2],'\t',img_time print 'experiment',experiment,'\t','video number',video_number,'\t',img_num,'\t',triangle[0],'\t',triangle[1],'\t',triangle[2],'\t',img_time print 'experiment',experiment,'\t','video number',video_number,'\t',img_num,'\t',triangle_hole[0],'\t',triangle_hole[1],'\t',triangle_hole[2],'\t',img_time f.write(value0) f.write(value1) f.write(value2) f.write(value3) img_num=img_num+1 img_time = time_calculation(img_num) k = cv2.waitKey(30) & 0xff if k == 27: break # Save last values positions of the animals in a video frame path_circle=data_path+'experiment'+str(experiment)+'/data/last_values/circle'+str(video_number)+'.npy' path_circle_hole=data_path+'experiment'+str(experiment)+'/data/last_values/circle_hole'+str(video_number)+'.npy' path_triangle=data_path+'experiment'+str(experiment)+'/data/last_values/triangle'+str(video_number)+'.npy' path_triangle_hole=data_path+'experiment'+str(experiment)+'/data/last_values/triangle_hole'+str(video_number)+'.npy' np.save(path_circle,circle) np.save(path_circle_hole,circle_hole) np.save(path_triangle,triangle) np.save(path_triangle_hole,triangle_hole) print img_num, frames_number, seconds_by_frame cap.release() #out.release() cv2.destroyAllWindows()