アイコンごとに異なる店舗の類似アプリケーションをグループ化する

かつて私は不幸なことに目を一つの魅力的な欠員に向けました。 すべてはうまくいきますが、いつものように、彼らはテストタスクを投げました。 つまり、異なる市場の同じアプリケーションへのリンクをグループ化する必要がありました。 リンクには、Skype、Skype WiFi、Skype Qik、Viber、Skywardという同じ名前の2つのゲームなどのアプリケーションが含まれていました。 ストアには、Google Play、App Store、およびWindows Phone市場がありました。 タスクにはレーキの説明も含まれていました。彼らは、アプリケーションの名前、開発会社の名前などに添付する必要はなかったと彼らは言います。 「しかし、結局のところ、馬鹿げたアイコンによって、同一のアプリケーションが異なるプラットフォームで簡単に認識できます」と私は考え、詳細を見つけました。 しかし、それほど単純ではありません。



これは、ViberとSkypeの異なるストアでアイコンがどのように見えるかです。







悲しいかな、アイコンは色とサイズが異なります。 もちろん、アイコンをハッシュし、ハッシュを比較するという元のアイデアは引き続き役立ちますが、この場合はそうではありません。 最初は、ブラウザに表示されるアイコンを分析のために引き出して間違えましたが、それらは非常に小さいものです。 少し後に、掘り下げて、300〜350ピクセルのサイズが見つかりました。これにより、測定の精度が向上しました。 一般に、画像をドラッグするコードは非常に簡単です。



私のタスクでは、OpenCVライブラリをグーグルで検索しました。 これは、さまざまな画像分析のための非常に洗練されたツールです。 当初、私はあまりにも夢中になっていたので、機能のマッチングを学ぶことができましたが、これは必要なものではありません。 そして、画像の輪郭を強調して、どういうわけかそれらを比較する必要がありました。



輪郭を作成するには、画像を適切に準備する必要があります-境界線を強調します。 これを行うには、Canny境界検出器を使用します。 たぶん、キャニーは正しいでしょう、私は知りません。 次のように機能します。







Skypeアイコンの場合、次の結果が得られました。







サイズの違いだけが残っているように見えるかもしれませんが、そうではありません。 選択した境界線はわずかに異なり、アイコンを同じサイズにするとエラーが追加されるだけです。

唯一の秘trickは、アルゴリズムの最小および最大しきい値を正しく選択することです。 100と200の値は絶対に満足しました。



次に、輪郭を見つけます。 これらは、2つのループの一致係数を計算することで比較できます。これは、私のタスクで非常に役立つプロパティです。 ニュアンスがあります-輪郭の回転角度はこの係数に影響しませんが、私の場合はほとんど天候に影響しません。 googleのskypeの場合、輪郭を作成した結果は次のとおりです。







回路は2つではなく、4つあります。 輪郭は、以前に定義された境界の外側と内側から作成されます。 RETR_LISTフラグを使用して、つまり階層なしで輪郭を検索し、画像の左上からソートしました。



私のアルゴリズムでは、輪郭の全長も計算する必要があります-OpenCVには 、このために別の関数arcLengthがあります。 アルゴリズム自体は、2つの画像が輪郭の長さの80%を超える場合、これらの画像が1つのアプリケーションのアイコンであると見なすという事実に要約されます。 輪郭自体はmatchShapes関数によって比較され、その結果は小さくなります-私の場合、輪郭一致の上限は0.15でした。



ただし、このアルゴリズムを使用して比較できなかった2番目のタイプのアイコンがあります。これらはSkywardゲームのアイコンです。







執筆時点では、これらのアイコンの色は異なりますが、しばらく前に2つの店舗に最初の色のオプションがありました。 アイコンのサイズのみが異なりますが、このため、輪郭はまったく一致せず、アイコンから何も判別できませんでした。 ただし、ここではimagehashライブラリが役立ちました。 Skywardゲームでは、ハッシュが正面から比較されました。 ただし、アイコンの配色が変更された瞬間から、この機能は機能しません。



「雇用者」は私の考えに反応しませんでした。 それは起こります。



ソースコード
import numpy as np import cv2 import requests from collections import namedtuple from bs4 import BeautifulSoup import imagehash from PIL import Image def itunes_find(content): icon, name = None, None soup = BeautifulSoup(content) found = soup.find(id="title") name = found.div.h1.get_text() found = soup.find('img',{'class':'artwork', 'alt': name}) imageurl = found['src-swap-high-dpi'] icon_r = requests.get(imageurl) if icon_r.status_code == 200: img_array = np.asarray(bytearray(icon_r.content), dtype=np.uint8) icon = cv2.imdecode(img_array, cv2.IMREAD_COLOR) return name, icon def google_find(content): icon, name = None, None soup = BeautifulSoup(content) found = soup.find('div',{'class':'cover-container'}) imageurl = found('img')[0]['src'] icon_r = requests.get(imageurl) if icon_r.status_code == 200: img_array = np.asarray(bytearray(icon_r.content), dtype=np.uint8) icon = cv2.imdecode(img_array, cv2.IMREAD_COLOR) found = soup.find('div',{'class':'document-title'}) if not found: found = soup.find('h1',{'class':'document-title'}) if not found: with open('olala1.html', 'w') as f: f.write(content) name = found.get_text() return name, icon def windows_find(content): icon, name = None, None soup = BeautifulSoup(content) found = soup.find('img', {'class':'appImage xlarge'}) imageurl = found['src'] icon_r = requests.get(imageurl) if icon_r.status_code == 200: img_array = np.asarray(bytearray(icon_r.content), dtype=np.uint8) icon = cv2.imdecode(img_array, cv2.IMREAD_COLOR) found = soup.find(id="application") name = found('h1')[0].get_text() return name, icon class Entry: def __init__(self, url, name, icon): self.url = url self.name = name self.icon = icon self.icon_hash = None self.contours = None items = {} def _go(url): r = requests.get(url, headers = {'User-agent': 'Mozilla/5.0'}, verify=False) if r.status_code == 200: if url.startswith('https://itunes.apple.com'): name, icon = itunes_find(r.content) elif url.startswith('https://play.google.com'): name, icon = google_find(r.content) elif url.startswith('http://www.windowsphone.com'): name, icon = windows_find(r.content) if name and icon is not None: items[url] = Entry(url, name, icon) url_list = [ 'https://itunes.apple.com/en/app/skype-for-iphone/id304878510?mt=8', 'https://itunes.apple.com/en/app/skype-for-ipad/id442012681?mt=8', 'https://play.google.com/store/apps/details?id=com.skype.raider&hl=en', 'http://www.windowsphone.com/ru-ru/store/app/skype/c3f8e570-68b3-4d6a-bdbb-c0a3f4360a51', 'https://play.google.com/store/apps/details?id=com.skype.android.access&hl=en', 'https://itunes.apple.com/en/app/skype-wifi/id444529922?mt=8', 'https://play.google.com/store/apps/details?id=com.skype.android.qik&hl=en', 'https://itunes.apple.com/us/app/skype-qik-group-video-messaging/id893994044?mt=8', 'https://play.google.com/store/apps/details?id=com.viber.voip&hl=en', 'https://itunes.apple.com/en/app/viber/id382617920?mt=8', 'https://play.google.com/store/apps/details?id=com.viber.voip&hl=en', 'https://play.google.com/store/apps/details?id=com.ketchapp.skyward&hl=en', 'https://itunes.apple.com/us/app/skyward/id943273841?mt=8', 'https://play.google.com/store/apps/details?id=cz.george.mecheche&hl=en', ] tr = 100 def _do(): for u in url_list: _go(u) for item in items.itervalues(): width = item.icon.shape[0] height = item.icon.shape[1] icon_c = cv2.cvtColor(item.icon, cv2.COLOR_BGR2RGB) pil_im = Image.fromarray(icon_c) item.icon_hash = imagehash.dhash(pil_im) edges = cv2.Canny(item.icon, tr, tr*2) def _s(x): x,y,w,h = cv2.boundingRect(x) return (x, y) contours, hierarchy = cv2.findContours(edges, cv2.RETR_LIST, 1) contours = sorted(contours, key = _s) item.contours = contours item.weight = sum([cv2.arcLength(cnt,True) for cnt in contours]) matches = [] ungrouped = [] items_copy = items.values() while items_copy: group = [] item = items_copy[0] current = items_copy[1:] items_copy = [] for other in current: if item.icon_hash == other.icon_hash: group.append(other.url) else: rating = 0 count = min(len(item.contours), len(other.contours)) for v in range(count): result = cv2.matchShapes(item.contours[v], other.contours[v], 1, 0.0) if result < 0.15: l = cv2.arcLength(item.contours[v],True) lo = cv2.arcLength(other.contours[v],True) rating += min(l/item.weight, lo/other.weight) if rating > 0.8: group.append(other.url) else: items_copy.append(other) if group: group.append(item.url) matches.append(group) else: ungrouped.append(item.url) for v in matches: print 'Found group: %s'%', '.join(set([items[u].name.strip() for u in v])) print 'Urls:\n%s\n'%'\n'.join(v) print "Ungrouped:" for v in ungrouped: print 'Name %s'%items[v].name print 'Url %s'%v _do()
      
      








All Articles