├── .gitignore ├── README.md ├── app ├── app.py ├── index.csv ├── pyimagesearch │ ├── __init__.py │ ├── __init__.pyc │ ├── colordescriptor.py │ ├── colordescriptor.pyc │ ├── searcher.py │ └── searcher.pyc ├── static │ ├── main.css │ ├── main.js │ └── queries │ │ ├── .DS_Store │ │ ├── 103100.png │ │ ├── 103300.png │ │ ├── 108100.png │ │ ├── 115100.png │ │ ├── 123600.png │ │ └── 127502.png └── templates │ ├── _base.html │ └── index.html ├── config ├── app.ini ├── nginx.conf └── supervisor.conf └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | app/pyimagesearch/*.pyc 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flask_image_search 2 | 3 | 这是一个练习项目。 4 | 5 | 代码参考 6 | 7 | Adding a web interface to our image search engine with Flask 8 | http://www.pyimagesearch.com/2014/12/08/adding-web-interface-image-search-engine-flask/ 9 | 10 | The complete guide to building an image search engine with Python and OpenCV 11 | http://www.pyimagesearch.com/2014/12/01/complete-guide-building-image-search-engine-python-opencv/ 12 | 13 | # 使用方法 14 | 15 | ``` 16 | cd flask_image_search 17 | python app/app.py 18 | ``` 19 | -------------------------------------------------------------------------------- /app/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import sys 4 | 5 | from flask import Flask, render_template, request, jsonify 6 | 7 | from pyimagesearch.colordescriptor import ColorDescriptor 8 | from pyimagesearch.searcher import Searcher 9 | 10 | # create flask instance 11 | app = Flask(__name__) 12 | 13 | INDEX = os.path.join(os.path.dirname(__file__), 'index.csv') 14 | 15 | 16 | # main route 17 | @app.route('/') 18 | def index(): 19 | return render_template('index.html') 20 | 21 | # search route 22 | @app.route('/search', methods=['POST']) 23 | def search(): 24 | 25 | if request.method == "POST": 26 | 27 | RESULTS_ARRAY = [] 28 | 29 | # get url 30 | image_url = request.form.get('img') 31 | 32 | 33 | try: 34 | 35 | # initialize the image descriptor 36 | cd = ColorDescriptor((8, 12, 3)) 37 | 38 | # load the query image and describe it 39 | from skimage import io 40 | import cv2 41 | # query = io.imread(image_url) 42 | # query = (query * 255).astype("uint8") 43 | # (r, g, b) = cv2.split(query) 44 | # query = cv2.merge([b, g, r]) 45 | 46 | image_url = "app/" + image_url[1:] 47 | # print "图像url路径:", image_url 48 | # print os.getcwd() 49 | # print sys.path[0] 50 | query = cv2.imread(image_url) 51 | # print "读取成功!" 52 | features = cd.describe(query) 53 | # print "描述子生成成功" 54 | 55 | # perform the search 56 | searcher = Searcher(INDEX) 57 | results = searcher.search(features) 58 | 59 | # loop over the results, displaying the score and image name 60 | for (score, resultID) in results: 61 | RESULTS_ARRAY.append( 62 | {"image": str(resultID), "score": str(score)}) 63 | 64 | # return success 65 | return jsonify(results=(RESULTS_ARRAY[:5])) 66 | 67 | except: 68 | 69 | # return error 70 | jsonify({"sorry": "Sorry, no results! Please try again."}), 500 71 | 72 | 73 | # run! 74 | if __name__ == '__main__': 75 | app.run('127.0.0.1', debug=True) 76 | -------------------------------------------------------------------------------- /app/pyimagesearch/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/pyimagesearch/__init__.py -------------------------------------------------------------------------------- /app/pyimagesearch/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/pyimagesearch/__init__.pyc -------------------------------------------------------------------------------- /app/pyimagesearch/colordescriptor.py: -------------------------------------------------------------------------------- 1 | # import the necessary packages 2 | import numpy as np 3 | import cv2 4 | 5 | 6 | class ColorDescriptor: 7 | def __init__(self, bins): 8 | # store the number of bins for the 3D histogram 9 | self.bins = bins 10 | 11 | def describe(self, image): 12 | # convert the image to the HSV color space and initialize 13 | # the features used to quantify the image 14 | image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) 15 | features = [] 16 | 17 | # grab the dimensions and compute the center of the image 18 | (h, w) = image.shape[:2] 19 | (cX, cY) = (int(w * 0.5), int(h * 0.5)) 20 | 21 | # divide the image into four rectangles/segments (top-left, 22 | # top-right, bottom-right, bottom-left) 23 | segments = [(0, cX, 0, cY), (cX, w, 0, cY), 24 | (cX, w, cY, h), (0, cX, cY, h)] 25 | 26 | # construct an elliptical mask representing the center of the 27 | # image 28 | (axesX, axesY) = (int(w * 0.75) / 2, int(h * 0.75) / 2) 29 | ellipMask = np.zeros(image.shape[:2], dtype="uint8") 30 | cv2.ellipse(ellipMask, (cX, cY), (axesX, axesY), 0, 0, 360, 255, -1) 31 | 32 | # loop over the segments 33 | for (startX, endX, startY, endY) in segments: 34 | # construct a mask for each corner of the image, subtracting 35 | # the elliptical center from it 36 | cornerMask = np.zeros(image.shape[:2], dtype="uint8") 37 | cv2.rectangle(cornerMask, (startX, startY), (endX, endY), 255, -1) 38 | cornerMask = cv2.subtract(cornerMask, ellipMask) 39 | 40 | # extract a color histogram from the image, then update the 41 | # feature vector 42 | hist = self.histogram(image, cornerMask) 43 | features.extend(hist) 44 | 45 | # extract a color histogram from the elliptical region and 46 | # update the feature vector 47 | hist = self.histogram(image, ellipMask) 48 | features.extend(hist) 49 | 50 | # return the feature vector 51 | return features 52 | 53 | def histogram(self, image, mask): 54 | # extract a 3D color histogram from the masked region of the 55 | # image, using the supplied number of bins per channel; then 56 | # normalize the histogram 57 | hist = cv2.calcHist([image], [0, 1, 2], mask, self.bins, 58 | [0, 180, 0, 256, 0, 256]) 59 | 60 | cv2.normalize(hist,hist) 61 | hist = hist.flatten() # flatten the matrix 62 | 63 | # return the histogram 64 | return hist 65 | -------------------------------------------------------------------------------- /app/pyimagesearch/colordescriptor.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/pyimagesearch/colordescriptor.pyc -------------------------------------------------------------------------------- /app/pyimagesearch/searcher.py: -------------------------------------------------------------------------------- 1 | # import the necessary packages 2 | import numpy as np 3 | import csv 4 | 5 | class Searcher: 6 | def __init__(self, indexPath): 7 | # store our index path 8 | self.indexPath = indexPath 9 | 10 | def search(self, queryFeatures, limit = 10): 11 | # initialize our dictionary of results 12 | results = {} 13 | 14 | # open the index file for reading 15 | with open(self.indexPath) as f: 16 | # initialize the CSV reader 17 | reader = csv.reader(f) 18 | 19 | # loop over the rows in the index 20 | for row in reader: 21 | # parse out the image ID and features, then compute the 22 | # chi-squared distance between the features in our index 23 | # and our query features 24 | features = [float(x) for x in row[1:]] 25 | d = self.chi2_distance(features, queryFeatures) 26 | 27 | # now that we have the distance between the two feature 28 | # vectors, we can udpate the results dictionary -- the 29 | # key is the current image ID in the index and the 30 | # value is the distance we just computed, representing 31 | # how 'similar' the image in the index is to our query 32 | results[row[0]] = d 33 | 34 | # close the reader 35 | f.close() 36 | 37 | # sort our results, so that the smaller distances (i.e. the 38 | # more relevant images are at the front of the list) 39 | results = sorted([(v, k) for (k, v) in results.items()]) 40 | 41 | # return our (limited) results 42 | return results[:limit] 43 | 44 | def chi2_distance(self, histA, histB, eps = 1e-10): 45 | # compute the chi-squared distance 46 | d = 0.5 * np.sum([((a - b) ** 2) / (a + b + eps) 47 | for (a, b) in zip(histA, histB)]) 48 | 49 | # return the chi-squared distance 50 | return d -------------------------------------------------------------------------------- /app/pyimagesearch/searcher.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/pyimagesearch/searcher.pyc -------------------------------------------------------------------------------- /app/static/main.css: -------------------------------------------------------------------------------- 1 | /* custom styles */ 2 | 3 | .center-container { 4 | text-align: center; 5 | padding-top: 20px; 6 | padding-bottom: 20px; 7 | } 8 | 9 | .active { 10 | border: 5px solid red; 11 | } 12 | 13 | 14 | .result-img { 15 | max-width: 100px; 16 | max-height: 100px; 17 | } -------------------------------------------------------------------------------- /app/static/main.js: -------------------------------------------------------------------------------- 1 | // ----- custom js ----- // 2 | 3 | // hide initial 4 | $("#searching").hide(); 5 | $("#results-table").hide(); 6 | $("#error").hide(); 7 | 8 | // global 9 | var url = 'http://static.pyimagesearch.com.s3-us-west-2.amazonaws.com/vacation-photos/dataset/'; 10 | var data = []; 11 | 12 | $(function() { 13 | 14 | // sanity check 15 | console.log( "ready!" ); 16 | 17 | // image click 18 | $(".img").click(function() { 19 | 20 | // empty/hide results 21 | $("#results").empty(); 22 | $("#results-table").hide(); 23 | $("#error").hide(); 24 | 25 | // add active class to clicked picture 26 | $(this).addClass("active") 27 | 28 | // grab image url 29 | var image = $(this).attr("src") 30 | console.log(image) 31 | 32 | // show searching text 33 | $("#searching").show(); 34 | console.log("searching...") 35 | 36 | // ajax request 37 | $.ajax({ 38 | type: "POST", 39 | url: "/search", 40 | data : { img : image }, 41 | // handle success 42 | success: function(result) { 43 | console.log(result.results); 44 | var data = result.results 45 | 46 | // loop through results, append to dom 47 | for (i = 0; i < data.length; i++) { 48 | $("#results").append(''+data[i]['score']+'') 50 | }; 51 | // show table 52 | $("#results-table").show(); 53 | $("#searching").hide(); 54 | // remove active class 55 | $(".img").removeClass("active") 56 | }, 57 | // handle error 58 | error: function(error) { 59 | console.log(error); 60 | // append to dom 61 | $("#error").append() 62 | } 63 | }); 64 | 65 | }); 66 | 67 | }); -------------------------------------------------------------------------------- /app/static/queries/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/static/queries/.DS_Store -------------------------------------------------------------------------------- /app/static/queries/103100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/static/queries/103100.png -------------------------------------------------------------------------------- /app/static/queries/103300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/static/queries/103300.png -------------------------------------------------------------------------------- /app/static/queries/108100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/static/queries/108100.png -------------------------------------------------------------------------------- /app/static/queries/115100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/static/queries/115100.png -------------------------------------------------------------------------------- /app/static/queries/123600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/static/queries/123600.png -------------------------------------------------------------------------------- /app/static/queries/127502.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BLKStone/flask_image_search/daa2c8b3005dc3e70753585fb1f9f3e9fff5d0b6/app/static/queries/127502.png -------------------------------------------------------------------------------- /app/templates/_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Image Search 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | {% for message in get_flashed_messages() %} 28 | 32 | {% endfor %} 33 | 34 | 35 | {% block content %} 36 | {% endblock %} 37 | 38 | 39 | {% if error %} 40 |

Error: {{ error }}

41 | {% endif %} 42 | 43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "_base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 | 7 |
8 |

Select an Image

9 |
10 |
11 |
12 |

13 |

14 |
15 |
16 |

17 |

18 |
19 |
20 |
21 | 22 | 23 |
24 |

Results

25 |
26 |

Searching...

27 |

Oh no! No results! Check your internet connection.

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
ImageScore
38 |
39 | 40 |
41 | 42 | 43 | 44 |
45 | 46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /config/app.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | chdir = /app/ 3 | chmod-socket = 666 4 | master = true 5 | module = app:app 6 | processes = 4 7 | socket = /config/app.sock 8 | -------------------------------------------------------------------------------- /config/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream app { 2 | server unix:/config/app.sock; 3 | } 4 | 5 | server { 6 | listen 80 default_server; 7 | charset utf-8; 8 | 9 | location / { 10 | uwsgi_pass app; 11 | uwsgi_param CONTENT_LENGTH $content_length; 12 | uwsgi_param CONTENT_TYPE $content_type; 13 | uwsgi_param DOCUMENT_ROOT $document_root; 14 | uwsgi_param HTTPS $https if_not_empty; 15 | uwsgi_param PATH_INFO $document_uri; 16 | uwsgi_param QUERY_STRING $query_string; 17 | uwsgi_param REMOTE_ADDR $remote_addr; 18 | uwsgi_param REMOTE_PORT $remote_port; 19 | uwsgi_param REQUEST_METHOD $request_method; 20 | uwsgi_param REQUEST_URI $request_uri; 21 | uwsgi_param SERVER_PORT $server_port; 22 | uwsgi_param SERVER_NAME $server_name; 23 | uwsgi_param SERVER_PROTOCOL $server_protocol; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /config/supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:app] 2 | command = uwsgi --ini /config/app.ini 3 | 4 | [program:nginx] 5 | command = service nginx restart 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | adium-theme-ubuntu==0.3.4 2 | apt-xapian-index==0.45 3 | backports.ssl-match-hostname==3.4.0.2 4 | BeautifulSoup==3.2.1 5 | beautifulsoup4==4.2.1 6 | bitarray==0.8.1 7 | bootstrap-admin==0.3.6 8 | capstone==3.0.4 9 | certifi==2015.4.28 10 | chardet==2.0.1 11 | colorama==0.2.5 12 | command-not-found==0.3 13 | compizconfig-python==0.9.11.3 14 | cssselect==0.9.1 15 | debtagshw==0.1 16 | decorator==4.0.2 17 | defer==1.0.6 18 | defusedxml==0.4.1 19 | dirspec==13.10 20 | Django==1.8.2 21 | django-bootstrap-toolkit==2.15.0 22 | dnspython==1.11.1 23 | docutils==0.12 24 | douban.fm==0.4.3 25 | duplicity==0.6.23 26 | Flask==0.10.1 27 | funcsigs==0.4 28 | functools32==3.2.3.post2 29 | gunicorn==19.4.5 30 | gyp==0.1 31 | html5lib==0.999 32 | httplib2==0.8 33 | ipykernel==4.0.3 34 | ipython==4.0.0 35 | ipython-genutils==0.1.0 36 | ipywidgets==4.0.2 37 | itsdangerous==0.24 38 | jieba==0.37 39 | Jinja2==2.8 40 | jsonschema==2.5.1 41 | jupyter==1.0.0 42 | jupyter-client==4.0.0 43 | jupyter-console==4.0.2 44 | jupyter-core==4.0.4 45 | lockfile==0.8 46 | lxml==3.3.3 47 | Markdown==2.6.2 48 | MarkupSafe==0.23 49 | matplotlib==1.4.3 50 | mistune==0.7.1 51 | mock==1.2.0 52 | mysql-connector-python==1.1.6 53 | MySQL-python==1.2.5 54 | mysql-utilities==1.3.5 55 | nbconvert==4.0.0 56 | nbformat==4.0.0 57 | networkx==1.10 58 | nose==1.3.7 59 | notebook==4.0.4 60 | numpy==1.9.2 61 | oauthlib==0.6.1 62 | oneconf==0.3.7.14.4.1 63 | PAM==0.4.2 64 | pandas==0.16.2 65 | paramiko==1.10.1 66 | path.py==7.6.1 67 | pbr==1.3.0 68 | pefile==1.2.9.1 69 | pexpect==3.3 70 | pickleshare==0.5 71 | Pillow==2.3.0 72 | piston-mini-client==0.7.5 73 | ply==3.6 74 | psutil==1.2.1 75 | psycopg2==2.6.1 76 | ptyprocess==0.5 77 | pyasn1==0.1.8 78 | PyAutoGUI==0.9.31 79 | pybloom==1.1 80 | pycrypto==2.6.1 81 | pycups==1.9.66 82 | pycurl==7.19.3 83 | Pygments==2.0.2 84 | pygobject==3.12.0 85 | PyInstaller==3.0 86 | pymongo==3.0.3 87 | PyMsgBox==1.0.3 88 | pyOpenSSL==0.13 89 | pyparsing==2.0.3 90 | PyScreeze==0.1.7 91 | pyserial==2.6 92 | pysmbc==1.0.14.1 93 | pysqlite==2.6.3 94 | pytesseract==0.1.6 95 | python-apt===0.9.3.5ubuntu1 96 | python-dateutil==2.4.2 97 | python-debian===0.1.21-nmu2ubuntu2 98 | PyTweening==1.0.2 99 | pytz==2012rc0 100 | pywapi==0.3.6 101 | pyxdg==0.25 102 | pyzmq==14.7.0 103 | qtconsole==4.0.1 104 | queuelib==1.2.2 105 | reportlab==3.0 106 | requests==2.2.1 107 | rsa==3.1.4 108 | scikit-image==0.11.3 109 | scikit-learn==0.16.1 110 | scipy==0.16.0 111 | Scrapy==1.0.0 112 | selenium==2.47.1 113 | sessioninstaller==0.0.0 114 | shadowsocks==2.8.2 115 | simplegeneric==0.8.1 116 | six==1.9.0 117 | sklearn==0.0 118 | SOAPpy==0.12.22 119 | software-center-aptd-plugins==0.0.0 120 | ssh-import-id==3.21 121 | stevedore==1.7.0 122 | symmetricjsonrpc==0.1.0 123 | system-service==0.1.6 124 | termcolor==1.1.0 125 | terminado==0.5 126 | tldr==0.1.1 127 | tornado==4.2.1 128 | traitlets==4.0.0 129 | Twisted==15.2.1 130 | Twisted-Core==13.2.0 131 | Twisted-Web==13.2.0 132 | ubuntu-kylin-software-center==0.2.9 133 | Unidecode==0.4.14 134 | unity-china-photo-scope==1.0 135 | unity-lens-photos==1.0 136 | urllib3==1.7.1 137 | virtualenv==13.1.2 138 | virtualenv-clone==0.2.6 139 | virtualenvwrapper==4.7.0 140 | w3lib==1.11.0 141 | Werkzeug==0.15.3 142 | wheel==0.24.0 143 | wstools==0.4.3 144 | xdiagnose===3.6.3build2 145 | zope.interface==4.0.5 146 | --------------------------------------------------------------------------------