AIFFEL(220117)_파이썬으로 이미지 파일 다루기
NODE : 16. 파이썬으로 이미지 파일 다루기
학습목표
- 컴퓨터에서 이미지가 표현되는 방식을 이해한다.
- Pillow와 OpenCV로 Python에서 이미지 파일을 열고 정보를 추출할 수 있다.
- CIFAR-100에서 히스토그램을 기반으로 유사한 이미지를 골라낼 수 있다.
📖 디지털 이미지
디지털 화면은 수많은 점들로 이루어져 있으며, 색상을 가지는 점 하나를 화소(pixel, picture element)라고 한다. 각 화소는 RGB(Red, Green, Blue) 세 개의 단일 색의 강도를 각각 조절하여 색상을 표현한다. 왜 세 개의 색일까?
이는 눈의 망막에 있는 시세포가 인간의 경우 대부분 세 가지로 이루어져 있기 때문이다. 아래 그림은 각 종류의 시세포가 반응하는 색상 영역을 나타낸다. 특이하게도 일부 인간들과 일부 새들은 시세포의 종류가 하나씩 더 있어서, 색을 조금 더 세밀하게 인식하거나 일부 자외선 영역을 감지할 수 있다.
이렇게 빨강, 초록, 파랑 세 가지 색의 강도로 표현되는 점들로 구성된 디지털 화면에 표시될 이미지를 저장하는 방법 중 가장 단순한 방법은, 각 점 하나하나의 색상 값을 저장하는 방식이다. 이를 래스터(raster) 또는 비트맵(bitmap) 방식의 이미지라고 하며, 보통 한 점마다 각 색상별로 8비트를 사용하여 0~255 사이의 값(2^8 = 256)으로 해당 색의 감도를 표시한다.
반면에 벡터(vector) 방식의 이미지는 상대적인 점과 선의 위치를 방정식으로써 기록해 두었다가, 확대 및 축소에 따라 디지털 화면의 각 화소에 어떻게 표현될지를 재계산하기에 깨짐이 없다. 우리가 주로 다루는 파일들 중에는 사진 파일들이 래스터 방식이며, 확대 축소가 자유로이 가능한 글꼴들이 주로 벡터 방식이다.
결국에는 디지털 화면의 화소로 표시되는 이미지를 저장하는 방법이 여러 가지인 것처럼, 결국에 RGB로 표시되는 색상 값도 꼭 RGB 형식으로만 저장될 필요는 없다. 예를 들어 흑백(grayscale) TV 시절에서 컬러 TV로 넘어가던 시절에는, 인간의 눈이 색상의 차이보다는 음영에 더 민감한 것을 역이용하여, 기존 흑백 채널에다가 그보다 1/4의 해상도를 가진 두 색상 채널을 덧붙여서 송출하는 YUV 방식(아래 사진)이 사용되었다.
또한 디지털 화면에서 색감을 수치적으로 조작할 때 조금 더 직관적으로 이해할 수 있는 HSV(Hue 색상, Saturation 채도, Value 명도)도 자주 사용된다. 이외에 인쇄 매체의 경우에는 색의 강도를 높일수록 어두워진다는 특성과, 또한 자주 사용되는 검은색을 표현할 때 각 색을 조합하면 잉크의 낭비가 심하다는 현실적인 이유 때문에, RGB가 아닌 CMYK(Cyan, Magenta, Yellow, Black) 네 가지 색상을 사용한다.
이렇게 색을 표현하는 다양한 방식을 각각 컬러 스페이스(color space, 색 공간)라고 하며, 각 컬러 스페이스를 구성하는 단일 축(RGB에서의 각각 R, G, B)을 채널(channel)이라고 한다.
하지만 이러한 색상 정보를 그대로 저장하기에는 생각보다 많은 용량을 차지합니다. 따라서 사진 저장에 흔히 쓰이는 JPEG 이미지 형식의 경우 근처에 있는 화소들을 묶어, 비슷한 색들을 뭉뚱그리는 방식으로 이미지를 압축한다.
이러한 방식에는 색상 정보의 손실이 있기에, 저장할 때 압축률을 높이거나, 여러 번 다시 저장하는 등 재압축이 일어나게 될 경우, 흔히 디지털 풍화라고 불리는 색상이 지저분해지는 현상을 볼 수 있다. 이와 반대로 스크린샷 등에 많이 사용되는 PNG 이미지 형식의 경우 색상의 손실 없이 이미지를 압축하는데, 이미지에 사용된 색상을 미리 정의해두고 그를 참조하는 팔레트 방식을 사용할 수 있기에, 사용된 색상이 적은 단순한 이미지의 경우 동일한 해상도의 JPEG 파일보다도 용량이 작을 수 있지만, 사진과 같이 이미지에 사용된 색상이 많아지면 JPEG 파일보다 쉽게 더 많은 용량을 차지한다. 이외에 움짤로 익숙한 GIF 형식의 이미지는 이미지 내에 여러 프레임을 두어 이를 움직이게 만들 수 있고, 또한 색상 정보를 손실 없이 저장하나, 256개의 색상만 기억할 수 있는 팔레트 방식으로 제한된다.
📖 Pillow 사용법
옛날 옛적에 파이썬에서의 이미지 처리를 위해 PIL(Python Image Library)이라는 라이브러리가 있었다. 하지만 이 라이브러리는 2011년 마지막 커밋을 이후로 개발이 중단되었다. 대신 Pillow가 이어받아 현재까지도 지속적으로 이어져 내려오고 있다.
간단한 이미지 작업에 Pillow는 Numpy와 결합하여 간편하게 사용할 수 있다.
PIL을 이용해 바로 이미지 하나를 직접 생성해 본다. 명심할 점은 결국 이미지는 배열 형태의 데이터라는 점이다. 예를 들어 가로 세로 각 32픽셀에 RGB 세 가지 색상 채널이 있다면, Numpy로 [32, 32, 3] 차원의 배열을 생성하면 된다. 또한 데이터 타입을 uint8, 즉 각 값이 부호가 없는(unsigned) 8비트 정수(integer)가 되어 0~255(2의 8승 = 256) 사이의 값을 나타내도록 해야 한다.
import numpy as np
from PIL import Image
data = np.zeros([32, 32, 3], dtype=np.uint8)
image = Image.fromarray(data, 'RGB')
image
아주 작은 32X32 짜리 까만 이미지 화면이 뜬다.
만들어진 배열을 PIL.Image.fromarray()
를 통해 바로 이미지 객체로 변환한 뒤 화면에 표시한다. 단 주피터 노트북이 아닌 IDE 혹은 개발 환경에서 이미지를 표시하려면.show()
메서드를 통해서 출력할 수 있다. np.zeros()
로 초기화된 모든 픽셀의 모든 채널의 값이 0이기 때문에 검은색 이미지가 나왔지만, 이번에는 모든 픽셀이 빨간색의 값을 가지도록 해 본다.
data[:, :] = [255, 0, 0]
image = Image.fromarray(data, 'RGB')
image
- 가로 세로 각 128 픽셀짜리 흰색 이미지를 만들어 화면에 표시해 보자.
data = np.zeros([128, 128, 3], dtype=np.uint8)
data[:, :] = [255, 255, 255]
image = Image.fromarray(data, 'RGB')
image
- 연습용 이미지를 열어 width와 height를 출력하고, .save()를 이용하여 jpg 파일 포맷으로 저장해 보자.
#- 문제 2 -#
from PIL import Image
import os
# 연습용 파일 경로
image_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/pillow_practice.png'
# 이미지 열기
im = Image.open(image_path)
im
# width와 height 출력
w, h = im.size
print('width :', w)
print('height :', h)
# JPG 파일 형식으로 저장해보기
new_image_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/jpg_pillow_practice.jpg'
im = im.convert('RGB')
im.save(new_image_path)
# 실행결과
width : 620
height : 465
- .resize()를 이용하여 이미지 크기를 100X200으로 변경하여 저장해 보자.
img_re = im.resize((100, 200))
img_re_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/pillow_practice_resized.png'
img_re.save(img_re_path)
img_re
- .crop()을 이용하여 눈 부분만 잘라내어 저장해 보자.
img_eye = im.crop((300, 100, 600, 400))
img_eye_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/pillow_practice_img_eye.png'
img_eye.save(img_eye_path)
img_eye
📖 Pillow를 활용한 데이터 전처리
CIFAR-100 데이터를 받아 개별 이미지 파일로 추출하기
이미지 데이터베이스 구축을 위해 CIFAR-100 데이터셋 을 활용해 본다. 32x32 화소 해상도의 이미지들이 100개 클래스 당 600장(각 학습용 500장, 테스트용 100장)하여 총 60,000장 있다.
압축 해제된 결과를 보면 이미지 파일들이 아니라 meta, test, train이라는 세 뭉텅이만 나온다. 이 중 train 파일만 사용하여, 본문 중 Dataset layout 아래 Python / Matlab versions 섹션 중 python3 버전에 따라 한번 파일을 열어본다. 그렇게 해서 추출된 내용(return dict에 의해 반환된 내용)을 train이라고 하고 한 번 들여다본다.
import os
import pickle
from PIL import Image
dir_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/cifar-100-python'
train_file_path = os.path.join(dir_path, 'train')
with open(train_file_path, 'rb') as f:
train = pickle.load(f, encoding='bytes')
print(type(train))
# 실행결과
<class 'dict'>
train 내용을 그대로 화면에 출력하면 한꺼번에 너무 많은 내용이 나와 갈피를 잡기가 힘들 것이다. 다시 본문으로 돌아가 이어지는 내용("Loaded in this way, each of the batch files contains a dictionary")에 따르면 파이썬의 dictionary 객체이니, 일단 어떤 키들이 있는지 한번 살펴본다.
train.keys()
# 실행결과
dict_keys([b'filenames', b'batch_label', b'fine_labels', b'coarse_labels', b'data'])
특이사항으로는 각 키들이 문자열(str
)이 아닌 b
로 시작하는 bytes
로 되어있다는 점이다. 유의하면서 일단 파일명(b'filenames'
)들을 한번 살펴본다.
type(train[b'filenames'])
# 실행결과
list
앞의 5개만 출력해 본다.
train[b'filenames'][0:5]
# 실행결과
[b'bos_taurus_s_000507.png',
b'stegosaurus_s_000125.png',
b'mcintosh_s_000643.png',
b'altar_boy_s_001435.png',
b'cichlid_s_000031.png']
파일 이름이 깔끔하게 나왔다. 이 파일 이름에 해당하는 이미지는 어디 있을까? 본문을 참고해보면
Data: a 10000x3072 numpy array of uint8s. Each row of the array stores a 32x32 colour image. The first 1024 entries contain the red channel values, the next 1024 the green, and the final 1024 the blue. The image is stored in row-major order, so that the first 32 entries of the array are the red channel values of the first row of the image.
딕셔너리에 b'data'
를 보라는 말 같다.
train[b'data'][0:5]
# 실행결과
array([[255, 255, 255, ..., 10, 59, 79],
[255, 253, 253, ..., 253, 253, 255],
[250, 248, 247, ..., 194, 207, 228],
[124, 131, 135, ..., 232, 236, 231],
[ 43, 32, 87, ..., 60, 29, 37]], dtype=uint8)
numpy 배열이 나왔다. 형태를 한 번 찍어본다.
train[b'data'][0].shape
# 실행결과
(3072,)
이 숫자와 위 본문을 보니, 3072라는 숫자는 빨강 초록 파랑 3채널 X 1024(=32 * 32)씩 각 화소에 해당하는 것 같다. 그렇다면 이 Numpy 배열을 잘 reshape하면 이미지 파일 원본이 복구된다는 뜻이다. 그럼 (32, 32, 3)으로 reshape한 후 이미지를 화면에 출력해 본다.
주의해야 할 게 하나 있다. 위에 언급된 바를 상기해 보면, 3072바이트의 이미지 데이터는 앞 1024바이트는 빨강(R), 그다음 1024는 녹색(G), 마지막 1024는 파랑(B)으로 되어 있다. RGB 순서가 맞는 것은 다행이지만, 그렇다고 그냥 모양만 맞추어 reshape하면 안 된다. 1024를 32X32에 채우는 것을 3번 반복하는 방식의 reshape이어야 한다. 이렇게 앞선 차원부터 데이터를 채우는 방식의 reshape를 위해 np.reshape에는 order라는 인자가 있다. 이 값을 F로 주면 원하는 형태로 진행된다.
image_data = train[b'data'][0].reshape([32, 32, 3], order='F') # order 주의!!
image = Image.fromarray(image_data) # Pillow를 사용하여 Numpy 배열을 Image객체로 만들어서
image # 화면에 띄우기
이미지의 X축과 Y축이 뒤집어져 나오고 있어 축을 바꿔주는 작업이 필요하다. 여기에는 np.swapaxes(0, 1)
을 사용한다.
image_data = image_data.swapaxes(0, 1)
image = Image.fromarray(image_data)
image
이렇게 CIFAR-100의 데이터셋 원본을 분석해서 이미지 파일을 뽑아내는 것까지 진행해 보았다. 그러나 이미지 파일을 실제 파일처럼 만들어 놓고 싶다. 데이터셋에 파일명과 파일 데이터 배열이 순서를 따라 저장되어 있는 것을 확인했으니, 차례차례 Numpy 배열로 읽어서 이를 이미지 파일로 저장해 준다.
import os
import pickle
from PIL import Image
import numpy
from tqdm import tqdm
dir_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/cifar-100-python'
train_file_path = os.path.join(dir_path, 'train')
# image를 저장할 cifar-100-python의 하위 디렉토리(images)를 생성합니다.
images_dir_path = os.getenv('HOME')+'/aiffel/python_image_proc/cifar-images'
if not os.path.exists(images_dir_path):
os.mkdir(images_dir_path) # images 디렉토리 생성
# 32X32의 이미지 파일 50000개를 생성합니다.
with open(train_file_path, 'rb') as f:
train = pickle.load(f, encoding='bytes')
for i in tqdm(range(len(train[b'filenames']))):
filename = train[b'filenames'][i].decode()
data = train[b'data'][i].reshape([32, 32, 3], order='F')
image = Image.fromarray(data.swapaxes(0, 1))
image.save(os.path.join(images_dir_path, filename))
# 실행결과
100%|██████████| 50000/50000 [00:21<00:00, 2325.48it/s]
📖 OpenCV_(1) 안녕, OpenCV
OpenCV(튜토리얼)는 오픈소스로 제공되는 컴퓨터 비전용 라이브러리인데, C++, Python, Java, MATLAB 등 다양한 언어에서 호출하여 사용할 수 있으며, 영상 처리에 대한 다양한 고급 기능들이 사용하기 쉽도록 구현되어 있다.
파이썬 튜토리얼 페이지에 들어가면 여러 가지 고급 예제들이 설명되어 있는데, 이 중에 이미지에서 특정 색을 가진 영역만 추출하는 예제를 한번 살펴본다.
Pillow의 실습에서 보았듯이 이미지는 결국 [너비, 높이, 채널]
형태를 가지는 배열이고, 컴퓨터 비전이란 결국 이러한 배열 형태의 데이터를 처리하는 것이 중요한 분야이다.
이미지의 내용 중 우리가 관심 있는 부분이 특정 색을 가지고 있다면, 이 정보를 통해 원하는 부분을 배경을 구분하고, 원하는 부분만 따로 떼어낼 수 있다.
(이미지를 읽어 들이고, 파란색을 찾기 쉽도록 컬러스페이스를 BGR(RGB)에서 HSV로 변환한 뒤, 해당 색상과 맞는 영역만 표시)
import os
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
img_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/cv_practice.png'
img = cv.imread(img_path)
# Convert BGR to HSV
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
# define range of blue color in HSV
lower_blue = np.array([100,100,100])
upper_blue = np.array([130,255,255])
# Threshold the HSV image to get only blue colors
mask = cv.inRange(hsv, lower_blue, upper_blue)
# Bitwise-AND mask and original image
res = cv.bitwise_and(img, img, mask=mask)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.show()
plt.imshow(cv.cvtColor(mask, cv.COLOR_BGR2RGB))
plt.show()
plt.imshow(cv.cvtColor(res, cv.COLOR_BGR2RGB))
plt.show()
📖 OpenCV_(2) 톺아보기
전체 코드를 이제 한 조각씩 함께 읽어보도록 한다.
import cv2 as cv
import numpy as np
- OpenCV는
pip
으로 설치 시opencv-pytho
n이라는 이름으로 설치 import
할 때는 cv2라는 이름을 쓴다.- OpenCV와 함께, 숫자 배열을 처리하는데 궁합이 잘 맞는 Numpy 불러오기
img_path = os.getenv('HOME')+'/aiffel/python_image_proc/data/cv_practice.png'
img = cv.imread(img_path)
- *imread 함수 호출
* 파일로부터 이미지를 읽어와 반환해 주는 함수
# Convert BGR to HSV
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
- cvtColor은 컬러 스페이스 변환(convert)을 위한 함수
OpenCV: Color Space Conversions
# define range of blue color in HSV
lower_blue = np.array([100,100,100])
upper_blue = np.array([130,255,255])
# Threshold the HSV image to get only blue colors
mask = cv.inRange(hsv, lower_blue, upper_blue)
- 숫자로 파란색이라는 부분 정의
- 이 값들을 기준으로 이미지에서 *마스크 생성
* 마스크란 수채화를 그리거나 인테리어 공사 중 실리콘을 바를 때 사용하는 마스킹 테이프의 역할과 동일, 원하는 부분만을 떼어낼 수 있도록 하는 역할 - 위 코드에서는 HSV 색 공간에서 색상(Hue) 값 110~130 사이, 채도(Saturation) 및 명도(Value) 값 50~255 사이의 색들을 파란색이라고 정의하고 있다.🔎
img
를 변환한hsv
에다가 이 기준들(lower_blue
,upper_blue
)를 적용cv.inRange()
: 해당하는 픽셀들에는1
, 그렇지 않은 픽셀들에는0
을 찍어놓은 배열을 반환
아래 이미지의 왼쪽 중 original frame으로 mask를 만든 뒤 1을 흰색으로, 0을 검정으로 표시한다면, 아래 이미지의 가운데에 있는 mask image와 동일하게 된다.
# Bitwise-AND mask and original image
res = cv.bitwise_and(img, img, mask=mask)
- 이미지 두 장을 받아서 AND 비트 연산을 한다.
- 두 장 다 같은 이미지(
img
,img
)를 넣어서 결국 동일한 이미지가 나오게 한다. - 대신 중요한
mask
를 같이 넣어줘서, 해당 영역만 따온다. - 따온 영역은 위 공식 문서 페이지의 함수 설명에 따라
dst
가 주어진다면 그 위에, 아니면 새로 빈 검정색 영역 위에 이미지를 만들고 반환한다.
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.show()
plt.imshow(cv.cvtColor(mask, cv.COLOR_BGR2RGB))
plt.show()
plt.imshow(cv.cvtColor(res, cv.COLOR_BGR2RGB))
plt.show()
plt
로 이미지를 보여줄 땐cv.cvtColor(res, cv.COLOR_BGR2RGB)
함수를 통해, cv 이미지 객체의 컬러를 변환해 준다. (OpenCV에서는 RGB가 아닌 BGR 순서)- 카메라에서 받아온 이미지
img
- 파란색인 영역만 골라낸 마스크
mask
- 이미지에 마스크를 적용한 결과
res
📖 실습 : 비슷한 이미지 찾아내기
CIFAR-100 이미지 중 비슷한 이미지 찾아내기
OpenCV에서 제공하는 기능 중에서 이미지에서 색상 히스토그램을 추출하고, 이를 서로 비교하는 기능들을 불러 사용해 본다.
히스토그램 : 이미지에서 픽셀 별 색상 값의 분포
이러한 히스토그램을 통해, 각 이미지의 색상 분포를 비교하여 서로 유사한 이미지를 판단하는 척도로 사용한다.
위의 예시는 이미지를 흑백으로 변환했을 때 밝기에 따른 히스토그램이다.
여기서는 RGB 각 채널별 분포를 사용한다. 또한, 0~255 사이 각 값에 해당하는 픽셀의 개수를 일일이 저장하기에는 계산량이 많아지므로, 단순화의 측면에서 이를 4개 구간(0~63, 64~127, 128~191, 192~255)로 나누어 픽셀 수를 센다.
OpenCV 자체는 C++로 구현되어 있고, 이를 파이썬에서 불러 쓸 수 있도록 하는 패키지인 opencv-python
를 설치해야 한다. 또한 히스토그램을 실제로 화면에 표시하기 위해 matplotlib
도 설치되어야 한다.
저장해둔 CIFAR-100 이미지 중 하나를 골라서, 아래 OpenCV 예제 페이지의 Plotting Histograms 중 Using Matplotlib 내 색상별 히스토그램을 그리는 코드를 참조하여 히스토그램을 그려보자.
OpenCV: Histograms - 1 : Find, Plot, Analyze !!!
import os
import pickle
import cv2
import numpy as np
from matplotlib import pyplot as plt
from tqdm import tqdm
from PIL import Image
# 전처리 시 생성했던 디렉토리 구조
dir_path = os.getenv('HOME')+'/aiffel/python_image_proc/'
train_file_path = os.path.join(dir_path, 'train')
images_dir_path = os.path.join(dir_path, 'cifar-images')
# 파일명을 인자로 받아 해당 이미지 파일과 히스토그램을 출력해 주는 함수
def draw_color_histogram_from_image(file_name):
image_path = os.path.join(images_dir_path, file_name)
# 이미지 열기
img = Image.open(image_path)
cv_image = cv2.imread(image_path)
# Image와 Histogram 그려보기
f=plt.figure(figsize=(10,3))
im1 = f.add_subplot(1,2,1)
im1.imshow(img)
im1.set_title("Image")
im2 = f.add_subplot(1,2,2)
color = ('b','g','r')
for i,col in enumerate(color):
# image에서 i번째 채널의 히스토그램을 뽑아서(0:blue, 1:green, 2:red)
histr = cv2.calcHist([cv_image],[i],None,[256],[0,256])
im2.plot(histr,color = col) # 그래프를 그릴 때 채널 색상과 맞춰서 그립니다.
im2.set_title("Histogram")
draw_color_histogram_from_image('adriatic_s_001807.png')
히스토그램을 그려보니 파란색이 확실히 튄다.
설계과정
- 프로그램이 실행된다.
build_histogram_db()
- CIFAR-100 이미지들을 불러온다.
- CIFAR-100 이미지들을 하나하나 히스토그램으로 만든다.
- 이미지 이름을 키로 하고, 히스토그램을 값으로 하는 딕셔너리
histogram_db
를 반환한다.
- CIFAR-100 히스토그램 중 입력된 이미지 이름에 해당하는 히스토그램을 입력 이미지로 선택하여
target_histogram
이라는 변수명으로 지정한다. - `search()
- 입력 이미지 히스토그램
target_histogram
와 전체 검색 대상 이미지들의 히스토그램을 가진 딕셔너리histogram_db
를 입력으로 받는다. - OpenCV의
compareHist()
함수를 사용하여 입력 이미지와 검색 대상 이미지 하나하나의 히스토그램 간 유사도를 계산한다. 결과는result
라는 이름의 딕셔너리로, 키는 이미지 이름, 값은 유사도로 한다. - 계산된 유사도를 기준으로 정렬하여 순서를 매긴다.
- 유사도 순서상으로 상위 5개 이미지만 골라서
result
에 남긴다.
- 입력 이미지 히스토그램
- 고른 이미지들을 표시한다.
- 프로그램이 종료된다.
def get_histogram(image):
histogram = []
# Create histograms per channels, in 4 bins each.
for i in range(3):
channel_histogram = cv2.calcHist(images = [image],
channels = [i],
mask = None,
histSize = [4],
ranges = [0, 255+1])
histogram.append(channel_histogram)
histogram = np.concatenate(histogram)
histogram = cv2.normalize(histogram, histogram)
return histogram
# get_histogram() 확인용 코드
filename = train[b'filenames'][0].decode()
file_path = os.path.join(images_dir_path, filename)
image = cv2.imread(file_path)
histogram = get_histogram(image)
histogram
# 실행결과
array([[0.3126804 ],
[0.4080744 ],
[0.14521089],
[0.21940625],
[0.18654831],
[0.23742512],
[0.30208108],
[0.35931748],
[0.06465594],
[0.35825753],
[0.36991683],
[0.29254165]], dtype=float32)
get_histogram()
이 정상 동작한다면, 이제 본격적으로 build_histogram_db()
를 구해본다.
import os
import pickle
import cv2
import numpy as np
from matplotlib import pyplot as plt
from tqdm import tqdm
def build_histogram_db():
histogram_db = {}
#디렉토리에 모아 둔 이미지 파일들을 전부 리스트업합니다.
path = images_dir_path
file_list = os.listdir(images_dir_path)
for file_name in tqdm(file_list):
file_path = os.path.join(images_dir_path, file_name)
image = cv2.imread(file_path)
histogram = get_histogram(image)
histogram_db[file_name] = histogram
return histogram_db
이 함수가 리턴할 객체 histogram_db
는 histogram_db['adriatic_s_001807.png']을 호출했을 때 12X1의 Numpy배열을 리턴해야 한다.
histogram_db = build_histogram_db()
histogram_db['adriatic_s_001807.png']
# 실행결과
100%|██████████| 49999/49999 [00:05<00:00, 9332.11it/s]
array([[0. ],
[0. ],
[0.29744893],
[0.5828624 ],
[0. ],
[0.17107612],
[0.41608465],
[0.29315054],
[0. ],
[0.34301192],
[0.37224102],
[0.16505837]], dtype=float32)
다음으로는 target_histogram에 입력받은 이미지의 히스토그램을 저장하는 함수를 만든다. 파이썬 모듈에서는 sys.argv[1]에 검색하고 싶은 이미지 파일명을 입력받게 되지만, 여기서는 Input으로 직접 입력받아 본다.
def get_target_histogram():
filename = input("이미지 파일명을 입력하세요: ")
if filename not in histogram_db:
print('유효하지 않은 이미지 파일명입니다.')
return None
return histogram_db[filename]
target_histogram = get_target_histogram()
target_histogram
# 실행결과
이미지 파일명을 입력하세요: adriatic_s_001807.png
array([[0. ],
[0. ],
[0.29744893],
[0.5828624 ],
[0. ],
[0.17107612],
[0.41608465],
[0.29315054],
[0. ],
[0.34301192],
[0.37224102],
[0.16505837]], dtype=float32)
이제 search 함수를 만들어 본다. 설계 단계에서 만들어야 하는 search 함수는 입력 이미지 히스토그램 target_histogram
과 전체 검색 대상 이미지들의 히스토그램을 가진 딕셔너리 histogram_db
를 입력으로 받는다고 했다. 입력부가 다 정의되었으므로, 구현 후 출력까지 가능하게 만든다. 입력부에 유사도 순으로 몇 개까지 결과에 남길지 top_k=5
라는 파라미터를 하나 추가한다.
def search(histogram_db, target_histogram, top_k=5):
results = {}
# Calculate similarity distance by comparing histograms.
for file_name, histogram in tqdm(histogram_db.items()):
distance = cv2.compareHist(H1=target_histogram,
H2=histogram,
method=cv2.HISTCMP_CHISQR)
results[file_name] = distance
results = dict(sorted(results.items(), key=lambda item: item[1])[:top_k])
return results
result = search(histogram_db, target_histogram)
result
# 실행결과
100%|██████████| 49999/49999 [00:00<00:00, 630785.68it/s]
{'adriatic_s_001807.png': 0.0,
'baby_s_001654.png': 0.047506706444185816,
'fogbank_s_000423.png': 0.059270738493642845,
'tank_s_000442.png': 0.060966690827361725,
'dessert_plate_s_000124.png': 0.06113711905561663}
위에서 입력받은 이미지 파일과 가장 유사한 상위 5개의 이미지가 검색되어 나오게 된다. 1번째는 당연히 입력받은 이미지 자신일 것이다. 5개의 이미지가 얼마나 실제로 유사한지 시각화를 통해 알아본다.
result를 입력받아 5개의 이미지를 화면에 출력하는 함수를 제작해 본다.
def show_result(result):
f=plt.figure(figsize=(10,3))
for idx, filename in enumerate(result.keys()):
img_path = os.path.join(images_dir_path, filename)
im = f.add_subplot(1,len(result),idx+1)
img = Image.open(img_path)
im.imshow(img)
show_result(result)
마지막으로, 지금까지의 작업을 총정리. 검색할 이미지를 Input으로 받으면 즉시 가장 유사한 이미지가 화면에 출력되는 코드이다.
target_histogram = get_target_histogram()
result = search(histogram_db, target_histogram)
show_result(result)
# 실행결과
이미지 파일명을 입력하세요: adriatic_s_001807.png
100%|██████████| 49999/49999 [00:00<00:00, 702116.64it/s]
Author And Source
이 문제에 관하여(AIFFEL(220117)_파이썬으로 이미지 파일 다루기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@mjk3136/AIFFEL211228파이썬-기본개념-이해저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)