OpenCV-Python Study documentation!¶
이 문서는 OpenCV-Python Tutorial 을 바탕으로 작성이 되었습니다.
Contents:
이미지 다루기¶
Goal¶
- 이미지 파일을 읽고, 보고, 저장하는 방법에 대해서 알아봅니다.
- 관련 함수인
cv2.imread()
,cv2.imshow()
,cv2.imwrite()
에 대해서 알아 봅니다.
이미지 읽기¶
우선 openCV모듈을 import합니다.:
>>> import cv2
cv2.imread()
함수를 이용하여 이미지 파일을 읽습니다. 이미지 파일의 경로는 절대/상대경로가 가능합니다.
>>> img = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)
-
cv2.
imread
(fileName, flag)¶ 이미지 파일을 flag값에 따라서 읽어들입니다.
Parameters: - fileName (str) – 이미지파일의 경로
- flag (int) – 이미지 파일을 읽을 때의 Option.
Returns: image객체 행렬
Return type: numpy.ndarray
이미지 읽기의 flag는 3가지가 있습니다.
cv2.IMREAD_COLOR
: 이미지 파일을 Color로 읽어들입니다. 투명한 부분은 무시되며, Default값입니다.cv2.IMREAD_GRAYSCALE
: 이미지를 Grayscale로 읽어 들입니다. 실제 이미지 처리시 중간단계로 많이 사용합니다.cv2.IMREAD_UNCHANGED
: 이미지파일을 alpha channel까지 포함하여 읽어 들입니다.
Note
3개의 flag대신에 1, 0, -1을 사용해도 됩니다.
img값은 numpy의 ndarray type입니다. numpy는 python에서 수학적 처리를 위한 모듈로 openCV에서도 많이 사용됩니다. img가 어떤 형태의 행렬인지 확인을 해보려면 아래와 같이 입력합니다.
>>> img.shape
(206, 207, 3)
이미지는 3차원 행렬로 return이 됩니다. 206은 행(Y축), 207은 열(X축), 3은 행과 열이 만나는 지점의 값이 몇개의 원소로 이루어져 있는지를 나타납니다. 위 값의 의미는 이미지의 사이즈는 207 X 206이라는 의미입니다.
그렇다면 3은 어떤 의미일까요. 바로 색을 표현하는 BGR값입니다. 일반적으로 RGB로 많이 나타내는데, openCV는 B(lue), G(reen), R(ed)로 표현을 합니다.
다음은 읽은 이미지를 보는 방법에 대해서 알아보겠습니다.
이미지 보기¶
cv2.imshow()
함수는 이미지를 사이즈에 맞게 보여줍니다.
>>> c22.imshow('image', img)
>>> cv2.waitKey(0)
>>> cv2.destroyAllWindows()
-
cv2.
imshow
(title, image)¶ 읽어들인 이미지 파일을 윈도우창에 보여줍니다.
Parameters: - title (str) – 윈도우 창의 Title
- image (numpy.ndarray) –
cv2.imread()
의 return값
cv2.waitKey()
는 keyboard입력을 대기하는 함수로 0이면 key입력까지 무한대기이며 특정 시간동안 대기하려면 milisecond값을 넣어주면 됩니다.
cv2.destroyAllWindows()
는 화면에 나타난 윈도우를 종료합니다. 일반적으로 위 3개는 같이 사용됩니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import cv2
fname = 'lena.jpg'
original = cv2.imread(fname, cv2.IMREAD_COLOR)
gray = cv2.imread(fname, cv2.IMREAD_GRAYSCALE)
unchange = cv2.imread(fname, cv2.IMREAD_UNCHANGED)
cv2.imshow('Original', original)
cv2.imshow('Gray', gray)
cv2.imshow('Unchange', unchange)
cv2.waitKey(0)
cv2.destroyAllWindows()
|

Sample Image
아래는 각 flag에 따른 이미지 결과입니다.

Original

Grayscale

Unchange
이미지 저장하기¶
cv2.imwrite()
함수를 이용하여 변환된 이미지나 동영상의 특정 프레임을 저장합니다.
>>> cv2.imwrite('lenagray.png', gray)
-
cv2.
imwrite
(fileName, image)¶ image파일을 저장합니다.
Parameters: - fileName (str) – 저장될 파일명
- image – 저장할 이미지
Sample Code
이미지를 읽어서 esc키를 누르면 종료, ‘s’ key를 누르면 grayscale이미지가 저장이 되는 Sample입니다.
cv2.waitKey()
사용을 잘 보시기 바랍니다.:
import cv2
img = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('image',img)
k = cv2.waitKey(0)
if k == 27: # esc key
cv2.destroyAllWindow()
elif k = ord('s'): # 's' key
cv2.imwrite('lenagray.png',img)
cv2.destroyAllWindow()
Warning
64bit OS의 경우 k = cv2.waitKey(0) & 0xFF로 bit연산을 수행해야 합니다.
Matplotlib 사용하기¶
Matplotlib는 다양한 plot기능을 가진 Python Plot Library입니다. 이미지를 zoom하거나 하나의 화면에 여러개의 이미지를 보고자 할 때 유용합니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 | #-*- coding:utf-8 -*-
import cv2
from matplotlib import pyplot as plt # as는 alias 적용시 사용
img = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)
plt.imshow(img)
plt.xticks([]) # x축 눈금
plt.yticks([]) # y축 눈금
plt.show()
|
Result

Matplotlib Result
그런데 결과가 좀 이상합니다. 원본은 붉은색 계열인데, 결과는 파란색 계열로 나타납니다.
이유는 openCV는 BGR로 사용하지만, Matplotlib는 RGB로 이미지를 보여주기 때문입니다.
즉 결과 값은 3차원 배열의 값중 첫번째와 세번째 배열값을 서로 바꿔 주여야 합니다.
그럼 변경된 Sample을 보시기 바랍니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 | #-*- coding:utf-8 -*-
import cv2
from matplotlib import pyplot as plt # as는 alias 적용시 사용
img = cv2.imread('lena.jpg', cv2.IMREAD_COLOR)
b, g, r = cv2.split(img) # img파일을 b,g,r로 분리
img2 = cv2.merge([r,g,b]) # b, r을 바꿔서 Merge
plt.imshow(img2)
plt.xticks([]) # x축 눈금
plt.yticks([]) # y축 눈금
plt.show()
|
Result

RGB값으로 변경한 결과
영상 다루기¶
Goal¶
- 동영상을 읽고, 보여주고, 저장할 수 있다.
- 관련 함수인
cv2.VideoCapure()
,cv2.VideoWriter()
에 대해서 알 수 있다.
Camera로 부터 영상 재생¶
Camera로부터 영상을 읽어, 화면에 보옂기 위해서 아래와 같은 순서로 진행을 합니다.
- VideoCapture Object를 생성합니다. 변수로는 camera device index나 동영상 파일명을 넘겨줍니다. 일반적으로 0 이면 Camera와 연결이 됩니다.
- Loop를 돌면서 frame을 읽어 들입니다.
- 읽은 frame에 대해서 변환작업을 수행한 후, 화면에 보여줍니다.
- 영상 재생이 끝나면, VideoCapure Object를 release하고 window를 닫습니다.
아래 예제는 동영상을 읽어 grayscale로 변환 후 재생하는 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # -*-coding: utf-8 -*-
import cv2
# cap 이 정상적으로 open이 되었는지 확인하기 위해서 cap.isOpen() 으로 확인가능
cap = cv2.VideoCapture(0)
# cap.get(prodId)/cap.set(propId, value)을 통해서 속성 변경이 가능.
# 3은 width, 4는 heigh
print 'width: {0}, height: {1}'.format(cap.get(3),cap.get(4))
cap.set(3,320)
cap.set(4,240)
while(True):
# ret : frame capture결과(boolean)
# frame : Capture한 frame
ret, frame = cap.read()
if (ret):
# image를 Grayscale로 Convert함.
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow('frame', gray)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
|
File로 부터 영상 재생¶
File로 부터 동영상 재생도 Camera에서 영상 재생과 동일합니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 | import cv2
cap = cv2.VideoCapture('vtest.avi')
while(cap.isOpened()):
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow('frame',gray)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
|
Note
동영상 재생시에는 해당 동영상의 Codec이 설치되어 있어야 합니다.
영상 저장¶
영상을 저장하기 위해서는 cv2.VideoWriter
Object를 생성해야 합니다.
-
cv2.
VideoWriter
(outputFile, fourcc, frame, size)¶ 영상을 저장하기 위한 Object
Parameters: - outputFile (str) – 저장될 파일명
- fourcc – Codec정보. cv2.VideoWriter_fourcc()
- frame (float) – 초당 저장될 frame
- size (list) – 저장될 사이즈(ex; 640, 480)
fourcc정보는 cv2.VideoWriter_fourcc('M','J','P','G')
또는 cv2.VideoWriter_fourcc(*'MJPG)
와 같이 표현할 수 있습니다. 각 OS마다 지원하는 codec 다릅니다.(Windows는 DIVX)
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # -*-coding: utf-8 -*-
import cv2
cap = cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*'DIVX')
out = cv2.ViewoWriter('output.avi', fourcc, 25.0, (640,480))
while (cap.isOpend()):
ret, frame = cap.read()
if ret:
# 이미지 반전, 0:상하, 1 : 좌우
frame = cv2.flip(frame, 0)
out.write(frame)
cv2.imshow('frame', frame)
if cv2.waitKey(0) & 0xFF == ord('q'):
break
else:
break
cap.release()
out.release()
cv2.destroyAllWindows()
|
도형 그리기¶
Goal¶
- 다양한 모양의 도형을 그릴 수 있다.
cv2.line()
,cv2.circle()
,cv2.rectangle()
,cv2.putText()
사용법을 알 수 있다.
도형그리기는 동영상이나 이미지에서 Match가 되는 영역을 찾은 후에 사용자가 인식하기 쉽게 표시하는 목적으로 사용됩니다.
Line 그리기¶
Start와 End 점을 연결하여 직선을 그립니다.
-
cv2.
line
(img, start, end, color, thickness)¶ Parameters: - img – 그림을 그릴 이미지 파일
- start – 시작 좌표(ex; (0,0))
- end – 종료 좌표(ex; (500. 500))
- color – BGR형태의 Color(ex; (255, 0, 0) -> Blue)
- thickness (int) – 선의 두께. pixel
Sample Code
1 2 3 4 5 6 7 8 9 10 | import numpy as np
import cv2
#모두 0으로 되어 있는 빈 Canvas(검정색)
img = np.zeros((512, 512, 3), np.uint8)
img = cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
사각형 그리기¶
top-left corner와 bottom-right corner점을 연결하는 사각형을 그립니다.
-
cv2.
rectangle
(img, start, end, color, thickness)¶ Parameters: - img – 그림을 그릴 이미지
- start – 시작 좌표(ex; (0,0))
- end – 종료 좌표(ex; (500. 500))
- color – BGR형태의 Color(ex; (255, 0, 0) -> Blue)
- thickness (int) – 선의 두께. pixel
Sample Code
img = cv2.rectangle(img, (384, 0), (510, 128), (0,255,0), 3)
원 그리기¶
-
cv2.
circle
(img, center, radian, color, thickness)¶ Parameters: - img – 그림을 그릴 이미지
- center – 원의 중심 좌표(x, y)
- radian – 반지름
- color – BGR형태의 Color
- thickness – 선의 두께, -1 이면 원 안쪽을 채움
Sample Code
img = cv2.circle(img, (447,63), 63, (0,0,255), -1)
타원 그리기¶
-
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]]) → img
Parameters: - img – image
- center – 타원의 중심
- axes – 중심에서 가장 큰 거리와 작은 거리
- angle – 타원의 기울기 각
- startAngle – 타원의 시작 각도
- endAngle – 타원이 끝나는 각도
- color – 타원의 색
- thickness – 선 두께 -1이면 안쪽을 채움
Sample Code
img = cv2.ellipse(img, (256,256), (100,50), 0, 0, 180, 255, -1)
Polygon 그리기¶
-
cv2.
polylines
(img, pts, isClosed, color, thickness)¶ Parameters: - img – image
- pts (array) – 연결할 꼭지점 좌표
- isClosed – 닫흰 도형 여부
- color – Color
- thickness – 선 두께
Sample Code
pts = np.array([[10,5], [20,30], [70,20], [50,10]], np.int32) # 각 꼭지점은 2차원 행렬로 선언
# 이미지에 표현하기 위해 3차원 행렬로 변환. 변환이전과 이후의 행렬 갯수는 동일해야함.
# -1은 원본에 해당하는 값을 그대로 유지.
pts = pts.reshape((-1, 1, 2))
img = cv2.polylines(img, [pts], True, (0,255,255))
이미지에 Text 추가¶
-
cv2.
putText
(img, text, org, font, fontSacle, color)¶ Parameters: - img – image
- text – 표시할 문자열
- org – 문자열이 표시될 위치. 문자열의 bottom-left corner점
- font – font type. CV2.FONT_XXX
- fontSacle – Font Size
- color – fond color
Sample Code
cv2.putText(img, 'OpenCV', (10,500), cv2.FONT_HERSHEY_SIMPLEX, 4, (255,255,255), 2)
지금까지 예제로 보여 주웠던 Sample Code를 실행시키면 아래와 같은 결과가 나옵니다.

Mouse로 그리기¶
Goal¶
- Mouse Event의 적용 방법에 대해서 알 수 있다.
cv2.setMouseCallback()
함수에 대해서 알 수 있다.
작동방법¶
OpenCV에는 이미 Mouse Event의 종류에 대해서 사전 정의가 되어 있습니다. 확인을 하기 위해서 Python Terminal에서 아래와 같이 입력해보시기 바랍니다.
>>> import cv2
>>> events = [i for i in dir(cv2) if 'EVENT' in i]
>>> print events
실행을 하면 다양한 Mouse Event의 종류를 알 수 있습니다. 어떤 종류의 Event인지는 이름을 보면 쉽게 알 수 있습니다.:
'EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEHWHEEL', 'EVENT_MOUSEMOVE', 'EVENT_MOUSEWHEEL', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP'
Mouse Event를 확인하고 Callback을 호출하는 함수가 cv2.setMouseCallback()
입니다.
-
cv2.
setMouseCallback
(windowName, callback, param=None)¶ Parameters: - windowName – windowName
- callback – callback함수. callback함수에는 (event, x, y, flags, param)가 전달 됨.
- param – callback함수에 전달되는 Data
간단한 Demo¶
아래 Demo는 화면에 Double-Click을 하면 원이 그려지는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import cv2
import numpy as np
# callback함수
def draw_circle(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDBLCLK:
cv2.circle(img,(x,y), 100,(255,0,0),-1)
# 빈 Image 생성
img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)
while(1):
cv2.imshow('image', img)
if cv2.waitKey(0) & 0xFF == 27:
break
cv2.destroyAllWindows()
|
Advanced Demo¶
다음은 마우스를 누른 상태에서 이동시 원 또는 사각형을 그리는 Demo입니다. 이 예제는 향후 대상 추적이나 이미지 Segmentaion시 응용될 수 있습니다.(ex; 이미지에서 대상을 마우스로 선택하고 동일한 대상을 찾는 경우)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
drawing = False #Mouse가 클릭된 상태 확인용
mode = True # True이면 사각형, false면 원
ix,iy = -1,-1
# Mouse Callback함수
def draw_circle(event, x,y, flags, param):
global ix,iy, drawing, mode
if event == cv2.EVENT_LBUTTONDOWN: #마우스를 누른 상태
drawing = True
ix, iy = x,y
elif event == cv2.EVENT_MOUSEMOVE: # 마우스 이동
if drawing == True: # 마우스를 누른 상태 일경우
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(255,0,0),-1)
else:
cv2.circle(img,(x,y),5,(0,255,0),-1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False; # 마우스를 때면 상태 변경
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(255,0,0),-1)
else:
cv2.circle(img,(x,y),5,(0,255,0),-1)
img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_circle)
while True:
cv2.imshow('image', img)
k = cv2.waitKey(1) & 0xFF
if k == ord('m'): # 사각형, 원 Mode변경
mode = not mode
elif k == 27: # esc를 누르면 종료
break
cv2.destroyAllWindows()
|
Trackbar¶
Goal¶
- trackbar와 OpenCV의 연동 방법에 대해서 알 수 있다.
cv2.getTrackbarPos()
,cv2.createTrackbar()
함수에 대해서 알 수 있다.
Demo¶
Trackbar에 대해서는 간단한 Demo를 보면서 설명하겠습니다. Demo의 내용은 4개의 Tranckbar로 구성이 되어 있습니다. 3개는 RGB의 값을 표현하며, 나머지 하나는 초기화 하는 기능입니다.
Demo에서 사용하는 cv2.getTrackbarPos()
, cv2.createTrackbar()
함수에 대해서 알아 보겠습니다.
-
cv2.
createTrackbar
(trackbarName, windowName, value, count, onChange)¶ Parameters: - trackbarName – trackbar Name
- windowName – Named Window
- value (int) – Trackbar가 생성될 때 초기 값
- count – Tracbar의 Max값. Min값은 항상 0
- onChange – Slide값이 변경될 때 호출 되는 Callback함수. 전달되는 Paramter는 trackbar Position
-
cv2.
getTrackbarPos
(trackbarName, windowName)¶ Parameters: - trackbarName – trackbar Name
- windowName – Trackbar가 등록된 Named Window
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #-*- coding: utf-8 -*-
import cv2
import numpy as np
def nothing(x):
pass
img = np.zeros((300,512,3), np.uint8)
cv2.namedWindow('image')
# trackbar를 생성하여 named window에 등록
cv2.createTrackbar('R', 'image', 0, 255, nothing)
cv2.createTrackbar('G', 'image', 0, 255, nothing)
cv2.createTrackbar('B', 'image', 0, 255, nothing)
switch = '0:OFF\n1:On'
cv2.createTrackbar(switch, 'image', 1, 1, nothing)
while(1):
cv2.imshow('image', img)
if cv2.waitKey(1) & 0xFF == 27:
break
r = cv2.getTrackbarPos('R','image')
g = cv2.getTrackbarPos('G', 'image')
b = cv2.getTrackbarPos('B', 'image')
s = cv2.getTrackbarPos(switch, 'image')
if s == 0:
img[:] = 0 # 모든 행/열 좌표 값을 0으로 변경. 검은색
else:
img[:] = [b,g,r] # 모든 행/열 좌표값을 [b,g,r]로 변경
cv2.destroyAllWindows()
|

Basic Operation¶
Goal¶
- 개별 Pixcel에 접근하고, 수정할 수 있다.
- 이미지의 기본 속성을 확인 할 수 있다.
- 이미지의 ROI(Region of Image)를 설정할 수 있다.
- 이미지를 분리하고, 합칠 수 있다.
Pixel Value¶
일반적으로 이미지를 Load하면 3차원 행렬형태로 생성이 됩니다. (100,300,3) 이러한 형태로 (행, 열, 색정보)를 의미합니다.
>>> import cv2
>>> import numpy as np
>>> img = cv2.imread('lena.jpg')
위와 같이 Load후에 특정 pixel을 값에 접근을 하려면 아래와 같이 접근할 수 있습니다.
>>> px = img[100,200]
>>> print px
[157 100 190]
즉, 100(행), 200(열)을 색값이 157(B), 100(G), 190(R) 인 것을 확인 할 수 있습니다. 만일 Blue값만을 확인하고 싶을 경우에는 아래와 같이 입력합니다.
>>> b = img[100,200,0]
>>> print b
157
세번째 Array값 중 0번째는 Blue, 1번째는 Green, 2번째는 Red를 의미합니다. 위와 같은 방법으로 특정 pixel의 값을 변경할 수도 있습니다.
>>> img[100,200] = [255, 255, 255]
위와 같이 입력을 하면 100,200의 색 값을 흰색으로 변경할 수 있습니다.
일반적으로 특정 Pixel값을 변경하기 위해서는 아래와 같이 사용합니다.(Numpy 사용 방법)
>>> img.item(10,10,2) # Red값
59
>>> img.itemset((10,10,2), 100) #Red값을 100으로 변경
>>> img.item(10,10,2)
100
이미지의 기본 속성¶
이미지를 Load한 후에 해당 이미지의 기본적인 정보를 확인해야 합니다. 행과 열의 갯수가 몇개인지, 몇개의 Channel로
구성이되어 있는지가 기본입니다.
이를 확인하기 위해서 img.shape
를 사용하면 tuple형태로 (행, 열, channel) 정보를 Return합니다.
>>> img.shape
(206, 207, 3)
Note
이미지가 Grayscale의 경우에는 행과 열만 Return이 됩니다.
전체 pixcel수의 확인은 img.size
로 확인이 가능합니다.
>>> img.size
42642
이미지의 Datatype은 img.dtype
으로 확인이 가능합니다.
>>> img.dtype
dtype('uint8')
이미지 ROI¶
이미지 작업시에는 특정 pixel단위 보다는 특정 영역단위로 작업을 하게 됩니다. 이것을 Region of Image(ROI)라고 합니다. ROI 설정은 Numpy의 indexing방법을 사용합니다. 만일 아래와 같이 특정 영역에 어떤 물체가 있다는 것을 알고 있으면 그 영역을 설정해서 Copy를 할 수도 있습니다.
>>> img = cv2.imread('baseball-player.jpg')
>>> ball = img[409:454, 817:884] # img[행의 시작점: 행의 끝점, 열의 시작점: 열의 끝점]
>>> img[470:515,817:884] = ball # 동일 영역에 Copy
>>> cv2.imshow('img', img)
>>> cv2.waitKey(0)
>>> cv2.destroyAllWindows()

Original

Result
이미지 Channels¶
Color Image는 3개의 채널 B,G,R로 구성이 되어 있습니다. 이것을 각 채널별로 분리할 수 있습니다.
>>> b, g, r= cv2.split(img)
>>> img = cv2.merge((r,g,b))
또는 Numpy indexing 접근 방법으로 표현할 수도 있습니다
>>> b = img[:,:,0] # 0 : Blue, 1 : Green, 2 : Red
Warning
cv2.split()
함수는 비용이 많이 드는 함수입니다. 가능하다면 Numpy indexing방법을 사용하는 효율적입니다.
특정 Channel의 값을 변경하려면 아래와 같이 입력합니다.
>>> img[:,:,2] = 0 #Red Channel을 0으로 변경. Red 제거하는 효과.
이미지 연산¶
Goal¶
- 이미지의 더하기, 빼기, 비트연산에 대해서 알 수 있다.
cv2.add()
,cv2.addWdighted()
함수에 대해서 알 수 있다.
이미지 더하기¶
cv2.add()
함수를 사용하는 방법과cv2.add()
는 Saturation 연산을 하고, Numpy는 modulo 연산을 합니다.Note
예제로 OpenCV와 Numpy의 결과가 어떻게 다른지 보겠습니다.

Original 1

Original 2

OpenCV Add

Numpy Add
이미지 Blending¶
이미지를 서로 합칠 때 가중치를 두어 합치는 방법입니다.
a 값이 0 -> 1로 변함에 따라서 이미지가 전환된다.
아래 예제는 trackbar의 값을 조정함에 따라서 이미지가 변환되는 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
img1 = cv2.imread('images/flower1.jpg')
img2 = cv2.imread('images/flower2.jpg')
def nothing(x):
pass
cv2.namedWindow('image')
cv2.createTrackbar('W', 'image', 0, 100, nothing)
while True:
w = cv2.getTrackbarPos('W','image')
dst = cv2.addWeighted(img1,float(100-w) * 0.01, img2,float(w) * 0.01,0)
cv2.imshow('dst', dst)
if cv2.waitKey(1) &0xFF == 27:
break;
cv2.destroyAllWindows()
|
Result

비트연산¶
비트연산은 AND, OR, NOT, XOR연산을 말한다. 비트연산은 이미지에서 특정 영역을 추출할 때 유용하게 사용된다. 예를 들면 이미지에서 바탕을 제거하고, 2개의 이미지를 합치는 경우입니다.
아래는 OpenCV Logo에서 바탕을 제거하고, 이미지에 추가하는 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
img1 = cv2.imread('images/logo.png')
img2 = cv2.imread('images/lena.jpg')
# 삽입할 이미지의 row, col, channel정보
rows, cols, channels = img1.shape
# 대상 이미지에서 삽입할 이미지의 영역을 추출
roi = img2[0:rows, 0:cols]
#mask를 만들기 위해서 img1을 gray로 변경후 binary image로 전환
#mask는 logo부분이 흰색(255), 바탕은 검은색(0)
#mask_inv는 logo부분이 검은색(0), 바탕은 흰색(255)
img2gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
#bitwise_and 연산자는 둘다 0이 아닌 경우만 값을 통과 시킴.
#즉 mask가 검정색이 아닌 경우만 통과가 되기때문에 mask영역 이외는 모두 제거됨.
#아래 img1_fg의 경우는 bg가 제거 되고 fg(logo부분)만 남게 됨.
#img2_bg는 roi영역에서 logo부분이 제거되고 bg만 남게 됨.
img1_fg = cv2.bitwise_and(img1, img1, mask=mask)
img2_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
#2개의 이미지를 합치면 바탕은 제거되고 logo부분만 합쳐짐.
dst = cv2.add(img1_fg, img2_bg)
#합쳐진 이미지를 원본 이미지에 추가.
img2[0:rows, 0:cols] = dst
cv2.imshow('res', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
Result

참고로 이미지 처리시 많이 사용되는 cv2.threshold()
함수에 대해서 다음 장에서 알아보겠습니다.
이미지 Processing¶
Goal¶
- 디지털 영상의 표현 방법에 대해서 알 수 있다.
- Color-space중 Binary Image, Grayscale, RGB, HSV에 대해서 알 수 있다.
- 각 Color-space 변환 방법에 대해서 알 수 있다.
- 동영상에서 간단한 Object Tracking을 할 수 있다.
cv2.cvtColor()
,cv2.inRange()
함수에 대해서 알 수 있다.
Digital Image¶

Pixel 표현 방법
Digital Image의 유형¶
Binary Image¶
Binary Image는 pixel당 1bit로 표현하는 영상을 의미합니다. 즉 흰색과 검은색으로만 표현이 되는 영상입니다.

Binary Image
위 이미지에서 좌측 상단의 이미지가 원본 이미지 입니다. 원본 이미지를 thresholding처리를 하여 binary image로 변환한 결과가 우측 상단의 이미지 입니다. 우측 하단의 이미지는 화면에 표현할 때 사용하는 방법으로 binary image의 밀도를 조절하여 밝기를 표현 하는 방법입니다. 이를 dithering 이라고 합니다.
Grayscale Image¶
Grayscale Image는 Pixel당 8bit, 즉 256단계의 명암(빛의 세기)을 표현할 수 있는 이미지입니다.

Grayscale Image(출처 위키피디아 )
Color Image¶
Color 이미지는 pixel의 색을 표현하기 위해서 pixel당 24bit를 사용합니다. 총 16,777,216 가지의 색을 표현할 수 있습니다. 이것을 일반적으로 True color image라고 합니다. pixel은 RGB 각각을 위해서 8bit를 사용하게 됩니다. OpenCV에서는 BGR로 표현을 하기 때문에 Blue->(255,0,0), Green->(0,255,0), Red->(0,0,255), White->(255,255,255), Black->(0,0,0)으로 표현할 수 있습니다.
각 pixel당 3Byte를 사용하기 때문에 용량이 큽니다. 이를 해결하기 위해서 lookup table을 사용하여, 해당 pixel에는 index만 을 저장하기도 합니다.

Indexed Color Image
HSV Color-space¶
이미지 처리에서 가장 많이 사용되는 형태의 Color 모델입니다. 하나의 모델에서 색과 채도, 명도를 모두 알 수 있습니다. 원뿔 형태의 모델로 표현이 됩니다.

HSV 모델
HSV의 의미는 다음과 같습니다.
- H(ue) : 색상. 일반적인 색을 의미함. 원추모형에서 각도로 표현이 됨.(0: Red, 120도 : Green, 240: Blue)
- S(aturation) : 채도. 색읜 순수성을 의미하며 일반적으로 짙다, 흐리다로 표현이 됨. 중심에서 바깥쪽으로 이동하면 채도가 높음.
- V(alue) : 명도. 색의 밝고 어두운 정도. 수직축의 깊이로 표현. 어둡다 밝다로 표현이 됨.
Color-space 변환¶
OpenCV에는 150여가지 변환 방법이 있습니다. 아래는 변환 방법을 확인하는 코드 입니다.
>>> import cv2
>>> flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
>>> print flags
그 중에 서 많이 사용되는 BGR<->Gray, BGR<->HSV에 대해서 알아 보겠습니다.
변환을 위해서 사용하는 함수는 cv2.cvtColor()
함수 입니다.
-
cv2.
cvtColor
(src, code)¶ Params src: image Params code: 변환 코드
BGR->Grayscale로 변환하기 위해서는 cv2.COLOR_BGR2GRAY 를 사용합니다. BGR->HSV로 변환하기 위해서는 cv2.COLOR_BGR2HSV 를 사용합니다.
Note
Hue는 [0,179], Saturation은 [0,255], Value는 [0,255]로 표현이 됩니다.
Object Tracking¶
다음 예제는 단순한 Object Tracking입니다. 영상에서 파란색 부분을 찿아서 binary image로 보여줍니다.
- Video로 부터 Frame을 읽어 들입니다.
- frame을 HSV로 변환을 합니다.
- 변환한 이미지에서 blue 영역을 찾아서 mask를 생성합니다.
- frame에 mask를 적용하여 이미지를 보여 줍니다.
Code는 아래와 같습니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #-*- coding: utf-8 -*-
import cv2
import numpy as np
# Camera 객체를 생성 후 사이즈르 320 X 240 으로 조정.
cap = cv2.VideoCapture(0)
cap.set(3,320)
cap.set(4,240)
while(1):
# camera에서 frame capture.
ret, frame = cap.read()
if ret:
# BGR->HSV로 변환
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# blue 영역의 from ~ to
lower_blue = np.array([110, 50, 50])
upper_blue = np.array([130, 255, 255])
#이미지에서 blue영역
mask = cv2.inRange(hsv, lower_blue, upper_blue)
#bit연산자를 통해서 blue영역만 남김.
res = cv2.bitwise_and(frame, frame, mask = mask)
cv2.imshow('frame', frame)
cv2.imshow('mask', mask)
cv2.imshow('res', res)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
|
Result

참고로 HSV의 색 영역을 확인하는 방법은 아래와 같습니다.
>>> green = np.uint8[[[0,255,0]]]
>>> green_hsv = cv2.cvtColor(green, cv2.COLOR_BGR2HSV)
>>> print green_hsv
[[[60, 255, 255]]]
위 결과에서 [H-10,100,100]과 [H+10,255,255]와 같이 상하한선을 정하여 색 영역 범위를 확인할 수 있습니다.
이미지 임계처리¶
Goal¶
- 이미지 이진화의 방법인 Simple thresholding, Adaptive thresholding, Otsu’s thresholding에 대해서 알 수 있다.
cv2.threshold()
,cv2.adaptiveThreshold()
함수에 대해서 알 수 있다.
기본 임계처리¶
이진화 처리는 간단하지만, 쉽지 않은 문제를 가지고 있다. 이진화란 영상을 흑/백으로 분류하여 처리하는 것을 말합니다. 이때 기준이 되는 임계값을 어떻게 결정할 것인지가 중요한 문제가 됩니다. 임계값보다 크면 백, 작으면 흑이 됩니다. 기본 임계처리는 사용자가 고정된 임계값을 결정하고 그 결과를 보여주는 단순한 형태입니다.
이때 사용하는 함수가 cv2.threshold()
입니다.
-
cv2.
threshold
(src, thresh, maxval, type) → retval, dst¶ Parameters: - src – input image로 single-channel 이미지.(grayscale 이미지)
- thresh – 임계값
- maxval – 임계값을 넘었을 때 적용할 value
- type – thresholding type
- thresholding type은 아래와 같습니다.
- cv2.THRESH_BINARY
- cv2.THRESH_BINARY_INV
- cv2.THRESH_TRUNC
- cv2.THRESH_TOZERO
- cv2.THRESH_TOZERO_INV
아래 예제는 각 type별 thresholding 결과입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('gradient.jpg',0)
ret, thresh1 = cv2.threshold(img,127,255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img,127,255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img,127,255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img,127,255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img,127,255, cv2.THRESH_TOZERO_INV)
titles =['Original','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img,thresh1,thresh2,thresh3,thresh4,thresh5]
for i in xrange(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
|
Note
여러 이미지를 하나의 화면에 보여줄때 plt.subplot()
함수를 사용합니다. 사용법은 위 소스나 Matplotlib Document를 참고하시기 바랍니다.
Result

적응 임계처리¶
이전 Section에서의 결과를 보면 한가지 문제점이 있습니다. 임계값을 이미지 전체에 적용하여 처리하기 때문에 하나의 이미지에 음영이 다르면 일부 영역이 모두 흰색 또는 검정색으로 보여지게 됩니다.
이런 문제를 해결하기 위해서 이미지의 작은 영역별로 thresholding을 하는 것입니다. 이때 사용하는 함수가 cv2.adaptiveThreshold()
입니다.
-
cv2.
adaptiveThreshold
(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)¶ Parameters: - src – grayscale image
- maxValue – 임계값
- adaptiveMethod – thresholding value를 결정하는 계산 방법
- thresholdType – threshold type
- blockSize – thresholding을 적용할 영역 사이즈
- C – 평균이나 가중평균에서 차감할 값
- Adaptive Method는 아래와 같습니다.
- cv2.ADAPTIVE_THRESH_MEAN_C : 주변영역의 평균값으로 결정
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C :
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/dave.png',0)
# img = cv2.medianBlur(img,5)
ret, th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,15,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,15,2)
titles = ['Original','Global','Mean','Gaussian']
images = [img,th1,th2,th3]
for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
|
Result

Otsu의 이진화¶
지금까지 thresholding처리를 하면서 임계값을 사용자가 결정하여 parameter로 전달하였습니다. 그렇다면 그 임계값을 어떻게 정의해야 할까요? 가장 일반적인 방법은 trial and error방식으로 결정했습니다. 그러나 bimodal image (히스토그램으로 분석하면 2개의 peak가 있는 이미지)의 경우는 히스토그램에서 임계값을 어느정도 정확히 계산 할 수 있습니다. Otsu의 이진화(Otsu’s Binarization)란 bimodal image에서 임계값을 자동으로 계산해주는 것을 말합니다.
적용 방법은 cv2.threshold()
함수의 flag에 추가로 cv2.THRESH_STSU
를 적용하면 됩니다. 이때 임계값은 0으로 전달하면 됩니다.
아래 예제는 global threshold값, Otsu thresholding적용, Gaussian blur를 통해 nosise를 제거한 후 Otsu thresholding적용 결과 입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/noise.png',0)
# global thresholding
ret1, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
blur = cv2.GaussianBlur(img,(5,5),0)
ret3, th3 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# plot all the images and their histograms
images = [img, 0, th1, img, 0, th2, blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)', 'Original Noisy Image','Histogram',"Otsu's Thresholding", 'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
for i in xrange(3):
plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()
|
Result

이미지의 기하학적 변형¶
Goal¶
- 기하학적 변형에 대해서 알 수 있다.
cv2.getPerspectiveTransform()
함수에 대해서 알 수 있다.
Transformations¶
변환이란 수학적으로 표현하면 아래와 같습니다.
- 좌표 x를 좌표 x’로 변환하는 함수
예로는 사이즈 변경(Scaling), 위치변경(Translation), 회전(Rotaion) 등이 있습니다. 변환의 종류에는 몇가지 분류가 있습니다.
- 강체변환(Ridid-Body) : 크기 및 각도가 보존(ex; Translation, Rotation)
- 유사변환(Similarity) : 크기는 변하고 각도는 보존(ex; Scaling)
- 선형변환(Linear) : Vector 공간에서의 이동. 이동변환은 제외.
- Affine : 선형변환과 이동변환까지 포함. 선의 수평성은 유지.(ex;사각형->평행사변형)
- Perspective : Affine변환에 수평성도 유지되지 않음. 원근변환
Scaling¶
Scaling은 이미지의 사이즈가 변하는 것 입니다. OpenCV에서는 cv2.resize()
함수를 사용하여 적용할 수 있습니다.
사이즈가 변하면 pixel사이의 값을 결정을 해야 하는데, 이때 사용하는 것을 보간법(Interpolation method)입니다.
많이 사용되는 보간법은 사이즈를 줄일 때는 cv2.INTER_AREA
, 사이즈를 크게할 때는 cv2.INTER_CUBIC
, cv2.INTER_LINEAR
을 사용합니다.
-
cv2.
resize
(img, dsize, fx, fy, interpolation)¶ Parameters: - img – Image
- dsize – Manual Size. 가로, 세로 형태의 tuple(ex; (100,200))
- fx – 가로 사이즈의 배수. 2배로 크게하려면 2. 반으로 줄이려면 0.5
- fy – 세로 사이즈의 배수
- interpolation – 보간법
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
img = cv2.imread('images/logo.png')
# 행 : Height, 열:width
height, width = img.shape[:2]
# 이미지 축소
shrink = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
# Manual Size지정
zoom1 = cv2.resize(img, (width*2, height*2), interpolation=cv2.INTER_CUBIC)
# 배수 Size지정
zoom2 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
cv2.imshow('Origianl', img)
cv2.imshow('Shrink', shrink)
cv2.imshow('Zoom1', zoom1)
cv2.imshow('Zoom2', zoom2)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
Result

Original

Shrink

Zoom
Translation¶
Translation은 이미지의 위치를 변경하는 변환입니다.
-
cv2.
warpAffine
(src, M, dsize)¶ Parameters: - src – Image
- M – 변환 행렬
- dsize (tuple) – output image size(ex; (width=columns, height=rows)
Warning
width은 column의 수 이고, height는 row의 수 입니다.
여기서 변환행렬은 2X3의 이차원 행렬입니다. [[1,0,x축이동],[0,1,y축이동]] 형태의 float32 type의 numpy array입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
img = cv2.imread('images/logo.png')
rows, cols = img.shape[:2]
# 변환 행렬, X축으로 10, Y축으로 20 이동
M = np.float32([[1,0,10],[0,1,20]])
dst = cv2.warpAffine(img, M,(cols, rows))
cv2.imshow('Original', img)
cv2.imshow('Translation', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
Result

Result
Rotation¶
물체를 평면상의 한 점을 중심으로 𝜃 만큼 회전하는 변환 입니다. 양의 각도는 시계반대방향으로 회전을 합니다.
역시 변환 행렬이 필요한데, 변환 행렬을 생성하는 함수가 cv2.getRotationMatrix2D()
함수입니다.
-
cv2.
getRotationMatrix2D
(center, angle, scale) → M¶ Parameters: - center – 이미지의 중심 좌표
- angle – 회전 각도
- scale – scale factor
위 결과에서 나온 변환행렬을 cv2.warpAffine()
함수에 적용합니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #-*- coding:utf-8 -*-
import cv2
img = cv2.imread('images/logo.png')
rows, cols = img.shape[:2]
# 이미지의 중심점을 기준으로 90도 회전 하면서 0.5배 Scale
M= cv2.getRotationMatrix2D((cols/2, rows/2),90, 0.5)
dst = cv2.warpAffine(img, M,(cols, rows))
cv2.imshow('Original', img)
cv2.imshow('Rotation', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
Result

Affine Transformation¶
Affine Transformation은 선의 평행성은 유지가 되면서 이미지를 변환하는 작업입니다. 이동, 확대, Scale, 반전까지 포함된 변환입니다. Affine 변환을 위해서는 3개의 Match가 되는 점이 있으면 변환행렬을 구할 수 있습니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/chessboard.jpg')
rows, cols, ch = img.shape
pts1 = np.float32([[200,100],[400,100],[200,200]])
pts2 = np.float32([[200,300],[400,200],[200,400]])
# pts1의 좌표에 표시. Affine 변환 후 이동 점 확인.
cv2.circle(img, (200,100), 10, (255,0,0),-1)
cv2.circle(img, (400,100), 10, (0,255,0),-1)
cv2.circle(img, (200,200), 10, (0,0,255),-1)
M = cv2.getAffineTransform(pts1, pts2)
dst = cv2.warpAffine(img, M, (cols,rows))
plt.subplot(121),plt.imshow(img),plt.title('image')
plt.subplot(122),plt.imshow(dst),plt.title('Affine')
plt.show()
|
Result

Perspective Transformation¶
Perspective(원근법) 변환은 직선의 성질만 유지가 되고, 선의 평행성은 유지가 되지 않는 변환입니다. 기차길은 서로 평행하지만 원근변환을 거치면 평행성은 유지 되지 못하고 하나의 점에서 만나는 것 처럼 보입니다.(반대의 변환도 가능)
4개의 Point의 Input값과이동할 output Point 가 필요합니다.
변환 행렬을 구하기 위해서는 cv2.getPerspectiveTransform()
함수가 필요하며, cv2.warpPerspective()
함수에 변환행렬값을 적용하여
최종 결과 이미지를 얻을 수 있습니다.
아래의 예는 원근법이 적용된 효과를 제거하는 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/perspective.jpg')
# [x,y] 좌표점을 4x2의 행렬로 작성
# 좌표점은 좌상->좌하->우상->우하
pts1 = np.float32([[504,1003],[243,1525],[1000,1000],[1280,1685]])
# 좌표의 이동점
pts2 = np.float32([[10,10],[10,1000],[1000,10],[1000,1000]])
# pts1의 좌표에 표시. perspective 변환 후 이동 점 확인.
cv2.circle(img, (504,1003), 20, (255,0,0),-1)
cv2.circle(img, (243,1524), 20, (0,255,0),-1)
cv2.circle(img, (1000,1000), 20, (0,0,255),-1)
cv2.circle(img, (1280,1685), 20, (0,0,0),-1)
M = cv2.getPerspectiveTransform(pts1, pts2)
dst = cv2.warpPerspective(img, M, (1100,1100))
plt.subplot(121),plt.imshow(img),plt.title('image')
plt.subplot(122),plt.imshow(dst),plt.title('Perspective')
plt.show()
|
Result

Image Smoothing¶
Goal¶
- 다양한 Filter를 이용하여 Blur 이미지를 만들 수 있다.
- 사용자 정의 Filter를 적용할 수 있다.
Image Filtering¶
이미지도 음성 신호처럼 주파수로 표현할 수 있습니다. 일반적으로 고주파는 밝기의 변화가 많은 곳, 즉 경계선 영역에서 나타나며, 일반적인 배경은 저주파로 나타납니다. 이것을 바탕으로 고주파를 제거하면 Blur처리가 되며, 저주파를 제거하면 대상의 영역을 확인할 수 있습니다.
Low-pass filter(LPF)와 High-pass filter(HPF)를 이용하여, LPF를 적용하면 노이즈제거나 blur처리를 할 수 있으며, HPF를 적용하면 경계선을 찾을 수 있습니다.
OpenCV에서는 cv2.filter2D()
함수를 이용하여 이미지에 kernel(filter)를 적용하여 이미지를 Filtering할 수 있습니다.
kernel은 행렬을 의미하는데 kernel의 크기가 크면 이미지 전체가 blur처리가 많이 됩니다.
일반적으로 5X5행렬을 아래와 같이 생성하여 적용합니다.
Filter가 적용되는 방법은
- 이미지의 각 pixel에 kernel을 적용합니다.
- 위 kernel을 예로들면 각 pixel에 5X5윈도우를 올려 놓고, 그 영역안에 포함되는 값의 Sum을 한 후에 25로 나눕니다.
- 그 결과는 해당 윈도우 영역안의 평균값이 되고, 그 값을 해당 pixel에 적용하는 방식입니다.
아래 trackbar를 이용하여 kernel사이즈를 조정하면서 결과를 확인할 수 있는 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
def nothing(x):
pass
img = cv2.imread('images/lena.jpg')
cv2.namedWindow('image')
cv2.createTrackbar('K','image',1,20, nothing)
while(1):
if cv2.waitKey(1) & 0xFF == 27:
break
k = cv2.getTrackbarPos('K','image')
#(0,0)이면 에러가 발생함으로 1로 치환
if k == 0:
k = 1
# trackbar에 의해서 (1,1) ~ (20,20) kernel생성
kernel = np.ones((k,k),np.float32)/(k*2)
dst = cv2.filter2D(img,-1,kernel)
cv2.imshow('image',dst)
cv2.destroyAllWindows()
|
Result

5X5 Kernel적용 결과
Image Blurring¶
Image Blurring은 low-pass filter를 이미지에 적용하여 얻을 수 있습니다. 고주파영역을 제거함으로써 노이즈를 제거하거나 경계선을 흐리게 할 수 있습니다. OpenCV에는 4가지 형태의 blurring 방법을 제공하고 있습니다.
Averaging¶
Box형태의 kernel을 이미지에 적용한 후 평균값을 box의 중심점에 적용하는 형태입니다. cv2.blur()
또는 cv2.boxFilter()
함수로 적용할 수 있습니다.
예를 들어 3x3형태의 필터는 아래와 같습니다.
-
cv2.
blur
(src, ksize) → dst¶ Parameters: - src – Chennel수는 상관없으나, depth(Data Type)은 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
- ksize – kernel 사이즈(ex; (3,3))
Note
OpenCV에서 이미지의 Data Type은 아래와 같이 표현이 됩니다.:
* CV_8U : 8-bit unsigned integer: uchar ( 0..255 )
* CV_8S : 8-bit signed integer: schar ( -128..127 )
* CV_16U : 16-bit unsigned integer: ushort ( 0..65535 )
* CV_16S : 16-bit signed integer: short ( -32768..32767 )
* CV_32S : 32-bit signed integer: int ( -2147483648..2147483647 )
* CV_32F : 32-bit floating-point number: float ( -FLT_MAX..FLT_MAX, INF, NAN )
* CV_64F : 64-bit floating-point number: double ( -DBL_MAX..DBL_MAX, INF, NAN )
일반적으로 Data Type과 채널수가 같이 표현이 되어 CV_8UC1 과 같이 표현이 됩니다.(8bit unsiged integer이면서 채널이 1개)
Gaussian Filtering¶
box filter는 동일한 값으로 구성된 kernel을 사용하지만, Gaussian Filter는 Gaussian함수를 이용한 Kernel을 적용합니다. 즉, kernel 행렬의 값을 Gaussian 함수를 통해서 수학적으로 생성하여 적용합니다. kernel의 사이즈는 양수이면서 홀수로 지정을 해야 합니다. 이미지의 Gaussian Noise (전체적으로 밀도가 동일한 노이즈, 백색노이즈)를 제거하는 데 가장 효과적입니다.
-
cv2.
GaussianBlur
(img, ksize, sigmaX)¶ Parameters: - img – Chennel수는 상관없으나, depth(Data Type)은 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
- ksize – (width, height) 형태의 kernel size. width와 height는 서로 다를 수 있지만, 양수의 홀수로 지정해야 함.
- sigmaX – Gaussian kernel standard deviation in X direction.
Median Filtering¶
kernel window와 pixel의 값들을 정렬한 후에 중간값을 선택하여 적용합니다. salt-and-pepper noise 제거에 가장 효과적입니다. 예를 들면 아래와 같이 kernel window을 적용시킨 결과가 다음과 같다면

크기순으로 정렬을 하면 33,54,67,84,102,163,189,212,224입니다. 이중에 중간값인 102가 중앙값으로 결정이 됩니다.(중앙에 있는 189가 102로 변경됨.)
-
cv2.
medianBlur
(src, ksize)¶ Parameters: - src – 1,3,4 channel image. depth가 CV_8U, CV_16U, or CV_32F 이면 ksize는 3또는5, CV_8U이면 더 큰 ksize가능
- ksize – 1보다 큰 홀수
Bilateral Filtering¶
지금까지의 Blur처리는 경계선까지 Blur처리가 되어, 경계선이 흐려지게 됩니다. Bilateral Filtering(양방향 필터)은 경계선을 유지하면서 Gaussian Blur처리를 해주는 방법입니다.
Gaussian 필터를 적용하고, 또 하나의 Gaussian 필터를 주변 pixel까지 고려하여 적용하는 방식입니다.
-
cv2.
bilateralFilter
(src, d, sigmaColor, sigmaSpace)¶ Parameters: - src – 8-bit, 1 or 3 Channel image
- d – filtering시 고려할 주변 pixel 지름
- sigmaColor – Color를 고려할 공간. 숫자가 크면 멀리 있는 색도 고려함.
- sigmaSpace – 숫자가 크면 멀리 있는 pixel도 고려함.
아래 지금까지 설명한 Blur처리 방법을 적용한 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #-*-coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/lena.jpg')
# pyplot를 사용하기 위해서 BGR을 RGB로 변환.
b,g,r = cv2.split(img)
img = cv2.merge([r,g,b])
# 일반 Blur
dst1 = cv2.blur(img,(7,7))
# GaussianBlur
dst2 = cv2.GaussianBlur(img,(5,5),0)
# Median Blur
dst3 = cv2.medianBlur(img,9)
# Bilateral Filtering
dst4 = cv2.bilateralFilter(img,9,75,75)
images = [img,dst1,dst2,dst3,dst4]
titles=['Original','Blur(7X7)','Gaussian Blur(5X5)','Median Blur','Bilateral']
for i in xrange(5):
plt.subplot(3,2,i+1),plt.imshow(images[i]),plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
|
Result

위 결과 이미지를 확대해서 보면 Gaussian과 Bilateral를 비교해보면 윤곽선에서 차이가 나타나는 것을 알 수 있습니다.
Morphological Transformations¶
- Morphological 방법인 Erosion, Dilation, Opening, Closing 에 대해서 알 수 있다.
cv2.erod()
,cv2.dilate()
,cv2.morphologyEx()
함수에 대해서 알 수 있다.
Theory¶
Morphologicla Transformation은 이미지를 Segmentation하여 단순화, 제거, 보정을 통해서 형태를 파악하는 목적으로 사용이 됩니다. 일반적으로 binary나 grayscale image에 사용이 됩니다. 사용하는 방법으로는 Dilation(팽창), Erosion(침식), 그리고 2개를 조합한 Opening과 Closing이 있습니다. 여기에는 2가지 Input값이 있는데, 하나는 원본 이미지이고 또 다른 하나는 structuring element입니다.
Note
structuring element는 원본 이미지에 적용되는 kernel입니다. 중심을 원점으로 사용할 수도 있고, 원점을 변경할 수도 있습니다. 일반적으로 꽉찬 사각형, 타원형, 십자형을 많이 사용합니다.
Erosion¶
각 Pixel에 structuring element를 적용하여 하나라도 0이 있으면 대상 pixel을 제거하는 방법입니다. 아래 그림은 대상 이미지에 십자형 structuring element를 적용한 결과 입니다.

Erosion(출처: KOCW )
위 그림에서 가운데 있는 십자형 Structuring Element를 Original Image에 적용을 합니다. 원본의 각 pixel에 적용을 하여 겹치는 부분이 없는 부분이 하나라도 있으면 그 중심 pixel을 제거하는 방식입니다. 최종적으로 우측의 분홍색 영역만 남게 됩니다. 이 방법은 작은 Object를 제거하는 효과가 있습니다.

-
cv2.
erode
(src, kernel, dst, anchor, iterations, borderType, borderValue)¶ Parameters: - src – the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
- kernel – structuring element.
cv2.getStructuringElemet()
함수로 만들 수 있음. - anchor – structuring element의 중심. default (-1,-1)로 중심점.
- iterations – erosion 적용 반복 횟수
Dilation¶
Erosion과 반대로 대상을 확장한 후 작은 구멍을 채우는 방법입니다. Erosion과 마찬가지로 각 pixel에 structuring element를 적용합니다. 대상 pixel에 대해서 OR 연산을 수행합니다. 즉 겹치는 부분이 하나라도 있으면 이미지를 확장합니다.

위 그림은 십자형 structuring element를 원본이미지에 OR 연산을 적용합니다. 최종적으로 확장된 이미지를 얻을 수 있습니다. 결과적으로 경계가 부드러워 지고, 구멍이 메꿔지는 효과를 얻을 수 있습니다.

-
cv2.
dilation
(src, kernel, dst, anchor, iterations, borderType, borderValue)¶ Parameters: - src – the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
- kernel – structuring element.
cv2.getStructuringElemet()
함수로 만들 수 있음. - anchor – structuring element의 중심. default (-1,-1)로 중심점.
- iterations – dilation 적용 반복 횟수
Opening & Closing¶
Opening과 Closing은 Erosion과 Dilation의 조합 결과 입니다. 차이는 어느 것을 먼저 적용을 하는 차이 입니다.
- Opeing : Erosion적용 후 Dilation 적용. 작은 Object나 돌기 제거에 적합
- Closing : Dilation적용 후 Erosion 적용. 전체적인 윤곽 파악에 적합

-
cv2.
morphologyEx
(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst¶ Parameters: - src – Source image. The number of channels can be arbitrary. The depth should be one of
CV_8U
,CV_16U
,CV_16S
,CV_32F` or ``CV_64F
. - op –
Type of a morphological operation that can be one of the following:
- MORPH_OPEN - an opening operation
- MORPH_CLOSE - a closing operation
- MORPH_GRADIENT - a morphological gradient. Dilation과 erosion의 차이.
- MORPH_TOPHAT - “top hat”. Opeining과 원본 이미지의 차이
- MORPH_BLACKHAT - “black hat”. Closing과 원본 이미지의 차이
- kernel – structuring element.
cv2.getStructuringElemet()
함수로 만들 수 있음. - anchor – structuring element의 중심. default (-1,-1)로 중심점.
- iterations – erosion and dilation 적용 횟수
- borderType – Pixel extrapolation method. See
borderInterpolate
for details. - borderValue – Border value in case of a constant border. The default value has a special meaning.
- src – Source image. The number of channels can be arbitrary. The depth should be one of
Structuring Element¶
사각형 모양의 structuring element는 numpy를 이용해서 만들 수 있습니다.
>>> import numpy as np
>>> kernel = np.ones((5,5), np.uini8)
하지만 원, 타원모양이 필요한 경우에는 OpenCV에서 제공하는 cv2.getStructuringElement()
함수를 이용해서 만들 수 있습니다.
>>> cv2.getStructuringElement(cv2.MORPH_REC,(5,5))
array([ [1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)
>>> cv2.getStructuringElement(cv2.MORP_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)
-
cv2.
getStructuringElement
(shape, ksize[, anchor]) → retval¶ Parameters: - shape –
Element의 모양.
- MORPH_RET : 사각형 모양
- MORPH_ELLIPSE : 타원형 모양
- MORPH_CROSS : 십자 모양
- ksize – structuring element 사이즈
- shape –
아래 지금까지 배운 Morphological 변환에 대한 예제입니다.
Sample Code
#-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
dotImage = cv2.imread('images/dot_image.png')
holeImage = cv2.imread('images/hole_image.png')
orig = cv2.imread('images/morph_origin.png')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
# kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
# kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
erosion = cv2.erode(dotImage,kernel,iterations = 1)
dilation = cv2.dilate(holeImage,kernel,iterations = 1)
opening = cv2.morphologyEx(dotImage, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(holeImage, cv2.MORPH_CLOSE,kernel)
gradient = cv2.morphologyEx(orig, cv2.MORPH_GRADIENT, kernel)
tophat = cv2.morphologyEx(orig, cv2.MORPH_TOPHAT, kernel)
blackhat = cv2.morphologyEx(orig, cv2.MORPH_BLACKHAT, kernel)
images =[dotImage, erosion, opening, holeImage, dilation, closing, gradient, tophat, blackhat]
titles =['Dot Image','Erosion','Opening','Hole Image', 'Dilation','Closing', 'Gradient', 'Tophat','Blackhot']
for i in xrange(9):
plt.subplot(3,3,i+1),plt.imshow(images[i]),plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
Result

Image Gradients¶
Goal¶
- Edge Detection에 대해서 알 수 있다.
Gradient(기울기)는 스칼라장(즉, 공간)에서 최대의 증가율을 나타내는 벡터장(방향과 힘)을 뜻합니다.
어렵죠? 영상처리에서 gradient는 영상의 edge 및 그 방향을 찾는 용도로 활용이 되는데요. 이미지 (x,y)에서의 벡터값(크기와 방향, 즉 밝기와 밝기의 변화하는 방향)을 구해서 해당 pixel이 edge에 얼마나 가까운지, 그리고 그 방향이 어디인지 쉽게 알수 있게 합니다. (일반적으로 이미지의 Gradient를 생각해보시면 밝기의 변화와 그 방향을 알 수 있습니다.)
아래 설명할 방법들은 Gradient를 이용해서 이미지의 edge를 검출하는 방법입니다.
Sobel & Scharr Filter¶
Gaussian smoothing과 미분을 이용한 방법입니다. 그래서 노이즈가 있는 이미지에 적용하면 좋습니다. X축과 Y축을 미분하는 방법으로 경계값을 계산합니다.
직선을 미분하면 상수, 곡선을 미분하면 또 다른 방정식이 나오는 성질을 이용하여 edge에 대한 선을 그려주는 기능을 합니다.
X축 미분은 수평선(수직선이 남음), Y축 미분은 수직선(수평선이 남음)을 미분하여 경계가 사라지는 효과가 있습니다.
미분시 소실되는 표본의 정보가 많을 수 있어 aperture_size
값을 이용하여 소실되는 정도를 조절할 수 있습니다.
만약 ksize가 -1이면 3x3 Scharr filter가 적용이 되어 Sobel의 3x3보다 좀 더 나은 결과를 보여 줍니다.
-
cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) → dst
Parameters: - src – input image
- ddepth – output image의 depth, -1이면 input image와 동일.
- dx – x축 미분 차수.
- dy – y축 미분 차수.
- ksize – kernel size(ksize x ksize)
-
cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) → dst
cv2.Sobel()
함수와 동일하나 ksize가 sobel의 3x3 보다 좀더 정확하게 적용이 됩니다.
Laplacian 함수¶
이미지의 가로와 세로에 대한 Gradient를 2차 미분한 값입니다. Sobel filter에 미분의 정도가 더해진 것과 비슷합니다.(dx와 dy가 2인 경우) blob(주위의 pixel과 확연한 picel차이를 나타내는 덩어리)검출에 많이 사용됩니다.
-
cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) → dst
Parameters: - src – source image
- ddepth – output iamge의 depth.
Canny Edge Detection¶
가장 유명한 Edge Detection방법입니다. 여러 단계의 Algorithm을 통해서 경계를 찾아 냅니다.
- Noise Reduction
이미지의 Noise를 제거합니다. 이때 5x5의 Gaussian filter를 이용합니다.
Edge Gradient Detection
이미지에서 Gradient의 방향과 강도를 확인합니다. 경계값에서는 주변과 색이 다르기 때문에 미분값이 급속도로 변하게 됩니다. 이를 통해 경계값 후보군을 선별합니다.
Non-maximum Suppression
이미지의 pixel을 Full scan하여 Edge가 아닌 pixel은 제거합니다.
Hysteresis Thresholding
이제 지금까지 Edge로 판단된 pixel이 진짜 edge인지 판별하는 작업을 합니다. max val과 minVal(임계값)을 설정하여 maxVal 이상은 강한 Edge, min과 max사이는 약한 edge로 설정합니다. 이제 약한 edge가 진짜 edge인지 확인하기 위해서 강한 edge와 연결이 되어 있으면 edge로 판단하고, 그러지 않으면 제거합니다.
이와 같은 일련의 작업을 통해서 경계값만을 남겨두고 제거합니다.
-
cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) → edges
Parameters: - image – 8-bit input image
- threshold1 – Hysteresis Thredsholding 작업에서의 min 값
- threshold2 – Hysteresis Thredsholding 작업에서의 max 값
아래는 지금까지 설명한 edge detection방법에 대한 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/dave.png')
canny = cv2.Canny(img,30,70)
laplacian = cv2.Laplacian(img,cv2.CV_8U)
sobelx = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=3)
sobely = cv2.Sobel(img,cv2.CV_8U,0,1,ksize=3)
images = [img,laplacian, sobelx, sobely, canny]
titles = ['Origianl', 'Laplacian', 'Sobel X', 'Sobel Y','Canny']
for i in xrange(5):
plt.subplot(2,3,i+1),plt.imshow(images[i]),plt.title([titles[i]])
plt.xticks([]),plt.yticks([])
plt.show()
|
Result

Image Pyramids¶
Goal¶
- Image Pyramid에 대해서 알 수 있다.
cv2.pyrUp()
와cv2.pyrDown()
에 대해서 알 수 있다.
Theory¶
일반적으로는 고정된 이미지 사이즈를 작업을 하지만, 때때로 동일한 이미지에 대해서 다양한 사이즈를 가지고 작업을 해야 하는 경우가 있습니다. 만일, 이미지에서 얼굴을 찾을 경우에 얼굴의 사이즈를 확신할 수 없습니다. 이럴 경우에는 원본 이미지에 대한 다양한 사이즈에서 얼굴을 찾는다면 좀더 정확하고 확실한 이미지를 찾을 수 있습니다. 이 처럼 동일 이미지의 서로 다른 사이즈의 set을 Image Pyramids라고 합니다(가장 아래에 가장 큰 해상도를 놓고 점점 줄여가면서 쌓아가는 형태입니다.)
Image Pyramid의 종류는 1) Gaussian Pyramids 와 2) Laplacian Pyramids 가 있습니다.
Gaussian Pyramid의 High Level(낮은 해상도. Pyramid의 상단)은 Lower level에서 row와 column을 연속적으로 제거하면서 생성됩니다. M x N 사이지의 이미지는 M/2 X N/2 가 적용되연 1/4사이즈로 줄어들게 됩니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #-*-coding:utf-8 -*-
import cv2
img = cv2.imread('images/lena.jpg')
lower_reso = cv2.pyrDown(img) # 원본 이미지의 1/4 사이즈
higher_reso = cv2.pyrUp(img) #원본 이미지의 4배 사이즈
cv2.imshow('img', img)
cv2.imshow('lower', lower_reso)
cv2.imshow('higher', higher_reso)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
Result

Laplacian Pyramid는 Gaussian Pyramid에서 만들어 집니다. cv2.pyrDown()
과 cv2.pyrUp()
함수를 사용하여 축소, 확장을 하면 원본과 동일한
이미지를 얻을 수 없습니다.(계산하면서 약간의 차이가 발생합니다.)
예를 들면 원본 이미지의 shape가 (225,400,3)을 cv2.pyrDown()
을 적용하면 행과 열이 2배씩 줄게 되고 소수점은 반올림이 되어 (113,200,3)이 됩니다.
이것을 다시 cv2.pyrUp()
을 시키면 (226,400,3) 이 되어 원본 이미지와 1row차이가 발생합니다. 이것을 resize를 통해서 동일한 shape로 만든 후에 2 배열을 차이를 구하면
아래와 같이 외곽선이 남게 됩니다.(짝수 해상도도 동일한 결과가 나옵니다.)
>>> import cv2
>>> img = cv2.imread('lena.jpg')
>>> img.shape
(225, 400, 3)
>>> GAD = cv2.pyrDown(img)
>>> GAD.shape
(113, 200, 3)
>>> GAU = cv2.pyrUp(GAD)
>>> GAU.shape
(226, 400, 3)
>>> temp = cv2.resize(GAU, (400, 255))
>>> res = cv2.subtract(img, temp)
>>> cv2.imshow(res)
>>> cv2.waitKey(0)
Result

이미지 Pyramid를 이용하면 이미지 결합을 자연스럽게 처리할 수 있습니다. 작업 순서는 아래와 같습니다.
- 2개의 이미지를 각각 Load함.
- 각 이미지에 대해서 적당한 Gaussian Pyramid를 생성함.
- Gaussian Pyramid를 이용하여 Laplacian Pyramid를 생성함.
- 각 단계의 Laplicain Pyramid를 이용하여 각 이미지의 좌측과 우측을 결함.
- 결함한 결과중 가장 작은 이미지를 확대하면서 동일 사이즈의 결합결과와 Add하여 외곽선을 선명하게 처리함.
위 작업단계 순서대로 2개의 이미지를 결함한 예제입니다.
#-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 1단계
A = cv2.imread('images/apple.jpg')
B = cv2.imread('images/orange.jpg')
# 2단계
# A 이미지에 대한 Gaussian Pyramid를 생성
# 점점 작아지는 Pyramid
G = A.copy()
gpA = [G]
for i in xrange(6):
G = cv2.pyrDown(G)
gpA.append(G)
# B 이미지에 대한 Gaussian Pyramid 생성
# 점점 작아지는 Pyramid
G = B.copy()
gpB = [G]
for i in xrange(6):
G = cv2.pyrDown(G)
gpB.append(G)
# 3단계
# A 이미지에 대한 Laplacian Pyramid 생성
lpA = [gpA[5]] # n번째 추가된 Gaussian Image
for i in xrange(5,0,-1):
GE = cv2.pyrUp(gpA[i]) #n번째 추가된 Gaussian Image를 Up Scale함.
temp = cv2.resize(gpA[i-1], (GE.shape[:2][1], GE.shape[:2][0])) # 행렬의 크기를 동일하게 만듬.
L = cv2.subtract(temp,GE) # n-1번째 이미지에서 n번째 Up Sacle한 이미지 차이 -> Laplacian Pyramid
lpA.append(L)
# A 이미지와 동일하게 B 이미지도 Laplacian Pyramid 생성
lpB = [gpB[5]]
for i in xrange(5,0,-1):
GE = cv2.pyrUp(gpB[i])
temp = cv2.resize(gpB[i - 1], (GE.shape[:2][1], GE.shape[:2][0]))
L = cv2.subtract(temp, GE)
# L = cv2.subtract(gpB[i-1],GE)
lpB.append(L)
# 4단계
# Laplician Pyramid를 누적으로 좌측과 우측으로 재결함
LS = []
for la,lb in zip(lpA,lpB):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:cols/2], lb[:,cols/2:]))
LS.append(ls)
# 5단계
ls_ = LS[0] # 좌측과 우측이 결합된 가장 작은 이미지
for i in xrange(1,6):
ls_ = cv2.pyrUp(ls_) # Up Sacle
temp = cv2.resize(LS[i],(ls_.shape[:2][1], ls_.shape[:2][0])) # 외곽선만 있는 이미지
ls_ = cv2.add(ls_, temp) # UP Sacle된 이미지에 외곽선을 추가하여 선명한 이미지로 생성
# 원본 이미지를 그대로 붙인 경우
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))
cv2.imshow('real', real)
cv2.imshow('blending', ls_)
cv2.destroyAllWindows()
위 예제에서 5단계의 역할에 대해서 알아보기 위하여 마지막 i = 5 일경우의 각 이미지의 결과는 아래와 같습니다.
![]()
cv2.pyUp(ls_)
수행 전의_ls
이미지![]()
cv2.pyUp(ls_)
수행 후의_ls
이미지![]()
temp
이미지. 잘 보이지 않지만 외곽선만 남아 있는 결과.![]()
Up Sacle된 결과와 외곽선 이미지가 결함한 최종 결과
위와 같은 단계를 거쳐 부드럽게 2개의 이미지가 결합이 되었습니다. 단순하게 원본이미지를 결합한 결과는 아래와 같습니다.
![]()
단순 결합 결과
Image Contours¶
Goal¶
- Contours에 대해서 알 수 있다
cv2.findContours()
,cv2.drawContours()
함수에 대해서 알 수 있다.
Contours¶
Contours란 동일한 색 또는 동일한 강도를 가지고 있는 영역의 경계선을 연결한 선입니다. 우리가 자주 보는 것으로는 등고선이나 일기예보에서 볼 수 있습니다.

등고선(Contours Line)(출처 위키피디아 )
대상의 외형을 파악하는데 유용하게 사용이 됩니다.
- 정확도를 높히기 위해서 Binary Image를 사용합니다. threshold나 canny edge를 선처리로 수행합니다.
cv2.findContours()
함수는 원본 이미지를 직접 수정하기 때문에, 원본 이미지를 보존 하려면 Copy해서 사용해야 합니다.- OpenCV에서는 contours를 찾는 것은 검은색 배경에서 하얀색 대상을 찾는 것과 비슷합니다. 그래서 대상은 흰색, 배경은 검은색으로 해야 합니다.
Find & Draw Contours¶
OpenCV에서 contours를 찾고, 그리기 위해서 아래 2개의 함수를 사용합니다.
-
cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) → image, contours, hierarchy
Parameters: - image – 8-bit single-channel image. binary image.
- mode –
contours를 찾는 방법
cv2.RETR_EXTERNAL
: contours line중 가장 바같쪽 Line만 찾음.cv2.RETR_LIST
: 모든 contours line을 찾지만, hierachy 관계를 구성하지 않음.cv2.RETR_CCOMP
: 모든 contours line을 찾으며, hieracy관계는 2-level로 구성함.cv2.RETR_TREE
: 모든 contours line을 찾으며, 모든 hieracy관계를 구성함.
- method –
contours를 찾을 때 사용하는 근사치 방법
cv2.CHAIN_APPROX_NONE
: 모든 contours point를 저장.cv2.CHAIN_APPROX_SIMPLE
: contours line을 그릴 수 있는 point 만 저장. (ex; 사각형이면 4개 point)cv2.CHAIN_APPROX_TC89_L1
: contours point를 찾는 algorithmcv2.CHAIN_APPROX_TC89_KCOS
: contours point를 찾는 algorithm
Returns: image, contours , hierachy
Method에 대해서 설명을 하면 아래 예제의 결과에서 처럼 사각형의 contours line을 그릴 때, cv2.CHAIN_APPROX_NONE
는 모든 point를 저장하고
cv2.CHAIN_APPROX_SIMPLE
는 4개의 point만을 저장하여 메모리를 절약합니다.
>>> contours[0].shape #cv2.CHAIN_APPROX_SIMPLE(4 point)
(4, 1, 2)
>>> contours[0].shape #cv2.CHAIN_APPROX_NONE(750 point)
(750, 1, 2)
mode에 대해서는 Hierachy를 설명할 때 같이 설명하도록 하겠습니다.
-
cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) → dst
Parameters: - image – 원본 이미지
- contours – contours정보.
- contourIdx – contours list type에서 몇번째 contours line을 그릴 것인지. -1 이면 전체
- color – contours line color
- thickness – contours line의 두께. 음수이면 contours line의 내부를 채움.
Returns: image에 contours가 그려진 결과
아래는 예제와 결과입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
img = cv2.imread('images/rectangle.jpg')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#threshold를 이용하여 binary image로 변환
ret, thresh = cv2.threshold(imgray,127,255,0)
#contours는 point의 list형태. 예제에서는 사각형이 하나의 contours line을 구성하기 때문에 len(contours) = 1. 값은 사각형의 꼭지점 좌표.
#hierachy는 contours line의 계층 구조
image, contours, hierachy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
image = cv2.drawContours(img, contours, -1, (0,255,0), 3)
cv2.imshow('image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
Result

Contour Feature¶
Goal¶
- Contours의 특징(영역, 중심점, bounding box 등)을 찾을 수 있습니다.
- Contours 특징을 찾는 다양한 함수에 대해서 알 수 있습니다.
Moments¶
Image Moment는 대상을 구분할 수 있는 특징을 의미합니다.. 특징으로는 Area, Perimeter, 중심점 등이 있습니다. Image Moments는 대상을 구분한 후, 다른 대상과 구분하기 위해 대상을 설명(describe)하는 자료로 사용됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
img = cv2.imread('images/rectangle.jpg')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierachy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# 첫번째 contours의 moment 특징 추출
cnt = contours[0]
M = cv2.moments(cnt)
print M.items()
|
>>> [('mu02', 35950058.66666663), ('mu03', 1.52587890625e-05), ('m11', 884446080.0), ('nu02', 0.03873697916666662), ('m12', 113614624853.33333), ('mu21', 1.9073486328125e-05), ('mu20', 166374058.6666665), ('nu20', 0.17927170868347322), ('m30', 570292325120.0), ('nu21', 1.1775050154231546e-16), ('mu11', 0.0), ('mu12', 3.814697265625e-06), ('nu11', 0.0), ('nu12', 2.3550100308463093e-17), ('m02', 463733162.6666666), ('m03', 63472543680.0), ('m00', 30464.0), ('m01', 3609984.0), ('mu30', 0.0001220703125), ('nu30', 7.53603209870819e-16), ('nu03', 9.420040123385237e-17), ('m10', 7463680.0), ('m20', 1994975658.6666665), ('m21', 236404615552.0)]
위 Dictionary Data에는 contours의 특징을 찾을 수 있는 기본 정보들이 있습니다(총 24개). 예를 들면 Contour의 중심값을 찾기 위해서는 아래 값을 사용하면 됩니다.
>>> cx = int(M['m10']/M['m00'])
>>> cy = int(M['m01']/M['m00'])
Contour Area¶
Contour면적은 moments의 m00
값이거나 cv2.contourArea()
함수로 구할 수 있다.
>>> cv2.contourArea(cnt)
30464.0
Contour Perimeter¶
Contour의 둘레 길이를 구할 수 있습니다. 사각형의 경우는 둘레길이의 합이 됩니다. 아래 함수의 2번째 argument가 true이면 폐곡선 도형을 만들어 둘레길이를 구하고, False이면 시작점과 끝점을 연결하지 않고 둘레 길이를 구합니다.
>>> cv2.arcLength(cnt, True)
750.0
>>> cv2.arcLength(cnt, False)
494.0
Contour Approximation¶
cv2.findContours()
함수에 의해서 찾은 contours line은 각각의 contours point를 가지고 있습니다. 이 Point를 연결하여 Line을 그리게 됩니다.
이때 이 point의 수를 줄여 근사한 line을 그릴 때 사용되는 방법입니다.
Point의 수를 줄이는데 사용되는 방식은 Douglas-Peucker algorithm 입니다.
근사치를 찾는데 사용되는 함수는 cv2.approxPolyDP()
입니다.
-
cv2.approxPolyDP(curve, epsilon, closed[, approxCurve]) → approxCurve
Parameters: - curve – contours point array
- epsilon – original cuve와 근사치의 최대거리. 최대거리가 클 수록 더 먼 곳의 Point까지 고려하기 때문에 Point수가 줄어듬.
- closed – 폐곡선 여부
Returns: 근사치가 적용된 contours point array
아래 예제는 epsilon의 크기를 다르게 한 결과 입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/bad_rect.png')
img1 = img.copy()
img2 = img.copy()
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierachy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
# 적용하는 숫자가 커질 수록 Point의 갯수는 감소
epsilon1 = 0.01*cv2.arcLength(cnt, True)
epsilon2 = 0.1*cv2.arcLength(cnt, True)
approx1 = cv2.approxPolyDP(cnt, epsilon1, True)
approx2 = cv2.approxPolyDP(cnt, epsilon2, True)
cv2.drawContours(img, [cnt],0,(0,255,0),3) # 215개의 Point
cv2.drawContours(img1, [approx1], 0,(0,255,0), 3) # 21개의 Point
cv2.drawContours(img2, [approx2], 0,(0,255,0), 3) # 4개의 Point
titles = ['Original', '1%', '10%']
images = [img, img1, img2]
for i in xrange(3):
plt.subplot(1,3,i+1), plt.title(titles[i]), plt.imshow(images[i])
plt.xticks([]), plt.yticks([])
plt.show()
|
Result

Convex Hull¶
Convex Hull이란 contours point를 모두 포함하는 볼록한 외관선을 의미합니다. Contour Approximation과 유사한 결과지만, 방법은 전혀 다릅니다.
아래 그림에서 붉은 선이 Convex Hull을 나타내고 화살표의 차이가 convexity defect라고 합니다. convexity defect는 contours와 hull과의 최대차이를 나타냅니다.

Convex Hull에 대한 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/hand.png')
img1 = img.copy()
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierachy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[1] # 1이 손모양 주변의 contour
hull = cv2.convexHull(cnt)
cv2.drawContours(img1, [hull], 0,(0,255,0), 3)
titles = ['Original','Convex Hull']
images = [img, img1]
for i in xrange(2):
plt.subplot(1,2,i+1), plt.title(titles[i]), plt.imshow(images[i])
plt.xticks([]), plt.yticks([])
plt.show()
|
Result

Checking Convexity¶
cv2.isContourConvex()
함수는 contour가 convex인지 아닌지 판단하여 True 또는 False를 Return합니다.
여기서 convex란 contour line이 볼록하거나 최소한 평평한 것을 의미합니다.(오목한 부분이 없는 것입니다.)
위 예제에는 2개의 contour가 있는데, 첫번째는 이미지의 전체 외곽선(사각형)이고 두번째는 손 모양의 contour line입니다. 그래서 결과는 아래와 같습니다.
>>> cv2.isContourConvex(contours[0]) # 외곽선 contour line
True
>>> cv2.isContourConvex(contours[1]) # 손 모양 contour line
False
Bounding Rectangle¶
Contours Line을 둘러싸는 사각형을 그리는 방법입니다. 사각형을 그리는 방법은 2가지가 있습니다.
- Straight Bounding Rectangle : 대상의 Rotation은 무시한 사각형 모양입니다.
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
- Rotated Rectangle : 대상을 모두 포함하면서, 최소한의 영역을 차지하는 사각형 모양입니다.
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
im = cv2.drawContours(im,[box],0,(0,0,255),2)
Minumum Enclosing Circle¶
Contours line을 완전히 포함하는 원 중 가장 작은 원을 그릴 수 있습니다.
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
Fitting an Ellipse¶
Contours Line을 둘러싸는 타원을 그릴 수 있습니다.
ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(im,ellipse,(0,255,0),2)
아래는 전체 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/lightning.jpg')
img1 = img.copy()
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierachy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[1]
# Straight Rectangle
x, y, w, h = cv2.boundingRect(cnt)
img1 = cv2.rectangle(img1,(x,y),(x+w, y+h),(0,255,0), 3) # green
# Rotated Rectangle
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
img1 = cv2.drawContours(img1, [box], 0, (0,0,255), 3) # blue
# Minimum Enclosing Circle
(x,y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
img1 = cv2.circle(img1, center, radius,(255,255,0),3) # yellow
# Fitting an Ellipse
ellipse = cv2.fitEllipse(cnt)
img1 = cv2.ellipse(img1, ellipse,(255,0,0),3) #red
titles = ['Original','Result']
images = [img, img1]
for i in xrange(2):
plt.subplot(1,2,i+1), plt.title(titles[i]), plt.imshow(images[i])
plt.xticks([]), plt.yticks([])
plt.show()
|
Result

Contour Property¶
Goal¶
- 대상의 속성으로 자주 사용되는 추가적인 속성에 대해서 알 수 있다.
Aspect Ratio¶
Contours Line의 가로 세로 비율 속성입니다.
cv2.boundingRect()
함수를 이용하여 가로/세로 크기를 구한 후에 사용합니다.:
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h
Extend¶
Contour Line을 포함하는 사각형 면적대비 Contour의 면적 비율입니다.
area = cv2.contourArea(cnt) # Contour Line의 면적
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h # 사각형 면적
extend = float(area) / rect_area
Solidity¶
Solidity Ratio(고형비)는 Convex hull 면적 대비 Contour의 면적 비율입니다.
area = cv2.contourArea(cnt) # Contour Line면적
hull = cv2.convexHull(cnt) # Convex hull line
hull_area = cv2.contourArea(hull) # Convex hull 면적
solidity = float(area) / hull_area
Extream Points¶
Contour Line의 좌우상하의 끝점을 찾는 방법입니다.:
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
cnt는 contour point가 포함된 array입니다. 여기서 cnt[:,:,0]
의 의미는 point의 x 좌표 값만 포함하는 배열이 됩니다.
여기에 argmin()
을 적용하면 x좌표가 가장 작은 array의 위치가 나오게 됩니다. 그 위치를 다시 cnt에서 찾으면 가장 왼쪽에 있는 좌표를 얻을 수 있습니다.
나머지도 동일한 방법으로 좌우상하의 끝점을 찾을 수 있습니다.
아래는 지도상에서 끝점을 찾아 표시하는 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/UK.jpg')
img1 = img.copy()
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,125,255,0)
image, contours, hierachy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[14] # 14번째가 지도의 contour line
# 끝점 좌표 찾기
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
# 좌표 표시하기
cv2.circle(img1,leftmost,20,(0,0,255),-1)
cv2.circle(img1,rightmost,20,(0,0,255),-1)
cv2.circle(img1,topmost,20,(0,0,255),-1)
cv2.circle(img1,bottommost,20,(0,0,255),-1)
img1 = cv2.drawContours(img1, cnt, -1, (255,0,0), 5)
titles = ['Original','Result']
images = [img, img1]
for i in xrange(2):
plt.subplot(1,2,i+1), plt.title(titles[i]), plt.imshow(images[i])
plt.xticks([]), plt.yticks([])
plt.show()
|
Result

Contours Hierarchy¶
Goal¶
- Contours의 Hierarchy구조에 대해서 알 수 있다.
Image에는 여러개의 Contours가 존재하고, 그 사이에는 서로 포함하는 관계가 존재합니다. 그 관계를 Contours Hierarchy라고 합니다.
이전, 이후, Parent, Child 관계를 파악할 수 있습니다. 이런 관계를 파악하기 위해서는 cv2.findContours()
에 Contour Retrieval Mode값에 의해서
결정이 됩니다.
그럼 먼저 Contours Hierarchy에 대해서 알아 보겠습니다.
Hierarchy¶
아래 원본 이미지에 대해서 Contour Line을 적용한 결과 입니다.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
import random
from matplotlib import pyplot as plt
img = cv2.imread('images/imageHierarchy.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,125,255,0)
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for i in xrange(len(contours)):
#각 Contour Line을 구분하기 위해서 Color Random생성
b = random.randrange(1,255)
g = random.randrange(1,255)
r = random.randrange(1,255)
cnt = contours[i]
img = cv2.drawContours(img, [cnt], -1,(b,g,r), 2)
titles = ['Result']
images = [img]
for i in xrange(1):
plt.subplot(1,1,i+1), plt.title(titles[i]), plt.imshow(images[i])
plt.xticks([]), plt.yticks([])
plt.show()
|
Result

위 결과는 총 9개의 contour line으로 구성이 되어 있습니다. 주의해서 봐야할 부분은 3,3a와 4,4a입니다. Hirerarchy 구성시 child의 child가 있을 경우 바깥선과 안쪽선이 서로 분리가 되어 contour line을 구성합니다. 이는 포함 관계를 표현하기 위해서 입니다.(5의 경우는 child만 있기 때문에 contour line이 분리되지 않았습니다.)
이제 contour retrival mode에 따라서 hirerarchy값이 어떻게 표현이 되는 지 확인해 보겠습니다.
RETR_LIST¶
hierarchy의 shape는 (1, x, 4)의 형태입니다. 여기서 3번째 차원의 4개의 값이 hierarchy를 표현합니다. 각 값의 의미는 (next, prev, child, parent) 입니다.
RETR_LIST는 선/후 관계만을 표현하고, parent/child관계를 표현하지 않는 mode입니다.
먼저 위 예제에서 mode를 cv2.RETR_LIST
로 한 결과를 확인해 보겠습니다.
>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[ 3, 1, -1, -1],
[ 4, 2, -1, -1],
[ 5, 3, -1, -1],
[ 6, 4, -1, -1],
[ 7, 5, -1, -1],
[ 8, 6, -1, -1],
[-1, 7, -1, -1]]])
mode의 특성대로 next와 prev는 값이 있지만, child와 parent는 모두 -1 입니다.(-1은 대상이 없음을 의미함.) 예를 들으 보면 contour-0는 next가 contour-1이고, prev와 child, parent가 없다는 의미 입니다. contour-1은 next가 contour-2이고, prev가 contour-0이고, child와 parent는 해당 사항이 없습니다. Hierarchy를 구할 필요가 없을 때 사용하면 좋습니다.
RETR_EXTERNAL¶
이 mode는 가장 바깥쪽(다른 Contours Line에 포함되지 않는)에 있는 contour만을 return합니다. 위 예에서는 1,2,3번 line입니다.(parent/child는 구성하지 않습니다.)
>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[-1, 1, -1, -1]]])
RETR_CCOMP¶
이 mode는 Hierarchy를 2-Level로 표현합니다. 바깥쪽(외곽선)은 모두 1-Level, 안에 포함된 것은 2-Level이 됩니다.

위 그림을 보면 괄호 밖 숫자는 contours의 순서이고, 괄호 안 숫자는 hierachy를 나타냅니다. 이전과 다른 점은 가장 안쪽에서 부터 contour의 순서를 부여하게 됩니다.
먼저 contour-0은 2개의 contour를 포함하기 때문에 hierarchy-1입니다. 동일 level의 next는 3이고, previous는 없습니다. child는 contour-1이고, parent는 없습니다. 그래서 결과적으로 [3,-1,1,-1]의 값을 갖게 됩니다.
contour-1은 contour-0에 포함이 되기 때문에 hierachy-2가 됩니다. 동일 level의 next는 contour-2가 되고, previous와 child는 없으며, parent는 contour-0입니다. 그래서 [2,-1,-1,0]의 값을 갖게 됩니다.
위와 같은 방식으로 나머지 contour 값을 찾으면 아래와 같습니다.
>>> hierarchy
array([[[ 3, -1, 1, -1],
[ 2, -1, -1, 0],
[-1, 1, -1, 0],
[ 5, 0, 4, -1],
[-1, -1, -1, 3],
[ 7, 3, 6, -1],
[-1, -1, -1, 5],
[ 8, 5, -1, -1],
[-1, 7, -1, -1]]])
RETR_TREE¶
이 mode는 Hierarchy를 완전한게 표현합니다. 즉 누구에게도 포함되지 않은 contour는 hierarchy-0이 되고, 그 안쪽으로 포함되는 contours는 순서대로 hierachy를 부여받습니다.

contour-0은 next는 contour-7, previous는 없으며, child는 contour-1, parent는 없습니다. 결과는 [7,-1,1,-1] 입니다. contour-1은 next는 없고, previous도 없고, child는 contour-2, parent는 contour-0입니다. 결과는 [-1,-1,2,0] 입니다.
위와 같은 방식으로 나머지 contour 값을 찾으면 아래와 같습니다.
>>> hierarchy
array([[[ 7, -1, 1, -1],
[-1, -1, 2, 0],
[-1, -1, 3, 1],
[-1, -1, 4, 2],
[-1, -1, 5, 3],
[ 6, -1, -1, 4],
[-1, 5, -1, 4],
[ 8, 0, -1, -1],
[-1, 7, -1, -1]]])
히스토그램¶
Goal¶
- OpenCV를 이용하여 Histogram을 찾을 수 있다.
- OpenCV와 Matplotlib를 이용하여 Histogram을 표현할 수 있다.
cv2.calcHist()
와np.histogram()
함수를 사용할 수 있다.
Histogram¶
Histogram은 이미지의 밝기의 분포를 그래프로 표현한 방식입니다. 히스토그램을 이용하면 이미지의 전체의 밝기 분포와 채도(색의 밝고 어두움)를 알 수 있습니다.

Histogram<출처 : Cambridgeincolor in Color
위 그림의 아래 그래프를 보면 X축이 색의 강도(0 ~ 255), Y축이 X축에 해당하는 색의 갯수 입니다. 그래프만 보면 이미지 밝기의 분포가 중간값은 거의 없고, 어둡고 밝은 색 분포가 많다는 것을 알 수 있습니다. 실제 원본 이미지를 보면 그래프만 보고 분석한 결과와 유사합니다.
히스토그램 찾기¶
히스토그램을 분석하기 전에 몇가지 용어에 대해서 알아 보겠습니다.
- BINS : 히스토그램 그래프의 X축의 간격입니다. 위 그림의 경우에는 0 ~ 255를 표현하였기 때문에 BINS값은 256이 됩니다. BINS값이 16이면 0 ~ 15, 16 ~ 31..., 240 ~ 255와 같이 X축이 16개로 표현이 됩니다. OpenCV에서는 BINS를 histSize 라고 표현합니다.
- DIMS : 이미지에서 조사하고자하는 값을 의미합니다. 빛의 강도를 조사할 것인지, RGB값을 조사할 것인지를 결정합니다.
- RANGE : 측정하고자하는 값의 범위입니다. 즉, X축의 from ~ to로 이해할 수 있습니다.
Histogram in OpenCV¶
OpenCV에서 Histogram분석을 위해서 cv2.calcHist()
함수를 사용합니다.
-
cv2.
calcHist
(images, channels, mask, histSize, ranges[, hist[, accumulate]])¶ Parameters: - image – 분석대상 이미지(uint8 or float32 type). Array형태.
- channels – 분석 채널(X축의 대상). 이미지가 graysacle이면 [0], color 이미지이면 [0],[0,1] 형태(1 : Blue, 2: Green, 3: Red)
- mask – 이미지의 분석영역. None이면 전체 영역.
- histSize – BINS 값. [256]
- ranges – Range값. [0,256]
아래 2가지 이미지를 Grayscale로 읽어 빛의 세기 분포를 보여주는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #-*- coding:utf-8 -*-
import cv2
import numpy as np
import random
from matplotlib import pyplot as plt
img1 = cv2.imread('images/flower1.jpg',0)
img2 = cv2.imread('images/flower2.jpg',0)
hist1 = cv2.calcHist([img1],[0],None,[256],[0,256])
hist2 = cv2.calcHist([img2],[0],None,[256],[0,256])
plt.subplot(221),plt.imshow(img1,'gray'),plt.title('Red Line')
plt.subplot(222),plt.imshow(img2,'gray'),plt.title('Green Line')
plt.subplot(223),plt.plot(hist1,color='r'),plt.plot(hist2,color='g')
plt.xlim([0,256])
plt.show()
|
Result

Red Line이미지는 전체적으로 어둡기 때문에 히스토그램에서 좌측의 분포가 높고, Green Line 이미지는 전체적으로 밝기 때문에 오른쪽의 분포가 높습니다.
Mask를 적용한 히스토그램¶
이미지의 특정 영역의 히스토그램을 분석하기 위해서 mask를 적용할 수 있습니다.
아래는 이미지에 mask를 적용한 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #-*-coding:utf-8-*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/lena.png');
# mask생성
mask = np.zeros(img.shape[:2],np.uint8)
mask[100:300,100:400] = 255
# 이미지에 mask가 적용된 결과
masked_img = cv2.bitwise_and(img,img,mask=mask)
# 원본 이미지의 히스토그램
hist_full = cv2.calcHist([img],[1],None,[256],[0,256])
# mask를 적용한 히스트로그램
hist_mask = cv2.calcHist([img],[1],mask,[256],[0,256])
plt.subplot(221),plt.imshow(img,'gray'),plt.title('Origianl Image')
plt.subplot(222),plt.imshow(mask,'gray'),plt.title('Mask')
plt.subplot(223),plt.imshow(masked_img,'gray'),plt.title('Masked Image')
# red는 원본이미지 히스토그램, blue는 mask적용된 히스토그램
plt.subplot(224),plt.title('Histogram')
plt.plot(hist_full,color='r'),plt.plot(hist_mask,color='b')
plt.xlim([0,256])
plt.show()
|
Result

히스토그램 균일화¶
Goal¶
- 히스토그램 균일화(Histogram Equalization)에 대해서 알 수 있고, 이것을 이용하여 이미지의 contrast를 향상시킬 수 있다.
Theory¶
이미지의 히스토그램이 특정영역에 너무 집중되어 있으면 contrast가 낮아 좋은 이미지라고 할 수 없습니다. 전체 영역에 골고루 분포가 되어 있을 때 좋은 이미지라고 할 수 있습니다. 아래 히스토그램을 보면 좌측 처럼 특정 영역에 집중되어 있는 분포를 오른쪽 처럼 골고루 분포하도록 하는 작업을 Histogram Equalization 이라고 합니다.

이론적인 방법은 이미지의 각 픽셀의 cumulative distribution function(cdf)값을 구하고 Histogram Equalization 공식에 대입하여 0 ~ 255 사이의 값으로 변환을 하게 됩니다. 이렇게 새롭게 구해진 값으로 이미지를 표현하면 균일화된 이미지를 얻을 수 있습니다.
자세한 내용은 Wikipedia 를 참고하시기 바랍니다.
그럼 우선 Numpy를 이용하여 균일화 작업을 하는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #-*-coding:utf-8-*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/hist_unequ.jpg');
hist, bins = np.histogram(img.flatten(), 256,[0,256])
cdf = hist.cumsum()
# cdf의 값이 0인 경우는 mask처리를 하여 계산에서 제외
# mask처리가 되면 Numpy 계산에서 제외가 됨
# 아래는 cdf array에서 값이 0인 부분을 mask처리함
cdf_m = np.ma.masked_equal(cdf,0)
#History Equalization 공식
cdf_m = (cdf_m - cdf_m.min())*255/(cdf_m.max()-cdf_m.min())
# Mask처리를 했던 부분을 다시 0으로 변환
cdf = np.ma.filled(cdf_m,0).astype('uint8')
img2 = cdf[img]
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.subplot(122),plt.imshow(img2),plt.title('Equalization')
plt.show()
|
Result

Note
Numpy의 masked array 는 대상에서 비정상적인 대상을 제거할 때 사용되는 모듈입니다. 자세한 사항은 Numpy document를 참고해주시기 바랍니다.
Histogram Equalization의 결과는 밝은 이미지나 어두운 이미지 어떤 것을 사용해도 동일한 결과가 나옵니다. 이것은 이미지의 인식을 할 때 유용합니다. 예를 들면 얼굴인식을 할때 대상 이미지를 Equalization을 하고 나면 동일한 밝기가 되기 때문에 동일한 환경에서 작업을 할 수가 있습니다.
OpenCV에서는 아래의 함수로 간단하게 Equalization을 처리할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #-*-coding:utf-8-*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/hist_unequ.jpg',0);
# OpenCV의 Equaliztion함수
img2 = cv2.equalizeHist(img)
img = cv2.resize(img,(400,400))
img2 = cv2.resize(img2,(400,400))
dst = np.hstack((img, img2))
cv2.imshow('img',dst)
cv2.waitKey()
cv2.destroyAllWindows()
|
Result

CLAHE (Contrast Limited Adaptive Histogram Equalization)¶
지금까지의 처리는 이미지의 전체적인 부분에 균일화를 적용하였습니다. 하지만 일반적인 이미지는 밝은 부분과 어두운 부분이 섞여 있기 때문에 전체에 적용하는 것은 그렇게 유용하지 않습니다. 아래 결과를 보시면 이해가 될겁니다.

위 결과에서 주변의 어두운 부분은 균일화가 적용되어 밝아졌지만, 가운데 이미지는 너무 밝아져 경계선을 알아볼 수 없게 되었습니다. 이 문제를 해결하기 위해서 adaptive histogram equalization을 적용하게 됩니다. 즉, 이미지를 작은 title형태로 나누어 그 title안에서 Equalization을 적용하는 방식입니다. 그런데 여기서도 한가지 문제가 있습니다. 작은 영역이다 보니 작은 노이즈(극단적으로 어둡거나, 밝은 영역)가 있으면 이것이 반영이 되어 원하는 결과를 얻을 수 없게 됩니다. 이 문제를 피하기 위해서 contrast limit라는 값을 적용하여 이 값을 넘어가는 경우는 그 영역은 다른 영역에 균일하게 배분하여 적용을 합니다.
그러면 CLAHE를 적용한 결과를 다시 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #-*-coding:utf-8-*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/clahe.png',0);
# contrast limit가 2이고 title의 size는 8X8
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img2 = clahe.apply(img)
img = cv2.resize(img,(400,400))
img2 = cv2.resize(img2,(400,400))
dst = np.hstack((img, img2))
cv2.imshow('img',dst)
cv2.waitKey()
cv2.destroyAllWindows()
|
Result

결과를 보면 가운데 이미지의 윤곽선도 유지가 되면서 전체적인 contrast가 높아진 것을 볼 수 있습니다.
2D Histogram¶
Goal¶
- 2D Histogram을 찿아서 plot형태로 그릴 수 있다.
소개¶
지금까지 Histogram은 1차원으로 grayscale 이미지의 pixel의 강도, 즉 빛의 세기를 분석한 결과였습니다. 2D Histogrm은 Color 이미지의 Hue(색상) & Saturation(채도)을 동시에 분석하는 방법입니다.
이 결과는 다음 장에서 설명할 Histogram Back-Projection에서 유용하게 사용이 됩니다.
적용¶
우선 Hue와 Saturation으로 분석을 하기 때문에 대상 이미지를 HSV Format로 변환을 해야 합니다.
그 다음에 calcHist()
라는 OpenCV의 Histogram분석 함수에 적용을 합니다.
-
calcHist
([image, ][channel, ]mask[, bins][, range])¶ Histogram 분석 함수
Parameters: - image – HSV로 변환된 이미지
- channel – 0-> Hue, 1-> Saturation
- bins – [180,256] 첫번째는 Hue, 두번째는 Saturation
- range – [0,180,0,256] : Hue(0~180), Saturation(0,256)
아래 이미지의 2D Histogram을 분석할 결과 입니다.

Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #-*- coding:utf-8-*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/home.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv],[0,1],None,[180,256],[0,180,0,256])
cv2.imshow('Hist',hist)
plt.imshow(hist) #, interpolation='nearest')
plt.show()
|

위 Histogram을 보면 X축은 Saturation, Y축은 Hue값을 나타냅니다. Y축을 보면 100근처에 값이 모여 있는 것을 알 수 있습니다. HSV모델에서 H가 100이면 하늘색입니다. 그리고 25근처에도 값이 모여 있습니다. H값이 25이면 노란색입니다. 즉 이 이미지는 하늘색과 노란색이 많이 분포되어 있다는 것을 2D Histogram을 통해서 알 수 있습니다.
다음 장에서는 이 2D Histogram을 이용하여 Histogram Backprojection에 대해서 알아 보겠습니다.
푸리에 변환¶
Goal¶
- Numpy와 OpenCV를 이용하여 푸리에 변환을 찾을 수 있다.
- 푸리에 변환을 이용하여 이미지를 변환할 수 있다.
푸리에 변환¶
푸리에 변환은 주파수를 분석하는데 사용되는 방법입니다. 주파수는 시간의 흐름에 따른 진동하는 파동의 횟수를 의미합니다. 이때 파동은 Sin, cos의 삼각함수로 표현할 수 있는데, 이렇게 되면 시간축을 제거하고 파동의 전체적인 모습을 한눈에 볼 수 있게 됩니다.
즉, 무한대의 시간축이 제거가 되고 주파수의 관점에서 분석이 가능해집니다. 아래 그래프를 보면 이해가 좀 쉽게 될 수 있습니다.


시간 도메인을 주파수 도메인으로 변환한 결과(출처 : Incodom )
수학에서는 어떤 문제를 풀기가 어려운 경우, 다른 형태으로 변형하여 문제를 해결하는 경우가 많습니다. 페르마의 정리를 증명할때 증명이 어려워, 해당 방정식을 타원방정식으로 변환하여 타원 방정식의 공식으로 증명을 한 경우도 있습니다.
푸리에 변환도 마찬가지로 변환을 통해서 수학적으로 문제를 해결하는 방법입니다.
그럼 이미지에 푸리에 변환이 어떻게 적용이 될까요. 이미지도 파동으로 변환을 할 수가 있는데, 주변 픽셀과의 밝기 변환가 많은 곳은 고주파로, 변환이 적은 곳은 저주파로 표현이 가능합니다.
즉, 이미지에서 고주파의 의미는 경계선을 의미하고, 저주파는 배경을 의미합니다. 그러므로 고주파를 제거하면 경계선이 사라지고, 저주파를 제거하면 경계선만 남게 됩니다.
이미지 -> 푸리에 변환 -> 고주파 또는 저주파 제거 -> 다시 이미지 변환 과정을 거쳐 경계 또는 배경만 남게 할 수 있습니다.
푸리에 변환 With Numpy¶
아래는 Numpy를 이용한 푸리에 변환 결과 입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #-*- coding:utf-8 -*-
"""
# Fourier Transform(푸리에 변환)
. 시간 도메인(X축)에서 표현된 신호(일반적인 파형 도표)를 주파수 도메인으로 변환.
. 시간축이 제거되어 대상의 전체적인 특징을 파악할 수 있음.
. 이미지에 적용이 되어 중심이 저주파 영역, 주변이 고주파 영역을 나타냄.
. 푸리에 변환을 하여 저주파 또는 고주파를 제거하고 다시 역으로 이미지로 변환 함으로써
이미지가공을 할 수 있음.
(ex; 푸리에 변환 후 중심의 저주파를 제거하고 다시 Image로 전환 하면 이미지의 경계선만 남게 됨.
푸리에 변환 후 주변의 고주파를 제거하면 모아레 패턴(휴대폰으로 모니터를 찍었을 때 나타나는 현상)
을 제거할 수 있음.(모니터의 고주파를 제거함.)
)
"""
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/lena.jpg')
b,g,r = cv2.split(img)
img = cv2.merge([r,g,b])
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
"""
# Fourier Transform을 적용.
적용을 하면 0,0, 즉 화면 좌측상단점이 중심이고, 거기에 저주파가 모여 있음.
분석을 용이하게 하기 위해 0,0을 이미지의 중심으로 이동 시키고 Log Scaling을 하여 분석이 용이한 결과값으로 변환
"""
f = np.fft.fft2(img) # 이미지에 푸리에 변환 적용
fshift = np.fft.fftshift(f) #분석을 용이하게 하기 위해 주파수가 0인 부분을 중앙에 위치시킴. 중앙에 저주파가 모이게 됨.
magnitude_spectrum = 20*np.log(np.abs(fshift)) #spectrum 구하는 수학식.
rows, cols = img.shape
crow, ccol = rows/2, cols/2 # 이미지의 중심 좌표
# 중앙에서 10X10 사이즈의 사각형의 값을 1로 설정함. 중앙의 저주파를 모두 제거
# 저주파를 제거하였기 때문에 배경이 사라지고 경계선만 남게 됨.
d = 10
fshift[crow-d:crow+d, ccol-d:ccol+d] = 1
#푸리에 변환결과를 다시 이미지로 변환
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)
#threshold를 적용하기 위해 float type을 int type으로 변환
img_new = np.uint8(img_back);
ret, thresh = cv2.threshold(img_new,30,255,cv2.THRESH_BINARY_INV)
plt.subplot(221),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(222),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Spectrum'), plt.xticks([]), plt.yticks([])
plt.subplot(223),plt.imshow(img_back, cmap = 'gray')
plt.title('FT'), plt.xticks([]), plt.yticks([])
plt.subplot(224),plt.imshow(thresh, cmap = 'gray')
plt.title('Threshold With FT'), plt.xticks([]), plt.yticks([])
plt.show()
|
Result

Numpy를 이용한 푸리에 변환 결과
푸리에 변환 with OpenCV¶
이번 예제는 OpenCV를 통한 푸리에 변환입니다. 위 예제와는 다르게 고주파 영역을 제거하는 예제입니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('images/lena_gray.png',0)
dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
rows, cols = img.shape
crow,ccol = rows/2 , cols/2
# 아래는 d 사이지의 사각형을 생성한 후, 사각형 바깥쪽을 제거하는 형태임.
# 즉, 고주파영역을 제거하게 됨.
# d값이 작을수록 사각형이 작고, 바깥영역 즉, 고주파영역이 많이 제거되기 때문에 이미지가 뭉게지고
# d값이 클수록 사각형이 크고, 바깥영역 즉, 고주파 영역이 적게 제거되기 때문에 원래 이미지와 가까워짐.
d = 30
mask = np.zeros((rows,cols,2),np.uint8)
mask[crow-d:crow+d, ccol-d:ccol+d] = 1
# apply mask and inverse DFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('FT'), plt.xticks([]), plt.yticks([])
plt.show()
|
Result

템플릿 매칭¶
Goal¶
- Template Matching을 이용하여 이미지를 찾을 수 있다.
cv2.matchTemplate()
,cv2.minMaxLoc()
함수에 대해서 알 수 있다
개요 ===
템플릿 매칭은 원본 이미지에서 특정 이미지를 찾는 방법입니다. 이때 사용하는 함수가 cv2.matchTemplate()
함수입니다.
원본 이미지에 템플릿 이미지를 좌측상단 부터 미끄러지듯이 우측으로 이동하면서 계속 비교를 하는 것입니다.
Return되는 값은 Gray 이미지로, 원본의 픽셀이 템플릿 이미지와 유사한 정도를 표현합니다. 이때 강도는 매칭 방법에 따라서 다릅니다.
아래는 매칭 방법에 따라서 결과가 어떻게 나오는지 보여주는 예제입니다.
cv2.TM_SQDIFF
, cv2.TM_SQDIFF_NORMED
은 가장 어두운 곳이 매칭지점이고, 나머지는 가장 밝은 곳이 매칭 지정이 됩니다.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #-*-coding:utf-8-*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/capture 0.png',0)
img2 = img.copy()
template = cv2.imread('images/cap_template.png',0)
# template 이미지의 가로/세로
w,h = template.shape[::-1]
# Template Match Method
methods = ['cv2.TM_CCOEFF','cv2.TM_CCOEFF_NORMED','cv2.TM_CCORR','cv2.TM_CCORR_NORMED','cv2.TM_SQDIFF','cv2.TM_SQDIFF_NORMED']
for meth in methods:
img = img2.copy()
method = eval(meth)
res = cv2.matchTemplate(img,template,method)
min_val,max_val,min_loc, max_loc = cv3.minMaxLoc(res)
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0]+w,top_left[1]+h)
cv2.rectangle(img,top_left,bottom_right,255,5)
plt.subplot(121),plt.title(meth),plt.imshow(res,cmap='gray'),plt.yticks([]),plt.xticks([])
plt.subplot(122),plt.imshow(img,cmap='gray')
plt.show()
|
Result

TM_CCOEFF Method

TM_CCOEFF_NORMED Method

TM_CCORR Method

TM_CCORR_NORMED Method

TM_SQDIFF Method

TM_SQDIFF_NORMED Method
위 결과는 보면 좌측 이미지가 Matching결과 이고, 붉은 상자 부분이 Matching Method에 따라 템플릿 이미지를 찾은 영역입니다.
허프 변환¶
Goal¶
- 허프 변환에 대해서 알수 있다.
- 허프 변환을 이용하여 이미지의 Line을 찾을 수 있다.
- 허프 변환에서 사용하는
cv2.HoughLines()
,cv2.HoughLinesP()
함수에 대해서 알 수 있다.
Theory¶
허프변환은 이미지에서 모양을 찾는 가장 유명한 방법입니다. 이 방법을 이용하면 이미지의 형태를 찾거나, 누락되거나 깨진 영역을 복원할 수 있습니다.
기본적으로 허프변환의 직선의 방정식을 이용합니다. 하나의 점을 지나는 무수한 직선의 방적식은 y=mx+c로 표현할 수 있으며, 이것을 삼각함수를 이용하여 변형하면 r = 𝑥 cos 𝜃 + 𝑦 sin 𝜃 으로 표현할 수 있습니다.
그럼 아래 이미지를 보고 설명을 하겠습니다. 3개의 점이 있고, 그중 우리가 찾는 직선은 핑크색 직선 입니다.
그럼 각 점(x,y)에 대해서 삼각함수를 이용하여 𝜃 값을 1 ~ 180까지 변화를 하면서 원점에서 (x,y)까지의 거리(r)을 구합니다. 그러면 (𝜃, r)로 구성된 180개의 2차원 배열을 구할 수 있습니다.
동일한 방법으로 두번째 점에 대해서도 𝜃값을 변화해 가면서 2차원 배열을 구합니다.

(출처: 위키 피디아 )
이렇게 해서 구해서 2차원 배열을 다시 그래프로 표현하면 아래와 같이 사인파 그래프로 표현이 됩니다. 아래 3개의 방정식의 만나는 점이 바로 직선인 확율이 높은 점 입니다. 즉, 𝜃가 60이고 거리가 80인 직선의 방정식을 구할 수 있는 것 입니다.

OpenCV를 이용한 허프변환¶
OpenCV는 위에서 설명한 수학적 이론이 cv2.HoughLines()
함수에 구현이 되어 있습니다.
-
cv2.HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]]) → lines
Parameters: - image – 8bit, single-channel binary image, canny edge를 선 적용.
- rho – r 값의 범위 (0 ~ 1 실수)
- theta – 𝜃 값의 범위(0 ~ 180 정수)
- threshold – 만나는 점의 기준, 숫자가 작으면 많은 선이 검출되지만 정확도가 떨어지고, 숫자가 크면 정확도가 올라감.
Sample Code
#-*- coding:utf-8-*-
import cv2
import numpy as np
img = cv2.imread(r'images\chessboard\frame01.jpg')
img_original = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize=3)
lines = cv2.HoughLines(edges,1,np.pi/180,100)
for i in xrange(len(lines)):
for rho, theta in lines[i]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0+1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 -1000*(a))
cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)
res = np.vstack((img_original,img))
cv2.imshow('img',res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result

threshold가 100일 경우

threshold가 130일 경우
확율 허프 변환¶
허프변환은 모든 점에 대해서 계산을 하기 때문에 시간이 많이 소요됩니다. 확율 허프변환(Probabilistic Hough Transform)은 이전 허프변환을 최적화 한 것 입니다. 모든 점을 대상으로 하는 것이 아니라 임의의 점을 이용하여 직선을 찾는 것입니다. 단 임계값을 작게 해야만 합니다.
cv2.HoughLinesP()
함수를 이용하는데, 장점은 선의 시작점과 끝점을 Return해주기 때문에
쉽게 화면에 표현할 수 있습니다.
-
cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap) → lines
Parameters: - image – 8bit, single-channel binary image, canny edge를 선 적용.
- rho – r 값의 범위 (0 ~ 1 실수)
- theta – 𝜃 값의 범위(0 ~ 180 정수)
- threshold – 만나는 점의 기준, 숫자가 작으면 많은 선이 검출되지만 정확도가 떨어지고, 숫자가 크면 정확도가 올라감.
- minLineLength – 선의 최소 길이. 이 값보다 작으면 reject.
- maxLineGap – 선과 선사이의 최대 허용간격. 이 값보다 작으며 reject.
Sample Code
import cv2
import numpy as np
img = cv2.imread('images\hough_images.jpg')
edges = cv2.Canny(img,50,200,apertureSize = 3)
gray = cv2.cvtColor(edges,cv2.COLOR_GRAY2BGR)
minLineLength = 100
maxLineGap = 0
lines = cv2.HoughLinesP(edges,1,np.pi/360,100,minLineLength,maxLineGap)
for i in xrange(len(lines)):
for x1,y1,x2,y2 in lines[i]:
cv2.line(img,(x1,y1),(x2,y2),(0,0,255),3)
cv2.imshow('img1',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result

MinLineLength = 100, MaxLineGap = 10

MinLineLength = 100, MaxLineGap = 0
Hough Circle Transform¶
Goal¶
- 이미지에서 원을 찾을 수 있는 허프변환에 대해서 알 수 있다.
cv2.HoughCircles()
함수에 대해서 알 수 있다.
Theory¶
원은 수학적으로 아래와 같이 표현이 됩니다.
위 수식에서는 3개의 변수가 있습니다. 이것을 모든 점에 대해서 수행을 하게 되면 상당히 비효율적입니다. 그래서 openCV에서는 가장자리에서 기울기를 측정하여 원을 그리는데 관련이 있는 점인지 확인할 수 있는 Hough Gradient Method를 사용합니다.
-
cv2.HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]]) → circles
Parameters: - image – 8-bit single-channel image. grayscale image.
- method – 검출 방법. 현재는 HOUGH_GRADIENT가 있음.
- dp – dp=1이면 Input Image와 동일한 해상도.
- minDist – 검출한 원의 중심과의 최소거리. 값이 작으면 원이 아닌 것들도 검출이 되고, 너무 크면 원을 놓칠 수 있음.
- param1 – 내부적으로 사용하는 canny edge 검출기에 전달되는 Paramter
- param2 – 이 값이 작을 수록 오류가 높아짐. 크면 검출률이 낮아짐.
- minRadius – 원의 최소 반지름.
- maxRadius – 원의 최대 반지름.
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #-*-coding:utf-8-*-
import cv2
import numpy as np
img = cv2.imread('images\copy.png',0)
img = cv2.medianBlur(img,5)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20,param1=50,param2=25,minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('img', cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
|
Result

Watershed 알고리즘을 이용한 이미지 분할¶
Goal¶
- Watershed 알고리즘을 이용하여 이미지를 구분할 수 있다.
cv2.watershed()
함수에 대해서 알 수 있다.
Theory¶
이미지를 Grayscale로 변환하면 각 Pixel의 값(0 ~255)은 높고 낮음으로 구분할 수 있을 것입니다. 이것을 지형의 높낮이로 가정하고 높은 부분을 봉우리, 낮은 부분을 계곡이라고 표현할 수 있습니다.
그럼 그곳에 물을 붓는다고 생각하면 물이 섞이는 부분이 생길것입니다. 그 부분에 경계선을 만들어 서로 섞이지 않게 합니다. 바로 그 경계선을 이미지의 구분지점으로 파악하여 이미지 분할을 하게 됩니다.
아래 그림은 위 내용을 이미지로 표현한 것입니다.
![]()
(출처: CMM Webpage )
Code¶
이제 어떤 순서로 진행이 되는지 알아보겠습니다.
아래는 이미지 분할을 위한 원본이미지 입니다.

먼저 이미지를 grayscale로 변환을 합니다.
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('images/water_coins.jpg')
# binaray image로 변환
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
다음으로 morphology를 이용하여 이미지의 노이즈나 hole을 제거 합니다.
#Morphology의 opening, closing을 통해서 노이즈나 Hole제거
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)
다음은 전경과 배경을 구분을 해야 합니다. dilate를 이용하여 경계를 확장을 시킵니다. 그러면 서로 연결되지 않은 부분을 배경으로 간주 합니다. 다음은 전경을 찾아야 합니다. 전경은 opning한 결과에 거리 변환함수를 적용합니다. 거리변환 함수를 적용하면 중심으로 부터 skeloton image를 얻을 수 있습니다. 즉, 중심으로 부터 점점 옅어져가는 영상을 파악할 수 있습니다. 그 결과에 threshold를 적용하여 확실한 전경을 찾아 냅니다.
# dilate를 통해서 확실한 Backgroud
sure_bg = cv2.dilate(opening,kernel,iterations=3)
#distance transform을 적용하면 중심으로 부터 Skeleton Image를 얻을 수 있음.
# 즉, 중심으로 부터 점점 옅어져 가는 영상.
# 그 결과에 thresh를 이용하여 확실한 FG를 파악
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform,0.5*dist_transform.max(),255,0)
sure_fg = np.uint8(sure_fg)
다음은 확실하지 않은 영역을 파악합니다. 이것은 이전에 구한 배경에서 전경을 뺀 영역입니다.
# Background에서 Foregrand를 제외한 영역을 Unknow영역으로 파악
unknown = cv2.subtract(sure_bg, sure_fg)
이제 전경에 labelling작업을 합니다. labelling은 서로 이어져 있는 부분에 라벨을 붙여 서로 동일한 객체라는 것을 구분하기 위함입니다.
# FG에 Labelling작업
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1
markers[unknown == 255] = 0
이제 watershed함수를 적용하고 그 결과값이 -1인 영역이 경계값이 됩니다. 그 부분에 붉은 색을 지정하면 동전의 경계에 붉은 원이 생긴것을 볼 수 있습니다.
# watershed를 적용하고 경계 영역에 색지정
markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]
아래는 전체 코드입니다.
#-*-coding:utf-8-*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
# img = cv2.imread('images/watershed.jpg')
img = cv2.imread('images/water_coins.jpg')
# binaray image로 변환
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
#Morphology의 opening, closing을 통해서 노이즈나 Hole제거
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)
# dilate를 통해서 확실한 Backgroud
sure_bg = cv2.dilate(opening,kernel,iterations=3)
#distance transform을 적용하면 중심으로 부터 Skeleton Image를 얻을 수 있음.
# 즉, 중심으로 부터 점점 옅어져 가는 영상.
# 그 결과에 thresh를 이용하여 확실한 FG를 파악
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform,0.5*dist_transform.max(),255,0)
sure_fg = np.uint8(sure_fg)
# Background에서 Foregrand를 제외한 영역을 Unknow영역으로 파악
unknown = cv2.subtract(sure_bg, sure_fg)
# FG에 Labelling작업
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1
markers[unknown == 255] = 0
# watershed를 적용하고 경계 영역에 색지정
markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]
images = [gray,thresh,sure_bg, dist_transform, sure_fg, unknown, markers, img]
titles = ['Gray','Binary','Sure BG','Distance','Sure FG','Unknow','Markers','Result']
for i in xrange(len(images)):
plt.subplot(2,4,i+1),plt.imshow(images[i]),plt.title(titles[i]),plt.xticks([]),plt.yticks([])
plt.show()
Result(새탭에서 이미지를 열어 크게 보세요)


k-Nearest Neighbour(kNN)¶
Goal¶
- k-Nearest Neighbour(kNN) 알고리즘에 대해서 알 수 있다.
Theory¶
Machine Learning에는 지도학습(Supervised Learning)과 비지도학습(Unsupervised Learning)가 있습니다.
지도학습은 훈련용 Data를 만들고, 사람이 답을 알려 줍니다. 그러면 컴퓨터는 알고리즘을 이용하여 훈련용으로 제시되지 않은 Data에 대해서도 값을 찾아 냅니다.
비지도학습은 훈련용 Data에 답을 제시하고 않고 컴퓨터가 스스로 답을 찾아내는 방법입니다.
kNN은 지도학습 중 단순한 알고리즘을 이용한 방법입니다.

위 그림은 삼각형과 사각형이 있는 공간 입니다. 이 공간에 가운데 초록색 원이 있습니다. 이 원은 삼각형일까요 사각형일까요. 이것을 판단하는 방법은 여러가지가 있을 것 입니다.
먼저 가장 가까운 점은 찾는 것 입니다. 위 이미지에서 보면 빨간색이 가까이에 있으니 초록색 원은 빨간색으로 판단할 수도 있습니다. 하지만 좀더 범위를 넓혀보면 오히려 파란색 점이 많이 있습니다. 이때 범위를 몇단계까지 넓혀 판단할 것인지에 결정하게 되는데 이때 넗히는 단계를 k값으로 정합니다.
위 그림에서 k가 3이면 빨간색 2개와 파란색 1개 이기 때문에 초록색원은 빨간색으로 판단할 수 있습니다. 만약 k값으 7로 하면 빨간색 2개와 파란색 5개가 있기 때문에 파란색으로 판단할 수 있습니다.
또한 k값에 가중치를 줄 수 있는데, 가까운곳에 더 많은 가중치를 두어서 판단할 수도 있습니다.
아래는 0 ~ 100의 좌표에 25개의 Random한 점을 생성합니다. Red는 0으로, Blue는 1로 분류를 한 후에 임의의 초록색 점을 생성하고, 그 값이 Red(0)인지 Blue(1)인지 판단하는 예제입니다.
import cv2
import numpy as np
from matplotlib import pyplot as plt
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)
response = np.random.randint(0,2,(25,1)).astype(np.float32)
red = trainData[response.ravel() == 0] #red는 0 class로 분류
plt.scatter(red[:,0],red[:,1], 80,'r','^')
blue = trainData[response.ravel() == 1] #blue는 1 Class분류
plt.scatter(blue[:,0], blue[:,1], 80, 'b', 's')
newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0], newcomer[:,1],80,'g', 'o')
knn = cv2.ml.KNearest_create()
knn.train(trainData, cv2.ml.ROW_SAMPLE, response)
ret, results, neighbours, dist = knn.findNearest(newcomer, 3) #k 값을 3으로 설정
print "result : ", results
print "neighbours :", neighbours
print "distance: ", dist
plt.show()
Result

>>> result : [[ 0.]]
>>> neighbours : [[ 1. 0. 0.]]
>>> distance: [[ 250. 293. 873.]]
kNN을 이용한 숫자 인식¶
Goal¶
- kNN Machine Learning 알고리즘을 이용하여 손글씨 숫자를 인식할 수 있다.
이번에는 kNN방식을 이용하여 손글씨 숫자를 인식하는 예제를 진행하도록 하겠습니다. 우선 traning data가 필요합니다. 아래는 Open CV에서 제공하는 Sample용 손글씨 이미지 입니다.

가로 100개, 세로 50개로 총 5000개의 숫자가 있습니다. 각 숫자는 20x20의 해상도를 가지고 있습니다. kNN을 이용하기 위해서 학습하기와 테스트로 나눠서 진행하겠습니다.
학습하기¶
- 우선 위 이미지를 가로/세로롤 잘라서 하나의 숫자를 배열에 넣습니다.
- 그러면 순서대로 0부터 9까지 각각 500개씩 배열에 넣어집니다.
- 배열값이 0 ~ 499까지는 1, 500 ~ 999까지는 2 ... 4499 ~ 4999는 9를 의미하는 이미지 값이 들어가게 됩니다.
- 그러면 500개씩 Loop를 수행하면서 각 배열에 Label작업을 합니다.
- 그리고 이 결과값으 numpy파일로 저장을 합니다.
테스트¶
- 학습한 numpy파일을 Load합니다.
- 마우스나 사진으로 찍은 손글씨 숫자를 학습할 때 사용한 동일한 해상도(20X20)으로 Resize를 합니다.
- kNN 알고리즘을 통해서 손글씩 숫자를 인식합니다.
재학습¶
- 테스트시 실제 손글씨와 컴퓨터가 인식한 값이 다를 경우 사람이 정확한 값을 입력해 줍니다.
- 이 값은 다시 numpy파일에 추가가 되어 재학습이 이루어 집니다.
아래는 학습과 테스트를 수행하는 예제입니다.
#-*- coding: utf-8 -*-
import cv2
import numpy as np
import glob
import sys
FNAME = 'digits.npz'
def machineLearning():
img = cv2.imread('images/digits.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
x = np.array(cells)
train = x[:,:].reshape(-1,400).astype(np.float32)
k = np.arange(10)
train_labels = np.repeat(k,500)[:,np.newaxis]
np.savez(FNAME,train=train,train_labels = train_labels)
def resize20(pimg):
img = cv2.imread(pimg)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
grayResize = cv2.resize(gray,(20,20))
ret, thresh = cv2.threshold(grayResize, 125, 255,cv2.THRESH_BINARY_INV)
cv2.imshow('num',thresh)
return thresh.reshape(-1,400).astype(np.float32)
def loadTrainData(fname):
with np.load(fname) as data:
train = data['train']
train_labels = data['train_labels']
return train, train_labels
def checkDigit(test, train, train_labels):
knn = cv2.ml.KNearest_create()
knn.train(train, cv2.ml.ROW_SAMPLE, train_labels)
ret, result, neighbours, dist = knn.findNearest(test, k=5)
return result
if __name__ == '__main__':
if len(sys.argv) == 1:
print 'option : train or test'
exit(1)
elif sys.argv[1] == 'train':
machineLearning()
elif sys.argv[1] == 'test':
train, train_labels = loadTrainData(FNAME)
saveNpz = False
for fname in glob.glob('images/num*.png'):
test = resize20(fname)
result = checkDigit(test, train, train_labels)
print result
k = cv2.waitKey(0)
if k > 47 and k<58:
saveNpz = True
train = np.append(train, test, axis=0)
newLabel = np.array(int(chr(k))).reshape(-1,1)
train_labels = np.append(train_labels, newLabel,axis=0)
cv2.destroyAllWindows()
if saveNpz:
np.savez(FNAME,train=train, train_labels=train_labels)
else:
print 'unknow option'
함수단위로 간단히 설명을 하면
- machineLearing
- resize20
- loadTrainData
- checkDigit
먼저 학습을 수행합니다. 학습을 수행하기 위해서 아래와 같이 입력합니다.
>>> python matchDigits.py train
위 작업을 수행하면 해당 폴더에 digits.npz
파일이 생성이 되어 있습니다. 이 파일이 학습한 결과 입니다.
이제 테스트를 진행해보겠습니다.
>>> python matchDigits.py test

위 화면에서 commnad창에 있는 숫자가 컴퓨터가 인식한 숫자이고, 오른쪽 작은 창에 있는 숫자가 테스트로 제공된 숫자입니다.
이렇게 잘못된 결과가 나왔을 때 정확한 값을 입력해주면 재학습이 이루어 집니다. 맞았을 경우는 숫자가 아닌 key를 눌러 다음 test로 넘어 갑니다.
아래는 첫번째 테스트의 결과 입니다.
- 첫번째 테스트 결과
손글씨 | 8 | 5 | 4 | 9 | 9 | 1 | 7 | 1 | 6 | 3 | 2 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
결과1 | 3 | 5 | 1 | 7 | 1 | 1 | 4 | 1 | 5 | 5 | 2 | 4 |
총 12개의 Sample에서 3번밖에 맞지 않았습니다. 이것은 학습에서 제공되었던 Data와 Test를 위해 마우스로 작성한 손글씨에 차이가 많이 있었기 때문입니다. 하지만 첫번째 테스트에서 재학습을 했기 때문에 다음 Test에서는 적중률이 더 높아 질 것 입니다.
그럼 다시 테스트를 진행하겠습니다.
손글씨 | 8 | 5 | 4 | 9 | 9 | 1 | 7 | 1 | 6 | 3 | 2 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
결과2 | 3 | 5 | 1 | 7 | 1 | 1 | 4 | 1 | 5 | 3 | 2 | 0 |
이번에는 12개중 5개가 맞았습니다. 한번 더 진행해 보겠습니다.
손글씨 | 8 | 5 | 4 | 9 | 9 | 1 | 7 | 1 | 6 | 3 | 2 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
결과3 | 6 | 5 | 1 | 7 | 1 | 1 | 4 | 1 | 6 | 3 | 2 | 0 |
결과4 | 8 | 5 | 1 | 9 | 9 | 1 | 7 | 1 | 6 | 3 | 2 | 0 |
결과5 | 8 | 5 | 4 | 9 | 9 | 1 | 7 | 1 | 6 | 3 | 2 | 0 |
총 4번의 테스트와 재학습결과 100% 적중률을 보였습니다. 이런식으로 많은 Data와 재학습을 통하면 정확도가 높아지게 됩니다.
Demo 준비1¶
사전 준비 작업¶
Demo를 위해서는 아래 프로그램이 설치가 되어 있어야 합니다.
- Python 2.7.X 버전
- Open CV 2.4.X 버전
- Python Library : numpy, Pygame. piSerial
또한 Raspberry Pi에 Pi Camera와 거리 측정을 위한 ultrasonic Sensor가 연결되어 있어야 합니다.
Demo소스 다운로드¶
Demo에 관련 소스는 Github 에서 확인할 수 있습니다.
git이 설치가 되어 있으면 terminal에서 아래와 같이 입력합니다.
>>> git clone https://github.com/hamuchiwa/AutoRCCar.git
git이 없으면 아래 ‘Clone or download’버튼을 눌러 zip 파일을 다운로드 받은 후 압축을 해제합니다.

동일한 작업을 Computer와 Raspberry Pi에서 수행을 하여 소스를 다운 받습니다.
소스 수정¶
다운 받은 소스에서 아래 부분을 Demo환경에 맞게 수정해야 합니다.
아두이노¶
- arduino/rc_keyboard_control.ino
// 아두이노와 RC Car와 연결된 Pin Number.
// 아래 번호를 수정하거나, 아래 Pin에 맞게 RC Car와 연결.
// 아래는 demo에 맞게 변경된 상태임.
int right_pin = 10;
int left_pin = 9;
int forward_pin = 6;
int reverse_pin = 7;
수정을 한 후에 아두이노 IDE를 통해서 컴파일 후 배포를 합니다.
Computer¶
- computer/xxxx.py
computer폴더 밑에 py소스들을 열어 보면 아래와 같은 부분이 있는 소스가 있습니다.
self.server_socket.bind(('192.168.1.100', 8000))
self.ser = serial.Serial('/dev/tty.usbmodem1421', 115200, timeout=1)
첫번째 라인은 Raspberry pI와 통신을 하기 위한 Socket IP입니다. PC의 IP로 변경을 합니다. 두번째 라인은 아두이노와 통신하기 위한 Serial Port입니다. 어떤 Serial Port를 사용하는지 확인은 아두이노 IDE에서 확인이 가능합니다.

Raspberry Pi¶
- raspberryPi/xxxxxx.py
라즈베리파이 관련해서는 이미지를 전송하는 Client(stream_clinet.py
)와 거리 측정을 위한 Client(ultrasoni_clinet.py
)가 있습니다. 이 2개의 소스에서도 socket관련 부분을 컴퓨터의 IP로 변경을 해주면 됩니다.
테스트¶
이제 각 연결이 제대로 되었는지 테스트를 해보겠습니다.
Computer - Arduino¶
위 테스트틀 위해서 우선 아두이노에 LED를 연결하여 키보드를 눌렀을 때 정상적으로 아두이노로 명령이 전달이 되는지 확인해보겠습니다.
우선 아두이노에 LED와 저항을 연결해야 합니다.


이제 아두이노와 PC를 USB 포트로 연결을 하고 test/rc_control_test.py
파일을 아래와 같이 수행합니다.
>>> python rc_control_test.py
command 창에서 키보드로 상하좌우 버튼을 누릅니다.그러면 상단부터 순서대로 LED가 깜빡거리를 것을 확인할 수 있습니다.
Computer - Raspberry Pi¶
Computer와 Raspberry Pi에서 확인할 사항은 이미지 전송과 거리측정 센서 연결 상태입니다.
우선 Computer에서 test/stream_server_test.py
를 실행합니다.
>>> python stream_server_test.py
그러면 Computer는 서버가 되어 Clinet의 요청을 기다리고 있습니다.
다음으로 Raspberry Pi에 접속을 하여 터미널에서 raspberryPi/stream_client.py
를 실행합니다.
>>> python stream_client.py // 이미지 전송 Clinet
그러면 Computer에 카메라의 이미지가 전송이 됩니다.
위 동영상에서 왼쪽이 Computer이고 오른쪽이 SSH로 접속한 Raspberry Pi입니다.
다음은 거리측정 센서 테스트 입니다.
우선 Computer에서 test/ultrasonic_server_test.py
를 실행합니다.
>>> python ultrasonic_server_test.py
Raspberry Pi에서 raspberryPi/ultrasonic_client.py
를 수행합니다.
>>> sudo python ultrasonic_client.py // 거리 측정 Data전송 Client
그러면 화면에 Cm단위로 측정된 거리가 보여집니다.
위와 같이 테스트가 완료가 되면 시스템 Setting은 완료가 된 상태입니다.
다음은 이제 Open CV의 Machine Learning기능을 테스트를 진행해 보겠습니다.
Demo 준비2¶
Machine Learning 순서¶
Machine Learning 진행 순서입니다.
- 학습하고자 하는 대상을 Camera를 통하여 촬영하고 사람이 답을 알려 줍니다.
- 그 결과 이미지에 Labelling이 진행이 되고, 해당 이미지를 가지고 Machine Learning을 수행합니다.
- Machine Learning을 수행한 결과를 xml파일로 생성을 합니다.
- 실제 적용시 xml을 Load하고, Camera에 촬영된 내용을 학습된 Data를 기반으로 판단을 합니다.
위 내용을 보면 사람을 학습시키는 방법과 유사합니다.
위 순서를 바탕으로, 카메라에 숫자를 보여주고, 상하좌우 버튼을 통해서 학습 시킨 후에 숫자를 보여주면 상하좌우와 연결된 LED가 깜빡이는 시나리오 입니다.
Machine Learning 학습¶
학습용 Data 생성¶
학습에 사용할 숫자 모형을 준비합니다. 그리고 Computer에서 computer/collect_training_data.py
를 수행합니다.
>>> python collect_training_data.py
Raspberry Pi에서는 RaspberryPi/stream_client.py
를 수행합니다.
>>> python stream_client.py
그러면 computer/training_images
폴더에 이미지 파일이 계속 생성이 됩니다. 이제 카메라로 숫자 4모형을 촬영하면서 좌측 화살표를 누릅니다. 이과정이 숫자 4 이미지에 좌측버튼 값(0)을 Labelling하는 과정입니다. 약 1분정도 버튼을 누르고 있습니다.
다음으로 숫자 8을 촬영하면서 우측 버튼을 누르고 있습니다. 동일하게 숫자 1를 촬영하면서 위쪽 버튼 , 숫자 3을 촬여하면 아래 버튼을 누르고 있습니다.
각각 1분정도 진행을 한 후에 q
를 누르면 작업이 종료가 되고, 총 촬영된 이미지와 Labelling된 이미지, 버려진 이미지 갯수가 나타납니다.
작업이 종료가 되면 Labelling된 이미지의 정보가 numpy파일로 computer/training_data_temp/test08.npz
로 저장이 됩니다.
이 파일을 computer/training_data
폴더로 복사 합니다. 이제 이 파일을 이용하여 학습을 진행합니다.
학습하기¶
학습은 computer/mlp_training.py
를 수행하면 됩니다.
>>> python mlp_training.py
약 몇분이 소요가 되고 학습 결과가 computer/mlp_xml/mlp.xml
파일로 생성이 됩니다.

학습시 사용하는 알고리즘은 다층 퍼셉트로 신경망(Multi Layer Perceptron)을 이용하고 있습니다.
테스트¶
이제 실제 숫자 모형을 보여주면 연관된 LED가 깜빡이는지 테스트를 진행하겠습니다.
Computer에서 computer/rc_driver.py
를 수행합니다.
>>> python rc_driver.py
Raspberry Pi에서 raspberryPi/stream_client.py
를 수행합니다.
>>> python stream_client.py
이제 카메라로 숫자를 촬영하면 이전에 학습한대로 LED가 깜빡이는것을 볼수 있습니다. 이제 무선 Controller의 건전지를 제거하고, 아래와 같이 LED에 연결된 선을 무선 Controller를 연결합니다. 일반적으로 1~2만원대 중국산 RC Car Controller는 아래와 유사합니다.

이제 숫자를 보여주면 바퀴가 전진, 후진, 좌/우 방향 전환 되는 것을 확인할 수 있습니다.