├── Keras_test_MAC.py ├── README.md ├── data ├── oxford5k │ └── example.txt └── paris6k │ └── example └── utils.py /Keras_test_MAC.py: -------------------------------------------------------------------------------- 1 | from keras.applications.vgg16 import VGG16, preprocess_input 2 | from keras.applications.resnet50 import ResNet50, preprocess_input 3 | from keras.preprocessing import image 4 | from keras.models import Model, load_model 5 | import os 6 | import os.path 7 | import time 8 | import numpy as np 9 | from numpy import linalg as LA 10 | from utils import * 11 | 12 | # Starting parameters 13 | layer = 'block5_pool' # block5_pool, res5a_branch1 , activation_43, res5c_relu 14 | network = 'VGG16' # VGG16, VGG19, ResNet50, ResNet101 15 | L = 3 16 | topResultsQE = 5 17 | nFiles = 100 18 | largeScaleRetrieval = False 19 | 20 | base_model = str_to_class(network)(weights='imagenet', include_top=False, input_shape=(None,None,3)) 21 | model = Model(inputs=base_model.input, outputs=base_model.get_layer(str(layer)).output) 22 | 23 | 24 | d = ["oxford5k","paris6k","holidays"] 25 | for dataset in d: 26 | elif (dataset == 'oxford5k'): 27 | topResultsQE = 8 28 | elif (dataset == "paris6k"): 29 | topResultsQE = 6 30 | elif (dataset == "holidays"): 31 | topResultsQE = 1 32 | 33 | print("-------------------------------------------------") 34 | print('Parameters') 35 | print('Dataset: ' + str(dataset)) 36 | if (dataset=="paris6k" or dataset=="holidays"): 37 | datasetPCA = 'oxford5k' 38 | elif (dataset=="oxford5k"): 39 | datasetPCA = "paris6k" 40 | print('PCA dataset: ' + str(datasetPCA)) 41 | print('Network: ' + str(network)) 42 | print('Layer: ' + str(layer)) 43 | print('R-MAC descriptors with ' + str(L) + ' scales') 44 | 45 | resolutionLevel = 3 46 | print('Multi-resolution activated (3 scales: original, +25%, -25% on the largest side)') 47 | 48 | 49 | print("Query expansion. Top results used for QE: " + str(topResultsQE)) 50 | 51 | if (largeScaleRetrieval): 52 | print("Activate large scale retrieval of",nFiles,"k files") 53 | 54 | print("------------------------------------------------") 55 | 56 | url = "results/" + dataset + "/" + network + "_L" + str(L) 57 | savingUrl = datasetPCA + "_"+str(network) 58 | 59 | 60 | url += "_multiResolution_pca" + datasetPCA 61 | 62 | 63 | PCAImages = readTraining(datasetPCA, False,0) 64 | print('PCA with '+str(len(PCAImages))+' images') 65 | PCAMAC = extractFeatures(PCAImages, model, True, L, resolutionLevel) 66 | W, Xm = learningPCA(PCAMAC) 67 | np.save('W'+savingUrl+'.npy',W) 68 | np.save('Xm'+savingUrl+'.npy',Xm) 69 | 70 | #after first execution comment the above snippet for the creation of the matrix W e Xm, usefull for the next PCA 71 | #W = np.load('W' + savingUrl + '.npy') 72 | #Xm = np.load('Xm' + savingUrl + '.npy') 73 | 74 | # ------------------ DB images: reading, descripting and whitening ----------------------- 75 | 76 | DbImages = readTraining(dataset, True) 77 | print('DB contains ' + str(len(DbImages)) + ' images') 78 | 79 | t1 = time.clock() 80 | DbMAC = extractFeatures(DbImages, model, True, L, resolutionLevel) 81 | print("PCA-whitening") 82 | DbMAC = apply_whitening(DbMAC, Xm, W) 83 | regions = np.copy(DbMAC) 84 | nRegions = regions.shape[0]//len(DbImages) 85 | DbMAC = sumPooling(DbMAC, len(DbImages), False) 86 | Dbtime = time.clock() - t1 87 | print("RMAC and PCA-whitening of terminated in",round(Dbtime),"s") 88 | 89 | # ------------------- query images: reading, descripting and whitening ----------------------- 90 | queryImages, bBox = readTest(dataset, full=True) 91 | print('QUERY are ' + str(len(queryImages)) + ' images') 92 | 93 | queryMAC = extractFeatures(queryImages, model, True, L, resolutionLevel,bBox, queryVersion) 94 | queryMAC = apply_whitening(queryMAC, Xm, W) 95 | queryMAC = sumPooling(queryMAC, 55, False) 96 | print("Query descriptors saved!") 97 | 98 | retrieval1 = time.clock() 99 | finalReRank = retrieveRegionsNEW(queryMAC, regions, topResultsQE,url, queryImages, DbImages, dataset) 100 | retrieval2 = time.clock() - retrieval1 101 | print("AVG query time:",round(retrieval2/len(queryImages),2),"s") 102 | 103 | retrieval1 = time.clock() 104 | finalReRank2 = retrieveQERegionsNEW(queryMAC, regions, topResultsQE, url,queryImages, DbImages, finalReRank, dataset) 105 | retrieval2 = time.clock() - retrieval1 106 | print("AVG query expansion time:",round(retrieval2/len(queryImages),2),"s") 107 | 108 | if (largeScaleRetrieval): 109 | queryMAClargeScale = np.copy(queryMAC) 110 | 111 | 112 | # ---------- large-scale retrieval ------------------------- 113 | 114 | if (largeScaleRetrieval): 115 | print("LARGE-scale retrieval") 116 | url += "_"+str(nFiles)+"k" 117 | distractorImages = readTraining("Flickr1M", False,nFiles) 118 | limits = nFiles*1000//20 119 | print("Added",len(distractorImages),"distractors from Flickr with limits",limits) 120 | t10 = time.clock() 121 | distractorsMAC = extractAndWhiteningNEW(distractorImages, model, True, L, resolutionLevel, Xm, W, limits, None) 122 | t11 = time.clock() - t10 123 | print("Features extracted in",t11,"s") 124 | DbMAC.extend(distractorsMAC) 125 | t12 = time.clock() 126 | finalReRank3 = retrieve(queryMAClargeScale, DbMAC, topResultsQE,url, queryImages, DbImages, dataset, True) 127 | t13 = time.clock() - t12 128 | t13 /= len(queryImages) 129 | print("Avg query time:",t13,"s") 130 | retrieveQE(queryMAClargeScale, DbMAC, topResultsQE, url, queryImages, DbImages, finalReRank3, dataset, True) 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keras_rmac_plus 2 | Keras implementation of R-MAC+ descriptors. 3 | 4 | [[paper](https://arxiv.org/pdf/1806.08565.pdf)] [[project](http://implab.ce.unipr.it/?page_id=858)] 5 | 6 | The image below represents the query phase exeucted for the R-MAC+ descriptors. 7 | 8 |  9 | 10 | ## Prerequisites for Python3 11 | * Keras (> 2.0.0) 12 | * Tensorflow (> 1.5) 13 | * Scipy 14 | * Sklearn 15 | * OpenCV 3 16 | 17 | ## Networks 18 | The pipeline was tested with VGG16 and ResNet50. For the VGG16 the best performance are reached when the features are extracted from the block5_pool, instead for ResNet from the activation_43. 19 | It is possible to try with other networks. Please before to try it, check if there are available the Keras weight for the selected network. 20 | 21 | ## Datasets 22 | * Holidays 23 | * Oxford5k 24 | * Paris6k 25 | 26 | Download the datasets and put it into the data folder. Then compile the script for the evaluation of the retrieval system. 27 | 28 | ## Test 29 | ` python3 Keras_test_MAC.py ` 30 | 31 | ## Results 32 | 33 | 34 | | Method | Network | Oxford5k | Paris6k | Holidays | 35 | | :------------- |:-------------:| :-----:| :---:| :---------:| 36 | | R-MAC | VGG16 | 65.56% | 82.80% | 87.65% | 37 | | R-MAC | ResNet50 | 71.77% | 83.31% | 92.55% | 38 | | M-R RMAC+ | ResNet50 | 78.88% | 88.63% | 94.63% / 95.58% | 39 | | M-R RMAC+ with retrieval based on 'db regions' | ResNet50 | 85.39 % | 91.90% | 94.37% / 95.87% | 40 | 41 | The R-MAC is an our re-implementation of the Tolias et al. 2016 paper, instead M-R RMAC comes from the Gordo et al. 2016 paper. 42 | The last two experiments are also executed on the rotated version of Holidays. 43 | 44 | ## References 45 | 46 |
@article{magliani2018accurate, 47 | title={An accurate retrieval through R-MAC+ descriptors for landmark recognition}, 48 | author={Magliani, Federico and Prati, Andrea}, 49 | journal={arXiv preprint arXiv:1806.08565}, 50 | year={2018} 51 | } 52 | 53 | @article{tolias2015particular, 54 | title={Particular object retrieval with integral max-pooling of CNN activations}, 55 | author={Tolias, Giorgos and Sicre, Ronan and J{\'e}gou, Herv{\'e}}, 56 | journal={arXiv preprint arXiv:1511.05879}, 57 | year={2015} 58 | } 59 | 60 | @inproceedings{gordo2016deep, 61 | title={Deep image retrieval: Learning global representations for image search}, 62 | author={Gordo, Albert and Almaz{\'a}n, Jon and Revaud, Jerome and Larlus, Diane}, 63 | booktitle={European Conference on Computer Vision}, 64 | pages={241--257}, 65 | year={2016}, 66 | organization={Springer} 67 | } 68 | 69 |70 | -------------------------------------------------------------------------------- /data/oxford5k/example.txt: -------------------------------------------------------------------------------- 1 | 2 | Download and copy here the jpg and lab folder. 3 | -------------------------------------------------------------------------------- /data/paris6k/example: -------------------------------------------------------------------------------- 1 | 2 | Download and copy here the jpg and lab folder. 3 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from keras.applications.vgg16 import VGG16, preprocess_input 2 | from keras.applications.resnet50 import ResNet50, preprocess_input 3 | from keras.preprocessing import image 4 | from keras.models import Model 5 | import os 6 | import os.path 7 | from PIL import Image, ImageFile 8 | import sys 9 | import numpy as np 10 | from numpy import linalg as LA 11 | import cv2 12 | import operator 13 | from sklearn.decomposition import PCA 14 | import scipy 15 | import math 16 | import time 17 | from sys import getsizeof 18 | from sklearn.preprocessing import normalize 19 | from sklearn.decomposition import PCA 20 | from collections import defaultdict 21 | from sklearn.metrics.pairwise import euclidean_distances 22 | from tqdm import tqdm 23 | from glob import glob 24 | import shutil 25 | 26 | def str_to_class(str): 27 | return getattr(sys.modules[__name__], str) 28 | 29 | def readTraining(dataset, rotated=True, nFiles=0, debug=False): 30 | if (dataset == 'oxford5k' or dataset == 'paris6k'): 31 | path = 'data/'+dataset+'/jpg/*.jpg' 32 | if (dataset == 'holidays' and rotated==True): 33 | path = 'dataset/holidays/jpg_rotated/Db/*.jpg' 34 | elif (dataset == 'holidays' and rotated==False): 35 | path = 'dataset/holidays/jpg_reduced/Db/*.jpg' 36 | elif (dataset == 'Flickr1M'): 37 | path = 'data/Flickr1M/im*/*/*.jpg' 38 | DbImages = np.sort(glob(path)) #da capire se funziona con Flickr1M 39 | 40 | if (dataset == 'Flickr1M'): 41 | DbImages = DbImages[0:int(nFiles*1000)] 42 | 43 | return DbImages 44 | 45 | def readTest(dataset, full=False, debug=False): 46 | bBox = [] 47 | if (dataset == 'holidays'): 48 | if (not full): #not rotated 49 | path = 'dataset/holidays/jpg_reduced/query/*.jpg' 50 | else: 51 | path = 'dataset/holidays/jpg_rotated/query/*.jpg' 52 | elif (dataset == 'oxford5k' or dataset == 'paris6k'): 53 | path = 'dataset/' + dataset + '/query' 54 | if (not full): 55 | path += '_reduced/*.jpg' 56 | else: 57 | path +='/*.jpg' 58 | 59 | queryImages = np.sort(queryImages) 60 | 61 | if ((dataset=="oxford5k" or dataset=="paris6k") and full): 62 | print("Creation of bBox list") 63 | #insert elements in bBox list 64 | url = 'dataset/'+dataset+'/gt_files/' 65 | lab_filenames = np.sort(os.listdir(url)) 66 | for e in lab_filenames: 67 | if e.endswith('_query.txt'): 68 | q_name = e[:-len('_query.txt')] 69 | q_data = open("{0}/{1}".format(url, e)).readline().split(" ") 70 | q_filename = q_data[0][5:] if q_data[0].startswith('oxc1_') else q_data[0] 71 | q_final = [s.rstrip() for s in q_data] 72 | bBox.append(q_final[1:]) 73 | for i,q in enumerate(queryImages,0): 74 | img = cv2.imread(q) 75 | h = img.shape[0] 76 | w = img.shape[1] 77 | bBox[i][0] = float(bBox[i][0]) / w 78 | bBox[i][2] = float(bBox[i][2]) / w 79 | bBox[i][1] = float(bBox[i][1]) / h 80 | bBox[i][3] = float(bBox[i][3]) / h 81 | 82 | return queryImages,bBox 83 | 84 | def calculateMAC(featureVector, listData): #max-pooling and l2-norm 85 | rows = featureVector.shape[1] * featureVector.shape[2] 86 | cols = featureVector.shape[3] 87 | features1 = np.reshape(featureVector, (rows, cols)) 88 | features2 = np.amax(features1, axis = 0) 89 | features2 /= np.linalg.norm(features2, 2) 90 | listData.append(features2) 91 | 92 | return 93 | 94 | def calculateRMAC(features, listData, L): 95 | W = features.shape[1] 96 | H = features.shape[2] 97 | # print("W",W,"H",H) 98 | 99 | for l in range(1,L+1): 100 | if (l==1): 101 | heightRegion = widthRegion = min(W,H) 102 | if (W