Beim Erstellen eines Datensatzes für maschinelles Lernen auf der Grundlage von aus dem Internet gesammelten Bildern müssen doppelte Bilder entfernt werden. Es ist immer noch gut, wenn die Trainingsdaten doppelte Bilder enthalten, aber wenn zwischen den Trainingsdaten und den Testdaten doppelte Bilder vorhanden sind, tritt eine sogenannte Leckage auf.
Der einfachste Weg, doppelte Bilder zu erkennen, besteht darin, den Hash-Wert einer Datei wie MD5 zu verwenden. Der Hash-Wert der Datei ist jedoch nur ein Hash der Binärzeichenfolge der Bilddatei, und selbst wenn dasselbe Bild durch Ändern des Speicherformats oder der Komprimierungsparameter geändert wird, wird die Erkennung nicht erkannt.
Daher werden wir in diesem Artikel Algorithmen vorstellen, die die Merkmale von Bildern selbst hashen, und wir werden die Eigenschaften dieser Hashing-Algorithmen durch einfache Experimente untersuchen.
Average Hash (aHash) Es ist ein Hashwert, der auf den Eigenschaften des Bildes basiert (Helligkeitsmuster) und mit einem einfachen Algorithmus berechnet werden kann. Das spezifische Verfahren ist wie folgt.
aHash bietet die Vorteile einfacher Algorithmen und schneller Berechnungen. Andererseits hat es auch den Nachteil, unflexibel zu sein. Beispielsweise ist der Hash-Wert eines gammakorrigierten Bildes weit vom Originalbild entfernt.
Perseptual Hash (pHash) Während aHash die Pixelwerte selbst verwendet, verwendet pHash die diskrete Cosinustransformation (DCT) des Bildes. DCT ist eine der Methoden zum Konvertieren von Signalen wie Bildern in den Frequenzbereich und wird beispielsweise für die JPEG-Komprimierung verwendet. Bei der JPEG-Komprimierung wird die Datenmenge durch DCT des Bildes reduziert und nur die Niederfrequenzkomponenten extrahiert, die vom Menschen leicht wahrgenommen werden können.
Ähnlich wie bei der JPGE-Komprimierung konzentriert sich pHash auf die Niederfrequenzkomponenten in der DCT des Bildes und hascht sie. Auf diese Weise ist es möglich, Merkmale, die vom Menschen leicht wahrgenommen werden, bevorzugt zu extrahieren, und es wird angenommen, dass ein robustes Hashing für die parallele Bewegung von Bildern und Änderungen der Helligkeit durchgeführt werden kann.
Es scheint verschiedene Variationen außer aHash und pHash zu geben. Einige Leute machen das Benchmarking [^ 1].
Wenden Sie verschiedene Verarbeitungen auf das Bild an und vergleichen Sie den Hashwert mit dem Originalbild. Berechnen Sie aHash und pHash als Hash-Werte.
Probieren Sie auch die Technik aus, die Ausgabe als Hash für die Ebene unmittelbar vor der letzten Ebene von ResNet50 zu betrachten. Diese Methode wurde in einer Arbeit [^ 2] [^ 3] übernommen.
OpenCV wird zur Berechnung von aHash und pHash verwendet, es gibt jedoch auch eine Bibliothek namens ImageHash. Außerdem können Sie in aHash und pHash den Brummabstand verwenden, um Hash-Werte zu vergleichen. Der Wertebereich ist "[0, 64]". Um diesem Bereich zu entsprechen, wird im Hash- (Schein-) Vergleich mit ResNet50 die Kosinusähnlichkeit berechnet und dann in den obigen Bereich konvertiert.
import copy
import pprint
import cv2.cv2 as cv2
import numpy as np
from keras import models
from keras.applications.resnet50 import ResNet50, preprocess_input
from sklearn.metrics.pairwise import cosine_similarity
class ImagePairGenerator(object):
"""
Eine Klasse, die experimentelle Bildpaare generiert
"""
def __init__(self, img: np.ndarray):
self._img = img
self._processings = self._prepare_processings()
def _prepare_processings(self):
h, w, _ = self._img.shape
#Position und Größe zum Zuschneiden um das Gesicht des Lenna-Bildes
org = np.array([128, 128])
size = np.array([256, 256])
# kind (processing description), img1, img2
processings = [
('Gleich',
lambda x: x,
lambda x: x),
('Graustufen',
lambda x: x,
lambda x: cv2.cvtColor(
cv2.cvtColor(x, cv2.COLOR_BGR2GRAY), cv2.COLOR_GRAY2BGR)),
*list(map(lambda s:
(f'1/{s:2}Schrumpfen auf',
lambda x: x,
lambda x: cv2.resize(x, (w // s, h // s))),
np.power(2, range(1, 5)))),
*list(map(lambda s:
(f'Glätten(kernel size = {s:2}',
lambda x: x,
lambda x: cv2.blur(x, (s, s))),
[3, 5, 7, 9, 11])),
*list(map(lambda s:
(f'Text einfügen(fontScale = {s})',
lambda x: x,
lambda x: cv2.putText(x, 'Text', org=(10, 30*s),
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=s,
color=(255, 255, 255),
thickness=3*s,
lineType=cv2.LINE_AA)),
range(1, 8))),
*list(map(lambda q:
(f'JPEG-Komprimierung(quality = {q})',
lambda x: x,
lambda x: img_encode_decode(x, q)),
range(10, 100, 10))),
*list(map(lambda gamma:
(f'Gamma-Korrektur(gamma = {gamma})',
lambda x: x,
lambda x: img_gamma(x, gamma)),
[0.2, 0.5, 0.8, 1.2, 1.5, 2.0])),
*list(map(lambda d:
(f'Parallelbewegung({d:2} pixels)',
lambda x: img_crop(x, org, size),
lambda x: img_crop(x, org + d, size)),
np.power(2, range(7)))),
]
return processings
def __iter__(self):
for kind, p1, p2 in self._processings:
yield (kind,
p1(copy.deepcopy(self._img)),
p2(copy.deepcopy(self._img)))
class ResNet50Hasher(object):
"""
Klasse zur Ausgabe der letzten Schicht von ResNet50 als Hashwert
"""
_input_size = 224
def __init__(self):
self._model = self._prepare_model()
def _prepare_model(self):
resnet50 = ResNet50(include_top=False, weights='imagenet',
input_shape=(self._input_size, self._input_size, 3),
pooling='avg')
model = models.Sequential()
model.add(resnet50)
return model
def compute(self, img: np.ndarray) -> np.ndarray:
img_arr = np.array([
cv2.resize(img, (self._input_size, self._input_size))
])
img_arr = preprocess_input(img_arr)
embeddings = self._model.predict(img_arr)
return embeddings
@staticmethod
def compare(x1: np.ndarray, x2: np.ndarray):
"""
Berechnen Sie die Kosinusähnlichkeit. Der Wertebereich ist[0, 1]。
Vergleichen Sie aHash und pHash entsprechend der Brummstrecke.
[0, 64]In den Wertebereich von konvertieren.
"""
cs = cosine_similarity(x1, x2)
distance = 64 + (0 - 64) * ((cs - 0) / (1 - 0))
return distance.ravel()[0] # np.array -> float
def img_crop(img: np.ndarray, org: np.ndarray, size: np.ndarray) -> np.ndarray:
"""
Beschneiden Sie einen beliebigen Bereich des Bildes.
"""
y, x = org
h, w = size
return img[y:y + h, x:x + w, :]
def img_encode_decode(img: np.ndarray, quality=90) -> np.ndarray:
"""
Reproduzieren Sie die Verschlechterung der JPEG-Komprimierung.
Referenz: https://qiita.com/ka10ryu1/items/5fed6b4c8f29163d0d65
"""
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
_, enc_img = cv2.imencode('.jpg', img, encode_param)
dec_img = cv2.imdecode(enc_img, cv2.IMREAD_COLOR)
return dec_img
def img_gamma(img: np.ndarray, gamma=0.5) -> np.ndarray:
"""
Gamma-Korrektur.
Referenz: https://www.dogrow.net/python/blog99/
"""
lut = np.empty((1, 256), np.uint8)
for i in range(256):
lut[0, i] = np.clip(pow(i / 255.0, gamma) * 255.0, 0, 255)
return cv2.LUT(img, lut)
def image_hashing_test():
image_path = 'resources/lena_std.tif'
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
h, w, _ = img.shape
hashers = [
('aHash', cv2.img_hash.AverageHash_create()),
('pHash', cv2.img_hash.PHash_create()),
('ResNet', ResNet50Hasher())
]
pairs = ImagePairGenerator(img)
result_dict = {}
for pair_kind, img1, img2 in pairs:
result_dict[pair_kind] = {}
for hasher_kind, hasher in hashers:
hash1 = hasher.compute(img1)
hash2 = hasher.compute(img2)
distance = hasher.compare(hash1, hash2)
result_dict[pair_kind][hasher_kind] = distance
#Überprüfen Sie das Bild visuell (nur wenn die Form identisch ist, da die Ausrichtung schwierig ist).
if img1.shape == img2.shape:
window_name = pair_kind
cv2.imshow(window_name, cv2.hconcat((img1, img2)))
cv2.waitKey()
cv2.destroyWindow(window_name)
pprint.pprint(result_dict)
if __name__ == '__main__':
image_hashing_test()
{'Gleich': {'ResNet': 0.0, 'aHash': 0.0, 'pHash': 0.0},
'Graustufen': {'ResNet': 14.379967, 'aHash': 0.0, 'pHash': 0.0},
'1/Reduziert auf 2': {'ResNet': 1.2773285, 'aHash': 3.0, 'pHash': 1.0},
'1/Reduziert auf 4': {'ResNet': 6.5748253, 'aHash': 4.0, 'pHash': 1.0},
'1/Reduziert auf 8': {'ResNet': 18.959282, 'aHash': 7.0, 'pHash': 3.0},
'1/Reduziert auf 16': {'ResNet': 34.8299, 'aHash': 12.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 10)': {'ResNet': 6.4169083, 'aHash': 2.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 20)': {'ResNet': 2.6065674, 'aHash': 1.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 30)': {'ResNet': 1.8446579, 'aHash': 0.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 40)': {'ResNet': 1.2492218, 'aHash': 0.0, 'pHash': 1.0},
'JPEG-Komprimierung(quality = 50)': {'ResNet': 1.0534592, 'aHash': 0.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 60)': {'ResNet': 0.99293137, 'aHash': 0.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 70)': {'ResNet': 0.7313309, 'aHash': 0.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 80)': {'ResNet': 0.58068085, 'aHash': 0.0, 'pHash': 0.0},
'JPEG-Komprimierung(quality = 90)': {'ResNet': 0.354187, 'aHash': 0.0, 'pHash': 0.0},
'Gamma-Korrektur(gamma = 0.2)': {'ResNet': 16.319721, 'aHash': 2.0, 'pHash': 1.0},
'Gamma-Korrektur(gamma = 0.5)': {'ResNet': 4.2003975, 'aHash': 2.0, 'pHash': 0.0},
'Gamma-Korrektur(gamma = 0.8)': {'ResNet': 0.48334503, 'aHash': 0.0, 'pHash': 0.0},
'Gamma-Korrektur(gamma = 1.2)': {'ResNet': 0.381176, 'aHash': 0.0, 'pHash': 1.0},
'Gamma-Korrektur(gamma = 1.5)': {'ResNet': 1.7187691, 'aHash': 2.0, 'pHash': 1.0},
'Gamma-Korrektur(gamma = 2.0)': {'ResNet': 4.074257, 'aHash': 6.0, 'pHash': 2.0},
'Text einfügen(fontScale = 1)': {'ResNet': 0.7838249, 'aHash': 0.0, 'pHash': 0.0},
'Text einfügen(fontScale = 2)': {'ResNet': 1.0911484, 'aHash': 0.0, 'pHash': 1.0},
'Text einfügen(fontScale = 3)': {'ResNet': 2.7721176, 'aHash': 0.0, 'pHash': 2.0},
'Text einfügen(fontScale = 4)': {'ResNet': 4.646305, 'aHash': 0.0, 'pHash': 4.0},
'Text einfügen(fontScale = 5)': {'ResNet': 8.435852, 'aHash': 2.0, 'pHash': 3.0},
'Text einfügen(fontScale = 6)': {'ResNet': 11.267036, 'aHash': 6.0, 'pHash': 3.0},
'Text einfügen(fontScale = 7)': {'ResNet': 15.272251, 'aHash': 2.0, 'pHash': 7.0},
'Glätten(kernel size = 3': {'ResNet': 1.3798943, 'aHash': 2.0, 'pHash': 0.0},
'Glätten(kernel size = 5': {'ResNet': 3.1528091, 'aHash': 4.0, 'pHash': 1.0},
'Glätten(kernel size = 7': {'ResNet': 4.903698, 'aHash': 4.0, 'pHash': 1.0},
'Glätten(kernel size = 9': {'ResNet': 6.8400574, 'aHash': 4.0, 'pHash': 1.0},
'Glätten(kernel size = 11': {'ResNet': 9.477722, 'aHash': 5.0, 'pHash': 2.0},
'Parallelbewegung( 1 pixels)': {'ResNet': 0.47764206, 'aHash': 6.0, 'pHash': 0.0},
'Parallelbewegung( 2 pixels)': {'ResNet': 0.98942566, 'aHash': 10.0, 'pHash': 3.0},
'Parallelbewegung( 4 pixels)': {'ResNet': 1.475399, 'aHash': 15.0, 'pHash': 5.0},
'Parallelbewegung( 8 pixels)': {'ResNet': 2.587471, 'aHash': 20.0, 'pHash': 13.0},
'Parallelbewegung(16 pixels)': {'ResNet': 3.1883087, 'aHash': 25.0, 'pHash': 21.0},
'Parallelbewegung(32 pixels)': {'ResNet': 4.8445663, 'aHash': 23.0, 'pHash': 31.0},
'Parallelbewegung(64 pixels)': {'ResNet': 9.34531, 'aHash': 28.0, 'pHash': 30.0}}
Bitte beachten Sie, dass ResNet das verwendet, was mit ImageNet vorab trainiert wurde. Mit anderen Worten, durch Aufbereiten von Trainingsdaten ist es möglich, ein Netzwerk mit Eigenschaften zu erhalten, die sich von den nachstehend beschriebenen unterscheiden.
Außerdem kann sich die Tendenz je nach Bild ändern. Für den praktischen Gebrauch ist es besser, mit einem anständigen Datensatz auszuwerten.
--Same: Offensichtlich ist der Abstand für alle Hashing-Methoden 0.
Ich habe eine Methode zum Hashing von Bildern eingeführt. Zusätzlich haben wir den Abstand zwischen dem verarbeiteten Bild und dem Originalbild gemessen und die Eigenschaften jeder Hashing-Methode beobachtet. Unter den in diesem Artikel verglichenen Algorithmen können wir beobachten, dass pHash gegenüber Bildskalierung, Komprimierungsverschlechterung und Glättung tendenziell robust ist.
Ich habe kürzlich etwas über xHash-basierte Algorithmen gelernt und denke, dass sie einfach sind und auf guten Ideen basieren. Ich denke, es ist ein relativ toter Algorithmus, aber ich hoffe, er kann an der richtigen Stelle verwendet werden.
--Versuchen Sie den Bildvergleich mit Perceptual Hash (Phash) http://hideack.hatenablog.com/entry/2015/03/16/194336
[^ 3]: Um die Wahrheit zu sagen, als ich diesen Artikel las, dachte ich: "Gibt es eine einfachere Möglichkeit, Duplikate zu identifizieren?", Was der Grund für das Schreiben dieses Artikels war.
Recommended Posts