본문 바로가기

Machine Learning/영상처리

유사한 이미지 판별 그리고 Average Hash

형태 인식을 이용하여 이미지 데이터를 인식하고 이와 비슷한 이미지를 찾아낼 수 있다. 이에 관련해 간단한 방법이 있다 바로 average hash이다 


Average Hash

 

average hash는 이미지를 비교가능한 해시 값으로 나타낸 것이다, 해시 함수 MD5, SHA256등을 이용하면 데이터 값을 간단한 해시 값으로 변환가능하다 하지만 이러한 함수들은 같은 이미지를 찾는데 유용하지만 유사한 이미지를 찾는데 사용불가하다. 구체적인 방법으로는

 

  1. 이미지 크기를 8x8로 축소한다.
  2. 색을 그레이스케일로 변환한다.
  3. 이미지의 각 픽셀의 평균을 계산한다.
  4. 각 픽셀의 어두운 정도가 평균보다 크면 1, 평균보다 작으면 0으로 입력한다.

위와 같은 방식으로 이미지의 형태를 나타내는 64비트 해시 값을 구할 수 있다. 이 방법은 64비트를 비교하기만 되므로 빠르게 유사한 이미지를 검색할 수 있다.

 

실습코드

from PIL import Image
import numpy as np

# 이미지 데이터를 Average Hash로 변환하기 --- (※1)
def average_hash(fname, size = 16):
    img = Image.open(fname) # 이미지 데이터 열기---(※2)
    img = img.convert('L') # 그레이스케일로 변환하기 --- (※3)
    img = img.resize((size, size), Image.ANTIALIAS) # 리사이즈하기 --- (※4)
    pixel_data = img.getdata() # 픽셀 데이터 가져오기 --- (※5)
    pixels = np.array(pixel_data) # Numpy 배열로 변환하기 --- (※6)
    pixels = pixels.reshape((size, size)) # 2차원 배열로 변환하기 --- (※7)
    avg = pixels.mean() # 평균 구하기 --- (※8)
    diff = 1 * (pixels > avg) # 평균보다 크면 1, 작으면 0으로 변환하기 --- (※9)
    return diff
    
# 이진 해시로 변환하기 --- (※10)
def np2hash(ahash):
    bhash = []
    for nl in ahash.tolist():
        sl = [str(i) for i in nl]
        s2 = "".join(sl)
        i = int(s2, 2) # 이진수를 정수로 변환하기
        bhash.append("%04x" % i)
    return "".join(bhash)
# Average Hash 출력하기

ahash = average_hash('tower.jpg')
print(ahash)
print(np2hash(ahash))

 

※3 부분에서 이미지를 그레이스케일로 변환해준다 convert() 매개변수에 "L"을 지정 하면 그레이스케일이 되고  "1"을 지정하면 이진화된다.

 

원본이미지

[출력 결과]

[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0]
[1 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0]
[0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0]
[0 1 0 1 1 1 1 1 1 1 1 1 0 0 0 0]
[0 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0]
[1 1 0 0 1 1 1 1 1 1 1 1 1 0 1 0]
[1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0]
[0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0]
[0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0]
[0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1]]
000001000180018003c003c003c087e07ff05ff07ff2cffa9ffe3ffe3ffe1fff

 


유사한 이미지 찾기

 

이미지 데이터를 해시값으로 변환하는 법을 알았으니 이번에는 실제로 해시값을 비교해 유사한 이미지를 찾도록 하겠다. 이떄 비교할 이미지는 의자이미지(chir/image_0016.jpg)이다.

 

from PIL import Image
import numpy as np
import os, re
# 파일 경로 지정하기
search_dir = "./image/101_ObjectCategories"
cache_dir = "./image/cache_avhash"
if not os.path.exists(cache_dir):
    os.mkdir(cache_dir)
# 이미지 데이터를 Average Hash로 변환하기 --- (※1)
def average_hash(fname, size = 16):
    fname2 = fname[len(search_dir):]
    # 이미지 캐시하기
    cache_file = cache_dir + "/" + fname2.replace('/', '_') + ".csv"
    if not os.path.exists(cache_file): # 해시 생성하기
        img = Image.open(fname)
        img = img.convert('L').resize((size, size), Image.ANTIALIAS)
        pixels = np.array(img.getdata()).reshape((size, size))
        avg = pixels.mean()
        px = 1 * (pixels > avg)
        np.savetxt(cache_file, px, fmt="%.0f", delimiter=",")
    else: # 캐시돼 있다면 읽지 않기
        px = np.loadtxt(cache_file, delimiter=",")
    return px

 

search_dir 은 이미지 파일이 저장되어있는 폴더, cache_dir 은 이미지의 해시값을 캐시해놓을 폴더이다. 

※1 에서 이미지데이터를 average hash을 이용해 해시 값으로 변환한다 이후 이를 캐시해놓는다.

 

# 해밍 거리 구하기 --- (※2)
def hamming_dist(a, b):
    aa = a.reshape(1, -1) # 1차원 배열로 변환하기
    ab = b.reshape(1, -1)
    dist = (aa != ab).sum()
    return dist
# 모든 폴더에 처리 적용하기 --- (※3)
def enum_all_files(path):
    for root, dirs, files in os.walk(path):
        for f in files:
            fname = os.path.join(root, f)
            if re.search(r'\.(jpg|jpeg|png)$', fname):
                yield fname

이미지와 이미지 비교를 위해 해밍 거리라는 것을 사용하였다. "해밍 거리"는 같은 문자 수를 가진 2개의 문자열에서 대응하는 위치에 있는 문자 중 다른것의 개수를 나타낸다, 현재 이미지를 256글자의 해시 값으로 나타냈으므로 이를 기반으로 몇 글자가 다른지 찾고, 이를 기반으로 이미지의 차이를 구분하는 것이다.

 

※3 에서는 모든 폴더를 찾는다. 정규표현식을 사용해 확장자가 JPEG , PNG인 파일을 찾고 리턴 한다. yield를 사용해 제너레이터 함수로 만들었다. 이처럼 제너레이터를 사용하면 for반복문을 활용해 효율적으로 파일을 찾을 수 있다.

제너레이터에 관하여 http://schoolofweb.net/blog/posts/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0-generator/

 

SchoolofWeb :: 파이썬 - 제너레이터 (Generator)

파이썬 제너레이터 (Generator) 강좌

schoolofweb.net

 

 

 이미지 찾기 --- (※4)
def find_image(fname, rate):
    src = average_hash(fname)
    for fname in enum_all_files(search_dir):
        dst = average_hash(fname)
        diff_r = hamming_dist(src, dst) / 256
        # print("[check] ",fname)
        if diff_r < rate:
            yield (diff_r, fname)
# 찾기 --- (※5)
srcfile = search_dir + "/chair/image_0016.jpg"
html = ""
sim = list(find_image(srcfile, 0.25))
sim = sorted(sim, key=lambda x:x[0])
for r, f in sim:
    print(r, ">", f)
    s = '<div style="float:left;"><h3>[ 차이 :' + str(r) + '-' + \
        os.path.basename(f) + ']</h3>'+ \
        '<p><a href="' + f + '"><img src="' + f + '" width=400>'+ \
        '</a></p></div>'
    html += s

 

※4 에서는 이전에 정의한 enum_all_files() 함수를 사용해 이미지 목록을 구하고, 해밍거리를 구한다, 그리고 차이가 지정한 값보다 작다면 결과로 리턴한다, 이떄도 yield를 사용해 제너레이터 함수로 만들었다.

 

※5에서는 실제로 파일경로를 지정하여 이미지르 검색한다 sorted()함수를 적용해 유사한 파일 순서로 정렬하고 결과를 출력한다 , html에도 출력한다.

 

# HTML로 출력하기
html = """<html><head><meta charset="utf8"></head>
<body><h3>원래 이미지</h3><p>
<img src='{0}' width=400></p>{1}
</body></html>""".format(srcfile, html)
with open("./avhash-search-output.html", "w", encoding="utf-8") as f:
    f.write(html)
print("ok")

'Machine Learning > 영상처리' 카테고리의 다른 글

이미지 OCR  (0) 2020.07.18
OpenCV로 얼굴인식하기  (0) 2020.07.17
규동 메뉴 이미지 판정하기  (0) 2020.07.16
CNN으로 이미지 분류하기  (1) 2020.07.15