├── LA_from_pc.m ├── CCfeatureExtraction.m ├── detection_assesment.m ├── main_Velodyne_LA_meanShape.m ├── main_Velodyne_fruit_detection.m ├── main_CrossVal_Velodyne_fruit_detection.m ├── cell2vec.m ├── ListFiles_txt.m ├── GroundTruthCenters.m ├── CC_split.m ├── pointCloudReading.m ├── Split_predict.m ├── LICENSE ├── ClusterLabelAssignment.m ├── LabelClusterMatrix.m ├── txt2cell.m ├── FP_removal.m ├── ListFiles_trial.m ├── mean_canopy_geometry.m ├── compute_predictions.m ├── trainingCCwmtoaFP.m ├── dbscan.m └── README.md /LA_from_pc.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAP-UdL-AT/fruit_detection_in_LiDAR_pointClouds/HEAD/LA_from_pc.m -------------------------------------------------------------------------------- /CCfeatureExtraction.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAP-UdL-AT/fruit_detection_in_LiDAR_pointClouds/HEAD/CCfeatureExtraction.m -------------------------------------------------------------------------------- /detection_assesment.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAP-UdL-AT/fruit_detection_in_LiDAR_pointClouds/HEAD/detection_assesment.m -------------------------------------------------------------------------------- /main_Velodyne_LA_meanShape.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAP-UdL-AT/fruit_detection_in_LiDAR_pointClouds/HEAD/main_Velodyne_LA_meanShape.m -------------------------------------------------------------------------------- /main_Velodyne_fruit_detection.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAP-UdL-AT/fruit_detection_in_LiDAR_pointClouds/HEAD/main_Velodyne_fruit_detection.m -------------------------------------------------------------------------------- /main_CrossVal_Velodyne_fruit_detection.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAP-UdL-AT/fruit_detection_in_LiDAR_pointClouds/HEAD/main_CrossVal_Velodyne_fruit_detection.m -------------------------------------------------------------------------------- /cell2vec.m: -------------------------------------------------------------------------------- 1 | function [vector] = cell2vec(cell) 2 | vector = []; 3 | for i=1:size(cell,1) 4 | aux = cell(i); 5 | vector = [vector, str2double(aux{1})]; 6 | end 7 | end -------------------------------------------------------------------------------- /ListFiles_txt.m: -------------------------------------------------------------------------------- 1 | function files = ListFiles_txt(directory) 2 | 3 | f = dir(directory); 4 | 5 | files = []; 6 | for i=1:size(f,1) 7 | if f(i).isdir==0 8 | if strcmp(f(i).name(end-2:end),'txt')==1 9 | files = [files ; f(i)]; 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /GroundTruthCenters.m: -------------------------------------------------------------------------------- 1 | %% Detection assesment 2 | 3 | function center_of_labels=GroundTruthCenters(ROIs_directory) 4 | 5 | ROIs= ListFiles_txt(ROIs_directory); 6 | 7 | center_of_labels=zeros(size(ROIs,1),3); 8 | for i=1:size(ROIs,1) 9 | ROIvertex=txt2cell(strcat(ROIs_directory,'/',ROIs(i).name)); 10 | ROIvertex=[cell2vec(ROIvertex(:,1))',cell2vec(ROIvertex(:,2))',cell2vec(ROIvertex(:,3))']; 11 | center_of_labels(i,:)=ROIvertex(1,:); 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /CC_split.m: -------------------------------------------------------------------------------- 1 | function class=CC_split(CCwmtoa_K,ptCloud_all_thresh60_notOutliers_xyz,class) 2 | 3 | 4 | for i=1:size(CCwmtoa_K,1) 5 | if CCwmtoa_K(i) 6 | CCxyz=ptCloud_all_thresh60_notOutliers_xyz(class==i,1:3); 7 | rng(1); % For reproducibility 8 | idx=kmeans(CCxyz,CCwmtoa_K(i)); 9 | idx_class=idx; 10 | idx_class(idx==1)=i; 11 | if max(idx)>1 12 | for j=2:max(idx) 13 | idx_class(idx==j)=max(class)+j-1; 14 | end 15 | end 16 | class(class==i)=idx_class; 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /pointCloudReading.m: -------------------------------------------------------------------------------- 1 | function [ptCloud_all,ptCloud_all_xyz]=pointCloudReading(Trials,pcDirectory_txt) 2 | 3 | ptCloud_all_xyz=[]; 4 | for i=1:size(Trials,2) 5 | if exist(strcat(pcDirectory_txt,Trials{i},'.mat'),'file') 6 | load(strcat(pcDirectory_txt,Trials{i},'.mat')); 7 | else 8 | fileID=fopen(strcat(pcDirectory_txt,Trials{i},'.txt'),'r'); 9 | ptCloud_xyz=fscanf(fileID,'%f %f %f %f %f',[5 Inf])'; 10 | fclose(fileID); 11 | save(strcat(pcDirectory_txt,Trials{i},'.mat'),'ptCloud_xyz'); 12 | end 13 | ptCloud_all_xyz=[ptCloud_all_xyz;[ptCloud_xyz,ones(size(ptCloud_xyz,1),1)*i]]; 14 | end 15 | ptCloud_all=pointCloud(ptCloud_all_xyz(:,1:3)); 16 | 17 | end -------------------------------------------------------------------------------- /Split_predict.m: -------------------------------------------------------------------------------- 1 | function [CCwmtoa_K]=Split_predict(Split_technique,SVMModelCCwoa,SVMModelCCwmtoa,CCfeatures_nonFP,ptCloud_all_thresh60_notOutliers_xyz,class,Training_features) 2 | 3 | 4 | CCwmtoa_K=zeros(size(CCfeatures_nonFP,1),1); 5 | 6 | 7 | if Split_technique==3 %Technique 2 + if CCF(:,6)>(0.0012)||(CCF(:,2)>400&&CCF(:,15)<0.062) - > 3 apples + if CCF(:,6)>(0.0016) -> 4 apples 8 | CCwmtoa_K=CCwmtoa_K+(CCwmtoa_K>0).*((CCfeatures_nonFP(:,6)>0.0012)|((CCfeatures_nonFP(:,2)>400)&(CCfeatures_nonFP(:,15)<0.6))); %3 apples 9 | CCwmtoa_K=CCwmtoa_K+(CCwmtoa_K>0).*(CCfeatures_nonFP(:,6)>0.0016); %4 apples 10 | end 11 | 12 | 13 | if Split_technique==5 %SVM 14 | 15 | CCwmtoa_index=predict(SVMModelCCwoa,CCfeatures_nonFP(:,Training_features)); 16 | CCwmtoa_K=zeros(size(CCwmtoa_index)); 17 | CCwmtoa_K(CCwmtoa_index>0)=predict(SVMModelCCwmtoa,CCfeatures_nonFP(CCwmtoa_index,Training_features))+2; 18 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 GRAP-UdL-AT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ClusterLabelAssignment.m: -------------------------------------------------------------------------------- 1 | %% Detection assesment 2 | 3 | function [LCM_LOC,LCM_LOC_COL]=ClusterLabelAssignment(LCM) 4 | 5 | LCM_logical=logical(LCM); 6 | Labels_per_cluster=sum(LCM_logical); 7 | Clusters_one_label=(Labels_per_cluster==1); %COL=Clusters_one_label 8 | LCM_COL=LCM_logical.*repmat(Clusters_one_label,size(LCM,1),1); %LCM with the clusters that has only one label. 9 | Clusters_per_labels_COL=sum(LCM_COL,2); 10 | LCM_COL_LTwoC=LCM_COL.*repmat(Clusters_per_labels_COL>1,1,size(LCM,2));%LCM with the clustersID that has more than on cluster with only one label. 11 | LCM_COL_LOC=LCM_COL.*((LCM_COL_LTwoC.*LCM)==max((LCM_COL_LTwoC.*LCM),[],2)); 12 | LWC=repmat(sum(LCM_COL_LOC,2),1,size(LCM,2)); %Labels with cluster assigned 13 | 14 | 15 | LCM_not_COL=LCM_logical; 16 | LCM_not_COL(LCM_COL==1)=0; %LCM with the clusters that has more than one label 17 | LCM_not_COL_LOC=(~LWC).*(LCM_not_COL.*((LCM_not_COL.*LCM)==max((LCM_not_COL.*LCM),[],2))); 18 | 19 | LCM_LOC=LCM_COL_LOC+LCM_not_COL_LOC;% LCM assigning only one cluster to each Label 20 | 21 | LCM_LOC_COL=(LCM_LOC.*((LCM_LOC.*LCM)==max((LCM_LOC.*LCM),[],1)));%LCM assigning only one cluster to each Label and one Label to each Cluster 22 | 23 | end 24 | -------------------------------------------------------------------------------- /LabelClusterMatrix.m: -------------------------------------------------------------------------------- 1 | function [LCM,PtsInLabel]=LabelClusterMatrix(ptCloud,ROIs_directory,Trials,clustersID) 2 | 3 | ROIs=[]; 4 | for i=1:size(Trials,2) 5 | files= ListFiles_txt(strcat(ROIs_directory,Trials{i}(1:6))); 6 | ROIs=[ROIs ; files]; 7 | end 8 | 9 | PtsInLabel=zeros(size(clustersID)); 10 | positive=unique(clustersID); 11 | LCM=zeros(size(ROIs,1),size(positive,2));%Labels_Clusters_matrix: matrix that relates the clustersID and the Labels 12 | for i=1:size(ROIs,1) 13 | ROIvertex=txt2cell(strcat(ROIs(i).folder,'/',ROIs(i).name)); 14 | ROIvertex=[cell2vec(ROIvertex(:,1))',cell2vec(ROIvertex(:,2))',cell2vec(ROIvertex(:,3))']; 15 | ROI=[min(ROIvertex)',max(ROIvertex)']; 16 | ROIindex=findPointsInROI(ptCloud,ROI); 17 | LCM(i,:)=histcounts(clustersID(ROIindex),[positive (max(positive)+1)]); 18 | PtsInLabel(ROIindex)=1; 19 | 20 | end 21 | 22 | LCM(LCM<10)=0; 23 | 24 | PtsInLabel=PtsInLabel.*clustersID; 25 | classPtsInLabel=histcounts(PtsInLabel,[positive (max(positive)+1)]); 26 | classPts=histcounts(clustersID,[positive (max(positive)+1)]); 27 | LCM(:,(classPtsInLabel./classPts)<0.5)=0; 28 | 29 | end 30 | -------------------------------------------------------------------------------- /txt2cell.m: -------------------------------------------------------------------------------- 1 | function cell = txt2cell(textfile, retreive_by, idxs) 2 | % Function that reads a textfile and returns a cell array with the 3 | % indicated columns. If there is no indicated columns the function returns 4 | % the whole textfile into a cell array. 5 | % Example: txt2cell(textfile) returns all columns and rows. 6 | % txt2cell(textfile, 'columns', [1 3 5]) returns the first, third and fifth 7 | % columns (in the specified order). 8 | % txt2cell(textfile, 'rows', [1 3 5]) returns the first, third and 9 | % fifth rows (in the specified order). 10 | if nargin < 2 11 | retreive_by = 'nan'; 12 | idxs = 0; 13 | end 14 | if nargin == 2 15 | disp('Error, requires index of selected columns or rows') 16 | return 17 | end 18 | 19 | file = fopen(textfile); 20 | cell = []; 21 | while(1) 22 | row = fgetl(file); 23 | if(row == -1) 24 | break 25 | else 26 | split_row = strsplit(row); 27 | if (isequal(retreive_by, 'columns')) 28 | cell = [cell; split_row(idxs)]; %Return selected columns 29 | else 30 | cell = [cell; split_row]; %Return the whole txt 31 | end 32 | end 33 | 34 | end 35 | fclose(file); 36 | if strcmp(retreive_by, 'rows') 37 | cell = cell(idxs, :); 38 | end 39 | end -------------------------------------------------------------------------------- /FP_removal.m: -------------------------------------------------------------------------------- 1 | function [class,CCfeatures_nonFP]=FP_removal(SVMModelFP,CCfeatures,class,technique_FP,Training_features,trial) 2 | 3 | disp(strcat('Inicio FP removal ',trial,'...')) 4 | t=toc; 5 | CCfeatures_nonFP=CCfeatures; 6 | T=sum(CCfeatures(:,1)==0); 7 | TP=0; 8 | FP=0; 9 | if technique_FP==1 10 | i=1; 11 | while (i<=max(class)) 12 | if (CCfeatures_nonFP(i,2)<70 && CCfeatures_nonFP(i,17)<65.25)||CCfeatures_nonFP(i,6)>0.001||CCfeatures_nonFP(i,15)<0.46 13 | TP=TP+1-(CCfeatures_nonFP(i,1)>0); 14 | FP=FP+1-(CCfeatures_nonFP(i,1)==0); 15 | class(class==i)=-1; 16 | class(class>i)=class(class>i)-1; 17 | CCfeatures_nonFP(i,:)=[]; 18 | i=i-1; 19 | end 20 | i=i+1; 21 | end 22 | end 23 | 24 | 25 | if technique_FP==2 26 | i=1; 27 | while (i<=max(class)) 28 | FPprediction=predict(SVMModelFP,CCfeatures_nonFP(i,Training_features)); 29 | if ~FPprediction 30 | TP=TP+1-(CCfeatures_nonFP(i,1)>0); 31 | FP=FP+1-(CCfeatures_nonFP(i,1)==0); 32 | class(class==i)=-1; 33 | class(class>i)=class(class>i)-1; 34 | CCfeatures_nonFP(i,:)=[]; 35 | i=i-1; 36 | end 37 | i=i+1; 38 | end 39 | end 40 | disp(strcat(' realizado en:__', num2str(toc-t), ' seg. __ De_',num2str(T),'_FP se han eliminado_',num2str(TP),' correctamente i_',num2str(FP),' incorrectamente')) 41 | -------------------------------------------------------------------------------- /ListFiles_trial.m: -------------------------------------------------------------------------------- 1 | function [Trials,Trial] = ListFiles_trial(directory,Trial,Late_fusion_comb) 2 | 3 | f = dir(directory); 4 | % 5 | % files = []; 6 | % for i=1:size(f,1) 7 | % if f(i).isdir==0 8 | % if strcmp(Trial,'all')==1 && strcmp(f(i).name(end-2:end),'txt')==1 9 | % files = [files ; f(i)]; 10 | % elseif strcmp(f(i).name(end-7:end),strcat(Trial,'.txt'))==1 11 | % files = [files ; f(i)]; 12 | % elseif strcmp(f(i).name(end-8:end),strcat(Trial,'.txt'))==1 13 | % files = [files ; f(i)]; 14 | % elseif strcmp(f(i).name(end-9:end),strcat(Trial,'.txt'))==1 15 | % files = [files ; f(i)]; 16 | % end 17 | % end 18 | % end 19 | 20 | Trials=struct('name',Trial); 21 | 22 | 23 | for T=1:size(Trials,2) 24 | k=1; 25 | for i=1:size(f,1) 26 | if strcmp(f(i).name(8:end-4),Trials(T).name)==1 && strcmp(f(i).name(end-2:end),'txt')==1 27 | Trials(T).trees(k).treeID=f(i).name(1:6); 28 | Trials(T).trees(k).file=f(i).name; 29 | k=k+1; 30 | end 31 | end 32 | end 33 | 34 | 35 | for combID=1:size(Late_fusion_comb,2) 36 | T=T+1; 37 | for k=1:size(Trials(Late_fusion_comb{combID}(1)).trees,2) 38 | Trials(T).trees(k).treeID=Trials(Late_fusion_comb{combID}(1)).trees(k).treeID; 39 | Trials(T).trees(k).file='late_fusion'; 40 | Trials(T).trees(k).fuseTrials=Late_fusion_comb{combID}; 41 | end 42 | Trial{T}=strcat(Trial{Late_fusion_comb{combID}}); 43 | end 44 | -------------------------------------------------------------------------------- /mean_canopy_geometry.m: -------------------------------------------------------------------------------- 1 | function [tree_shape_E,tree_shape_W,mean_height,mean_width]=mean_canopy_geometry(ptCloud_nOut,center,steps) 2 | 3 | %Este codigo solamente es valido cuando la hilera de arboles es paralela al 4 | %eje de coordenadas "Y" y perpendicular al eje de coordenadas "Z". 5 | %% Procesado 6 | x_min=min(ptCloud_nOut(:,1)); 7 | x_max=max(ptCloud_nOut(:,1)); 8 | y_min=min(ptCloud_nOut(:,2)); 9 | y_max=max(ptCloud_nOut(:,2)); 10 | z_min=min(ptCloud_nOut(:,3)); 11 | z_max=max(ptCloud_nOut(:,3)); 12 | 13 | y_sub=y_min; 14 | z_sub=z_min; 15 | x_sub_min=zeros(round(z_max-z_min/steps)-1,round(y_max-y_min/steps)-1); 16 | x_sub_max=zeros(round(z_max-z_min/steps)-1,round(y_max-y_min/steps)-1); 17 | i=1; 18 | j=1; 19 | 20 | while(y_suby_sub&ptCloud_nOut(:,2)<(y_sub+steps),:); 22 | while(z_subz_sub&ptCloud_nOut_sub(:,3)<(z_sub+steps),:); 24 | 25 | %size(ptCloud_nOut_sub_sub,1) 26 | 27 | if size(ptCloud_nOut_sub_sub,1) 28 | x_sub_min(j,i)=min(ptCloud_nOut_sub_sub(:,1)); 29 | x_sub_max(j,i)=max(ptCloud_nOut_sub_sub(:,1)); 30 | else 31 | x_sub_min(j,i)=center; 32 | x_sub_max(j,i)=center; 33 | end 34 | j=j+1; 35 | z_sub=z_sub+steps; 36 | end 37 | z_sub=z_min; 38 | j=1; 39 | i=i+1; 40 | y_sub=y_sub+steps; 41 | end 42 | 43 | m_x_sub_max=mean(x_sub_max,2); 44 | m_x_sub_min=mean(x_sub_min,2); 45 | tree_shape_E=m_x_sub_min-center; 46 | tree_shape_W=m_x_sub_max-center; 47 | 48 | mean_height=mean(max(repmat([1:size(x_sub_min,1)]'./10,1,size(x_sub_min,2)).*[x_sub_min~=center])); 49 | mean_width=mean(max(x_sub_max-x_sub_min)); 50 | 51 | end 52 | -------------------------------------------------------------------------------- /compute_predictions.m: -------------------------------------------------------------------------------- 1 | function [ab, predicted, error, RMSE, b_origin, predicted_origin, error_origin, RMSE_origin]=compute_predictions(R,C,Trials) 2 | 3 | ab=zeros(size(R,1),2,size(C,2)); 4 | b_origin=zeros(size(R,1),size(C,2)); 5 | predicted=zeros(size(R,1),size(C,2)); 6 | predicted_origin=zeros(size(R,1),size(C,2)); 7 | error=zeros(size(R,1),size(C,2)); 8 | error_origin=zeros(size(R,1),size(C,2)); 9 | color=[255 0 0 ; 0 255 0 ; 0 0 255 ; 255 255 0 ; 255 0 255 ; 200 50 0 ; 0 255 255 ; 200 200 200 ; 255 150 0 ; 0 150 0 ; 100 100 100]; 10 | 11 | 12 | for j=1:size(C,2) 13 | fig=figure; 14 | for i=1:size(R,1) 15 | ab(i,:,j)=polyfit(C(1:size(R,1)~=i,j),R(1:size(R,1)~=i),1); 16 | b_origin(i,j)=C(1:size(R,1)~=i,j)\R(1:size(R,1)~=i); 17 | predicted(i,j)=ab(i,1,j)*C(i,j)+ab(i,2,j); 18 | predicted_origin(i,j)=b_origin(i,j)*C(i,j); 19 | error(i,j)=(R(i)-predicted(i,j))*100/R(i); 20 | error_origin(i,j)=(R(i)-predicted_origin(i,j))*100/R(i); 21 | coR=corrcoef(C(1:size(R,1)~=i,j),R(1:size(R,1)~=i)); 22 | R2(i,j)=coR(2,1).^2; 23 | leg{i}=strcat('Tree\_',num2str(i),' ; a=',num2str(ab(i,1,j)),' ; b=',num2str(ab(i,2,j))); 24 | % leg{i*2-1}=strcat('Tree\_',num2str(i),' - R^2=',num2str(R2(i,j)),' - Error=',num2str(error(i,j)),'%'); 25 | % leg{i*2}=strcat('Tree\_',num2str(i),' - R^2=',num2str(R2(i,j)),' - Error=',num2str(error(i,j)),'%'); 26 | p1(i)=plot(C(i,j),R(i),'o','LineWidth',2,'MarkerSize',8,'MarkerEdgeColor','k','MarkerFaceColor',color(i,:)/255); 27 | hold on 28 | p2(i)=plot(C(:,j),polyval(ab(i,:,j),C(:,j)),'-','LineWidth',1,'Color',color(i,:)/255); 29 | hold on 30 | 31 | 32 | 33 | end 34 | 35 | legend(p2,leg); 36 | title(strcat('Trial_',Trials(j).name),'Interpreter','none'); 37 | xlabel('Counted (TP+FP)'); 38 | ylabel('Ground truth'); 39 | set(fig.CurrentAxes,'fontsize',10,'FontName','Times New Roman'); 40 | 41 | end 42 | 43 | RMSE=sqrt(sum(error.^2)/size(error,1)); 44 | RMSE_origin=sqrt(sum(error_origin.^2)/size(error_origin,1)); 45 | 46 | end 47 | -------------------------------------------------------------------------------- /trainingCCwmtoaFP.m: -------------------------------------------------------------------------------- 1 | function [SVMModelFP,SVMModelCCwoa,SVMModelCCwmtoa]=trainingCCwmtoaFP(CCfeatures,trial,Training_features,... 2 | StandardizeFP,KernelFunctionFP,BoxConstrainFP,... 3 | StandardizeCCwoa,KernelFunctionCCwoa,BoxConstrainCCwoa,... 4 | StandardizeCCwmtoa,KernelFunctionCCwmtoa,BoxConstrainCCwmtoa,models_Directory,session) 5 | 6 | 7 | %% Training SVM FP 8 | disp(strcat(' FP training (Standardize: ',num2str(StandardizeFP),' - Kernel: ',KernelFunctionFP,' - BoxConstrain: ',num2str(BoxConstrainFP),')')) 9 | t=toc; 10 | X=CCfeatures(:,Training_features); 11 | Y=(CCfeatures(:,1)>0); 12 | if size(unique(Y),1)==1 13 | BoxConstrainFP=1; 14 | end 15 | SVMModelFP = fitcsvm(X,Y,'Standardize',StandardizeFP,'KernelFunction',KernelFunctionFP,'BoxConstrain',BoxConstrainFP); 16 | disp(strcat(' realizado en:__', num2str(toc-t), ' seg.')) 17 | t=toc; 18 | 19 | %% Training SVM CCwoa 20 | if BoxConstrainCCwoa>0 21 | disp(strcat(' CCwoa training (Standardize: ',num2str(StandardizeCCwoa),' - Kernel: ',KernelFunctionCCwoa,' - BoxConstrain: ',num2str(BoxConstrainCCwoa),')')) 22 | X=CCfeatures(CCfeatures(:,1)>0,Training_features); 23 | Y=(CCfeatures(CCfeatures(:,1)>0,1)>1); 24 | if size(unique(Y),1)==1 25 | BoxConstrainCCwoa=1; 26 | end 27 | SVMModelCCwoa = fitcsvm(X,Y,'Standardize',StandardizeCCwoa,'KernelFunction',KernelFunctionCCwoa,'BoxConstrain',BoxConstrainCCwoa); 28 | % saveCompactModel(SVMModelCCwoa,strcat(models_Directory,'SVMModelCCwoa_',trial(1:end-4),'_s',num2str(session),'.mat')); 29 | disp(strcat(' realizado en:__', num2str(toc-t), ' seg.')) 30 | t=toc; 31 | else 32 | SVMModelCCwoa=[]; 33 | end 34 | 35 | %% Training SVM CCwmtoa 36 | if BoxConstrainCCwmtoa>0 37 | disp(strcat(' CCwmtoa training (Standardize: ',num2str(StandardizeCCwmtoa),' - Kernel: ',KernelFunctionCCwmtoa,' - BoxConstrain: ',num2str(BoxConstrainCCwmtoa),')')) 38 | X=CCfeatures(CCfeatures(:,1)>1,Training_features); 39 | Y=(CCfeatures(CCfeatures(:,1)>1,1)>2); 40 | if size(unique(Y),1)==1 41 | BoxConstrainCCwmtoa=1; 42 | end 43 | SVMModelCCwmtoa = fitcsvm(X,Y,'Standardize',StandardizeCCwmtoa,'KernelFunction',KernelFunctionCCwmtoa,'BoxConstrain',BoxConstrainCCwmtoa); 44 | % saveCompactModel(SVMModelCCwmtoa,strcat(models_Directory,'SVMModelCCwmtoa_',trial(1:end-4),'_s',num2str(session),'.mat')); 45 | disp(strcat(' realizado en:__', num2str(toc-t), ' seg.')) 46 | t=toc; 47 | else 48 | SVMModelCCwmtoa=[]; 49 | end 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /dbscan.m: -------------------------------------------------------------------------------- 1 | 2 | % ------------------------------------------------------------------------- 3 | % Function: [class,type]=dbscan(x,k,Eps) 4 | % ------------------------------------------------------------------------- 5 | % Aim: 6 | % Clustering the data with Density-Based Scan Algorithm with Noise (DBSCAN) 7 | % ------------------------------------------------------------------------- 8 | % Input: 9 | % x - data set (m,n); m-objects, n-variables 10 | % k - number of objects in a neighborhood of an object 11 | % (minimal number of objects considered as a cluster) 12 | % Eps - neighborhood radius, if not known avoid this parameter or put [] 13 | % ------------------------------------------------------------------------- 14 | % Output: 15 | % class - vector specifying assignment of the i-th object to certain 16 | % cluster (m,1) 17 | % type - vector specifying type of the i-th object 18 | % (core: 1, border: 0, outlier: -1) 19 | % ------------------------------------------------------------------------- 20 | % Example of use: 21 | % x=[randn(30,2)*.4;randn(40,2)*.5+ones(40,1)*[4 4]]; 22 | % [class,type]=dbscan(x,5,[]); 23 | % ------------------------------------------------------------------------- 24 | % References: 25 | % [1] M. Ester, H. Kriegel, J. Sander, X. Xu, A density-based algorithm for 26 | % discovering clusters in large spatial databases with noise, proc. 27 | % 2nd Int. Conf. on Knowledge Discovery and Data Mining, Portland, OR, 1996, 28 | % p. 226, available from: 29 | % www.dbs.informatik.uni-muenchen.de/cgi-bin/papers?query=--CO 30 | % [2] M. Daszykowski, B. Walczak, D. L. Massart, Looking for 31 | % Natural Patterns in Data. Part 1: Density Based Approach, 32 | % Chemom. Intell. Lab. Syst. 56 (2001) 83-92 33 | % ------------------------------------------------------------------------- 34 | % Written by Michal Daszykowski 35 | % Department of Chemometrics, Institute of Chemistry, 36 | % The University of Silesia 37 | % December 2004 38 | % http://www.chemometria.us.edu.pl 39 | 40 | function [class,type]=dbscan(x,k,Eps) 41 | 42 | [m,n]=size(x); 43 | 44 | if nargin<3 || isempty(Eps) 45 | [Eps]=epsilon(x,k); 46 | end 47 | 48 | x=[[1:m]' x]; 49 | [m,n]=size(x); 50 | type=zeros(1,m); 51 | no=1; 52 | touched=zeros(m,1); 53 | 54 | for i=1:m 55 | if touched(i)==0; 56 | ob=x(i,:); 57 | D=dist(ob(2:n),x(:,2:n)); 58 | ind=find(D<=Eps); 59 | 60 | if length(ind)>1 & length(ind)=k+1; 71 | type(i)=1; 72 | class(ind)=ones(length(ind),1)*max(no); 73 | 74 | while ~isempty(ind) 75 | ob=x(ind(1),:); 76 | touched(ind(1))=1; 77 | ind(1)=[]; 78 | D=dist(ob(2:n),x(:,2:n)); 79 | i1=find(D<=Eps); 80 | 81 | if length(i1)>1 82 | class(i1)=no; 83 | if length(i1)>=k+1; 84 | type(ob(1))=1; 85 | else 86 | type(ob(1))=0; 87 | end 88 | 89 | for i=1:length(i1) 90 | if touched(i1(i))==0 91 | touched(i1(i))=1; 92 | ind=[ind i1(i)]; 93 | class(i1(i))=no; 94 | end 95 | end 96 | end 97 | end 98 | no=no+1; 99 | end 100 | end 101 | end 102 | 103 | i1=find(class==0); 104 | class(i1)=-1; 105 | type(i1)=-1; 106 | 107 | 108 | %........................................... 109 | function [Eps]=epsilon(x,k) 110 | 111 | % Function: [Eps]=epsilon(x,k) 112 | % 113 | % Aim: 114 | % Analytical way of estimating neighborhood radius for DBSCAN 115 | % 116 | % Input: 117 | % x - data matrix (m,n); m-objects, n-variables 118 | % k - number of objects in a neighborhood of an object 119 | % (minimal number of objects considered as a cluster) 120 | 121 | 122 | 123 | [m,n]=size(x); 124 | 125 | Eps=((prod(max(x)-min(x))*k*gamma(.5*n+1))/(m*sqrt(pi.^n))).^(1/n); 126 | 127 | 128 | %............................................ 129 | function [D]=dist(i,x) 130 | 131 | % function: [D]=dist(i,x) 132 | % 133 | % Aim: 134 | % Calculates the Euclidean distances between the i-th object and all objects in x 135 | % 136 | % Input: 137 | % i - an object (1,n) 138 | % x - data matrix (m,n); m-objects, n-variables 139 | % 140 | % Output: 141 | % D - Euclidean distance (m,1) 142 | 143 | 144 | 145 | [m,n]=size(x); 146 | D=sqrt(sum((((ones(m,1)*i)-x).^2)')); 147 | 148 | if n==1 149 | D=abs((ones(m,1)*i-x))'; 150 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Matlab implementation for fruit detection in 3D LiDAR point clouds. 2 | 3 | ## Introduction 4 | This project is a matlab implementation for fruit detection in 3D point clouds acquired with LiDAR sensor Velodyne VLP-16 (Velodyne LIDAR Inc., San Jose, CA, USA). 5 | 6 | This implementation was used to evaluate the [LFuji-air dataset](http://www.grap.udl.cat/en/publications/lfuji-air-dataset/), which contains 3D LiDAR data of 11Fuji apple trees with the corresponding fruit position annotations. Find more information in: 7 | 8 | * [Fruit detection, yield prediction and canopy geometric characterization using LiDAR with forced air flow [1]](https://doi.org/10.1016/j.compag.2019.105121) 9 | 10 | 11 | 12 | ## Preparation 13 | 14 | 15 | First of all, clone the code 16 | ``` 17 | git clone https://github.com/ GRAP-UdL-AT/fruit_detection_in_LiDAR_pointClouds.git 18 | ``` 19 | 20 | Then, create a folder named “data” in the same directory where the code were saved. 21 | Inside the /data folder, save the ground truth and point cloud data (“AllTrees_Groundtruth” and “AllTrees_pcloud”) available at [LFuji-air dataset](http://www.grap.udl.cat/en/publications/LFuji_air_dataset.html). 22 | 23 | 24 | ### Pre-requisites 25 | 26 | * MATLAB R2018 (we have not tested it in other matlab versions) 27 | * Computer Vision System Toolbox 28 | * Statistics and Machine Learning Toolbox 29 | 30 | ### Data Preparation 31 | 32 | * **LFuji-air dataset**: 33 | Save the [LFuji-air dataset](http://www.grap.udl.cat/en/publications/LFuji_air_dataset.html) in /data folder. 34 | 35 | ## Cross-vailidation (fruit detection) 36 | 37 | Open the matlab file **_main_CrossVal_Velodyne_fruit_detection.m_** and set the following parameters: 38 | ``` 39 | directory = $”code_directory”$; %Write the directory where the code and the /data folder are placed. 40 | Trials2eval = ${trials_to_evaluate}$; %List the trials to evaluate 41 | ``` 42 | example: 43 | ``` 44 | directory = 'F:\fruit_detection\vel_air'; 45 | Trials2eval = {'H1_n_E_O','H1_n_E','H1_n_O','H1_H2_n_E_O','H1_n_af_E_O'}; 46 | ``` 47 | Execute the file **_main_CrossVal_Velodyne_fruit_detection.m_**. 48 | 49 | ## Train (fruit detection) 50 | Open the matlab file **_main_Velodyne_fruit_detection.m_** and set the following parameters: 51 | ``` 52 | directory = $”code_directory”$; %Write the directory where the code and the /data folder are placed. 53 | pcDiectory_txt = strcat(directory, $”training_data_folder”$ ); %Write the name of the training data folder. 54 | train = $logical_number$; %Set this parameter to 1 for training the svm models. 55 | ``` 56 | example: 57 | ``` 58 | directory = 'F:\fruit_detection\vel_air'; 59 | pcDiectory_txt = strcat(directory, '\data\TrainingData\'); 60 | train = 1; 61 | ``` 62 | Execute the file **_main_Velodyne_fruit_detection.m_**. 63 | 64 | ## Test (fruit detection) 65 | Open the matlab file **_main_Velodyne_fruit_detection.m_** and set the following parameters: 66 | ``` 67 | directory = $”code_directory”$; %Write the directory where the code and the /data folder are placed. 68 | pcDiectory_txt = strcat(directory, $”test_data_folder”$ ); %Write the name of the test data folder. 69 | train = $logical_number$; %Set this parameter to 0 to test data using a previously trained svm models. 70 | ``` 71 | example: 72 | ``` 73 | directory = 'F:\fruit_detection\vel_air'; 74 | pcDiectory_txt = strcat(directory, '\data\TestData\'); 75 | train = 0; 76 | ``` 77 | Execute the file **_main_Velodyne_fruit_detection.m_**. 78 | 79 | ## Canopy geomtry characterization 80 | In [[1]]((http://www.grap.udl.cat/en/publications/index.html)), the [LFuji-air dataset](http://www.grap.udl.cat/en/publications/LFuji_air_dataset.html) is used to evaluate the fruit detection performance, but also to compute canopy geometrical measurements such as mean height, mean width, canopy contour, mean canopy cross section and leave area. To compute this canopy geometrical parameters from a 3D LiDAR point cloud, do the following: 81 | 82 | Open the matlab file **_main_Velodyne_LA_meanShape.m_** and set the following parameters: 83 | ``` 84 | directory = $”code_directory”$; % Write the directory where the code and the /data folder are placed. 85 | Trials2eval = ${trials_to_evaluate}$; %List the trials to evaluate 86 | ``` 87 | example: 88 | ``` 89 | directory = 'F:\fruit_detection\vel_air'; 90 | Trials2eval = {'H1_n_E_O','H1_n_E','H1_n_O','H1_H2_n_E_O','H1_n_af_E_O'}; 91 | ``` 92 | Execute the file **_main_Velodyne_LA_meanShape.m_**. 93 | 94 | ## Authorship 95 | 96 | This project is contributed by [GRAP-UdL-AT](http://www.grap.udl.cat/en/index.html). 97 | 98 | Please contact authors to report bugs @ j.gene@eagrof.udl.cat 99 | 100 | 101 | ## Citation 102 | 103 | If you find this implementation or the analysis conducted in our report helpful, please consider citing: 104 | 105 | @article{Gene-Mola2019, 106 | Author = {Gen{\'e}-Mola, Jordi and Gregorio, Eduard and Auat Cheein, Fernando and Guevara, Javier and Llorens, Jordi and Sanz-Cortiella, Ricardo and Escol{\`a}, Alexandre and Rosell-Polo, Joan R}, 107 | Title = {Fruit detection, yield prediction and canopy geometric characterization using LiDAR with forced air flow}, 108 | Journal = {Submitted}, 109 | Year = {2019} 110 | } 111 | 112 | ## References 113 | 114 | [[1] Gené-Mola J, Gregorio E, Auat Cheein F, Guevara J, Llorens J, Sanz-Cortiella R, Escolà A, Rosell-Polo JR. 2020. Fruit detection, yield prediction and canopy geometric characterization using LiDAR with forced air flow. Computers and Electronics in Agriculture, 168 (2020), 105121. DOI: 10.1016/j.compag.2019.105121](https://doi.org/10.1016/j.compag.2019.105121). 115 | 116 | #### Acknowledgements 117 | This work was partly funded by the Spanish Ministry of Science, Innovation and Universities (grant RTI2018-094222-B-I00[[PAgFRUIT project]]( https://www.pagfruit.udl.cat/en/) by MCIN/AEI/10.13039/501100011033 and by “ERDF, a way of making Europe”, by the European Union). 118 | 119 | --------------------------------------------------------------------------------