├── .gitignore ├── L1.m ├── L2.m ├── README.md ├── cbires.fig ├── cbires.m ├── colorAutoCorrelogram.m ├── colorMoments.m ├── confMatGet.m ├── confMatPlot.m ├── dataset_pca.mat ├── dataset_with_img_names.mat ├── gaborWavelet.m ├── hsvHistogram.m ├── lowpassfilter.m ├── matPlot.m ├── reference.pdf ├── relativeDeviation.m ├── svm.m └── waveletTransform.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin/ 3 | bin-debug/ 4 | bin-release/ 5 | 6 | # Other files and folders 7 | .settings/ 8 | 9 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 10 | # should NOT be excluded as they contain compiler settings and other important 11 | # information for Eclipse / Flash Builder. 12 | # Compiled source # 13 | ################### 14 | *.com 15 | *.class 16 | *.dll 17 | *.exe 18 | *.o 19 | *.so 20 | 21 | # Packages # 22 | ############ 23 | # it's better to unpack these files and commit the raw source 24 | # git has its own built in compression methods 25 | *.7z 26 | *.dmg 27 | *.gz 28 | *.iso 29 | *.jar 30 | *.rar 31 | *.tar 32 | *.zip 33 | 34 | # Logs and databases # 35 | ###################### 36 | *.log 37 | *.sql 38 | *.sqlite 39 | 40 | # OS generated files # 41 | ###################### 42 | .DS_Store 43 | .DS_Store? 44 | ._* 45 | .Spotlight-V100 46 | .Trashes 47 | Icon? 48 | ehthumbs.db 49 | Thumbs.db 50 | 51 | # Don't track folders/files # 52 | ############################# 53 | images/ 54 | test/ 55 | 56 | # Datasets # 57 | ############ 58 | *.mat 59 | -------------------------------------------------------------------------------- /L1.m: -------------------------------------------------------------------------------- 1 | function L1(numOfReturnedImages, queryImageFeatureVector, dataset, folder_name, img_ext) 2 | % input: 3 | % numOfReturnedImages : num of images returned by query 4 | % queryImageFeatureVector: query image in the form of a feature vector 5 | % dataset: the whole dataset of images transformed in a matrix of 6 | % features 7 | % 8 | % output: 9 | % plot: plot images returned by query 10 | 11 | % extract image fname from queryImage and dataset 12 | query_image_name = queryImageFeatureVector(:, end); 13 | dataset_image_names = dataset(:, end); 14 | 15 | queryImageFeatureVector(:, end) = []; 16 | dataset(:, end) = []; 17 | 18 | % compute manhattan distance 19 | manhattan = zeros(size(dataset, 1), 1); 20 | for k = 1:size(dataset, 1) 21 | % manhattan(k) = sum( abs(dataset(k, :) - queryImageFeatureVector) ); 22 | % ralative manhattan distance 23 | manhattan(k) = sum( abs(dataset(k, :) - queryImageFeatureVector) ./ ( 1 + dataset(k, :) + queryImageFeatureVector ) ); 24 | end 25 | 26 | % add image fnames to manhattan 27 | manhattan = [manhattan dataset_image_names]; 28 | 29 | % sort them according to smallest distance 30 | [sortedDist indx] = sortrows(manhattan); 31 | sortedImgs = sortedDist(:, 2); 32 | 33 | % clear axes 34 | arrayfun(@cla, findall(0, 'type', 'axes')); 35 | 36 | % display query image 37 | str_name = int2str(query_image_name); 38 | queryImage = imread( strcat(folder_name, '\', str_name, img_ext) ); 39 | subplot(3, 7, 1); 40 | imshow(queryImage, []); 41 | title('Query Image', 'Color', [1 0 0]); 42 | 43 | % dispaly images returned by query 44 | for m = 1:numOfReturnedImages 45 | img_name = sortedImgs(m); 46 | img_name = int2str(img_name); 47 | str_name = strcat(folder_name, '\', img_name, img_ext); 48 | returnedImage = imread(str_name); 49 | subplot(3, 7, m+1); 50 | imshow(returnedImage, []); 51 | end 52 | 53 | end -------------------------------------------------------------------------------- /L2.m: -------------------------------------------------------------------------------- 1 | function L2(numOfReturnedImages, queryImageFeatureVector, dataset, metric, folder_name, img_ext) 2 | % input: 3 | % numOfReturnedImages : num of images returned by query 4 | % queryImageFeatureVector: query image in the form of a feature vector 5 | % dataset: the whole dataset of images transformed in a matrix of 6 | % features 7 | % 8 | % output: 9 | % plot: plot images returned by query 10 | 11 | % extract image fname from queryImage and dataset 12 | query_img_name = queryImageFeatureVector(:, end); 13 | dataset_img_names = dataset(:, end); 14 | 15 | queryImageFeatureVector(:, end) = []; 16 | dataset(:, end) = []; 17 | 18 | euclidean = zeros(size(dataset, 1), 1); 19 | 20 | if (metric == 2) 21 | % compute euclidean distance 22 | for k = 1:size(dataset, 1) 23 | euclidean(k) = sqrt( sum( power( dataset(k, :) - queryImageFeatureVector, 2 ) ) ); 24 | end 25 | elseif (metric == 3) 26 | % compute standardized euclidean distance 27 | weights = nanvar(dataset, [], 1); 28 | weights = 1./weights; 29 | for q = 1:size(dataset, 2) 30 | euclidean = euclidean + weights(q) .* (dataset(:, q) - queryImageFeatureVector(1, q)).^2; 31 | end 32 | euclidean = sqrt(euclidean); 33 | elseif (metric == 4) % compute mahalanobis distance 34 | weights = nancov(dataset); 35 | [T, flag] = chol(weights); 36 | if (flag ~= 0) 37 | errordlg('The matrix is not positive semidefinite. Please choose another similarity metric!'); 38 | return; 39 | end 40 | weights = T \ eye(size(dataset, 2)); %inv(T) 41 | del = bsxfun(@minus, dataset, queryImageFeatureVector(1, :)); 42 | dsq = sum((del/T) .^ 2, 2); 43 | dsq = sqrt(dsq); 44 | euclidean = dsq; 45 | elseif (metric == 5) 46 | euclidean = pdist2(dataset, queryImageFeatureVector, 'cityblock'); 47 | elseif (metric == 6) 48 | euclidean = pdist2(dataset, queryImageFeatureVector, 'minkowski'); 49 | elseif (metric == 7) 50 | euclidean = pdist2(dataset, queryImageFeatureVector, 'chebychev'); 51 | elseif (metric == 8) 52 | euclidean = pdist2(dataset, queryImageFeatureVector, 'cosine'); 53 | elseif (metric == 9) 54 | euclidean = pdist2(dataset, queryImageFeatureVector, 'correlation'); 55 | elseif (metric == 10) 56 | euclidean = pdist2(dataset, queryImageFeatureVector, 'spearman'); 57 | elseif (metric == 11) 58 | % compute normalized euclidean distance 59 | for k = 1:size(dataset, 1) 60 | euclidean(k) = sqrt( sum( power( dataset(k, :) - queryImageFeatureVector, 2 ) ./ std(queryImageFeatureVector) ) ); 61 | end 62 | end 63 | 64 | % add image fnames to euclidean 65 | euclidean = [euclidean dataset_img_names]; 66 | 67 | % sort them according to smallest distance 68 | [sortEuclidDist indxs] = sortrows(euclidean); 69 | sortedEuclidImgs = sortEuclidDist(:, 2); 70 | 71 | % clear axes 72 | arrayfun(@cla, findall(0, 'type', 'axes')); 73 | 74 | % display query image 75 | str_name = int2str(query_img_name); 76 | query_img = imread( strcat(folder_name, '\', str_name, img_ext) ); 77 | subplot(3, 7, 1); 78 | imshow(query_img, []); 79 | title('Query Image', 'Color', [1 0 0]); 80 | 81 | % dispaly images returned by query 82 | for m = 1:numOfReturnedImages 83 | img_name = sortedEuclidImgs(m); 84 | img_name = int2str(img_name); 85 | str_img_name = strcat(folder_name, '\', img_name, img_ext); 86 | returned_img = imread(str_img_name); 87 | subplot(3, 7, m+1); 88 | imshow(returned_img, []); 89 | end 90 | 91 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ImageRetrieval 2 | ============== 3 | 4 | Content Based Image Retrieval Techniques (e.g. knn, svm using MatLab GUI). 5 | 6 | When cloning the repository you'll have to create a directory inside it and name it images. 7 | 8 | Inside the images directory you're gonna put your own images which in a sense actually forms your image dataset. 9 | 10 | Then you're gonna have to build the features dataset by pressing the red button with the label "Create DB of image features" 11 | 12 | If you don't have any images you can use the wang dataset which is the one I used to demonstrate the above techniques. 13 | 14 | You can download it from here: http://wang.ist.psu.edu/docs/related/ 15 | 16 | For a more detailed description checkout "reference.pdf" 17 | 18 | For an even more detailed description have a look at **http://arxiv.org/abs/1608.03811** 19 | 20 | If you find this work useful please cite it using the following format: 21 | ``` 22 | @article{DBLP:journals/corr/Mitro16, 23 | author = {Joani Mitro}, 24 | title = {Content-based image retrieval tutorial}, 25 | journal = {CoRR}, 26 | volume = {abs/1608.03811}, 27 | year = {2016}, 28 | url = {http://arxiv.org/abs/1608.03811}, 29 | archivePrefix = {arXiv}, 30 | eprint = {1608.03811}, 31 | timestamp = {Mon, 13 Aug 2018 01:00:00 +0200}, 32 | biburl = {https://dblp.org/rec/bib/journals/corr/Mitro16}, 33 | bibsource = {dblp computer science bibliography, https://dblp.org} 34 | } 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /cbires.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirk86/ImageRetrieval/cab6a346bcd67035e7e657ceca4f955eb0405aea/cbires.fig -------------------------------------------------------------------------------- /cbires.m: -------------------------------------------------------------------------------- 1 | function varargout = cbires(varargin) 2 | % CBIRES MATLAB code for cbires.fig 3 | % CBIRES, by itself, creates a new CBIRES or raises the existing 4 | % singleton*. 5 | % 6 | % H = CBIRES returns the handle to a new CBIRES or the handle to 7 | % the existing singleton*. 8 | % 9 | % CBIRES('CALLBACK',hObject,eventData,handles,...) calls the local 10 | % function named CALLBACK in CBIRES.M with the given input arguments. 11 | % 12 | % CBIRES('Property','Value',...) creates a new CBIRES or raises the 13 | % existing singleton*. Starting from the left, property value pairs are 14 | % applied to the GUI before cbires_OpeningFcn gets called. An 15 | % unrecognized property name or invalid value makes property application 16 | % stop. All inputs are passed to cbires_OpeningFcn via varargin. 17 | % 18 | % *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one 19 | % instance to run (singleton)". 20 | % 21 | % See also: GUIDE, GUIDATA, GUIHANDLES 22 | 23 | % Edit the above text to modify the response to help cbires 24 | 25 | % Last Modified by GUIDE v2.5 23-May-2013 22:01:15 26 | 27 | % Begin initialization code - DO NOT EDIT 28 | gui_Singleton = 1; 29 | gui_State = struct('gui_Name', mfilename, ... 30 | 'gui_Singleton', gui_Singleton, ... 31 | 'gui_OpeningFcn', @cbires_OpeningFcn, ... 32 | 'gui_OutputFcn', @cbires_OutputFcn, ... 33 | 'gui_LayoutFcn', [] , ... 34 | 'gui_Callback', []); 35 | if nargin && ischar(varargin{1}) 36 | gui_State.gui_Callback = str2func(varargin{1}); 37 | end 38 | 39 | if nargout 40 | [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); 41 | else 42 | gui_mainfcn(gui_State, varargin{:}); 43 | end 44 | % End initialization code - DO NOT EDIT 45 | 46 | 47 | % --- Executes just before cbires is made visible. 48 | function cbires_OpeningFcn(hObject, eventdata, handles, varargin) 49 | % This function has no output args, see OutputFcn. 50 | % hObject handle to figure 51 | % eventdata reserved - to be defined in a future version of MATLAB 52 | % handles structure with handles and user data (see GUIDATA) 53 | % varargin command line arguments to cbires (see VARARGIN) 54 | 55 | % Choose default command line output for cbires 56 | handles.output = hObject; 57 | 58 | % Update handles structure 59 | guidata(hObject, handles); 60 | 61 | % UIWAIT makes cbires wait for user response (see UIRESUME) 62 | % uiwait(handles.figure1); 63 | 64 | 65 | % --- Outputs from this function are returned to the command line. 66 | function varargout = cbires_OutputFcn(hObject, eventdata, handles) 67 | % varargout cell array for returning output args (see VARARGOUT); 68 | % hObject handle to figure 69 | % eventdata reserved - to be defined in a future version of MATLAB 70 | % handles structure with handles and user data (see GUIDATA) 71 | 72 | % Get default command line output from handles structure 73 | varargout{1} = handles.output; 74 | 75 | 76 | % --- Executes on button press in btn_BrowseImage. 77 | function btn_BrowseImage_Callback(hObject, eventdata, handles) 78 | % hObject handle to btn_BrowseImage (see GCBO) 79 | % eventdata reserved - to be defined in a future version of MATLAB 80 | % handles structure with handles and user data (see GUIDATA) 81 | 82 | [query_fname, query_pathname] = uigetfile('*.jpg; *.png; *.bmp', 'Select query image'); 83 | 84 | if (query_fname ~= 0) 85 | query_fullpath = strcat(query_pathname, query_fname); 86 | imgInfo = imfinfo(query_fullpath); 87 | [pathstr, name, ext] = fileparts(query_fullpath); % fiparts returns char type 88 | 89 | if ( strcmp(lower(ext), '.jpg') == 1 || strcmp(lower(ext), '.png') == 1 ... 90 | || strcmp(lower(ext), '.bmp') == 1 ) 91 | 92 | queryImage = imread( fullfile( pathstr, strcat(name, ext) ) ); 93 | % handles.queryImage = queryImage; 94 | % guidata(hObject, handles); 95 | 96 | % extract query image features 97 | queryImage = imresize(queryImage, [384 256]); 98 | if (strcmp(imgInfo.ColorType, 'truecolor') == 1) 99 | hsvHist = hsvHistogram(queryImage); 100 | autoCorrelogram = colorAutoCorrelogram(queryImage); 101 | color_moments = colorMoments(queryImage); 102 | % for gabor filters we need gary scale image 103 | img = double(rgb2gray(queryImage))/255; 104 | [meanAmplitude, msEnergy] = gaborWavelet(img, 4, 6); % 4 = number of scales, 6 = number of orientations 105 | wavelet_moments = waveletTransform(queryImage, imgInfo.ColorType); 106 | % construct the queryImage feature vector 107 | queryImageFeature = [hsvHist autoCorrelogram color_moments meanAmplitude msEnergy wavelet_moments str2num(name)]; 108 | elseif (strcmp(imgInfo.ColorType, 'grayscale') == 1) 109 | grayHist = imhist(queryImage); 110 | grayHist = grayHist/sum(grayHist); 111 | grayHist = grayHist(:)'; 112 | color_moments = [mean(mean(queryImage)) std(std(double(queryImage)))]; 113 | [meanAmplitude, msEnergy] = gaborWavelet(queryImage, 4, 6); % 4 = number of scales, 6 = number of orientations 114 | wavelet_moments = waveletTransform(queryImage, imgInfo.ColorType); 115 | % construct the queryImage feature vector 116 | queryImageFeature = [grayHist color_moments meanAmplitude msEnergy wavelet_moments str2num(name)]; 117 | end 118 | 119 | % update handles 120 | handles.queryImageFeature = queryImageFeature; 121 | handles.img_ext = ext; 122 | handles.folder_name = pathstr; 123 | guidata(hObject, handles); 124 | helpdlg('Proceed with the query by executing the green button!'); 125 | 126 | % Clear workspace 127 | clear('query_fname', 'query_pathname', 'query_fullpath', 'pathstr', ... 128 | 'name', 'ext', 'queryImage', 'hsvHist', 'autoCorrelogram', ... 129 | 'color_moments', 'img', 'meanAmplitude', 'msEnergy', ... 130 | 'wavelet_moments', 'queryImageFeature', 'imgInfo'); 131 | else 132 | errordlg('You have not selected the correct file type'); 133 | end 134 | else 135 | return; 136 | end 137 | 138 | 139 | % --- Executes on selection change in popupmenu_DistanceFunctions. 140 | function popupmenu_DistanceFunctions_Callback(hObject, eventdata, handles) 141 | % hObject handle to popupmenu_DistanceFunctions (see GCBO) 142 | % eventdata reserved - to be defined in a future version of MATLAB 143 | % handles structure with handles and user data (see GUIDATA) 144 | 145 | % Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_DistanceFunctions contents as cell array 146 | % contents{get(hObject,'Value')} returns selected item from popupmenu_DistanceFunctions 147 | 148 | handles.DistanceFunctions = get(handles.popupmenu_DistanceFunctions, 'Value'); 149 | guidata(hObject, handles); 150 | 151 | 152 | % --- Executes during object creation, after setting all properties. 153 | function popupmenu_DistanceFunctions_CreateFcn(hObject, eventdata, handles) 154 | % hObject handle to popupmenu_DistanceFunctions (see GCBO) 155 | % eventdata reserved - to be defined in a future version of MATLAB 156 | % handles empty - handles not created until after all CreateFcns called 157 | 158 | % Hint: popupmenu controls usually have a white background on Windows. 159 | % See ISPC and COMPUTER. 160 | if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) 161 | set(hObject,'BackgroundColor','white'); 162 | end 163 | 164 | 165 | % --- Executes on selection change in popupmenu_NumOfReturnedImages. 166 | function popupmenu_NumOfReturnedImages_Callback(hObject, eventdata, handles) 167 | % hObject handle to popupmenu_NumOfReturnedImages (see GCBO) 168 | % eventdata reserved - to be defined in a future version of MATLAB 169 | % handles structure with handles and user data (see GUIDATA) 170 | 171 | % Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_NumOfReturnedImages contents as cell array 172 | % contents{get(hObject,'Value')} returns selected item from popupmenu_NumOfReturnedImages 173 | 174 | handles.numOfReturnedImages = get(handles.popupmenu_NumOfReturnedImages, 'Value'); 175 | guidata(hObject, handles); 176 | 177 | 178 | % --- Executes during object creation, after setting all properties. 179 | function popupmenu_NumOfReturnedImages_CreateFcn(hObject, eventdata, handles) 180 | % hObject handle to popupmenu_NumOfReturnedImages (see GCBO) 181 | % eventdata reserved - to be defined in a future version of MATLAB 182 | % handles empty - handles not created until after all CreateFcns called 183 | 184 | % Hint: popupmenu controls usually have a white background on Windows. 185 | % See ISPC and COMPUTER. 186 | if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) 187 | set(hObject,'BackgroundColor','white'); 188 | end 189 | 190 | 191 | % --- Executes on button press in btnExecuteQuery. 192 | function btnExecuteQuery_Callback(hObject, eventdata, handles) 193 | % hObject handle to btnExecuteQuery (see GCBO) 194 | % eventdata reserved - to be defined in a future version of MATLAB 195 | % handles structure with handles and user data (see GUIDATA) 196 | 197 | % check for image query 198 | if (~isfield(handles, 'queryImageFeature')) 199 | errordlg('Please select an image first, then choose your similarity metric and num of returned images!'); 200 | return; 201 | end 202 | 203 | % check for dataset existence 204 | if (~isfield(handles, 'imageDataset')) 205 | errordlg('Please load a dataset first. If you dont have one then you should consider creating one!'); 206 | return; 207 | end 208 | 209 | % set variables 210 | if (~isfield(handles, 'DistanceFunctions') && ~isfield(handles, 'numOfReturnedImages')) 211 | metric = get(handles.popupmenu_DistanceFunctions, 'Value'); 212 | numOfReturnedImgs = get(handles.popupmenu_NumOfReturnedImages, 'Value'); 213 | elseif (~isfield(handles, 'DistanceFunctions') || ~isfield(handles, 'numOfReturnedImages')) 214 | if (~isfield(handles, 'DistanceFunctions')) 215 | metric = get(handles.popupmenu_DistanceFunctions, 'Value'); 216 | numOfReturnedImgs = handles.numOfReturnedImages; 217 | else 218 | metric = handles.DistanceFunctions; 219 | numOfReturnedImgs = get(handles.popupmenu_NumOfReturnedImages, 'Value'); 220 | end 221 | else 222 | metric = handles.DistanceFunctions; 223 | numOfReturnedImgs = handles.numOfReturnedImages; 224 | end 225 | 226 | if (metric == 1) 227 | L1(numOfReturnedImgs, handles.queryImageFeature, handles.imageDataset.dataset, handles.folder_name, handles.img_ext); 228 | elseif (metric == 2 || metric == 3 || metric == 4 || metric == 5 || metric == 6 || metric == 7 || metric == 8 || metric == 9 || metric == 10 || metric == 11) 229 | L2(numOfReturnedImgs, handles.queryImageFeature, handles.imageDataset.dataset, metric, handles.folder_name, handles.img_ext); 230 | else 231 | relativeDeviation(numOfReturnedImgs, handles.queryImageFeature, handles.imageDataset.dataset, handles.folder_name, handles.img_ext); 232 | end 233 | 234 | 235 | % --- Executes on button press in btnExecuteSVM. 236 | function btnExecuteSVM_Callback(hObject, eventdata, handles) 237 | % hObject handle to btnExecuteSVM (see GCBO) 238 | % eventdata reserved - to be defined in a future version of MATLAB 239 | % handles structure with handles and user data (see GUIDATA) 240 | 241 | % check for image query 242 | if (~isfield(handles, 'queryImageFeature')) 243 | errordlg('Please select an image first!'); 244 | return; 245 | end 246 | 247 | % check for dataset existence 248 | if (~isfield(handles, 'imageDataset')) 249 | errordlg('Please load a dataset first. If you dont have one then you should consider creating one!'); 250 | return; 251 | end 252 | 253 | numOfReturnedImgs = get(handles.popupmenu_NumOfReturnedImages, 'Value'); 254 | metric = get(handles.popupmenu_DistanceFunctions, 'Value'); 255 | 256 | % call svm function passing as parameters the numOfReturnedImgs, queryImage and the dataset 257 | [~, ~, cmat] = svm(numOfReturnedImgs, handles.imageDataset.dataset, handles.queryImageFeature, metric, handles.folder_name, handles.img_ext); 258 | 259 | % plot confusion matrix 260 | opt = confMatPlot('defaultOpt'); 261 | opt.className = { 262 | 'Africa', 'Beach', 'Monuments', ... 263 | 'Buses', 'Dinosaurs', 'Elephants', ... 264 | 'Flowers', 'Horses', 'Mountains', ... 265 | 'Food' 266 | }; 267 | opt.mode = 'both'; 268 | figure('Name', 'Confusion Matrix'); 269 | confMatPlot(cmat, opt); 270 | xlabel('Confusion Matrix'); 271 | 272 | 273 | 274 | % --- Executes on button press in btnPlotPrecisionRecall. 275 | function btnPlotPrecisionRecall_Callback(hObject, eventdata, handles) 276 | % hObject handle to btnPlotPrecisionRecall (see GCBO) 277 | % eventdata reserved - to be defined in a future version of MATLAB 278 | % handles structure with handles and user data (see GUIDATA) 279 | 280 | if (~isfield(handles, 'imageDataset')) 281 | errordlg('Please select a dataset first!'); 282 | return; 283 | end 284 | 285 | % set variables 286 | numOfReturnedImgs = 20; 287 | database = handles.imageDataset.dataset; 288 | metric = get(handles.popupmenu_DistanceFunctions, 'Value'); 289 | 290 | precAndRecall = zeros(2, 10); 291 | 292 | for k = 1:15 293 | randImgName = randi([0 999], 1); 294 | randStrName = int2str(randImgName); 295 | randStrName = strcat('images\', randStrName, '.jpg'); 296 | randQueryImg = imread(randStrName); 297 | 298 | % extract query image features 299 | queryImage = imresize(randQueryImg, [384 256]); 300 | hsvHist = hsvHistogram(queryImage); 301 | autoCorrelogram = colorAutoCorrelogram(queryImage); 302 | color_moments = colorMoments(queryImage); 303 | % for gabor filters we need gary scale image 304 | img = double(rgb2gray(queryImage))/255; 305 | [meanAmplitude, msEnergy] = gaborWavelet(img, 4, 6); % 4 = number of scales, 6 = number of orientations 306 | wavelet_moments = waveletTransform(queryImage, imgInfo.ColorType); 307 | % construct the queryImage feature vector 308 | queryImageFeature = [hsvHist autoCorrelogram color_moments meanAmplitude msEnergy wavelet_moments randImgName]; 309 | 310 | disp(['Random Image = ', num2str(randImgName), '.jpg']); 311 | [precision, recall] = svm(numOfReturnedImgs, database, queryImageFeature, metric); 312 | precAndRecall(1, k) = precision; 313 | precAndRecall(2, k) = recall; 314 | end 315 | 316 | figure; 317 | plot(precAndRecall(2, :), precAndRecall(1, :), '--mo'); 318 | xlabel('Recall'), ylabel('Precision'); 319 | title('Precision and Recall'); 320 | legend('Recall & Precision', 'Location', 'NorthWest'); 321 | 322 | 323 | % --- Executes on button press in btnSelectImageDirectory. 324 | function btnSelectImageDirectory_Callback(hObject, eventdata, handles) 325 | % hObject handle to btnSelectImageDirectory (see GCBO) 326 | % eventdata reserved - to be defined in a future version of MATLAB 327 | % handles structure with handles and user data (see GUIDATA) 328 | 329 | % select image directory 330 | folder_name = uigetdir(pwd, 'Select the directory of images'); 331 | if ( folder_name ~= 0 ) 332 | handles.folder_name = folder_name; 333 | guidata(hObject, handles); 334 | else 335 | return; 336 | end 337 | 338 | 339 | % --- Executes on button press in btnCreateDB. 340 | function btnCreateDB_Callback(hObject, eventdata, handles) 341 | % hObject handle to btnCreateDB (see GCBO) 342 | % eventdata reserved - to be defined in a future version of MATLAB 343 | % handles structure with handles and user data (see GUIDATA) 344 | 345 | if (~isfield(handles, 'folder_name')) 346 | errordlg('Please select an image directory first!'); 347 | return; 348 | end 349 | 350 | % construct folder name foreach image type 351 | pngImagesDir = fullfile(handles.folder_name, '*.png'); 352 | jpgImagesDir = fullfile(handles.folder_name, '*.jpg'); 353 | bmpImagesDir = fullfile(handles.folder_name, '*.bmp'); 354 | 355 | % calculate total number of images 356 | num_of_png_images = numel( dir(pngImagesDir) ); 357 | num_of_jpg_images = numel( dir(jpgImagesDir) ); 358 | num_of_bmp_images = numel( dir(bmpImagesDir) ); 359 | totalImages = num_of_png_images + num_of_jpg_images + num_of_bmp_images; 360 | 361 | jpg_files = dir(jpgImagesDir); 362 | png_files = dir(pngImagesDir); 363 | bmp_files = dir(bmpImagesDir); 364 | 365 | if ( ~isempty( jpg_files ) || ~isempty( png_files ) || ~isempty( bmp_files ) ) 366 | % read jpg images from stored folder name 367 | % directory and construct the feature dataset 368 | jpg_counter = 0; 369 | png_counter = 0; 370 | bmp_counter = 0; 371 | oldHisv = 0; 372 | oldautoCorrelogram = 0; 373 | for k = 1:totalImages 374 | 375 | if ( (num_of_jpg_images - jpg_counter) > 0) 376 | imgInfoJPG = imfinfo( fullfile( handles.folder_name, jpg_files(jpg_counter+1).name ) ); 377 | if ( strcmp( lower(imgInfoJPG.Format), 'jpg') == 1 ) 378 | % read images 379 | sprintf('%s \n', jpg_files(jpg_counter+1).name) 380 | % extract features 381 | image = imread( fullfile( handles.folder_name, jpg_files(jpg_counter+1).name ) ); 382 | [pathstr, name, ext] = fileparts( fullfile( handles.folder_name, jpg_files(jpg_counter+1).name ) ); 383 | image = imresize(image, [384 256]); 384 | end 385 | 386 | jpg_counter = jpg_counter + 1; 387 | 388 | elseif ( (num_of_png_images - png_counter) > 0) 389 | imgInfoPNG = imfinfo( fullfile( handles.folder_name, png_files(png_counter+1).name ) ); 390 | if ( strcmp( lower(imgInfoPNG.Format), 'png') == 1 ) 391 | % read images 392 | sprintf('%s \n', png_files(png_counter+1).name) 393 | % extract features 394 | image = imread( fullfile( handles.folder_name, png_files(png_counter+1).name ) ); 395 | [pathstr, name, ext] = fileparts( fullfile( handles.folder_name, png_files(png_counter+1).name ) ); 396 | image = imresize(image, [384 256]); 397 | end 398 | 399 | png_counter = png_counter + 1; 400 | 401 | elseif ( (num_of_bmp_images - bmp_counter) > 0) 402 | imgInfoBMP = imfinfo( fullfile( handles.folder_name, bmp_files(bmp_counter+1).name ) ); 403 | if ( strcmp( lower(imgInfoBMP.Format), 'bmp') == 1 ) 404 | % read images 405 | sprintf('%s \n', bmp_files(bmp_counter+1).name) 406 | % extract features 407 | image = imread( fullfile( handles.folder_name, bmp_files(bmp_counter+1).name ) ); 408 | handle = image(image); 409 | imgmodel = imagemodel(handle); 410 | str = getImageType(imgmodel); 411 | disp([str]) 412 | return; 413 | 414 | [pathstr, name, ext] = fileparts( fullfile( handles.folder_name, bmp_files(bmp_counter+1).name ) ); 415 | image = imresize(image, [384 256]); 416 | end 417 | 418 | bmp_counter = bmp_counter + 1; 419 | 420 | end 421 | 422 | switch (ext) 423 | case '.jpg' 424 | imgInfo = imgInfoJPG; 425 | case '.png' 426 | imgInfo = imgInfoPNG; 427 | case '.bmp' 428 | imgInfo = imgInfoBMP; 429 | end 430 | 431 | if (strcmp(imgInfo.ColorType, 'grayscale') == 1) 432 | grayHist = imhist(image); 433 | grayHist = grayHist/sum(grayHist); 434 | grayHist = grayHist(:)'; 435 | color_moments = [mean(mean(image)) std(std(double(image)))]; 436 | [meanAmplitude, msEnergy] = gaborWavelet(image, 4, 6); % 4 = number of scales, 6 = number of orientations 437 | wavelet_moments = waveletTransform(image, imgInfo.ColorType); 438 | % construct the dataset 439 | set = [grayHist color_moments meanAmplitude msEnergy wavelet_moments]; 440 | elseif (strcmp(imgInfo.ColorType, 'truecolor') == 1) 441 | hsvHist = 0; 442 | try 443 | hsvHist = hsvHistogram(image); 444 | autoCorrelogram = colorAutoCorrelogram(image); 445 | oldHisv = hsvHist; 446 | oldautoCorrelogram = autoCorrelogram; 447 | catch 448 | hsvHist = oldHisv; 449 | autoCorrelogram = oldautoCorrelogram; 450 | end 451 | color_moments = colorMoments(image); 452 | % for gabor filters we need gray scale image 453 | img = double(rgb2gray(image))/255; 454 | [meanAmplitude, msEnergy] = gaborWavelet(img, 4, 6); % 4 = number of scales, 6 = number of orientations 455 | wavelet_moments = waveletTransform(image, imgInfo.ColorType); 456 | % construct the dataset 457 | set = [hsvHist autoCorrelogram color_moments meanAmplitude msEnergy wavelet_moments]; 458 | end 459 | 460 | % add to the last column the name of image file we are processing at 461 | % the moment 462 | dataset(k, :) = [set str2num(name)]; 463 | 464 | % clear workspace 465 | clear('image', 'img', 'hsvHist', 'autoCorrelogram', 'color_moments', ... 466 | 'gabor_wavelet', 'wavelet_moments', 'set', 'imgInfoJPG', 'imgInfoPNG', ... 467 | 'imgInfoGIF', 'imgInfo'); 468 | end 469 | 470 | % prompt to save dataset 471 | uisave('dataset', 'dataset1'); 472 | % save('dataset.mat', 'dataset', '-mat'); 473 | clear('dataset', 'jpg_counter', 'png_counter', 'bmp_counter'); 474 | end 475 | 476 | 477 | % --- Executes on button press in btn_LoadDataset. 478 | function btn_LoadDataset_Callback(hObject, eventdata, handles) 479 | % hObject handle to btn_LoadDataset (see GCBO) 480 | % eventdata reserved - to be defined in a future version of MATLAB 481 | % handles structure with handles and user data (see GUIDATA) 482 | [fname, pthname] = uigetfile('*.mat', 'Select the Dataset'); 483 | if (fname ~= 0) 484 | dataset_fullpath = strcat(pthname, fname); 485 | [pathstr, name, ext] = fileparts(dataset_fullpath); 486 | if ( strcmp(lower(ext), '.mat') == 1) 487 | filename = fullfile( pathstr, strcat(name, ext) ); 488 | handles.imageDataset = load(filename); 489 | guidata(hObject, handles); 490 | % make dataset visible from workspace 491 | % assignin('base', 'database', handles.imageDataset.dataset); 492 | helpdlg('Dataset loaded successfuly!'); 493 | else 494 | errordlg('You have not selected the correct file type'); 495 | end 496 | else 497 | return; 498 | end 499 | -------------------------------------------------------------------------------- /colorAutoCorrelogram.m: -------------------------------------------------------------------------------- 1 | function colorAutoCorrelogram = colorAutoCorrelogram(image) 2 | % input: image in uint8 form, from wich to extract the color auto correlogram 3 | % output: 1x64 feature vector containing the color auto correlogram 4 | 5 | % quantize image into 64 colors = 4x4x4, in RGB space 6 | [img_no_dither, map] = rgb2ind(image, 64, 'nodither'); 7 | % figure, imshow(img_no_dither, map); 8 | 9 | rgb = ind2rgb(img_no_dither, map); % rgb = double(rgb) 10 | % imshow(rgb); 11 | % rgb = cat(3, r, g, b); 12 | 13 | % clear workspace 14 | clear('img_no_dither'); 15 | 16 | % 4 predefined distances between 17 | % neighbor pixel intensities 18 | % according to "Image Indexing Using Color Correlograms" paper 19 | distances = [1 3 5 7]; 20 | 21 | colorAutoCorrelogram = correlogram(rgb, map, distances); 22 | % For some images like documents, its colorAutoCorrelogram contains less than 64. 23 | % It is a compromise. 24 | colorAutoCorrelogramFix = zeros(1,64); 25 | for i = 1, size(colorAutoCorrelogram, 2) 26 | colorAutoCorrelogramFix(i) = colorAutoCorrelogram(i); 27 | end 28 | colorAutoCorrelogram = reshape(colorAutoCorrelogramFix, [4 4 4]); 29 | 30 | % consturct final correlogram using distances 31 | colorAutoCorrelogram(:, :, 1) = colorAutoCorrelogram(:, :, 1)*distances(1); 32 | colorAutoCorrelogram(:, :, 2) = colorAutoCorrelogram(:, :, 2)*distances(2); 33 | colorAutoCorrelogram(:, :, 3) = colorAutoCorrelogram(:, :, 3)*distances(3); 34 | colorAutoCorrelogram(:, :, 4) = colorAutoCorrelogram(:, :, 4)*distances(4); 35 | 36 | % reform it to vector format 37 | colorAutoCorrelogram = reshape(colorAutoCorrelogram, 1, 64); 38 | 39 | end 40 | 41 | % check if point is a valid pixel 42 | function valid = is_valid(X, Y, point) 43 | if point(1) < 0 || point(1) >= X 44 | valid = 0; 45 | end 46 | if point(2) < 0 || point(2) >= Y 47 | valid = 0; 48 | end 49 | valid = 1; 50 | end 51 | 52 | % find pixel neighbors 53 | function Cn = get_neighbors(X, Y, x, y, dist) 54 | cn1 = [x+dist, y+dist]; 55 | cn2 = [x+dist, y]; 56 | cn3 = [x+dist, y-dist]; 57 | cn4 = [x, y-dist]; 58 | cn5 = [x-dist, y-dist]; 59 | cn6 = [x-dist, y]; 60 | cn7 = [x-dist, y+dist]; 61 | cn8 = [x, y+dist]; 62 | 63 | points = {cn1, cn2, cn3, cn4, cn5, cn6, cn7, cn8}; 64 | Cn = cell(1, length(points)); 65 | 66 | for ii = 1:length(points) 67 | valid = is_valid(X, Y, points{1, ii}); 68 | if (valid) 69 | Cn{1, ii} = points{1, ii}; 70 | end 71 | end 72 | 73 | end 74 | 75 | % get correlogram 76 | function colors_percent = correlogram(photo, Cm, K) 77 | [X, Y, ttt] = size(photo); 78 | colors_percent = []; 79 | 80 | % for k = 1:length(K) % loop over distances 81 | for k = 1:K % loop over distances 82 | countColor = 0; 83 | 84 | color = zeros(1, length(Cm)); 85 | 86 | for x = 2:floor(X/10):X % loop over image width 87 | for y = 2:floor(Y/10):Y % loop over image height 88 | Ci = photo(x, y); 89 | % Cn = get_neighbors(X, Y, x, y, K(k)); 90 | Cn = get_neighbors(X, Y, x, y, k); 91 | 92 | for jj = 1:length(Cn) % loop over neighbor pixels 93 | Cj = photo( Cn{1, jj}(1), Cn{1, jj}(2) ); 94 | 95 | for m = 1:length(Cm) % loop over map colors 96 | if isequal(Cm(m), Ci) && isequal(Cm(m), Cj) 97 | countColor = countColor + 1; 98 | color(m) = color(m) + 1; 99 | end 100 | end 101 | end 102 | end 103 | end 104 | 105 | for ii = 1:length(color) 106 | color(ii) = double( color(ii) / countColor ); 107 | end 108 | 109 | colors_percent = color; 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /colorMoments.m: -------------------------------------------------------------------------------- 1 | function colorMoments = colorMoments(image) 2 | % input: image to be analyzed and extract 2 first moments from each R,G,B 3 | % output: 1x6 vector containing the 2 first color momenst from each R,G,B 4 | % channel 5 | 6 | % extract color channels 7 | R = double(image(:, :, 1)); 8 | G = double(image(:, :, 2)); 9 | B = double(image(:, :, 3)); 10 | 11 | % compute 2 first color moments from each channel 12 | meanR = mean( R(:) ); 13 | stdR = std( R(:) ); 14 | meanG = mean( G(:) ); 15 | stdG = std( G(:) ); 16 | meanB = mean( B(:) ); 17 | stdB = std( B(:) ); 18 | 19 | % construct output vector 20 | colorMoments = zeros(1, 6); 21 | colorMoments(1, :) = [meanR stdR meanG stdG meanB stdB]; 22 | 23 | % clear workspace 24 | clear('R', 'G', 'B', 'meanR', 'stdR', 'meanG', 'stdG', 'meanB', 'stdB'); 25 | 26 | end -------------------------------------------------------------------------------- /confMatGet.m: -------------------------------------------------------------------------------- 1 | function confMat = confMatGet(desiredOutput, computedOutput) 2 | %confMatGet: Get confusion matrix from recognition results 3 | % 4 | % Usage: 5 | % confMat = confMatGet(desiredOutput, computedOutput) 6 | % 7 | % Description: 8 | % confMatGet(desiredOutput, computedOutput) returns the confusion matrix of a given classifier based on 9 | % its desired output (ground truth) and computed output. 10 | % 11 | % Example: 12 | % desired=[1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5]; 13 | % computed=[1 5 5 1 1 1 1 1 5 5 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 2 5 5 5 5 5 5 5 5 3 5 5 5]; 14 | % confMat = confMatGet(desired, computed); 15 | % confMatPlot(confMat); 16 | % 17 | % See also confMatPlot. 18 | 19 | % Category: Classification analysis 20 | % Roger Jang, 20060523, 20070504 21 | 22 | if nargin<1, selfdemo; return; end 23 | 24 | classCount=length(unique(desiredOutput)); 25 | 26 | confMat=zeros(classCount, classCount); 27 | for i=1:classCount 28 | index=find(desiredOutput==i); 29 | roi=computedOutput(index); 30 | for j=1:classCount 31 | confMat(i,j)=length(find(roi==j)); 32 | end 33 | end 34 | 35 | % ====== Self demo 36 | function selfdemo 37 | mObj=mFileParse(which(mfilename)); 38 | strEval(mObj.example); 39 | -------------------------------------------------------------------------------- /confMatPlot.m: -------------------------------------------------------------------------------- 1 | function [obj, overall]=confMatPlot(confMat, opt) 2 | %confMatPlot: Display the confusion matrix 3 | % 4 | % Usage: 5 | % confMatPlot(confMat) 6 | % confMatPlot(confMat, opt) 7 | % 8 | % Description: 9 | % confMatPlot(confMat) plots the confusion matrix of classification result. 10 | % confMatPlot(confMat, opt) labels the class names along the confusion matrix. 11 | % opt: Options for this function 12 | % opt.mode: different mode of plotting 13 | % 'dataCount': displays data counts 14 | % 'percentage': displays percentages 15 | % 'both': displays both data counts and percentages 16 | % opt.className: Class names for plotting 17 | % opt.matPlotOpt: Options that are passed to "matPlot". 18 | % 19 | % Note that each row is the true class, while each column is the predicted class. 20 | % 21 | % Example: 22 | % desired=[1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5]; 23 | % computed=[1 5 5 1 1 1 1 1 5 5 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 2 5 5 5 5 5 5 5 5 3 5 5 5]; 24 | % confMat = confMatGet(desired, computed); 25 | % opt=confMatPlot('defaultOpt'); 26 | % opt.className={'Canada', 'China', 'Japan', 'Taiwan', 'US'}; 27 | % % === Example 1: Data count plot 28 | % opt.mode='dataCount'; 29 | % figure; confMatPlot(confMat, opt); 30 | % % === Example 2: Percentage plot 31 | % opt.mode='percentage'; 32 | % opt.format='8.2f'; 33 | % figure; confMatPlot(confMat, opt); 34 | % % === Example 3: Plot of both data count and percentage 35 | % opt.mode='both'; 36 | % figure; confMatPlot(confMat, opt); 37 | % 38 | % See also confMatGet. 39 | 40 | % Category: Classification analysis 41 | % Roger Jang, 20060421, 20070504 42 | 43 | if nargin<1, selfdemo; return; end 44 | if ischar(confMat) && strcmpi(confMat, 'defaultOpt') % Set the default options 45 | obj.mode='both'; 46 | obj.format='8.2f'; 47 | obj.className={}; % To be populated later 48 | obj.matPlotOpt=matPlot('defaultOpt'); % Use the default opt of matPlot as the basic options 49 | return 50 | end 51 | if nargin<2||isempty(opt), opt=feval(mfilename, 'defaultOpt'); end 52 | 53 | if isempty(opt.className) 54 | for i=1:size(confMat, 1) 55 | opt.className{i}=int2str(i); 56 | end 57 | end 58 | 59 | %colordef black; 60 | [m, n]=size(confMat); 61 | fontSize=10; 62 | prob = confMat./(sum(confMat')'*ones(1, size(confMat,1))); 63 | diagCount=sum(diag(confMat)); 64 | allCount=sum(sum(confMat)); 65 | overall = diagCount/allCount; 66 | newProb = round(prob*100000000)/1000000; 67 | newOverall = round(overall*100000000)/1000000; 68 | 69 | % Propogate the options to matPlot 70 | opt.matPlotOpt.format=opt.format; 71 | opt.matPlotOpt.rowLeftLabel=opt.className; 72 | opt.matPlotOpt.colUpLabel=opt.className; 73 | 74 | switch(lower(opt.mode)) 75 | case lower('dataCount') 76 | opt.matPlotOpt.matrixName=sprintf('Data counts, RR = %d/%d = %g%%', diagCount, allCount, newOverall); 77 | obj=matPlot(confMat, opt.matPlotOpt); 78 | % === Modify "23.00" into "23" 79 | for i=1:m 80 | for j=1:n 81 | str = get(obj.element(i,j), 'string'); 82 | set(obj.element(i,j), 'string', int2str(eval(str))); 83 | end 84 | end 85 | for i=1:m 86 | str = get(obj.rowRightLabel(i), 'string'); 87 | set(obj.rowRightLabel(i), 'string', int2str(eval(str))); 88 | end 89 | for j=1:n 90 | str = get(obj.colDownLabel(j), 'string'); 91 | set(obj.colDownLabel(j), 'string', int2str(eval(str))); 92 | end 93 | case lower('percentage') 94 | opt.matPlotOpt.matrixName=sprintf('Percentages, RR = %g%%', newOverall); 95 | opt.matPlotOpt.showColSum=0; 96 | obj=matPlot(newProb, opt.matPlotOpt); 97 | % === Add the percentage sign 98 | for i=1:m 99 | for j=1:n 100 | str = get(obj.element(i,j), 'string'); 101 | % set(obj.element(i,j), 'string', [str, '%']); 102 | if eval(str) 103 | set(obj.element(i,j), 'string', [str, '%']); 104 | else 105 | set(obj.element(i,j), 'string', '0'); 106 | end 107 | end 108 | end 109 | for i=1:m 110 | str = get(obj.rowRightLabel(i), 'string'); 111 | set(obj.rowRightLabel(i), 'string', [str, '%']); 112 | end 113 | for j=1:n 114 | str = get(obj.colDownLabel(j), 'string'); 115 | set(obj.colDownLabel(j), 'string', [str, '%']); 116 | end 117 | case lower('both') 118 | opt.matrixName=sprintf('Data counts, RR = %d/%d = %g%%', diagCount, allCount, newOverall); 119 | rowSum=sum(confMat, 2); 120 | for i=1:m, opt.rowRightLabel{i}=['100%', 10, '(', mat2str(rowSum(i)), ')']; end 121 | colSum=sum(confMat, 1); 122 | for j=1:n, opt.colDownLabel{j}=['(', mat2str(colSum(j)), ')']; end 123 | [m, n]=size(confMat); 124 | for i=1:m 125 | for j=1:n 126 | if confMat(i,j)==0 127 | strMat{i,j}='0'; 128 | else 129 | strMat{i,j}=[num2str(newProb(i,j), ['%', opt.format]), '%', 10, '(', num2str(confMat(i,j)), ')']; 130 | end 131 | end 132 | end 133 | obj=matPlot(strMat, opt.matPlotOpt); 134 | end 135 | for i=1:length(obj.rowLeftLabel) 136 | set(obj.rowLeftLabel(i), 'string', opt.matPlotOpt.rowLeftLabel{i}); 137 | end 138 | for i=1:length(obj.colUpLabel) 139 | set(obj.colUpLabel(i), 'string', opt.matPlotOpt.colUpLabel{i}); 140 | end 141 | 142 | % ====== Self demo 143 | function selfdemo 144 | mObj=mFileParse(which(mfilename)); 145 | strEval(mObj.example); 146 | -------------------------------------------------------------------------------- /dataset_pca.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirk86/ImageRetrieval/cab6a346bcd67035e7e657ceca4f955eb0405aea/dataset_pca.mat -------------------------------------------------------------------------------- /dataset_with_img_names.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirk86/ImageRetrieval/cab6a346bcd67035e7e657ceca4f955eb0405aea/dataset_with_img_names.mat -------------------------------------------------------------------------------- /gaborWavelet.m: -------------------------------------------------------------------------------- 1 | % gaborWavelet - Function for computing gabor features of a gray-scale image 2 | % 3 | % This function calculates gabor features. Mean-squared energy & meanAmplitude 4 | % for each scale % and orientation is returned. 5 | % 6 | % There are potentially many arguments, here is the full usage: 7 | % 8 | % [gaborSquareEnergy, gaborMeanAmplitude] = ... 9 | % gaborWavelet(im, nscale, norient ) 10 | %NOTE: nscale & norient are optional arguments 11 | 12 | % 13 | % However, apart from the image, all parameters have defaults and the 14 | % usage can be as simple as: 15 | % 16 | % [gaborSquareEnergy, gaborMeanAmplitude ]= gaborWavelet(im); 17 | % 18 | % Arguments: 19 | % Default values Description 20 | % 21 | % nscale 5 - Number of wavelet scales, try values 3-6 22 | % norient 6 - Number of filter orientations. 23 | % 24 | % Return values: 25 | % msEnergy - Mean square energy 26 | % orientation - Mean amplitude 27 | 28 | % 29 | % The convolutions are done via the FFT. Many of the parameters relate to the 30 | % specification of the filters in the frequency plane. The values do not seem 31 | % to be very critical and the defaults are usually fine. You may want to 32 | % experiment with the values of 'nscales' and 'k', the noise compensation factor. 33 | % 34 | % For maximum speed the input image should have dimensions that correspond to 35 | % powers of 2, but the code will operate on images of arbitrary size. 36 | 37 | function[gaborSquareEnergy, gaborMeanAmplitude] = gaborWavelet(varargin) 38 | 39 | % Get arguments and/or default values 40 | [im, nscale, norient, minWaveLength, mult, sigmaOnf, dThetaOnSigma,k, ... 41 | polarity] = checkargs(varargin(:)); 42 | 43 | v = version; Octave = v(1) < '5'; % Crude Octave test 44 | epsilon = .0001; % Used to prevent division by zero. 45 | 46 | % Calculate the standard deviation of the angular Gaussian function 47 | % used to construct filters in the frequency plane. 48 | thetaSigma = pi/norient/dThetaOnSigma; 49 | 50 | [rows,cols] = size(im); 51 | imagefft = fft2(im); % Fourier transform of image 52 | zero = zeros(rows,cols); 53 | 54 | totalEnergy = zero; % Matrix for accumulating weighted phase 55 | % congruency values (energy). 56 | totalSumAn = zero; % Matrix for accumulating filter response 57 | % amplitude values. 58 | orientation = zero; % Matrix storing orientation with greatest 59 | % energy for each pixel. 60 | estMeanE2n = []; 61 | EO = cell(nscale, norient); % Cell array of convolution results 62 | ifftFilterArray = cell(1, nscale); % Cell array of inverse FFTs of filters 63 | 64 | 65 | % Pre-compute some stuff to speed up filter construction 66 | 67 | % Set up X and Y matrices with ranges normalised to +/- 0.5 68 | % The following code adjusts things appropriately for odd and even values 69 | % of rows and columns. 70 | if mod(cols,2) 71 | xrange = [-(cols-1)/2:(cols-1)/2]/(cols-1); 72 | else 73 | xrange = [-cols/2:(cols/2-1)]/cols; 74 | end 75 | 76 | if mod(rows,2) 77 | yrange = [-(rows-1)/2:(rows-1)/2]/(rows-1); 78 | else 79 | yrange = [-rows/2:(rows/2-1)]/rows; 80 | end 81 | 82 | [x,y] = meshgrid(xrange, yrange); 83 | 84 | radius = sqrt(x.^2 + y.^2); % Matrix values contain *normalised* radius from centre. 85 | theta = atan2(-y,x); % Matrix values contain polar angle. 86 | % (note -ve y is used to give +ve 87 | % anti-clockwise angles) 88 | 89 | radius = ifftshift(radius); % Quadrant shift radius and theta so that filters 90 | theta = ifftshift(theta); % are constructed with 0 frequency at the corners. 91 | radius(1,1) = 1; % Get rid of the 0 radius value at the 0 92 | % frequency point (now at top-left corner) 93 | % so that taking the log of the radius will 94 | % not cause trouble. 95 | 96 | sintheta = sin(theta); 97 | costheta = cos(theta); 98 | clear x; clear y; clear theta; % save a little memory 99 | 100 | % Filters are constructed in terms of two components. 101 | % 1) The radial component, which controls the frequency band that the filter 102 | % responds to 103 | % 2) The angular component, which controls the orientation that the filter 104 | % responds to. 105 | % The two components are multiplied together to construct the overall filter. 106 | 107 | % Construct the radial filter components... 108 | 109 | % First construct a low-pass filter that is as large as possible, yet falls 110 | % away to zero at the boundaries. All log Gabor filters are multiplied by 111 | % this to ensure no extra frequencies at the 'corners' of the FFT are 112 | % incorporated as this seems to upset the normalisation process when 113 | % calculating phase congrunecy. 114 | lp = lowpassfilter([rows,cols], .4, 10); % Radius .4, 'sharpness' 10 115 | 116 | logGabor = cell(1, nscale); 117 | 118 | for s = 1:nscale 119 | wavelength = minWaveLength*mult^(s-1); 120 | fo = 1.0/wavelength; % Centre frequency of filter. 121 | logGabor{s} = exp((-(log(radius/fo)).^2) / (2 * log(sigmaOnf)^2)); 122 | logGabor{s} = logGabor{s}.*lp; % Apply low-pass filter 123 | logGabor{s}(1, 1) = 0; % Set the value at the 0 frequency point of the filter 124 | % back to zero (undo the radius fudge). 125 | end 126 | 127 | % Then construct the angular filter components... 128 | spread = cell(1, norient); 129 | 130 | for o = 1:norient 131 | angl = (o-1)*pi/norient; % Filter angle. 132 | 133 | % For each point in the filter matrix calculate the angular distance from 134 | % the specified filter orientation. To overcome the angular wrap-around 135 | % problem sine difference and cosine difference values are first computed 136 | % and then the atan2 function is used to determine angular distance. 137 | 138 | ds = sintheta * cos(angl) - costheta * sin(angl); % Difference in sine. 139 | dc = costheta * cos(angl) + sintheta * sin(angl); % Difference in cosine. 140 | dtheta = abs(atan2(ds,dc)); % Absolute angular distance. 141 | spread{o} = exp((-dtheta.^2) / (2 * thetaSigma^2)); % Calculate the 142 | % angular filter component. 143 | end 144 | 145 | count = 1; 146 | gaborSquareEnergy = []; 147 | gaborMeanAmplitude = []; 148 | % The main loop... 149 | for o = 1:norient, % For each orientation. 150 | fprintf('Processing orientation %d \r', o); 151 | if Octave fflush(1); end 152 | 153 | sumAn_ThisOrient = zero; 154 | Energy_ThisOrient = zero; 155 | 156 | for s = 1:nscale, % For each scale. 157 | 158 | filter = logGabor{s} .* spread{o}; % Multiply radial and angular 159 | % components to get filter. 160 | 161 | ifftFilt = real(ifft2(filter))*sqrt(rows*cols); % Note rescaling to match power 162 | ifftFilterArray{s} = ifftFilt; % record ifft2 of filter 163 | 164 | % Convolve image with even and odd filters returning the result in EO 165 | EO{s, o} = ifft2(imagefft .* filter); 166 | An = abs(EO{s,o}); % Amplitude of even & odd filter response. 167 | sumAn_ThisOrient = sumAn_ThisOrient + An; % Sum of amplitude responses. 168 | 169 | % % Display of individual components 170 | % if o == 6 171 | % figure, imagesc( An ), colormap(gray), title( sprintf( 'orient : %d, scale : %d', o,s) ); 172 | % imwrite( An, sprintf( 'orient/%d/%d.png', o,s ) ); 173 | 174 | gaborSquareEnergy(count) = sum(sum( An.^2 ) ); 175 | gaborMeanAmplitude(count) = mean2( An ); 176 | count = count + 1; 177 | 178 | % end 179 | 180 | if s==1 181 | EM_n = sum(sum(filter.^2)); % Record mean squared filter value at smallest 182 | end % scale. This is used for noise estimation. 183 | 184 | end % ... and process the next scale 185 | 186 | % calculate the phase symmetry measure 187 | 188 | if polarity == 0 % look for 'white' and 'black' spots 189 | for s = 1:nscale, 190 | Energy_ThisOrient = Energy_ThisOrient ... 191 | + abs(real(EO{s,o})) - abs(imag(EO{s,o})); 192 | end 193 | 194 | elseif polarity == 1 % Just look for 'white' spots 195 | for s = 1:nscale, 196 | Energy_ThisOrient = Energy_ThisOrient ... 197 | + real(EO{s,o}) - abs(imag(EO{s,o})); 198 | end 199 | 200 | elseif polarity == -1 % Just look for 'black' spots 201 | for s = 1:nscale, 202 | Energy_ThisOrient = Energy_ThisOrient ... 203 | - real(EO{s,o}) - abs(imag(EO{s,o})); 204 | end 205 | 206 | end 207 | 208 | % Compensate for noise 209 | % We estimate the noise power from the energy squared response at the 210 | % smallest scale. If the noise is Gaussian the energy squared will 211 | % have a Chi-squared 2DOF pdf. We calculate the median energy squared 212 | % response as this is a robust statistic. From this we estimate the 213 | % mean. The estimate of noise power is obtained by dividing the mean 214 | % squared energy value by the mean squared filter value 215 | 216 | medianE2n = median(reshape(abs(EO{1,o}).^2,1,rows*cols)); 217 | meanE2n = -medianE2n/log(0.5); 218 | estMeanE2n = [estMeanE2n meanE2n]; 219 | 220 | noisePower = meanE2n/EM_n; % Estimate of noise power. 221 | 222 | % Now estimate the total energy^2 due to noise 223 | % Estimate for sum(An^2) + sum(Ai.*Aj.*(cphi.*cphj + sphi.*sphj)) 224 | 225 | EstSumAn2 = zero; 226 | for s = 1:nscale 227 | EstSumAn2 = EstSumAn2+ifftFilterArray{s}.^2; 228 | end 229 | 230 | EstSumAiAj = zero; 231 | for si = 1:(nscale - 1) 232 | for sj = (si + 1):nscale 233 | EstSumAiAj = EstSumAiAj + ifftFilterArray{si} .* ifftFilterArray{sj}; 234 | end 235 | end 236 | 237 | EstNoiseEnergy2 = 2*noisePower*sum(sum(EstSumAn2)) + 4*noisePower*sum(sum(EstSumAiAj)); 238 | 239 | tau = sqrt(EstNoiseEnergy2/2); % Rayleigh parameter 240 | EstNoiseEnergy = tau*sqrt(pi/2); % Expected value of noise energy 241 | EstNoiseEnergySigma = sqrt( (2-pi/2)*tau^2 ); 242 | 243 | T = EstNoiseEnergy + k*EstNoiseEnergySigma; % Noise threshold 244 | 245 | % The estimated noise effect calculated above is only valid for the PC_1 246 | % measure. The PC_2 measure does not lend itself readily to the same 247 | % analysis. However empirically it seems that the noise effect is 248 | % overestimated roughly by a factor of 1.7 for the filter parameters 249 | % used here. 250 | T = T/1.7; 251 | 252 | % Apply noise threshold 253 | Energy_ThisOrient = max(Energy_ThisOrient - T, zero); 254 | 255 | % Update accumulator matrix for sumAn and totalEnergy 256 | totalSumAn = totalSumAn + sumAn_ThisOrient; 257 | totalEnergy = totalEnergy + Energy_ThisOrient; 258 | 259 | % Update orientation matrix by finding image points where the energy in 260 | % this orientation is greater than in any previous orientation (the 261 | % change matrix) and then replacing these elements in the orientation 262 | % matrix with the current orientation number. 263 | 264 | if(o == 1), 265 | maxEnergy = Energy_ThisOrient; 266 | else 267 | change = Energy_ThisOrient > maxEnergy; 268 | orientation = (o - 1).*change + orientation.*(~change); 269 | maxEnergy = max(maxEnergy, Energy_ThisOrient); 270 | end 271 | 272 | end % For each orientation 273 | fprintf(' \r'); 274 | 275 | 276 | display( 'Code for Starts' ); 277 | % 278 | % Desc: 279 | % We are trying out a variant of phase symetry. Our measure of symetry 280 | % is average across all orientations and scales, instead of normal 281 | % addition. This was done so that, maximas in particular direction only 282 | % could be eliminated. Also, the obervation was that a cell-center 283 | % point would be symetric in all direction and a high value in all 284 | % direction. 285 | averageDirectionalEnergy = zero; 286 | % for s=1:nscale 287 | % for o=1:norient 288 | % averageDirectionalEnergy = averageDirectionalEnergy + abs(EO{s,o}); 289 | % end 290 | % end 291 | % averageDirectionalEnergy = averageDirectionalEnergy / (nscale * norient ); 292 | % 293 | 294 | 295 | % 296 | % Computing median image 297 | % median is computed over orientations. which means for a 298 | % particular scale, orientation is varied to take the median 299 | 300 | 301 | for sc = 1:nscale 302 | clear XA; 303 | clear XE; 304 | display( sprintf( 'Taking Median for scale %d/%d', sc, nscale ) ); 305 | scale_current = sc; 306 | % for a fixed scale, iterate thru each orientation 307 | for ori=1:norient 308 | XA(:, :, ori) = abs( EO{scale_current, ori} ); 309 | XE(:, :, ori) = abs( real(EO{scale_current, ori}) ) - abs( imag(EO{scale_current, ori}) ); 310 | end 311 | 312 | 313 | % % % Basic Approach 314 | %mA(:,:,scale_current) = median( XA, 3 ); 315 | %mE(:,:,scale_current) = median( XE, 3 ); 316 | 317 | % % % Approach for optimization. 3d array reshaped to 2d 318 | appr_r_XA = reshape( XA, [ size(XA,1)*size(XA,2) norient ] ); 319 | appr_r_median_XA = median( appr_r_XA, 2 ); 320 | mA = reshape( appr_r_median_XA, [size(XA,1) size(XA,2) ] ); 321 | 322 | appr_r_XE = reshape( XE, [ size(XE,1)*size(XE,2) norient ] ); 323 | appr_r_median_XE = median( appr_r_XE, 2 ); 324 | mE = reshape( appr_r_median_XE, [size(XE,1) size(XE,2) ] ); 325 | 326 | 327 | % figure,imagesc( tmp ) 328 | % colormap(gray), title( sprintf( 'scale : %d', sc ) ); 329 | end 330 | A = sum( mA, 3 ); 331 | E = sum( mE, 3 ); 332 | 333 | averageDirectionalEnergy = E ./ (A + epsilon); 334 | 335 | % for ori=1:norient 336 | % clear XP; 337 | % % for a fixed scale, iterate thru each orientation 338 | % for sc = 1:nscale 339 | % scale_current = sc; 340 | % XP(:,:,sc) = abs( EO{scale_current,ori} ); 341 | % end 342 | % 343 | % tmp= median( XP, 3 ); 344 | % %rel(:,:,scale_current) 345 | % 346 | % figure,imagesc( tmp ) 347 | % colormap(gray), title( sprintf( 'orientation : %d', ori ) ); 348 | % end 349 | 350 | 351 | display( 'Code End for ' ); 352 | 353 | % 354 | % End of code for 355 | % 356 | 357 | 358 | % disp('Mean Energy squared values recorded with smallest scale filter at each orientation'); 359 | % disp(estMeanE2n); 360 | 361 | % Normalize totalEnergy by the totalSumAn to obtain phase symmetry 362 | phaseSym = totalEnergy ./ (totalSumAn + epsilon); 363 | 364 | % Convert orientation matrix values to degrees 365 | orientation = orientation * (180 / norient); 366 | 367 | 368 | %------------------------------------------------------------------ 369 | % CHECKARGS 370 | % 371 | % Function to process the arguments that have been supplied, assign 372 | % default values as needed and perform basic checks. 373 | 374 | function [im, nscale, norient, minWaveLength, mult, sigmaOnf, ... 375 | dThetaOnSigma,k, polarity] = checkargs(arg); 376 | 377 | nargs = length(arg); 378 | 379 | if nargs < 1 380 | error('No image supplied as an argument'); 381 | end 382 | 383 | % Set up default values for all arguments and then overwrite them 384 | % with with any new values that may be supplied 385 | im = []; 386 | nscale = 5; % Number of wavelet scales. 387 | norient = 6; % Number of filter orientations. 388 | minWaveLength = 3; % Wavelength of smallest scale filter. 389 | mult = 2.1; % Scaling factor between successive filters. 390 | sigmaOnf = 0.55; % Ratio of the standard deviation of the 391 | % Gaussian describing the log Gabor filter's 392 | % transfer function in the frequency domain 393 | % to the filter center frequency. 394 | dThetaOnSigma = 1.2; % Ratio of angular interval between filter orientations 395 | % and the standard deviation of the angular Gaussian 396 | % function used to construct filters in the 397 | % freq. plane. 398 | k = 2.0; % No of standard deviations of the noise 399 | % energy beyond the mean at which we set the 400 | % noise threshold point. 401 | 402 | polarity = 0; % Look for both black and white spots of symmetrry 403 | 404 | 405 | % Allowed argument reading states 406 | allnumeric = 1; % Numeric argument values in predefined order 407 | keywordvalue = 2; % Arguments in the form of string keyword 408 | % followed by numeric value 409 | readstate = allnumeric; % Start in the allnumeric state 410 | 411 | if readstate == allnumeric 412 | for n = 1:nargs 413 | if isa(arg{n}, 'char') 414 | readstate = keywordvalue; 415 | break; 416 | else 417 | if n == 1, im = arg{n}; 418 | elseif n == 2, nscale = arg{n}; 419 | elseif n == 3, norient = arg{n}; 420 | elseif n == 4, minWaveLength = arg{n}; 421 | elseif n == 5, mult = arg{n}; 422 | elseif n == 6, sigmaOnf = arg{n}; 423 | elseif n == 7, dThetaOnSigma = arg{n}; 424 | elseif n == 8, k = arg{n}; 425 | elseif n == 9, polarity = arg{n}; 426 | end 427 | end 428 | end 429 | end 430 | 431 | % Code to handle parameter name - value pairs 432 | if readstate == keywordvalue 433 | while n < nargs 434 | 435 | if ~isa(arg{n},'char') | ~isa(arg{n+1}, 'double') 436 | error('There should be a parameter name - value pair'); 437 | end 438 | 439 | if strncmpi(arg{n},'im' ,2), im = arg{n+1}; 440 | elseif strncmpi(arg{n},'nscale' ,2), nscale = arg{n+1}; 441 | elseif strncmpi(arg{n},'norient' ,2), norient = arg{n+1}; 442 | elseif strncmpi(arg{n},'minWaveLength',2), minWavelength = arg{n+1}; 443 | elseif strncmpi(arg{n},'mult' ,2), mult = arg{n+1}; 444 | elseif strncmpi(arg{n},'sigmaOnf',2), sigmaOnf = arg{n+1}; 445 | elseif strncmpi(arg{n},'dthetaOnSigma',2), dThetaOnSigma = arg{n+1}; 446 | elseif strncmpi(arg{n},'k' ,1), k = arg{n+1}; 447 | elseif strncmpi(arg{n},'polarity',2), polarity = arg{n+1}; 448 | else error('Unrecognised parameter name'); 449 | end 450 | 451 | n = n+2; 452 | if n == nargs 453 | error('Unmatched parameter name - value pair'); 454 | end 455 | 456 | end 457 | end 458 | 459 | if isempty(im) 460 | error('No image argument supplied'); 461 | end 462 | 463 | if ~isa(im, 'double') 464 | im = double(im); 465 | end 466 | 467 | if nscale < 1 468 | error('nscale must be an integer >= 1'); 469 | end 470 | 471 | if norient < 1 472 | error('norient must be an integer >= 1'); 473 | end 474 | 475 | if minWaveLength < 2 476 | error('It makes little sense to have a wavelength < 2'); 477 | end 478 | 479 | if polarity ~= -1 & polarity ~= 0 & polarity ~= 1 480 | error('Allowed polarity values are -1, 0 and 1') 481 | end -------------------------------------------------------------------------------- /hsvHistogram.m: -------------------------------------------------------------------------------- 1 | function hsvColorHistogram = hsvHistogram(image) 2 | % input: image to be quantized in hsv color space into 8x2x2 equal bins 3 | % output: 1x32 vector indicating the features extracted from hsv color 4 | % space 5 | 6 | [rows, cols, numOfBands] = size(image); 7 | % totalPixelsOfImage = rows*cols*numOfBands; 8 | image = rgb2hsv(image); 9 | 10 | % split image into h, s & v planes 11 | h = image(:, :, 1); 12 | s = image(:, :, 2); 13 | v = image(:, :, 3); 14 | 15 | % quantize each h,s,v equivalently to 8x2x2 16 | % Specify the number of quantization levels. 17 | % thresholdForH = multithresh(h, 7); % 7 thresholds result in 8 image levels 18 | % thresholdForS = multithresh(s, 1); % Computing one threshold will quantize ... 19 | % % the image into three discrete levels 20 | % thresholdForV = multithresh(v, 1); % 7 thresholds result in 8 image levels 21 | % 22 | % seg_h = imquantize(h, thresholdForH); % apply the thresholds to obtain segmented image 23 | % seg_s = imquantize(s, thresholdForS); % apply the thresholds to obtain segmented image 24 | % seg_v = imquantize(v, thresholdForV); % apply the thresholds to obtain segmented image 25 | 26 | % quantize each h,s,v to 8x2x2 27 | % Specify the number of quantization levels. 28 | numberOfLevelsForH = 8; 29 | numberOfLevelsForS = 2; 30 | numberOfLevelsForV = 2; 31 | 32 | % Find the max. 33 | maxValueForH = max(h(:)); 34 | maxValueForS = max(s(:)); 35 | maxValueForV = max(v(:)); 36 | 37 | % create final histogram matrix of size 8x2x2 38 | hsvColorHistogram = zeros(8, 2, 2); 39 | 40 | % create col vector of indexes for later reference 41 | index = zeros(rows*cols, 3); 42 | 43 | % Put all pixels into one of the "numberOfLevels" levels. 44 | count = 1; 45 | for row = 1:size(h, 1) 46 | for col = 1 : size(h, 2) 47 | quantizedValueForH(row, col) = ceil(numberOfLevelsForH * h(row, col)/maxValueForH); 48 | quantizedValueForS(row, col) = ceil(numberOfLevelsForS * s(row, col)/maxValueForS); 49 | quantizedValueForV(row, col) = ceil(numberOfLevelsForV * v(row, col)/maxValueForV); 50 | 51 | % keep indexes where 1 should be put in matrix hsvHist 52 | index(count, 1) = quantizedValueForH(row, col); 53 | index(count, 2) = quantizedValueForS(row, col); 54 | index(count, 3) = quantizedValueForV(row, col); 55 | count = count+1; 56 | end 57 | end 58 | 59 | % put each value of h,s,v to matrix 8x2x2 60 | % (e.g. if h=7,s=2,v=1 then put 1 to matrix 8x2x2 in position 7,2,1) 61 | for row = 1:size(index, 1) 62 | if (index(row, 1) == 0 || index(row, 2) == 0 || index(row, 3) == 0) 63 | continue; 64 | end 65 | hsvColorHistogram(index(row, 1), index(row, 2), index(row, 3)) = ... 66 | hsvColorHistogram(index(row, 1), index(row, 2), index(row, 3)) + 1; 67 | end 68 | 69 | % normalize hsvHist to unit sum 70 | hsvColorHistogram = hsvColorHistogram(:)'; 71 | hsvColorHistogram = hsvColorHistogram/sum(hsvColorHistogram); 72 | 73 | % clear workspace 74 | clear('row', 'col', 'count', 'numberOfLevelsForH', 'numberOfLevelsForS', ... 75 | 'numberOfLevelsForV', 'maxValueForH', 'maxValueForS', 'maxValueForV', ... 76 | 'index', 'rows', 'cols', 'h', 's', 'v', 'image', 'quantizedValueForH', ... 77 | 'quantizedValueForS', 'quantizedValueForV'); 78 | 79 | % figure('Name', 'Quantized leves for H, S & V'); 80 | % subplot(2, 3, 1); 81 | % imshow(seg_h, []); 82 | % subplot(2, 3, 2); 83 | % imshow(seg_s, []); 84 | % title('Quatized H,S & V by matlab function imquantize'); 85 | % subplot(2, 3, 3); 86 | % imshow(seg_v, []); 87 | % subplot(2, 3, 4); 88 | % imshow(quantizedValueForH, []); 89 | % subplot(2, 3, 5); 90 | % imshow(quantizedValueForS, []); 91 | % title('Quatized H,S & V by my function'); 92 | % subplot(2, 3, 6); 93 | % imshow(quantizedValueForV, []); 94 | 95 | end -------------------------------------------------------------------------------- /lowpassfilter.m: -------------------------------------------------------------------------------- 1 | % LOWPASSFILTER - Constructs a low-pass butterworth filter. % % usage: f = lowpassfilter(sze, cutoff, n) % % where: sze is a two element vector specifying the size of filter % to construct [rows cols]. % cutoff is the cutoff frequency of the filter 0 - 0.5 % n is the order of the filter, the higher n is the sharper % the transition is. (n must be an integer >= 1). % Note that n is doubled so that it is always an even integer. % % 1 % f = -------------------- % 2n % 1.0 + (w/cutoff) % % The frequency origin of the returned filter is at the corners. % function f = lowpassfilter(sze, cutoff, n) if cutoff < 0 | cutoff > 0.5 error('cutoff frequency must be between 0 and 0.5'); end if rem(n,1) ~= 0 | n < 1 error('n must be an integer >= 1'); end if length(sze) == 1 rows = sze; cols = sze; else rows = sze(1); cols = sze(2); end % Set up X and Y matrices with ranges normalised to +/- 0.5 % The following code adjusts things appropriately for odd and even values % of rows and columns. if mod(cols, 2) xrange = [-(cols-1)/2:(cols-1)/2]/(cols-1); else xrange = [-cols/2:(cols/2-1)]/cols; end if mod(rows, 2) yrange = [-(rows-1)/2:(rows-1)/2]/(rows-1); else yrange = [-rows/2:(rows/2-1)]/rows; end [x, y] = meshgrid(xrange, yrange); radius = sqrt(x.^2 + y.^2); % A matrix with every pixel = radius relative to centre. f = ifftshift( 1 ./ (1.0 + (radius ./ cutoff).^(2*n)) ); % The filter -------------------------------------------------------------------------------- /matPlot.m: -------------------------------------------------------------------------------- 1 | function obj=matPlot(a, opt); 2 | % matPlot: Display a matrix in a figure window 3 | % 4 | % Usage: 5 | % matPlot(mat, opt); 6 | % 7 | % Example: 8 | % % === Example 1 9 | % opt=matPlot('defaultOpt'); 10 | % opt.matName='Magic matrix of size 10'; 11 | % figure; matPlot(magic(8), opt); 12 | % % === Example 2 13 | % opt=matPlot('defaultOpt'); 14 | % opt.matName='Random matrix of size 5'; 15 | % opt.format='8.2f'; 16 | % figure; matPlot(randn(4), opt); 17 | % % === Example 3 18 | % opt=matPlot('defaultOpt'); 19 | % opt.showRowRightLabel=0; 20 | % opt.showColDownLabel=0; 21 | % opt.highlightDiagonal=0; 22 | % opt.matName='Months of the year'; 23 | % opt.rowLabel={'Q1', 'Q2', 'Q3', 'Q4'}; 24 | % opt.colLabel={'M1', 'M2', 'M3'}; 25 | % mat={'Jan', 'Feb', 'Mar'; 'Apr', 'May', 'Jun'; 'Jul', 'Aug', 'Sept'; 'Oct', 'Nov', 'Dec'}; 26 | % figure; matPlot(mat, opt); 27 | % 28 | % Roger Jang, 20071009, 20120120 29 | 30 | if nargin<1, selfdemo; return; end 31 | % ====== Set the default options 32 | if ischar(a) && strcmpi(a, 'defaultOpt') 33 | obj.matrixName=''; 34 | obj.gridColor='k'; 35 | obj.fontSize=10; 36 | obj.fontColor='a'; 37 | obj.rowLabel=[]; 38 | obj.colLabel=[]; 39 | obj.highlightDiagonal=1; 40 | obj.showRowLeftLabel=1; 41 | obj.showRowRightLabel=1; 42 | obj.showColUpLabel=1; 43 | obj.showColDownLabel=1; 44 | obj.rowLeftLabel=''; 45 | obj.rowRightLabel=''; 46 | obj.colUpLabel=''; 47 | obj.colDownLabel=''; 48 | obj.format='11.4g'; 49 | return 50 | end 51 | if nargin<2||isempty(opt), opt=feval(mfilename, 'defaultOpt'); end 52 | if nargin<3, plotOpt=0; end 53 | 54 | % Clear the current axis 55 | cla reset; 56 | [m,n]=size(a); 57 | 58 | if opt.highlightDiagonal 59 | for i=1:min(m, n); 60 | patch(i-1+[0 1 1 0 0], min(m,n)-i+[0 0 1 1 0], 'y'); 61 | end 62 | end 63 | 64 | % Place the text in the correct locations 65 | for i=1:m % Index over number of rows 66 | for j=1:n % Index over number of columns 67 | theStr=a(i,j); 68 | if isnumeric(a(i,j)) 69 | theStr=num2str(a(i,j), ['%', opt.format]); 70 | end 71 | obj.element(i,j)=text(j-.5, m-i+.5, theStr, ... 72 | 'HorizontalAlignment', 'center', ... 73 | 'Color', 'b', ... 74 | 'FontWeight', 'bold', ... 75 | 'FontSize', opt.fontSize); 76 | end 77 | end 78 | 79 | % ====== Fill row labels and row sum 80 | for i=1:m 81 | if opt.showRowLeftLabel 82 | if isempty(opt.rowLeftLabel) 83 | for p=1:m, opt.rowLeftLabel{p}=int2str(p); end 84 | end 85 | obj.rowLeftLabel(i)=text(-0.1, m-i+.5, opt.rowLeftLabel{i}, ... 86 | 'HorizontalAlignment', 'right', ... 87 | 'Color', 'r', ... 88 | 'FontWeight', 'bold', ... 89 | 'FontSize', opt.fontSize); 90 | end 91 | if opt.showRowRightLabel 92 | if isempty(opt.rowRightLabel) 93 | if isnumeric(a) 94 | rowSum=sum(a, 2); 95 | for p=1:m, opt.rowRightLabel{p}=num2str(rowSum(p), ['%', opt.format]); end 96 | end 97 | end 98 | if ~isempty(opt.rowRightLabel) 99 | obj.rowRightLabel(i)=text(n+0.5, m-i+.5, opt.rowRightLabel{i}, ... 100 | 'HorizontalAlignment', 'center', ... 101 | 'Color', 'b', ... 102 | 'FontWeight', 'bold', ... 103 | 'FontSize', opt.fontSize); 104 | end 105 | end 106 | end 107 | 108 | % ====== Fill column labels and column sum 109 | for j=1:n 110 | if opt.showColUpLabel 111 | if isempty(opt.colUpLabel) 112 | for p=1:n, opt.colUpLabel{p}=int2str(p); end 113 | end 114 | obj.colUpLabel(j)=text(j-.5, m+.1, opt.colUpLabel{j}, ... 115 | 'HorizontalAlignment', 'left', ... 116 | 'rot', 90, ... 117 | 'Color', 'r', ... 118 | 'FontWeight', 'bold', ... 119 | 'FontSize', opt.fontSize); 120 | end 121 | if opt.showColDownLabel 122 | if isempty(opt.colDownLabel) 123 | if isnumeric(a) 124 | colSum=sum(a, 1); 125 | for p=1:m, opt.colDownLabel{p}=num2str(colSum(p), ['%', opt.format]); end 126 | end 127 | end 128 | if ~isempty(opt.colDownLabel) 129 | obj.colDownLabel(j)=text(j-.5, -.2, opt.colDownLabel{j}, ... 130 | 'HorizontalAlignment', 'center', ... 131 | 'Color', 'b', ... 132 | 'FontWeight', 'bold', ... 133 | 'FontSize', opt.fontSize); 134 | end 135 | end 136 | end 137 | 138 | set(gca,'Box', 'on', ... 139 | 'Visible', 'on', ... 140 | 'xLim', [0 n], ... 141 | 'xGrid', 'on', ... 142 | 'xTickLabel', [], ... 143 | 'xTick', 0:n, ... 144 | 'yGrid', 'on', ... 145 | 'yLim', [0 m], ... 146 | 'yTickLabel', [], ... 147 | 'yTick', 0:m, ... 148 | 'DataAspectRatio', [1, 1, 1], ... 149 | 'GridLineStyle', ':', ... 150 | 'LineWidth', 3, ... 151 | 'XColor', opt.gridColor, ... 152 | 'YColor', opt.gridColor); 153 | 154 | xlabel(opt.matrixName); 155 | set(get(gca, 'xlabel'), 'position', [n/2, -1]) 156 | set(gcf, 'numbertitle', 'off', 'name', opt.matrixName); 157 | 158 | % ====== Self demo 159 | function selfdemo 160 | mObj=mFileParse(which(mfilename)); 161 | strEval(mObj.example); 162 | -------------------------------------------------------------------------------- /reference.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirk86/ImageRetrieval/cab6a346bcd67035e7e657ceca4f955eb0405aea/reference.pdf -------------------------------------------------------------------------------- /relativeDeviation.m: -------------------------------------------------------------------------------- 1 | function relativeDeviation(numOfReturnedImages, queryImageFeatureVector, dataset, folder_name, img_ext) 2 | % input: 3 | % numOfReturnedImages : num of images returned by query 4 | % queryImageFeatureVector: query image in the form of a feature vector 5 | % dataset: the whole dataset of images transformed in a matrix of 6 | % features 7 | % 8 | % output: 9 | % plot: plot images returned by query 10 | 11 | % extract image fname from queryImage and dataset 12 | query_im_name = queryImageFeatureVector(:, end); 13 | dataset_im_names = dataset(:, end); 14 | 15 | queryImageFeatureVector(:, end) = []; 16 | dataset(:, end) = []; 17 | 18 | % compute relative deviation 19 | relDeviation = zeros(length(dataset), 1); 20 | for k = 1:length(dataset) 21 | relDeviation(k) = sqrt( sum( power( dataset(k, :) - queryImageFeatureVector, 2 ) ) ) ./ 1/2 * ( sqrt( sum( power( dataset(k, :), 2 ) ) ) + sqrt( sum( power( queryImageFeatureVector, 2 ) ) ) ); 22 | end 23 | 24 | % add image fnames to euclidean 25 | relDeviation = [relDeviation dataset_im_names]; 26 | 27 | % sort them according to smallest distance 28 | [sortRelDist indxs] = sortrows(relDeviation); 29 | sortedRelImgs = sortRelDist(:, 2); 30 | 31 | % clear axes 32 | arrayfun(@cla, findall(0, 'type', 'axes')); 33 | 34 | % display query image 35 | str_img_name = int2str(query_im_name); 36 | query_im = imread( strcat(folder_name, '\', str_img_name, img_ext) ); 37 | subplot(3, 7, 1); 38 | imshow(query_im, []); 39 | title('Query Image', 'Color', [1 0 0]); 40 | 41 | % dispaly images returned by query 42 | for m = 1:numOfReturnedImages 43 | im_name = sortedRelImgs(m); 44 | im_name = int2str(im_name); 45 | str_im_name = strcat(folder_name, '\', im_name, img_ext); 46 | returned_im = imread(str_im_name); 47 | subplot(3, 7, m+1); 48 | imshow(returned_im, []); 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /svm.m: -------------------------------------------------------------------------------- 1 | function [precision, recall, cmat] = svm(numOfReturnedImgs, dataset, queryImageFeatureVector, metric, folder_name, img_ext) 2 | %# load dataset and extract image names 3 | img_names = dataset(:, end); 4 | dataset(:, end) = []; 5 | 6 | % extract image name from queryImageFeatureVector 7 | query_img_name = queryImageFeatureVector(:, end); 8 | queryImageFeatureVector(:, end) = []; 9 | 10 | % construct labels 11 | lbls = zeros(length(dataset), 1); 12 | for k = 0:length(lbls)-1 13 | if (img_names(k+1) >= 0 && img_names(k+1) <= 99) 14 | lbls(k+1) = 1; 15 | elseif (img_names(k+1) > 99 && img_names(k+1) <= 199) 16 | lbls(k+1) = 2; 17 | elseif (img_names(k+1) > 199 && img_names(k+1) <= 299) 18 | lbls(k+1) = 3; 19 | elseif (img_names(k+1) > 299 && img_names(k+1) <= 399) 20 | lbls(k+1) = 4; 21 | elseif (img_names(k+1) > 399 && img_names(k+1) <= 499) 22 | lbls(k+1) = 5; 23 | elseif (img_names(k+1) > 499 && img_names(k+1) <= 599) 24 | lbls(k+1) = 6; 25 | elseif (img_names(k+1) > 599 && img_names(k+1) <= 699) 26 | lbls(k+1) = 7; 27 | elseif (img_names(k+1) > 699 && img_names(k+1) <= 799) 28 | lbls(k+1) = 8; 29 | elseif (img_names(k+1) > 799 && img_names(k+1) <= 899) 30 | lbls(k+1) = 9; 31 | elseif (img_names(k+1) > 899 && img_names(k+1) <= 999) 32 | lbls(k+1) = 10; 33 | end 34 | end 35 | 36 | [g gn] = grp2idx(lbls); %# nominal class to numeric 37 | 38 | %# split training/testing sets 39 | [trainIdx testIdx] = crossvalind('HoldOut', lbls, 1/2); % split the train and test labels 50%-50% 40 | 41 | pairwise = nchoosek(1:size(gn, 1), 2); %# 1-vs-1 pairwise models 42 | svmModel = cell(size(pairwise, 1), 1); %# store binary-classifers 43 | predTest = zeros(sum(testIdx), numel(svmModel)); %# store binary predictions 44 | 45 | %# classify using one-against-one approach, SVM with 3rd degree poly kernel 46 | for k=1:numel(svmModel) 47 | %# get only training instances belonging to this pair 48 | idx = trainIdx & any( bsxfun(@eq, g, pairwise(k,:)) , 2 ); 49 | 50 | %# train 51 | % svmModel{k} = svmtrain(dataset(idx,:), g(idx), ... 52 | % 'BoxConstraint',2e-1, 'Kernel_Function','polynomial', 'Polyorder',3); 53 | svmModel{k} = svmtrain(dataset(idx,:), g(idx), ... 54 | 'BoxConstraint', Inf, 'Kernel_Function', 'rbf', 'rbf_sigma', 14.51); 55 | 56 | %# test 57 | predTest(:,k) = svmclassify(svmModel{k}, dataset(testIdx,:)); % matlab native svm function 58 | end 59 | pred = mode(predTest, 2); %# voting: clasify as the class receiving most votes 60 | 61 | %# performance 62 | cmat = confusionmat(g(testIdx), pred); %# g(testIdx) == targets, pred == outputs 63 | final_acc = 100*sum(diag(cmat))./sum(cmat(:)); 64 | fprintf('SVM (1-against-1):\naccuracy = %.2f%%\n', final_acc); 65 | fprintf('Confusion Matrix:\n'), disp(cmat) 66 | % assignin('base', 'cmatrix', cmat); 67 | 68 | % Precision and recall 69 | % 1st class 70 | precision = zeros(size(gn, 1), 1); 71 | recall = zeros(size(gn, 1), 1); 72 | 73 | precision = cmat(1, 1)/sum(cmat(:, 1)); % tp/tp+fp, where tp = true positive, fp = false positive 74 | recall = cmat(1, 1)/sum(cmat(1, :)); % tp/tp+fn, where fn = false negatives 75 | % % 2nd class and forward 76 | for c = 2:size(gn, 1) 77 | precision(c) = cmat(c, c)/sum(cmat(c:end, c)); 78 | recall(c) = cmat(c, c)/sum(cmat(c, c:end)); 79 | end 80 | 81 | % verify predictions 82 | % dataset = [dataset img_names lbls]; 83 | % testData = dataset(testIdx, :); 84 | % classesInTestData = sort(testData(:, end)); % 500 samples from 10 classes 85 | % predictedImgs = dataset(pred, :); 86 | % dataset(:, end) = []; 87 | 88 | for k = 1:numel(svmModel) 89 | %# test 90 | predQueryImg(:, k) = svmclassify(svmModel{k}, queryImageFeatureVector); % queryImage = x.jpg, row=x from dataset 91 | end 92 | predFinalQueryImg = mode(predQueryImg, 2); % predicted final image in class x using voting 93 | fprintf('Predicted Query Image Belongs to Class = %d\n', predFinalQueryImg); 94 | 95 | % take all images from dataset that belong to class x 96 | dataset = [dataset img_names lbls]; 97 | imgsInClassX = dataset( find( dataset(:, end) == predFinalQueryImg ), : ); 98 | 99 | % Perform knn with queryImage and imgsInClassX 100 | imgsInClassXWithoutLbls = imgsInClassX; 101 | imgsInClassXWithoutLbls(:, end) = []; 102 | % imgsInClassXWithoutLbls(:, end) = []; 103 | 104 | L2(numOfReturnedImgs, [queryImageFeatureVector query_img_name], imgsInClassXWithoutLbls, metric, folder_name, img_ext); 105 | 106 | end -------------------------------------------------------------------------------- /waveletTransform.m: -------------------------------------------------------------------------------- 1 | function waveletMoments = waveletTransform(image, spaceColor) 2 | % input: image to process and extract wavelet coefficients from 3 | % output: 1x20 feature vector containing the first 2 moments of wavelet 4 | % coefficients 5 | 6 | if (strcmp(spaceColor, 'truecolor') == 1) 7 | imgGray = double(rgb2gray(image))/255; 8 | imgGray = imresize(imgGray, [256 256]); 9 | elseif (strcmp(spaceColor, 'grayscale') == 1) 10 | imgGray = imresize(image, [256 256]); 11 | end 12 | 13 | coeff_1 = dwt2(imgGray', 'coif1'); 14 | coeff_2 = dwt2(coeff_1, 'coif1'); 15 | coeff_3 = dwt2(coeff_2, 'coif1'); 16 | coeff_4 = dwt2(coeff_3, 'coif1'); 17 | 18 | % construct the feaute vector 19 | meanCoeff = mean(coeff_4); 20 | stdCoeff = std(coeff_4); 21 | 22 | waveletMoments = [meanCoeff stdCoeff]; 23 | 24 | end --------------------------------------------------------------------------------