image thumbnail
from MATLAB codes for canopy image analysis by Lauri Korhonen
Codes for thresholding skyward-looking canopy images and calculating proportion of canopy pixels.

matlab_cc.m
%
%	A MATLAB program for automated analysis of vertical canopy images	
%
% 	Authors: Jaakko Heikkinen & Lauri Korhonen 2006-2009
%	Citation: Korhonen, L. & Heikkinen, J. 2008. Automated analysis of in situ canopy images for the estimation of forest canopy cover. 
%		Forest Science 55(4): 323-334.
%	Thresholding algorithm: Nobis, M. & Hunziker, U. 2005. Automatic thresholding for hemispherical canopy photographs based on 
%		edge detection. Agricultural and forest meteorology 128: 243-250.
%	This version analyzes 4:3 area of the image determined by hor_angle parameter.
%	If the thresholding fails, you can try adjusting min- or max_thresh and sky parameters to make binarization work.

%Constants

filetype='jpg';	%Which type of files are read
distance=8;		%Distance used in defining whether the maxima are real
sky=11; 		%If the maximum brightness difference between sky and canopy is smaller than this value, the image is supposed to be pure sky!
medf_dist=7; 	%Distance used in the median filtering of the maxima
min_thresh=60;
max_thresh=220;	%These define what range of 8-bit values (0-255) is analyzed. Speeds up the measurement and eliminates some extra peaks.
elem_size=10;	%Diameter of the circular structuring element used in painting; 10 OR for 640x480 resolution images
true_focal=6.5;	%Focal length of the camera, mm
ccd_hor=6.16;	%Horizontal size of the ccd sensor
ccd_ver=4.62;	%Vertical size of the ccd sensor
hor_angle=90 	%Horizontal angle of view that should be used, degrees. Larger than cameras AOV -> whole image is analyzed.
thresh=180;		%Initial guess for the threshold

files=dir(['*.', filetype]); 	%Read all files of the specified type

results=cell(length(files),4);	%Create array for the results
results{1,1}='Image';
results{1,2}='Threshold'; 
results{1,3}='C_Closure'; 
results{1,4}='C_Cover'; 

disp('Analyzing image:');

%%%%%%%%%%%%%%% START LOOP %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

for h=1:length(files) 	%Loop goes through all images h in directory
    
	fileid=files(h).name(1:length(files(h).name)-(length(filetype)+1));
	disp(fileid);
	
	rgb=imread(files(h).name); 		%Read RGB image
	[rows,cols,channels]=size(rgb);	%Read image dimensions
	if cols ~=640					%Image is scaled to 640x480 so that size of the structuring element is correct
		rgb = imresize(rgb,[480 640],'nearest');
	end
	
	
	subplot(221) 
	image(rgb); %Show RGB-image in upper left subplot
	title(files(h).name,'FontSize',14,'Interpreter','none');
	axis image
	hold on;
	[rows,cols,channels]=size(rgb); %Save image dimesions
	
	orig_angle=2*atan(ccd_hor/(2*true_focal))*180/pi; %Calculate horizontal angle of view in degrees
	
	blue=rgb(:,:,3);	%Extract blue channel
	blue=medfilt2(blue); %Median filtering to decrease noise
	
	
	%%%%%%%%%%%%%%%  THRESHOLDING  %%%%%%%%%%%%%%%%%%%%
	
	blue11=blue(1:(rows-1),1:(cols-1));	%Left top corner subpixel matrix
	blue12=blue(1:(rows-1),2:cols);		%Right top corner subpixel matrix
	blue21=blue(2:rows,1:(cols-1));		%Left bottom corner subpixel matrix
	blue22=blue(2:rows,2:cols);			%Right bottom corner subpixel matrix

	bin=false(size(blue));   			%Make empty 0-array
	difftable=zeros(256,1);          	%Empty vector for later use
	
	for k=min_thresh:max_thresh	%Loop through 8-bit blue channel value range
		
		bin=(blue<=k);	%Binarization with the current threshold

		bin11=bin(1:(rows-1),1:(cols-1));	%Subimages for comparing the pixels
		bin12=bin(1:(rows-1),2:cols);
		bin21=bin(2:rows,1:(cols-1));
		bin22=bin(2:rows,2:cols);
		
		%Pixels 11 vs 12
		is_difference=bin11~=bin12;
		dif11_12=abs(blue11(is_difference)-blue12(is_difference));
		
		%Pixels 11 vs 21
		is_difference=bin11~=bin21;
		dif11_21=abs(blue11(is_difference)-blue21(is_difference));
		
		%Pixels 11 vs 22
		is_difference=bin11~=bin22;
		dif11_22=abs(blue11(is_difference)-blue22(is_difference));
		
		%Pixels 12 vs 21
		is_difference=bin12~=bin21;
		dif12_21=abs(blue12(is_difference)-blue21(is_difference));
		
		%Calculate denominator
		div=(length(dif11_12)+length(dif11_21)+length(dif11_22)+length(dif12_21));
		if div ~=0
			%Calculate mean brightness diffenrence between the threshold pixels
			difftable(k)=( sum(dif11_12)+sum(dif11_21)+sum(dif11_22)+sum(dif12_21))/div;
		else
			difftable(k)=0; %No differences 
		end
		
	end 

	%Median filtering to smooth the difference histogram
	midmed=medfilt1(difftable, medf_dist);
	
	%%%%%%%%%%%% REDUCTION OF AOV FOR FURTHER ANALYSIS %%%%%%%%%%%%%%% 
	%Reduction is done in 4:3 aspect ratio, angle defined in horizontal direction
	wholeblue=rgb(:,:,3);	%Extract the blue channel for whole image for later use
	if orig_angle > hor_angle
	
		%Calculate the dimensions of the area to be analyzed
		new_cols=round(hor_angle*cols/orig_angle);
		new_rows=round(rows*new_cols/cols);	
		cmin=round(cols/2-new_cols/2);
		cmax=round(cols/2+new_cols/2);
		rmin=round(rows/2-new_rows/2);
		rmax=round(rows/2+new_rows/2);	
		
		blue=rgb(rmin:rmax,cmin:cmax,3);	%Extract the blue channel from the area to be analyzed
		[rows,cols]=size(blue); 			%New image dimensions
		
		%Delineate the area to be analyzed in the image
		frame=[cmin,rmin;cmax,rmin;cmax,rmax;cmin,rmax;cmin,rmin];
		plot(frame(:,1),frame(:,2),'LineWidth',1,'Color','r');
		hold off;
	end
		

	%%%%%%%%%% DRAW THRESHOLD PLOT %%%%%%%%%%%%%%%%
	
	subplot(222)
	bar(0:255,difftable,'FaceColor',[0.5 0.5 0.5],'EdgeColor',[0.5 0.5 0.5]);
	hold on

	plot(0:255,midmed, 'LineWidth',2,'Color','black');
		
	%The maximum difference is accepted only if the values at the distance of 8 and 16 are smaller than the peak value   
	is_maximum=false(256,1);

	%Find the possible maxima
	for top=min_thresh:max_thresh
		midleft=midmed(top-distance);
		midright=midmed(top+distance);
		extremeleft=midmed(top-2*distance);
		extremeright=midmed(top+2*distance);
		is_maximum(top)= (extremeleft<midleft) & (midleft<top)...
			& (midright<top) & (extremeright<midright);
	end
	possible_maxima=find(is_maximum==true);	
	
	if ~ isempty(possible_maxima) 
		%If possible maxima were found, find the largest one
		[medimax,max_index]=max(midmed(possible_maxima));

		if medimax>sky	%The brightness difference indicates the image is not only sky
		
			thresh=possible_maxima(max_index);	%Final threshold is determined
			
			%Finish the threshold plot
			plot(thresh,midmed(thresh),'Marker','o','MarkerSize',12,...
				'LineWidth',2,'MarkerFaceColor','white','MarkerEdgeColor','black');
			text(thresh-15,1.12*midmed(thresh),num2str(thresh),'FontSize',12);
			set(gca,'XLim',[0 255],'YLim',[0 1.3*midmed(thresh)]);
			title(strcat('Threshold = ',num2str(thresh)),'FontSize',13);
		else
			set(gca,'XLim',[0 255],'YLim',[0 1.3*max(midmed)]);
			disp('Empty image')
		end
	else
		set(gca,'XLim',[0 255],'YLim',[0 1.3*max(midmed)]);
	end
	hold off

	
	%%%%%%%%%%%%%%% CANOPY CLOSURE %%%%%%%%%%%%%%%%%%%%%%%%%
   
	binim=ones(size(blue));	%Initialize result image with white
	wholebinim=ones(size(wholeblue));	%Initialize result image with white
	%If a maximum was found and the image is not just sky, create a binary image
	if (~isempty(possible_maxima) && medimax>sky)
		binim(blue<thresh)=0;
		wholebinim(wholeblue<thresh)=0;
	end 
	%Otherwise use threshold from previous image
	
	%Calculate canopy closure
	whites=sum(sum(binim));
	cclosure=(1-whites/(rows*cols))*100;	
	%Print binary image
	colormap(gray(2));	%Set colors	
	subplot(223)
	set(gca,'YDir','normal');   
	image(binim*2);	%Plot image, multiplication needed to see the color differences
	axis image
	title(strcat('Canopy closure = ',num2str(round(cclosure)),'%'),'FontSize',13);
	
	
	%%%%%%%%%%%%%%%%%  CANOPY COVER %%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	%Paint the crowns black using morphological opening and closing
	elem=strel('disk',elem_size);
	opeclo=imopen(binim,elem);	%Because objects are black, do the closing with opening operation
	opeclo=imclose(opeclo,elem);

	%Calculate pixel sum
	whites=sum(sum(opeclo));
	ccover=(1-whites/(rows*cols))*100;
	
	%Plot painted image
	subplot(224)
	image(opeclo*2);	%Plot image, multiplication needed to see the color differences
	axis image
	title(strcat('Canopy cover = ',num2str(round(ccover)),'%'),'FontSize',13);
	orient portrait
	
	%%%%%%%%%%%%%% SAVE BIN AND CHECK IMAGES%%%%%%%%%%%%%%%%%%%%%
    
	%Save check image as jpg 
	print('-djpeg','-r100',['check_',files(h).name])
	
	%Save thresholded and painted images as bmp
	binim=binim>0; %Convert to 2-bit
	imwrite(binim, ['abin_',fileid,'_',num2str(hor_angle),'.bmp'],'bmp')	%Only analyzed part, binary
	opeclo=opeclo>0;
	imwrite(opeclo, ['painted_',fileid,'_',num2str(hor_angle),'.bmp'],'bmp')	%Only analyzed part, painted
	wholebinim=wholebinim>0;
	imwrite(wholebinim, ['bin_',fileid,'.bmp'],'bmp')	%Whole image, binary
	close all
	
	%Add results to array
	results{h+1,1}=files(h).name;
	results{h+1,2}=thresh;
	results{h+1,3}=cclosure;
	results{h+1,4}=ccover;
	
end
%Show and save results
results
xlswrite(['Matlab_CC',num2str(hor_angle),'.xls'],results);

clear all %Clear memory

Contact us at files@mathworks.com