이번 장에서는 몇개의 opencv-python 모듈을 이용하여 이미지의 특정 부분만 남기는 작업을 진행해 보겠다.

이번 장은 왜 하는지 이해가 안갈 수 있지만 다음에 진행될 배경 추출에서 유용하게 사용되는 모듈로써 배워두면 좋다

 

1. 이미지의 영역 확장과 침식 

각 순서대로 이미지의 특정 영역에 대한 변환이 어떻게 작용하는지 imshow를 통해 띄어 보았다

import numpy as np
import cv2

image_path = './test.JPG'

image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
image = cv2.resize(image,(300,300))
return_value, image = cv2.threshold(image, 127,255,cv2.THRESH_BINARY)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))

erosion = cv2.erode(image,kernel,iterations=1)
dilate = cv2.dilate(image,kernel,iterations=1)
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
blackhat = cv2.morphologyEx(image,cv2.MORPH_BLACKHAT, kernel)
tophat = cv2.morphologyEx(image,cv2.MORPH_TOPHAT, kernel)
gradient = cv2.morphologyEx(image,cv2.MORPH_GRADIENT, kernel)


image = cv2.putText(image,'origin',(30,30),1,2,255,2)
erosion = cv2.putText(erosion,'Erosion',(30,30),1,2,255,2)
dilate = cv2.putText(dilate,'dilate',(30,30),1,2,255,2)
opening = cv2.putText(opening,'opening',(30,30),1,2,255,2)
closing = cv2.putText(closing,'closing',(30,30),1,2,255,2)
blackhat = cv2.putText(blackhat,'blackhat',(30,30),1,2,255,2)
tophat = cv2.putText(tophat,'tophat',(30,30),1,2,255,2)
gradient = cv2.putText(gradient,'gradient',(30,30),1,2,255,2)


concat_image1 = np.concatenate((erosion,dilate,blackhat,image),axis=1)
concat_image2 = np.concatenate((opening,closing,tophat,gradient),axis=1)
concat_image = np.concatenate((concat_image1,concat_image2),axis=0)

cv2.imshow('concat_image',concat_image)
cv2.moveWindow('concat_image',10,10)
cv2.waitKey(0)

 

  • cv2.threshold
    - grayscale 이미지를 0 or 1 의 BINARY 이미지로 변경한다 - 이유는 침식과 확장에 간편하게 목표하는 값을 설정하기 위해서다 ( 0~255의 이미지는 너무 다양해서 특정 값을 변경해도 눈에 띄는 효과가 없다)
  • cv2.getStructuringElement
    - 확장과 침식의 범위를 정해줄 kernel을 정의한다 - 아래에 자세히 설명하겠다 
  • cv2.erode
    - 이미지에서 침식을 담당한다 binary 이미지의 경우 검정색인 0의 부분을 확장하여 원본 이미지에서 검은색이 많아진다 
  • cv2.dilate
    - 이미지에서 확장을 담당한다. binary 이미지의 경우 흰색인 1의 부분을 확장하여 원본 이미지에서 흰색이 많아진다 
  • cv2.MORPH_OPEN
    - 이미지에서 침식을 한 후 확장을 한다. 위의 사진과 같이 원본에 있던 작은 noise라고 할 수 있는 부분이 없어진것을 볼 수 있다
  • cv2.MORPH_CLOSE
    - 이미지에서 확장을 한 후 침식을 한다.
  • cv2.MORPH_BLACKHAT
    - CLOSE와 원본 이미지의 차이를 보여준다
  • cv2.MORPH_TOPHAT
    - OPEN과 원본 이미지의 차이를 보여준다 
  • cv2.MORPH_GRADIENT
    - dilate와 erode의 차이를 보여준다

 

2. cv2.getStructuringElement

위의 코드에서 kernel의 설정에 따라 어떻게 변하는지 보여주겠다 

import numpy as np
import cv2

image_path = './test.JPG'

image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
image = cv2.resize(image,(300,300))
return_value, image = cv2.threshold(image, 127,255,cv2.THRESH_BINARY)

kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT,(11,11))
kernel3 = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
kernel4 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))

kernel_list = [kernel1,kernel2,kernel3,kernel4]

kernel1 = cv2.dilate(image,kernel1,iterations=1)
kernel2 = cv2.dilate(image,kernel2,iterations=1)
kernel3 = cv2.dilate(image,kernel3,iterations=1)
kernel4 = cv2.dilate(image,kernel4,iterations=1)

image = cv2.putText(image,'origin',(30,30),1,2,255,2)
kernel1 = cv2.putText(kernel1,'RECT 5x5',(30,30),1,2,255,2)
kernel2 = cv2.putText(kernel2,'RECT 11x11',(30,30),1,2,255,2)
kernel3 = cv2.putText(kernel3,'CROSS 5x5',(30,30),1,2,255,2)
kernel4 = cv2.putText(kernel4,'ELLIPSE 5x5',(30,30),1,2,255,2)

concat_image = np.concatenate((image,kernel1,kernel2,kernel3,kernel4),axis=1)

cv2.imshow('concat_image',concat_image)
cv2.moveWindow('concat_image',10,10)
cv2.waitKey(0)
  • getSturucturingElement
    - 첫 번쨰 파라미터는 배열의 모양을 뜻한다 
    - RECT의 경우 배열에서 1을 사각형 모양으로 채운다
    - CROSS의 경우 배열에서 1을 십자 모양으로 채운다
    - ELLIPSE의 경우 배열에서 1을 타원 모양으로 채운다( 육각형에 가깝다 ) 
    - 두 번째 파라미터는 배열의 크기를 뜻한다 아래 그림을 보다시피 RECT (11,11)의 경우 배열의 크기가 각각 가로 세로 11, 11 인것을 볼 수 있다
    다음에는 이번 장에서 학습한 내용을 가지고 배경추출에 대해서 알아보겠다

이전 장에서 학습한 2D histogram 분석을 이용해 여러 label 데이터의 분석을 진행하겠다 

 

1. image data

test 이미지와 labeling 이 된 이미지 

  • 위의 사진을 보면 가운데 차량과 가상에 주차되어있는 차량에 바운딩박스가 쳐져 있다 
  • 이 데이터를 가지고 각 각 바운딩 박스가 쳐져 있는 차량들의 2D histogram을 생성하여 어떤 분포를 가지고 있는지 비교하여 보겠다
import cv2
import numpy as np
import matplotlib.pyplot as plt

image = cv2.imread('test.JPG')
# hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h,w,_ = image.shape
box_list = []
mask_image = []
temp_image = []
seed = 8
with open('test.txt','r') as rd:
    boxes = rd.readlines()
    for box in boxes:
        box = box.strip()
        box = box.split()
        box = list(map(float,box))
        box_width = box[3] * w
        box_height = box[4] * h
        box_center_x = box[1] * w 
        box_center_y = box[2] * h

        xmin = int(box_center_x - box_width/2)
        ymin = int(box_center_y - box_height/2)
        xmax = int(box_center_x + box_width/2)
        ymax = int(box_center_y + box_height/2)
        mask = np.zeros((h,w), np.uint8)
        mask[ymin:ymax, xmin:xmax] = 255 
        mask_image.append( cv2.resize(image[ymin:ymax,xmin:xmax],(300,300)))

for i in range(seed,len(mask_image)):
    if i == seed:
        temp_image = mask_image[i]
    else:
        temp_image = np.concatenate((temp_image,mask_image[i]), axis=1)
cv2.imshow('bounding boxes',temp_image)
cv2.waitKey(0)

  • 바운딩박스 영역의 객체들을 잘라내어 표시해 보았다 
  • resize를 하여 2D histogram에 같은 비율로 적용이 되도록 하였다 
  • 처음 알았는데 opencv imshow는 사용자의 해상도에 맞춰 이미지를 최대 해상도 까지만 표현한다 

2. cv2.compareHist(hist1, hist2, method)

  • hist1 과 hist2 는 서로 비교할 histogram을 넣는다 
  • method는 https://docs.opencv.org/3.4/d8/dc8/tutorial_histogram_comparison.html 에 공식까지 잘 나와있다 
    cv2.HISTCMP_CORREL : 두 히스토그램의 상관관계를 분석한다 
    cv2.COMP_CHISQR : 카이제곱검정으로 분석한다
    cv2.COMP_INTERSECT : Intersection 
    cv2.COMP_BHATTACHARYYA : bhattacharyya distance - 바타챠랴 거리 측정법으로 분석한다 hellinger distance와 같다
    cv2.COMP_KL_DIV : Kullback-leibler divergence - 쿨백-라이블러 발산으로 확률분포의 차이를 계산한다
import cv2
import numpy as np
import matplotlib.pyplot as plt

image = cv2.imread('test.JPG')
# hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h,w,_ = image.shape

mask_image = []
hist_list = []
with open('test.txt','r') as rd:
    boxes = rd.readlines()
    for i, box in enumerate(boxes[10:15]):
        box = box.strip()
        box = box.split()
        box = list(map(float,box))
        box_width = box[3] * w
        box_height = box[4] * h
        box_center_x = box[1] * w 
        box_center_y = box[2] * h

        xmin = int(box_center_x - box_width/2)
        ymin = int(box_center_y - box_height/2)
        xmax = int(box_center_x + box_width/2)
        ymax = int(box_center_y + box_height/2)
        mask = np.zeros((h,w), np.uint8)
        mask[ymin:ymax, xmin:xmax] = 255 
        add_image = cv2.resize(image[ymin:ymax,xmin:xmax], (200,200))
        cv2.imshow(str(i),add_image)
        add_image = cv2.cvtColor(add_image, cv2.COLOR_BGR2HSV)
        hsv_hist = cv2.calcHist([add_image],[0,1],None,[360,256],[0,360,0,256])
        cv2.normalize(hsv_hist,hsv_hist,0,1, cv2.NORM_MINMAX)
        hist_list.append(hsv_hist)
        

# cv2.compareHist
for i,hist1  in enumerate(hist_list):
    for j, hist2 in enumerate(hist_list):
        
        ret1 = cv2.compareHist(hist1,hist2, cv2.HISTCMP_CORREL)
        ret2 = cv2.compareHist(hist1,hist2, cv2.HISTCMP_CHISQR)
        ret3 = cv2.compareHist(hist1,hist2, cv2.HISTCMP_INTERSECT)
        ret4 = cv2.compareHist(hist1,hist2, cv2.HISTCMP_BHATTACHARYYA)
        ret5 = cv2.compareHist(hist1,hist2, cv2.HISTCMP_HELLINGER)
        ret6 = cv2.compareHist(hist1,hist2, cv2.HISTCMP_KL_DIV)
        print("\t\t\t\t\t\t\t{} 번과 {} 번의 비교".format(i,j))
        print("-------------------------------------------------------------------------------------------------------------------------------------------")
        print(" 상관관계 : {:.4f} \t 카이제곱 : {:.4f} \t 인터섹션 : {:.4f} \t 바타챠랴 : {:.4f} \t 헬링거 : {:.4f} \t 콜백발산 : {:.4f} ".format(
            ret1,ret2,ret3,ret4,ret5,ret6
        ))
        print("-------------------------------------------------------------------------------------------------------------------------------------------")
cv2.waitKey(0)

  • calcHist로 계산한 결과를 normalize하여 0과 1의 값으로 보기 좋게 하였다 
  • 바타챠랴 거리와 헬링거는 같은 함수이다 

 

https://github.com/dldidfh/tistory_code/tree/master/multi%20image%20histogram

 

GitHub - dldidfh/tistory_code

Contribute to dldidfh/tistory_code development by creating an account on GitHub.

github.com

 

이전 장에서 학습한 HSV포맷을 기준으로 2D Histogram 분석을 진행하겠다 

 

1. 2D Histogram 

  • calcHist()  : 1D와 마찬가지로 calcHist 함수를 이용하여 HSV 포맷의 Hue와 Saturation의 histogram을 생성하겠다
from typing import no_type_check
import cv2
import numpy as np
from matplotlib import pyplot as plt

image = cv2.imread('test.jpg')
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
hist = cv2.calcHist(hsv_image,[0,1], None, [360,256], [0,360,0,256])
stack_hist = np.stack((hist,)*3,axis=-1)
print('image의 shape \t\t: ',image.shape)
print('hist의 shape \t\t: ',hist.shape)
print('stack hist의 shape \t: ',stack_hist.shape)

fig = plt.figure(figsize=(8,3))
plt.tight_layout()
plt.subplot((131)),plt.imshow(image)
plt.subplot((132)),plt.imshow(hist)
plt.subplot((133)),plt.imshow(stack_hist)

plt.show()

  • 기존 1D Histogram을 그릴 때와 변경된 부분은
    4번째 파라미터인 x축 간격이 2개의 값을 넘겨 x와 y축으로 변경되었고
    5번째 파라미터인 축의 범위또한 x, y 각각 0~360 , 0~256의 범위로 설정되었다 
  • 1D 일 때는 ply.plot으로 그래프를 그렸지만 2D에서는 imshow를 통해 보였다 그 이유는 1D에서는 각 그 값에 해당하는 픽셀의 개수를 표현했지만 2D에서는 H일 때 S 픽셀이 있나 를 표현해서 0, 1 의값이 리턴된다 그렇기 때문에 plot으로 2D를 그리면 0과 1만 표현된다 
  • hist의 결과값은 H, S 2개의 값이 리턴되기 때문에 shape이 (360,256)이 된다 위에 가운데 사진에 보다시피 imshow로 보였을 때 잘 구분이 되지 않는다 
  • 좀더 보기 쉽게 np.stack을 통해 RGB 같은 (360,256,3)의 shape으로 변경하였다
  • 여기서 의문이 생긴다 각 값이 0과 1로 표현됐는데 왜 결괏값으로 보이는 이미지는 왜 검은색과 하얀색으로 표현되나 하얀색은 255가 아닌가? matplotlib.pyplot에서 0과 1로 되어있는 데이터는 자동으로 0,255로 변경해준다 

  • 자 이제 나온 결과값을 해석해 보자 
  • 세로의 값은 Hue를 뜻한다 (100,110) 정도에 점이 찍혀있다 그 뜻은 Hue:110, Saturation:100이란 뜻이므로 약간 어두운 초록색 부분이 많다는 뜻이다 사진에서 숲의 영역을 뜻한다
  • 또한 H:160~250 S:150~200 까지의 부분에 점이 몰려있다 그 뜻은 밝은 하늘색이 다양한 밝기와 색상으로 존재한다는 뜻이다 사진에서 바다와 하늘을 뜻한다

 

다음장에서는 바운딩 박스를 읽어와

해당 바운딩박스 내에 객체들이

어떤 특성을 가지고 있는지 분석해 보겠다

이전 글에서는 히스토그램을 이용한 1D 분석에 대하여 작성하였다 

1D 히스토그램을 이용하면 Gray scale로 이미지의 밝기 분포를 볼 수 있고 

Color scale을 통해 R,G,B 값의 분포를 확인할 수 있다. 

 

이번 글에서는 2D Histogram을 이용한 채도( saturation ), 색상(Hue) 에 대하여 분석하기 앞서
HSV - Hue, Saturation, Value 포멧에 대하여 설명하겠다 

1. HSV Color space 

  • Hue - 색상 : 일반적인 색깔을 의미한다 . HSV 포멧에서는 각도로 표현된다 0º 부터 120º 마다 각각 Red Green Blue를 뜻한다 
  • Saturation - 채도 : 색의 순수성을 의미한다. 색이 짙다 흐리다로 표현된다 HSV 포멧에서 중심에서 바깥쪽으로 이동하면 채도가 높다 
  • Value - 명도 : 밝기를 의미한다. HSV 포멧에서 수직측의 깊이로 표현된다 값이 작을 수록 어둡다  
import cv2
import numpy as np
from matplotlib import pyplot as plt

origin_image = cv2.imread('puppy.jpg')
hsv_image = cv2.cvtColor(origin_image, cv2.COLOR_BGR2HSV)
image = np.concatenate((origin_image,hsv_image), axis=1)

print('원본 : \t',origin_image[0,0,:])
print('hsv : \t',hsv_image[0,0,:])

cv2.imshow('HSV', image)
cv2.waitKey(0)

BGR과 HSV 포멧의 imshow

  • BGR 포멧과 HSV 포멧을 imshow를 통해 보여봤다 hsv 포멧에서 이미지의 색상이 이상하게 변하는것은 현재 imread가 hsv 포멧을 생각안하고 BGR 포멧이라고 인식하여 읽어왔기 때문이다 

  • 각 원본 이미지와 hsv 포멧의 가장 왼쪽 위의 값을 찍어보면 다음과 같다 각각  B,G,R를 뜻하던 값이 H,S,V를 뜻하는 값으로 변경되었다
  • 원본에서 눈으로 봤을 때 하얀색과 가깝다 그렇기 때문에 BGR값이 모두 높은 220대가 나왔다 
  • HSV의 값은 120의 H 값은 위의 HSV 색상 공간 그림을 봤을 떄는 초록색의 방향이다 하지만 S의 값이 11이라는 낮은 값이 나와 채도는 의미가 낮아졌고 다음 V의 값이 232로 높은 값이 나와 밝은 값이라고 알 수 있다.
  • 강아지의 눈동자 부분의 값을 찍어보았다 BGR의 값이 전체적으로 낮아져 어두운 색상을 의미한다 
  • HSV의 경우 밝기를 뜻하는 V의 값이 낮아져 어두운 부분이라고 알 수 있다.

 

2. 이미지 값 조절 

  • 이미지에서 각 포멧의 값을 늘리거나 줄이는 방법을 이용하여 이미지의 특정 부분을 수정할 수 있다 
  • cv2.add(image, value_array), cv2.subtract(image, value_array)
  • 각 함수의 첫번쨰 파라미터는 변경할 이미지
  • 두번째 파라미터는 더하거나 빼거나 할 값이다
  • cv2.add, cv2.subtract에서는 0이하의 값들은 모두 0으로 취급하고 255이상의 값은 모두 255로 취급한다
import cv2
import numpy as np
from matplotlib import pyplot as plt

origin_image = cv2.imread('puppy.jpg')
hsv_image = cv2.cvtColor(origin_image, cv2.COLOR_BGR2HSV)

val = 100
array = np.full(hsv_image.shape, (0,0,val), dtype=np.uint8)

val_add_image = cv2.add(hsv_image, array)

print('BGR : \t',origin_image[55,116,:])
print('hsv : \t',hsv_image[55,116,:])
print('hsv 밝기(v) 증가 : \t',val_add_image[55,116,:])

val_add_image = cv2.cvtColor(val_add_image, cv2.COLOR_HSV2BGR)
image = np.concatenate((origin_image,val_add_image), axis=1)

cv2.imshow('add, subtract', image)
cv2.waitKey(0)

  • numpy 를 이용하여 이미지와 shape같은 배열을 생성한 뒤 이미지와 생성된 배열을 합한다 
  • 여기서 중요한 점은 생성한 배열의 dtype이다 
  • imread는 이미지 데이터를 불러올 때 기본 dtype이 uint8로 되어있다 그렇게 때문에 배열을 생성할 때도 dtype을 uint8로 설정해 줘야 더하거나 뺄때 type mismatch가 발생하지 않는다 
  • 강아지 사진을 불러와 HSV 포멧으로 변경한 후 HSV의 V값인 밝기를 100만큼 증가시켰다 
  • 증가시킨뒤 눈으로 확인하기 위해 다시 BGR 포멧으로 변경한 후 imshow 하였다
     
  • 강아지의 눈 부분의 값 변화를 체크해 보겠다
  • 보다시피 HSV 각각에 0, 0, 100의 값을 추가하였기 때문에 밝기 증가 hsv 에서 V의 값만 100이 증가하였다 

3. 특정 부분 이미지 값 변경

  • 이번에는 특정 부분의 밝기만 변경해 보자 
  • 위에서 말했듯이 cv2.add, cv2.subtract 에서는 0이하의 값은 모두 0으로 취급한다 
import cv2
import numpy as np
from matplotlib import pyplot as plt

origin_image = cv2.imread('puppy.jpg')
hsv_image = cv2.cvtColor(origin_image, cv2.COLOR_BGR2HSV)

val = 255

mask = hsv_image[:50,:100,:]

array = np.full(mask.shape, (0,0,val), dtype=np.uint8)

sub_mask_image = cv2.subtract(mask, array)

hsv_image[:50,:100,:] = sub_mask_image

val_sub_image = cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)

print("원본 bgr \t:",origin_image[0,0,:])
print("변환 bgr \t:",val_sub_image[0,0,:])

image = np.concatenate((origin_image,val_sub_image), axis=1)

cv2.imshow('add, subtract', image)
cv2.waitKey(0)

  • 원본 이미지에서 특정 부분을 선택하여 해당 부분의 HSV- V의 값을 255만큼 뺏다
  • 특정 부분을 다시 원본 이미지에 대입하였다 
  • V의 값이 0이 되었기 때문에 BGR 포멧으로 다시 변환한 후 해당 영역의 값을 확인 해보면 BGR 모두 0이 된것을 확인할 수 있다 

이미지 형태, 비디오 형태의 분석을 위한 기초 분석 

 

1. cv2.calcHist(images, channels, mask, histSize, ranges)

  • images : 분석 대상 이미지 (uint8 or float32) 
  • channels : 분석 채널 grayscale은 [0], color는 [0],[0,1] : 1 Blue 2:Green 3: Red
  • mask : 이미지의 분석 영역, None 은 전체 영역 
  • histSIze : 분석 값 x축의 간격 하나의 
  • range : x 축 범위  
import cv2
import os 
import numpy as np
from matplotlib import pyplot as plt

image = cv2.imread('test.jpg', cv2.IMREAD_COLOR)
image = cv2.resize(image, (512,512))
# image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 0 : Blue, 1: Green, 2: Red
blue_hist = cv2.calcHist([image],[0],None,[256],[0,256])
green_hist = cv2.calcHist([image],[1],None,[256],[0,256])
red_hist = cv2.calcHist([image],[2],None,[256],[0,256])

plt.subplot(111), 
plt.plot(blue_hist , color='b'), 
plt.plot(green_hist, color='g'), 
plt.plot(red_hist, color='r'),

cv2.imshow('test',image)

plt.xlim([0,256])

plt.show()

 

  • 위의 코드에서 주석처리된 부분에 cvt color 부분을 주석 처리한 이유는 opencv는 기본적으로 모든 색상 순서가 BGR로 되어있다. 그래서 변환 없이 바로 imshow로 보여주면 bgr을 bgr로 보여주기 때문에 cv2.imshow를 할 때는 ctvCOLOR를 이용할 이유가 없다. 하지만 plt.imshow()의 경우는 ctvCOLOR를 이용하여 보여줘야 원래 이미지대로 볼 수 있다. 

    같은 이미지이지만 왼쪽은 cv2.imshow()이고 오른쪽은 plt.imshow()이다 
  • 왼쪽은 cv2.imshow()를 한 이미지, 오른쪽은 plt.imshow()를 한 이미지 
  • cv2.imshow는 BGR로 불러와 내보낼 때도 BGR로 내보내지만 cv2로 읽어온 객체를 plt로 내보내면 RGB 순서로 돼있
  • 다는 가정하에 읽어오기 때문에 파란 이미지가 빨간색으로 표현된다 


    BGR과 RGB가 햇갈려서 테스트를 통해 확인하였다
  • cv2.calcHist의 2번째 파라미터는 0: blue, 1:green, 2:red 이다 
  • 각각 히스토그램의 shape를 찍어본 이유는 cv2.calcHist의 return value를 확인하기 위해서다. 각 1차원의 결괏값을 가진다 

  • 다음과 같이 hist의 return 값은 x와 y 축의 2차원 배열이다 shape은 (256,1) 로써 첫 차원에는 0~ 255까지의 pixel의 개수가 포함되어 있다
  • 왼쪽 사진은 위의 파란 이미지의 BGR 히스토그램 return값을 print 한것이다 
    Blue는 255 색상뿐이여서 0, 0, 0이라 나왔고 
    green과 red는 0 색상뿐이여서 배열의 첫 번째인 0 값이 해당 이미지의 넓이만큼 나왔다 ( 512x 512 = 262144 ) = blue는 모두 255이고 green, red는 모두 0이다라는 뜻 

2. calcHist mask 

  • calcHist에 파라미터인 mask를 추가하여 특정 영역의 히스토그램을 추출할 수 있다 
  • 차량의 레이블을 생성하여 읽어와 해당 레이블 영역의 히스토그램을 추출하여 보겠다 
    # print(image.dtype) : uint8
    mask = np.zeros(image.shape[:2], np.uint8)
    mask[box_ymin:box_ymax, box_xmin:box_xmax ] = 255
    
    mask_image = image[box_ymin:box_ymax, box_xmin:box_xmax]
    
    blue_hist = cv2.calcHist([image],[0],mask,[256],[0,256])
    green_hist = cv2.calcHist([image],[1],mask,[256],[0,256])
    red_hist = cv2.calcHist([image],[2],mask,[256],[0,256])
    
    plt.subplot(221),plt.imshow(image)
    plt.subplot(222),plt.imshow(testtestest)
    plt.subplot(223),plt.imshow(mask_image)
    plt.subplot(224), plt.plot(blue_hist, color='b'),plt.plot(green_hist, color='g'),plt.plot(red_hist, color='r')
    plt.xlim([-10,266])
    
    plt.show()​

  • 기존의 calcHist 함수에 None으로 돼있던 위치에 mask 값을 추가하였다 
  • mask 는 해당 이미지에서 추출할 영역을 선택한다 
  • 중요한 점은 cv2.imread()로 읽어온 이미지는 uint8형 자료이기 때문에 mask를 적용할 때 mask의 값 또한 uint8로 맞춰줘야 한다 그렇기 때문에 mask = np.zeros()를 할 때 dtype=np.uint8로 지정하였다 

 

소스코드https://github.com/dldidfh/tistory_code/blob/master/%ED%9E%88%EC%8A%A4%ED%86%A0%EA%B7%B8%EB%9E%A8/histogram_opencv.py

 

GitHub - dldidfh/tistory_code

Contribute to dldidfh/tistory_code development by creating an account on GitHub.

github.com

 

 

1. image 결합 

file_name = "checker_board.png"

image = cv2.imread(file_name, cv2.IMREAD_COLOR)
image2 = cv2.imread(file_name, cv2.IMREAD_GRAYSCALE)

image2 = np.stack((image2,)*3, axis=-1)

image = cv2.resize(image, (400,400))
image2 = cv2.resize(image2, (400,400))

image = np.concatenate((image, image2),axis=1)

  • opencv imread 는 numpy 객체로 이미지를 불러오게 된다 그렇기에 numpy 모듈을 사용하여 이미지를 수직, 수평으로 이어붙이기를 하여 원본이미지와 변경 이미지를 한눈에 보기 쉽게 펼칠 수 있다
  • grayscale 이미지와 color 이미지의 차원이 다르기 때문에 grayscale의 차원을 numpy의 stack을 이용하여 같은 이미지를 3번 중복하여 차원을 (x, x, 3) 으로 color 차원과 같게 맞추었다 
  • numpy.concatenate = 넘파이 객체를 잇는다
  • 첫 파라미터는 tuple 형태의 image 객체 2개이고 두 번째 파라미터는 수직 또는 수평을 결정하는 axis 이다
  • axis=0 은 수직(세로)으로 잇는다 axis=1은 수평(가로)으로 잇는다

 

2. 외곽선 검출

image = cv2.imread('milk.jpg', cv2.IMREAD_COLOR)
image2 = image.copy()
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

return_value, threshold_image = cv2.threshold(gray_image,0,255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)

contours, hierarchy = cv2.findContours(threshold_image, 
                        cv2.RETR_LIST, 
                        cv2.CHAIN_APPROX_NONE)

for contour in contours:
    cv2.drawContours(image,contour,-1,(0,0,255),2)
    
threshold_image = np.stack((threshold_image,)*3, axis=-1)
image = np.concatenate((image,threshold_image), axis=1)
image = np.concatenate((image,image2), axis=1)

    • cv2.threshold( image, min_value, max_value, TYPE )
    • 2차원 이미지만 입력으로 넣을 수 있기 떄문에 grayscale로 변환헀다
    • threshold를 해주는 이유는 외곽선을 찾을 때 색상정보를 단순하게 변경하여 (0과 255로) 외곽선을 더 찾기 쉽게 할 수 있다. 
    • threshold의 파라미터는 (image_source, min_value, max_value, TYPE)이다 
      TYPE의 종류
      cv2.THRESH_BINARY 
      cv2.THRESH_BINARY_INV 
      cv2.THRESH_TRUNC 
      cv2.THRESH_TOZERO 
      cv2.THRESH_TOZERO_INV 
      cv2. THRESH_OTSU : 이진화를 할 때 히스토그램 분석을 통해 자동으로 임계값을 찾아준다

https://opencv-python.readthedocs.io/en/latest/doc/09.imageThresholding/imageThresholding.html

 

    • cv2.findContours( image, mode, method)
    • 이진 이미지에서 외곽선을 자동으로 검출해 준다 
    • mode 의 종류 
      cv2.RETR_EXTERNAL : 외곽선중 가장 바깥쪽 선만 리턴한다 
      cv2.RETR_LIST : 모든 외곽선을 찾지만 계층관계는 구성하지 않는다 
      cv2.RETR_CCOMP : 모든 외곽선을 찾으며 2단계의 계층관계를 구성한다 
      cv2.RETR_TREE : 모든 외곽선을 찾으며 모든 계층관계를 구성한다 
      여기서 계층이란 검출된 외곽선 안에 있는 또다른 외곽선을 뜻한다 
    • method 의 종류
      cv2.CHAIN_APPROX_NONE : 모든 point를 저장
      cv2.CHAIN_APPROX_SIMPLE : 4개의 point만을 저장하여 메모리를 절약 
      cv2.CHAIN_APPROX_TC89_L1 : Teh_Chin 연결 근사 알고리즘 L1버전을 적용하여 point 개수를 줄임 
      cv2.CHAIN_APPROX_TC89_KCOS : Teh_Chin 연결 근사 알고리즘 KCOS 버전을 적용하여 point 개수를 줄임 
      return 값으로 오는 contours의 shape를 찍어보면 각각 method마다 shape이 다른것을 볼 수 있다. 
      각각 method마다의 contours[-1]의 draw 와 shape 

CNN 모델에 학습을 시키기 전 하고자 하는 목표에 맞게 이미지를 전처리하여 학습을 시키면 더 좋은 효과를 볼 수 있다. 

이미지에서 필요없는 부분이 있는지, 특정 영역만을 추출해도 되는지 개발자의 주관적 생각으로 판단하여야 한다. 

 

여기서 주관적이라는 말이 매우 거슬리지만 이미지란 참 복잡하다. 

 

만약 사람을 검출하고 싶은데 사람의 머리카락이 검은색이라 검은색만을 추출했더니 옆에 있던 맨홀이 나온다던지 할 수 있다. 

 

나도 computer vision에 대하여 잘 알지 못하지만 이제 시작함으로써 내용정리를 시작한다. 

 

 

1. imread

image = cv2.imread("FILE_PATH", cv2.IMREAD_COLOR)

  • imread = image read 이미지를 읽는 함수이다 
  • 첫 파라미터로 파일의 경로(image.jpg)를 입력하고 두 번째 파라미터로 어떤 색상으로 읽어올 건지에 대해 입력한다. 
    • cv2.IMREAD_COLOR : 이미지 파일을 Color로 읽어들입니다. 투명한 부분은 무시되며, Default값입니다.
    • cv2.IMREAD_GRAYSCALE : 이미지를 Grayscale로 읽어 들입니다. 실제 이미지 처리 시 중간단계로 많이 사용합니다.
    • cv2.IMREAD_UNCHANGED : 이미지파일을 alpha channel까지 포함하여 읽어 들인다. 하지만 이미지에 alpha channel에 대한 정보가 있어야 다르다 위에 사진과 같이 COLOR와 UNCHANGED가 똑같다

 

2. imshow, waitKey, destroyAllWindows

cv2.imshow("window_name", image)
key = cv2.waitKey(0)
cv2.destroyAllWindows()
  • imshow = image show 이미지를 윈도우 창에 보여주는 함수이다
  • 첫 파라미터로 띄어질 윈도우의 이름을 설정하고 두 번째 파라미터로 어떤 이미지를 띄울것인지 입력한다
  • waitKey와 destroyAllWindows 는 함께 쌍으로 자주 쓰이는 함수이다. waitKey는 사용자의 키보드 입력이 올때까지 대기하고 destroyAllWindows는 Opencv에서 띄운 모든 윈도우를 종료한다
  • 위의 코드를 보면 imshow 이후 cv2.waitKey(0)에 의해서 그 다음 문단으로 가지않고 sleep()처럼 대기된다. 여기서 waitKey의 함수안에 파라미터로 0이 있는데 이것은 무한대로 기다린다는 의미이다. 만약 사용자의 키보드 입력이 들어오면 waitKey 함수는 끝나게되고 뒤에있는 destroyAllWindows 함수에 의해 띄어졌던 이미지가 종료된다

 

3. imwrite

cv2.imwrite("test.jpg", image)

  • imwrite = image write 이미지를 저장하는 함수이다.
  • 첫 파라미터로 파일이 저장될 경로(test.jpg)를 설정한다 두 번째 파라미터로 저장할 이미지변수를 입력한다.

 

4. cvtColor

image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

  • cvtColor = convert color 이미지의 색상을 변환시키는 함수이다. 
  • 첫 파라미터로 변경할 이미지변수를 입력하고 두 번째 파라미터로 변경할 색상을 입력한다 
  • 변환 가능한 색상 종류에는 BGR2RGB, HSV, GRAY, LAB, LUV, XYZ 등 여러가지가 존재한다 

 

5. resize

image = cv2.resize(image,(608,608))

  • resize = 말그대로 사이즈를 변경한다 
  • 첫 파라미터로 변경할 이미지변수를 입력하고 두 번쨰 파라미터로 변경할 사이즈를 tuple형태로 입력한다
  • 사이즈를 더 작게, 더 크게 변경이 가능하다 하지만 원본의 비율과 다르게 변환하면 형태가 변경될 수 있고 더 작게 변경한다면 비율이 안맞는 부분에 자동으로 padding이 들어가게 된다

 

 

이번에는 기초적으로 이미지를 읽고 이미지 색을 변경하는 법을 정리하였다 

다음에는 numpy를 이용해서 이미지를 합쳐서 보는 법과 간단한 이미지 처리 기법에 대하여 정리하겠다

+ Recent posts