├── 1.jpg ├── 2.jpg ├── README.md ├── extract_cnn_vgg16_keras.py ├── img ├── dasima_0.jpg ├── dasima_1.jpg ├── dasima_2.jpg ├── dasima_3.jpg ├── dasima_4.jpg ├── dasima_5.jpg ├── dasima_6.jpg ├── dasima_7.jpg ├── dasima_8.jpg ├── dasima_9.jpg ├── qrcode_for_gh_cf77d20d7eb8_430.jpg ├── wuwukai_10.jpg ├── wuwukai_11.jpg ├── wuwukai_12.jpg ├── wuwukai_13.jpg ├── wuwukai_14.jpg ├── wuwukai_15.jpg ├── wuwukai_16.jpg ├── wuwukai_17.jpg ├── wuwukai_18.jpg ├── wuwukai_19.jpg └── 深度截图_选择区域_20190508214737.png ├── index.py ├── lol.h5 ├── pachong.py └── query_online.py /1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/1.jpg -------------------------------------------------------------------------------- /2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/2.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Retrieval Engine Based on Keras 2 | 3 | 基于内容的图像检索( Content-Based Image Retrieval ,CBIR) 4 | ## 演示 5 | 6 | 详细讲解、操作截图和视频演示 7 | 8 | [以图搜图展示](https://mp.weixin.qq.com/s?__biz=MzU4NTY4Mzg1Mw==&mid=2247484752&idx=1&sn=063168237aab0d4fba2c0ae426be2e4a&chksm=fd8783b2caf00aa4bffcfc9f4730bdf2b3c5a4f1d3fdee91115cdd3fc23709a3ac70f1d9c876&token=403649858&lang=zh_CN#rd) 9 | 10 | 首发于公众号:AI算法与图像处理 11 | 12 | ![公众号](https://github.com/DWCTOD/flask-keras-cnn-image-retrieval-master/blob/master/img/qrcode_for_gh_cf77d20d7eb8_430.jpg) 13 | 14 | ## 环境 15 | 16 | ```python 17 | import keras 18 | Using Theano backend 19 | ``` 20 | 21 | 本人采用的是keras 2.24版本,使用python3.6 22 | 23 | 原文作者备注:https://github.com/willard-yuan/flask-keras-cnn-image-retrieval 24 | 25 | keras 2.0.1 及 2.0.5 版本均经过测试可用。推荐Python 2.7,支持Python 3.6. 26 | 27 | 此外需要numpy, matplotlib, os, h5py, argparse. 推荐使用anaconda安装 28 | 29 | ## 文件说明 30 | 31 | img 文件夹:图片库——用于搭建索引库和后续查询结果的显示 32 | 33 | 1.jpg 2.jpg :用于测试 34 | 35 | extract_cnn_vgg16_keras.py:提取图片特征 36 | 37 | index.py:建立索引库并保存模型 38 | 39 | lol.h5:本人事先建好的模型,可以用于直接测试 40 | 41 | pachong.py:爬取百度的图片 42 | 43 | query_online.py:运行代码实现以图搜图在线测试 44 | 45 | ## 使用 46 | 47 | ```python 48 | #操作汇总 49 | # (1)打开终端执行index.py代码 50 | python3 index.py -database img -index lol.h5 51 | # 此时已经将图片库转成索引库并保存和输出lol.h5模型 52 | # (2)继续运行代码 query_online.py 53 | python3 query_online.py 54 | # 按照提示,如果要退出输入 exit 查询直接enter 55 | # 输入测试图片 名字即可(如果测试图片额代码不在同一路径下需要增加路径——这里 设置相对路径) 56 | ``` 57 | 58 | 59 | 60 | 备注:本人对源代码进行了一些修改 61 | 62 | 1)增加了一个异常处理操作,主要是为了方便,即使手误输错也能继续运行,这个在之前的文章中有讲解过; 63 | 64 | [学会这招再也不怕手误让代码崩掉](http://mp.weixin.qq.com/s?__biz=MzU4NTY4Mzg1Mw==&mid=2247484695&idx=1&sn=530d383d799e1aaa4554747098c53e01&chksm=fd8783f5caf00ae38c93613aab97df7feb9d6b13c7018e1fa2f378ec86bb97d164e93e7a1a2b&scene=21#wechat_redirect) 65 | 66 | (2)作者最终显示的结果只能一张一张的展示,没有对比图,因此我稍微修改了一下,让可视化的效果更加的美观一些,有兴趣的可以参考我的代码; 67 | 68 | (3)我对参数输入也进行了修改,将模型名字和图片库的路径都固定了,这样子测试的时候比较方便,大家在使用的时候请注意下,如果修改了名字要对应起来。 69 | 70 | ![代码](https://github.com/DWCTOD/flask-keras-cnn-image-retrieval-master/blob/master/img/%E6%B7%B1%E5%BA%A6%E6%88%AA%E5%9B%BE_%E9%80%89%E6%8B%A9%E5%8C%BA%E5%9F%9F_20190508214737.png) 71 | 72 | 备注:对于第一次使用的小伙伴,请保持心态良好,一开始要下载VGG16的预训练模型,可能等待时间比较久,此时可以考虑泡一杯coffee,如果下载速度慢是由于国内镜像源的问题,可以自行百度如何切换“源”,当然也可以找其他人上传的模型链接例如: https://github.com/fchollet/deep-learning-models/releases -------------------------------------------------------------------------------- /extract_cnn_vgg16_keras.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: yongyuan.name 3 | 4 | import numpy as np 5 | from numpy import linalg as LA 6 | 7 | from keras.applications.vgg16 import VGG16 8 | from keras.preprocessing import image 9 | from keras.applications.vgg16 import preprocess_input 10 | 11 | class VGGNet: 12 | def __init__(self): 13 | # weights: 'imagenet' 14 | # pooling: 'max' or 'avg' 15 | # input_shape: (width, height, 3), width and height should >= 48 16 | self.input_shape = (224, 224, 3) 17 | self.weight = 'imagenet' 18 | self.pooling = 'max' 19 | self.model = VGG16(weights = self.weight, input_shape = (self.input_shape[0], self.input_shape[1], self.input_shape[2]), pooling = self.pooling, include_top = False) 20 | self.model.predict(np.zeros((1, 224, 224 , 3))) 21 | 22 | ''' 23 | Use vgg16 model to extract features 24 | Output normalized feature vector 25 | ''' 26 | def extract_feat(self, img_path): 27 | img = image.load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1])) 28 | img = image.img_to_array(img) 29 | img = np.expand_dims(img, axis=0) 30 | img = preprocess_input(img) 31 | feat = self.model.predict(img) 32 | norm_feat = feat[0]/LA.norm(feat[0]) 33 | return norm_feat 34 | -------------------------------------------------------------------------------- /img/dasima_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_0.jpg -------------------------------------------------------------------------------- /img/dasima_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_1.jpg -------------------------------------------------------------------------------- /img/dasima_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_2.jpg -------------------------------------------------------------------------------- /img/dasima_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_3.jpg -------------------------------------------------------------------------------- /img/dasima_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_4.jpg -------------------------------------------------------------------------------- /img/dasima_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_5.jpg -------------------------------------------------------------------------------- /img/dasima_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_6.jpg -------------------------------------------------------------------------------- /img/dasima_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_7.jpg -------------------------------------------------------------------------------- /img/dasima_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_8.jpg -------------------------------------------------------------------------------- /img/dasima_9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/dasima_9.jpg -------------------------------------------------------------------------------- /img/qrcode_for_gh_cf77d20d7eb8_430.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/qrcode_for_gh_cf77d20d7eb8_430.jpg -------------------------------------------------------------------------------- /img/wuwukai_10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_10.jpg -------------------------------------------------------------------------------- /img/wuwukai_11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_11.jpg -------------------------------------------------------------------------------- /img/wuwukai_12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_12.jpg -------------------------------------------------------------------------------- /img/wuwukai_13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_13.jpg -------------------------------------------------------------------------------- /img/wuwukai_14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_14.jpg -------------------------------------------------------------------------------- /img/wuwukai_15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_15.jpg -------------------------------------------------------------------------------- /img/wuwukai_16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_16.jpg -------------------------------------------------------------------------------- /img/wuwukai_17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_17.jpg -------------------------------------------------------------------------------- /img/wuwukai_18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_18.jpg -------------------------------------------------------------------------------- /img/wuwukai_19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/wuwukai_19.jpg -------------------------------------------------------------------------------- /img/深度截图_选择区域_20190508214737.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/img/深度截图_选择区域_20190508214737.png -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: yongyuan.name 3 | import os 4 | import h5py 5 | import numpy as np 6 | import argparse 7 | 8 | from extract_cnn_vgg16_keras import VGGNet 9 | 10 | 11 | ap = argparse.ArgumentParser() 12 | ap.add_argument("-database", required = True, 13 | help = "Path to database which contains images to be indexed") 14 | ap.add_argument("-index", required = True, 15 | help = "Name of index file") 16 | args = vars(ap.parse_args()) 17 | 18 | 19 | ''' 20 | Returns a list of filenames for all jpg images in a directory. 21 | ''' 22 | def get_imlist(path): 23 | return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')] 24 | 25 | 26 | ''' 27 | Extract features and index the images 28 | ''' 29 | if __name__ == "__main__": 30 | 31 | db = args["database"] 32 | img_list = get_imlist(db) 33 | 34 | print("--------------------------------------------------") 35 | print(" feature extraction starts") 36 | print("--------------------------------------------------") 37 | 38 | feats = [] 39 | names = [] 40 | 41 | model = VGGNet() 42 | for i, img_path in enumerate(img_list): 43 | norm_feat = model.extract_feat(img_path) 44 | img_name = os.path.split(img_path)[1] 45 | feats.append(norm_feat) 46 | names.append(img_name) 47 | print("extracting feature from image No. %d , %d images in total" %((i+1), len(img_list))) 48 | 49 | feats = np.array(feats) 50 | # print(feats) 51 | # directory for storing extracted features 52 | output = args["index"] 53 | 54 | print("--------------------------------------------------") 55 | print(" writing feature extraction results ...") 56 | print("--------------------------------------------------") 57 | 58 | 59 | h5f = h5py.File(output, 'w') 60 | h5f.create_dataset('dataset_1', data = feats) 61 | #h5f.create_dataset('dataset_2', data = names) 62 | h5f.create_dataset('dataset_2', data = np.string_(names)) 63 | h5f.close() 64 | -------------------------------------------------------------------------------- /lol.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWCTOD/flask-keras-cnn-image-retrieval-master/b40c919bea9d5dd4e4dfa7a9ba296a7d53fe388c/lol.h5 -------------------------------------------------------------------------------- /pachong.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | from urllib import error 4 | from bs4 import BeautifulSoup 5 | import os 6 | 7 | num = 0 8 | numPicture = 0 9 | file = '' 10 | List = [] 11 | 12 | 13 | def Find(url): 14 | global List 15 | print('正在检测图片总数,请稍等.....') 16 | t = 0 17 | i = 1 18 | s = 0 19 | while t < 1000: 20 | Url = url + str(t) 21 | try: 22 | Result = requests.get(Url, timeout=7) 23 | except BaseException: 24 | t = t + 60 25 | continue 26 | else: 27 | result = Result.text 28 | pic_url = re.findall('"objURL":"(.*?)",', result, re.S) # 先利用正则表达式找到图片url 29 | s += len(pic_url) 30 | if len(pic_url) == 0: 31 | break 32 | else: 33 | List.append(pic_url) 34 | t = t + 60 35 | return s 36 | 37 | 38 | def recommend(url): 39 | Re = [] 40 | try: 41 | html = requests.get(url) 42 | except error.HTTPError as e: 43 | return 44 | else: 45 | html.encoding = 'utf-8' 46 | bsObj = BeautifulSoup(html.text, 'html.parser') 47 | div = bsObj.find('div', id='topRS') 48 | if div is not None: 49 | listA = div.findAll('a') 50 | for i in listA: 51 | if i is not None: 52 | Re.append(i.get_text()) 53 | return Re 54 | 55 | 56 | def dowmloadPicture(html, keyword): 57 | global num 58 | # t =0 59 | pic_url = re.findall('"objURL":"(.*?)",', html, re.S) # 先利用正则表达式找到图片url 60 | print('找到关键词:' + keyword + '的图片,即将开始下载图片...') 61 | for each in pic_url: 62 | print('正在下载第' + str(num + 1) + '张图片,图片地址:' + str(each)) 63 | try: 64 | if each is not None: 65 | pic = requests.get(each, timeout=7) 66 | else: 67 | continue 68 | except BaseException: 69 | print('错误,当前图片无法下载') 70 | continue 71 | else: 72 | string ='./' +file + '/' + keyword + '_' + str(num) + '.jpg' 73 | fp = open(string, 'wb') 74 | fp.write(pic.content) 75 | fp.close() 76 | num += 1 77 | if num >= numPicture: 78 | return 79 | 80 | 81 | if __name__ == '__main__': # 主函数入口 82 | tm = int(input('请输入每类图片的下载数量 ')) 83 | numPicture = tm 84 | line_list = [] 85 | with open('./name.txt', encoding='utf-8') as file: 86 | line_list = [k.strip() for k in file.readlines()] # 用 strip()移除末尾的空格 87 | 88 | for word in line_list: 89 | url = 'http://image.baidu.com/search/flip?tn=baiduimage&ie=utf-8&word=' + word + '&pn=' 90 | tot = Find(url) 91 | Recommend = recommend(url) # 记录相关推荐 92 | print('经过检测%s类图片共有%d张' % (word, tot)) 93 | file = word + '文件' 94 | y = os.path.exists(file) 95 | if y == 1: 96 | print('该文件已存在,请重新输入') 97 | file = word+'文件夹2' 98 | os.mkdir(file) 99 | else: 100 | os.mkdir(file) 101 | t = 0 102 | tmp = url 103 | while t < numPicture: 104 | try: 105 | url = tmp + str(t) 106 | result = requests.get(url, timeout=10) 107 | print(url) 108 | except error.HTTPError as e: 109 | print('网络错误,请调整网络后重试') 110 | t = t + 60 111 | else: 112 | dowmloadPicture(result.text, word) 113 | t = t + 60 114 | numPicture = numPicture + tm 115 | 116 | print('当前搜索结束,感谢使用') -------------------------------------------------------------------------------- /query_online.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Author: AI算法与图像处理 3 | from extract_cnn_vgg16_keras import VGGNet 4 | 5 | import numpy as np 6 | import h5py 7 | from matplotlib import pyplot as plt 8 | #import matplotlib.pyplot as plt 9 | import matplotlib.image as mpimg 10 | import argparse 11 | 12 | 13 | ap = argparse.ArgumentParser() 14 | #ap.add_argument("-query", required = False, default='TestImages/0.png', 15 | # help = "Path to query which contains image to be queried") 16 | # ap.add_argument("-index", required = False,default='LACEfeatureCNN.h5', 17 | # help = "Path to index") 18 | # ap.add_argument("-result", required = False,default='lace', 19 | # help = "Path for output retrieved images") 20 | # 总数据 21 | ap.add_argument("-index", required = False,default='lol.h5', 22 | help = "Path to index") 23 | ap.add_argument("-result", required = False,default='img', 24 | help = "Path for output retrieved images") 25 | 26 | args = vars(ap.parse_args()) 27 | 28 | 29 | # read in indexed images' feature vectors and corresponding image names 30 | h5f = h5py.File(args["index"],'r') 31 | # feats = h5f['dataset_1'][:] 32 | feats = h5f['dataset_1'][:] 33 | print(feats) 34 | imgNames = h5f['dataset_2'][:] 35 | print(imgNames) 36 | h5f.close() 37 | 38 | print("--------------------------------------------------") 39 | print(" searching starts") 40 | print("--------------------------------------------------") 41 | 42 | def url_is_correct(index_t): 43 | 44 | if index_t >5: 45 | print('超出请求次数!!!') 46 | exit() 47 | 48 | try: 49 | url = input('请输入正确的图片路径:') 50 | 51 | queryDir = url 52 | 53 | src = mpimg.imread(queryDir) 54 | return queryDir 55 | 56 | except: 57 | print('有误的图片路径,请重新输入:') 58 | return url_is_correct(index_t+1) 59 | 60 | 61 | while True: 62 | print('----------**********-------------') 63 | op = input("退出请输 exit,查询请输 enter : ") 64 | if op == 'exit': 65 | break 66 | else: 67 | # read and show query image 68 | # 设置多张图片共同显示 69 | figure,ax=plt.subplots(4,4) 70 | global index_t 71 | index_t = 1 72 | queryDir = url_is_correct(index_t) 73 | queryImg = mpimg.imread(queryDir) 74 | x=0 75 | ax[x][x].set_title('Test-Image',fontsize=10) 76 | ax[x][x].imshow(queryImg,cmap=plt.cm.gray) 77 | ax[x][x].axis('off') # 显示第一张测试图片 78 | #plt.title("Query Image") 79 | #plt.imshow(queryImg) 80 | #plt.show() 81 | 82 | # init VGGNet16 model 83 | model = VGGNet() 84 | 85 | # extract query image's feature, compute simlarity score and sort 86 | queryVec = model.extract_feat(queryDir) 87 | scores = np.dot(queryVec, feats.T) 88 | rank_ID = np.argsort(scores)[::-1] 89 | rank_score = scores[rank_ID] 90 | #print rank_ID 91 | #print(rank_score) 92 | 93 | 94 | # number of top retrieved images to show 95 | maxres = 15 96 | imlist = [imgNames[index] for i,index in enumerate(rank_ID[0:maxres])] 97 | print("top %d images in order are: " %maxres, imlist) 98 | 99 | # # show top #maxres retrieved result one by one 100 | # for i,im in enumerate(imlist): 101 | # image = mpimg.imread(args["result"]+"/"+str(im, 'utf-8')) 102 | # plt.title("search output %d" %(i+1)) 103 | # plt.imshow(image) 104 | # plt.show() 105 | 106 | # 显示多张图片 107 | 108 | for i,im in enumerate(imlist): 109 | image = mpimg.imread(args["result"]+"/"+str(im, 'utf-8')) 110 | im_name = str(im).split('\'')[1] 111 | ax[int((i+1)/4)][(i+1)%4].set_title('%d Image %s -- %.2f' % (i+1,im_name,rank_score[i]),fontsize=10) 112 | #ax[int(i/maxres)][i%maxres].set_title('Image_name is %s' % im,fontsize=2) 113 | ax[int((i+1)/4)][(i+1)%4].imshow(image,cmap=plt.cm.gray) 114 | ax[int((i+1)/4)][(i+1)%4].axis('off') 115 | plt.show() 116 | --------------------------------------------------------------------------------