% class for all vision functionality classdef qrotor_vision < handle properties n = 1; % for testing with sample_vid mlab_log = 'mlab_log.csv'; % log filename mlab = 'mlab.csv' % current vals filename pyth = 'pyth.csv' % altitude filename frame_count % number of frames analyzed gifname = 'testvid.gif'; % filename of gif tstamp % current time (shared between log and preview frame) fsize % [xres yres] % frame size in pixels fps_loc % [xcoord ycoord] for data text on plots fcenter % center of frame vid % video object b_preview = 0; % 1 shows preview plot, 0 does not b_cam = 1; % camera used? 0 uses simulated images in a loop framerate stride_time = .5; can_see % can the camera see the parrot quit_search % set to quit the search function altitude = 22; % of quadrotor nom_altitude = 15; % nominal altitude, where the PID is tuned bw_cutoff trace_begin % point to start tracing boundary past_centroid % [x y] location of previous centroid centroid % [x y] location of latest centroid in pixels (starts in middle of cal frame) c_int = 0; % [x y] integral value of centroid locations c_der = 0; % [x y] derivative value of centroid (velocity) kp = 5; ki = .05; kd = 3; end methods % constructor function self = qrotor_vision % initialize csv fid = fopen(self.mlab_log,'wt'); header = strcat('x correction,x_pro,x_int,x_der,',... 'y correction,y_pro,y_int,y_der,',... 'altitude,framerate,centroid_x,centroid_y\n'); fprintf(fid,header); fclose(fid); self.cam_cal if self.b_cam == 1 self.centroid = self.fcenter; % center by default else % test vid self.centroid = [600 600]; self.fsize = [792 612]; self.fps_loc = [690 20]; self.fcenter = [396 306]; end self.track_cent % starts infinite loop end % destructor function delete(self) imaqreset % disconnect camera fclose('all'); % close all log files end % adjust cam resolution (also recreates vid object, out of necessity) function res_adjust(self) if self.altitude > 20 res = 'MJPG_1920x1080'; self.fsize = [1920 1080]; self.fps_loc = [1800 20]; self.fcenter = [960 540]; elseif self.altitude > 10 res = 'MJPG_1280x720'; self.fsize = [1280 720]; self.fps_loc = [1160 20]; self.fcenter = [640 360]; elseif self.altitude > 5 res = 'MJPG_640x360'; self.fsize = [640 360]; self.fps_loc = [540 20]; self.fcenter = [320 180]; else % good for at least 8 feet res = 'MJPG_320x240'; self.fsize = [320 240]; self.fps_loc = [240 20]; self.fcenter = [160 120]; end try self.vid = videoinput('winvideo',1,res); triggerconfig(self.vid,'manual'); start(self.vid); catch end end % res_adjust % centroid tracker function track_cent(self) % tom needs to split this into multiple methods while 1 % this is the main loop update_duration = 0; while update_duration < self.stride_time tic BW = self.parrot_boundary; boundary = bwtraceboundary(BW,self.trace_begin,'N'); boundary_image = poly2mask(boundary(:,2),boundary(:,1),self.fsize(2),self.fsize(1)); % save last centroid location try self.past_centroid = self.centroid; catch end % update centroid location stat = regionprops(boundary_image,'centroid'); try self.centroid = [stat(1).Centroid(1) stat(1).Centroid(2)]; %disp(strcat(num2str(stat(1).Centroid(1)),', ',num2str(stat(1).Centroid(2)))); catch end % run control self.control_calcs %self.n if self.b_cam == 0 if self.n < 18 self.n = self.n + 1; else self.n = 1; end end % timing stuff update_duration = update_duration + toc; self.framerate = 1/toc; end % show result if self.b_preview == 1 clf imshow(boundary_image); hold on; try %centroid_marker = plot(stat(1).Centroid(1),stat(1).Centroid(2),'ro'); plot(stat(1).Centroid(1),stat(1).Centroid(2),'ro'); catch %err %disp(err.identifier); end try info_str = {strcat(num2str(self.framerate),' fps'),... strcat(num2str(stat(1).Centroid(1)),', ',num2str(stat(1).Centroid(2))),... self.tstamp{1}}; text(self.fps_loc(1),self.fps_loc(2),info_str,... 'BackgroundColor',[0 0 0],'Color',[1 1 1],'EdgeColor',[1 1 1]); catch % leave out centroid coords if no centroid was found info_str = {strcat(num2str(self.framerate),' fps'),self.tstamp{1}}; text(self.fps_loc(1),self.fps_loc(2),info_str,... 'BackgroundColor',[0 0 0],'Color',[1 1 1],'EdgeColor',[1 1 1]); end hold off; drawnow gif_frame = getframe(gcf); % grab both plots im = frame2im(gif_frame); [imind,cm] = rgb2ind(im,256); imwrite(imind,cm,self.gifname,'gif','WriteMode','append'); end %self.altitude = dlmread(self.pyth); % see if altitude will change res_adjust, run if necessary? end end % start or stop preview plot function set.b_preview(self,bool) if bool == 1 self.b_preview = 1; elseif bool == 0 self.b_preview = 0; else warning('Input 0 or 1.'); end end % b_preview function cam_cal(self) % set up camera if self.b_cam == 1 self.res_adjust; frame = getsnapshot(self.vid); else frame = imread('wm1.jpg'); end warea = zeros(1,99); % white area deriv_area = ones(1,95); % derivative of warea for n = 1:99 BW = imcomplement(im2bw(frame,1-(n/100))); if self.b_preview == 1 imshow(BW); end warea(n) = nnz(BW)/(1280*720); if n > 1 && n < 95 % desired value should never be outside of these deriv_area(n) = warea(n-1)-warea(n); if warea(n) < .1 || warea(n) > .3 deriv_area(n) = 1; % restricts results end end if self.b_preview == 1 drawnow end end if self.b_preview == 1 % gif init gif_frame = getframe(gcf); % grab both plots im = frame2im(gif_frame); [imind,cm] = rgb2ind(im,256); imwrite(imind,cm,self.gifname,'gif','Loopcount',inf); end [~,index] = min(deriv_area); %self.bw_cutoff = 1-((index+1)/100); % +5 was too dark self.bw_cutoff = .35; %warea(index); % white area %self.altitude = 4; %self.res_adjust; end function parrot_search(self) % scan edges of frame. if qrotor lost/on gps nav self.quit_search = 0; while 1; self.can_see = 0; % can be checked periodically by autoflight? % image capture if self.b_cam == 1 frame = getsnapshot(self.vid); % camera else frame = imread(strcat('wm',num2str(self.n),'.jpg')); % canned image end % convert to inv binary BW = imcomplement(im2bw(frame,self.bw_cutoff)); col = 0; row = find(BW(:,col),1); if isempty(row) && ~self.quit_search col = self.fsize(2); row = find(BW(:,col),1); if isempty(row) row = 0; col = find(BW(row,:),1); if isempty(col) row = self.fsize(1); col = find(BW(row,:),1); else self.trace_begin = [row,col]; break end else self.trace_begin = [row,col]; break end else self.trace_begin = [row,col]; break end self.can_see = 1; end end function BW = parrot_boundary(self) % determine start point for boundary trace % image capture if self.b_cam == 1 frame = getsnapshot(self.vid); % camera else frame = imread(strcat('wm',num2str(self.n),'.jpg')); % canned image self.can_see = 1; end % convert to inv binary BW = imcomplement(im2bw(frame,self.bw_cutoff)); % search at last centroid col = round(self.centroid(1)); row = find(BW(:,col),1); if isempty(row) % then try further in direction of last velocity (assume it was going fast) % have x and y velocity saved. search col if y velocity greater, or search row if x velocity greater % move the searched line according to c_der. how much? [~, idx] = max(self.c_der); % determine if x or y velocity is greater if idx == 1 % x velocity is greater, so row won't change as fast row = round(self.centroid(2)); col = find(BW(row,:),1); else % y velocity is greater, so col won't change as fast col = round(self.centroid(1)); row = find(BW(:,col),1); end if isempty(row) && isempty(col) % still not found % then try closer to center (assume correction started) xdiff = (self.centroid(1) - self.fsize(1)/2); ydiff = (self.centroid(2) - self.fsize(2)/2); col = round(self.centroid(1) + xdiff/2); row = find(BW(:,col),1); if isempty(row) self.can_see = 0; self.parrot_search % so that it doesnt escape the frame else self.trace_begin = [row,col]; end else self.trace_begin = [row,col]; end else self.trace_begin = [row,col]; end end function csv_update(self,prop,control_out) dlmwrite(self.mlab,... [control_out(1) control_out(2) self.can_see self.centroid(1) self.centroid(2)]); % current vals only csv_data = strcat(num2str(control_out(1)),',',num2str(self.centroid(1)),',',... num2str(prop(1)),',',num2str(self.c_int(1)),',',num2str(self.c_der(1)),',',... num2str(control_out(2)),',',num2str(self.centroid(2)),',',num2str(prop(2)),',',... num2str(self.c_int(2)),',',num2str(self.c_der(2)),',',num2str(self.altitude),',',... num2str(self.framerate),',',num2str(self.can_see),',',self.tstamp(1)); dlmwrite(self.mlab_log,csv_data,'-append','delimiter','','newline','pc'); % full log w/ history end function control_calcs(self) % PID controller prop = self.fcenter - self.centroid; prop(2) = -prop(2); self.c_int = .8*(self.c_int + .6*prop); % has to stay updated self.c_der = self.centroid - self.past_centroid; % also used elsewhere self.c_der(1) = -self.c_der(1); %alt_coeff = self.altitude/self.nom_altitude; control_out = (self.kp*prop + self.ki*self.c_int + self.kd*self.c_der)... ./(self.fsize./10); %[x y] disp(control_out); self.tstamp = datestr(now,'MM:SS FFF'); self.tstamp = cellstr(self.tstamp); self.csv_update(prop,control_out) % pass arguments instead of class vars end end end