8 분 소요

OpenCV를 활용한 기본적인 이미지 핸들링 방법에 대해 포스팅하겠습니다.

  1. 이미지 출력
  2. 동영상 출력
  3. 카메라 출력
  4. 도형 그리기
  5. 텍스트 넣기(한글 포함)
  6. 파일 저장
  7. 이미지 크기 조정(보간법)
  8. 이미지 자르기
  9. 이미지 대칭
  10. 이미지 회전
  11. 이미지 흑백 변환(RGB -> GRAY)
  12. 이미지 블러 처리
  13. 이미지 변형 (마우스 핸들링 포함)
  14. 이미지 이진화(흑 or 백)
  15. 경계선 검출(Canny Edge Detection)
  16. 윤곽선 검출(Otsu 알고리즘)
import cv2
import numpy as np
print(cv2.__version__)
print(np.__version__)
4.5.5
1.21.2

1. 이미지 출력

img = cv2.imread('img.jpg')
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
img_gray = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE) # 또는 _COLOR, _UNCHANGED
cv2.imshow('gray', img_gray)
cv2.waitKey(0)
cv2.destroyAllWindows()

shape

이미지의 height, width, channel 정보

img.shape
(390, 640, 3)

2.동영상 출력

여러 프레임이 움직이면서 보여주는 게 동영상

저장된 동영상 파일 출력

cap = cv2.VideoCapture('video.mp4')

while cap.isOpened(): #동영상 파일이 올바로 열렸는지 확인
    ret, frame = cap.read() #ret(return값):성공여부, frame:받아온 이미지
    if not ret:
        print("더 이상 가져올 프레임이 없어요")
        break
    cv2.imshow('video', frame)
    
    if cv2.waitKey(10) == ord('q'): #10ms만큼 기다림
        print("사용자 입력에 의해 종료합니다")
        break
    
cap.release() #자원 해제
cv2.destroyAllWindows()
사용자 입력에 의해 종료합니다

3.카메라 출력

cap = cv2.VideoCapture(0) # 0번 째 카메라 장치( Device ID)

if not cap.isOpened():
    exit() # 프로그램 종료
    
while True:
    ret, frame = cap.read()
    if not ret:
        break
    cv2.imshow('camera', frame)
    
    if cv2.waitKey(10) == ord('q'):
        print("사용자 입력에 의해 종료합니다")
        break
    
cap.release() #자원 해제
cv2.destroyAllWindows()  
사용자 입력에 의해 종료합니다

4.도형 그리기

# 세로 480, 가로 640, 채널 3(RGB)에 해당하는 스케치북 만들기
img = np.zeros((480,640,3), dtype=np.uint8)

#전체 공간을 흰 색으로 채우기
img[:] = (255,255,255) 

#일부 영역 색칠
img[0:200, 150:200] = (0,0,0) #행방향, 열방향

# 직선 긋기
col = (0,255,255)
thickness = 3   
cv2.line(img, (0,0),(400,100), col,thickness, cv2.LINE_8)  #출발점, 도착점의 x좌표,y좌표

# 원 그리기
radius = 50
cv2.circle(img,(200,100), radius, col ,cv2.FILLED,cv2.LINE_AA ) #꽉찬 원
cv2.circle(img,(500,400), radius, col ,thickness,cv2.LINE_AA ) #속이 빈 원

# 사각형
cv2.rectangle(img, (300,300), (500,500), col, thickness) #좌측 상단점, 우측 하단점의 좌표
cv2.rectangle(img, (200,300), (300,500), col, cv2.FILLED) #좌측 상단점, 우측 하단점의 좌표

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

5.텍스트 넣어보기

OpenCV에서 사용하는 글꼴 종류

  1. cv2.FONT_HERSHEY_SIMPLEX : 보통 크기의 산 세리프 글꼴
  2. cv2.FONT_HERSHEY_PLAIN : 작은 크기의 산 세리프 글꼴
  3. cv2.FONT_HERSHEY_SCRIPT_SIMPLEX : 필기체 스타일 글꼴
  4. cv2.FONT_HERSHEY_TRIPLEX : 보통 크기의 세리프 글꼴
  5. cv2.FONT_ITALIC : 기울임
img = np.zeros((480,640,3), dtype=np.uint8)

SCALE = 1 #글자 크기
COLOR = (255,255,255)
THICKNESS = 1 #글자 두께

        # 그릴곳, 텍스트내용, 시작위치, 폰트종류, 크기, 색깔, 두께
cv2.putText(img, "Simplex", (20,50), cv2.FONT_HERSHEY_SIMPLEX, SCALE, COLOR, THICKNESS)

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

한글 적는법

from PIL import Image, ImageFont, ImageDraw

def myPutText(src, text, pos, font_size, font_color):
    img_pil = Image.fromarray(src)
    draw = ImageDraw.Draw(img_pil)
    font = ImageFont.truetype("font/gulim.ttc", font_size)
    draw.text(pos, text, font=font, fill=font_color)
    return np.array(img_pil)
img = np.zeros((480,640,3), dtype=np.uint8)

SCALE = 30 #글자 크기
COLOR = (255,255,255)
THICKNESS = 1 #글자 두께

        # 그릴곳, 텍스트내용, 시작위치, 폰트종류, 크기, 색깔, 두께
img = myPutText(img, "식드리치", (20,50),  SCALE, COLOR)

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

6.파일 저장

# 사진
img = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imwrite('img_gray.jpg', img) #.png적으면 png로 저장되는 거임
#동영상
cap = cv2.VideoCapture('video.mp4')
fourcc = cv2.VideoWriter_fourcc(*'DIVX') #코텍 정의
width = round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) #저장할 동영상의 width정의
height = round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) #저장할 동영상의 height정의
fps = cap.get(cv2.CAP_PROP_FPS) #저장할 동영상의 fps정의

out = cv2.VideoWriter('output.avi', fourcc, fps, (width, height)) #어떤식으로 동영상 저장할지 결정

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
        
    out.write(frame) #영상 데이터만 저장(소리X)
    cv2.imshow('video', frame)
    if cv2.waitKey(10) == ord('q'):
        break

out.release()
cap.release()
cv2.destroyAllWindows()

7.크기조정

고정 크기로 조정

#이미지
img = cv2.imread('img.jpg')
dst = cv2.resize(img, (400,500)) #width,height resize한 이미지

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

비율로 설정

#이미지
img = cv2.imread('img.jpg')
dst = cv2.resize(img, None, fx=0.5,fy=0.5) #비율을 이용해 resize한 이미지(0.5배)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

보간법

  1. cv2.INTER_AREA : 크기 줄일 때 사용
  2. cv2.INTER_CUBIC : 크기 늘릴 때 사용 (속도 느림, 퀄리티 좋음)
  3. cv2.INTER_LINEAR : 크기 늘릴 때 사용 (기본값)

보간법을 적용하여 축소

#이미지
img = cv2.imread('img.jpg')
dst = cv2.resize(img, None, fx=0.5,fy=0.5, interpolation=cv2.INTER_AREA) #비율을 이용해 resize한 이미지(0.5배)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

보간법을 적용하여 확대

#이미지
img = cv2.imread('img.jpg')
dst = cv2.resize(img, None, fx=2,fy=2, interpolation=cv2.INTER_CUBIC) #비율을 이용해 resize한 이미지(0.5배)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

동영상은 중간중간 frame을 resize해주면 됨

8.이미지 자르기

img = cv2.imread('img.jpg')
img.shape #(390, 640, 3)

crop = img[100:200, 300:400] #img[행범위 , 열범위]
cv2.imshow('crop', crop)
cv2.waitKey(0)
cv2.destroyAllWindows()

9.이미지 대칭

cv2.flip에서 파라미터 flipCode에 대해

  1. flipCode > 0 : 좌우대칭
  2. flipCode == 0 : 상하대칭 1.flipCode < 0 :상하좌우대칭
# 좌우대칭
img = cv2.imread('img.jpg')
flip_horizontal = cv2.flip(img, flipCode=1) 

cv2.imshow('img', img)
cv2.imshow('horizontal', flip_horizontal)
cv2.waitKey(0)
cv2.destroyAllWindows()

10.이미지 회전

# 90도 회전
img = cv2.imread('img.jpg')
rotate_90 = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) 

cv2.imshow('img', img)
cv2.imshow('rotate_90', rotate_90)
cv2.waitKey(0)
cv2.destroyAllWindows()

11.이미지 흑백 변환

불러온 이미지를 흑백으로 변경

img = cv2.imread('img.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('img',img)
cv2.imshow('img_gray',img_gray)
cv2.waitKey(0)
cv2.destroyAllWindows()

12.블러 처리(이미지를 흐리게 변경) -> 외곽선 추출할 때 유용

가우시안 블러

커널 사이즈 또는 표준편차에 따라서 흐림 정도를 정할 수 있음 </br> (3,3) or (5,5) or (7,7)

img = cv2.imread('img.jpg')

kernel_3 = cv2.GaussianBlur(img, (3,3), 0) #커널사이즈, 표준편차

cv2.imshow('img',img)
cv2.imshow('kernel_3', kernel_3)
cv2.waitKey(0)
cv2.destroyAllWindows()

13.이미지 변형(원근)

사다리꼴 이미지 직사각형으로 펼치기

img = cv2.imread('newspaper.jpg')

width, height = 640, 240 #가로 640, 세로 240으로 결과물 출력

# 좌상, 우상, 우하, 좌하 (좌표 위치)
src = np.array([[246,189],[513,185],[559,289],[224,284]], dtype=np.float32) #input 4개 지점
dst = np.array([[0,0],[width,0],[width,height],[0,height]], dtype=np.float32) #output 4개 지점

matrix = cv2.getPerspectiveTransform(src,dst) #Matrix 얻어옴
result = cv2.warpPerspective(img, matrix, (width,height)) #matrix대로 변환을 함

cv2.imshow('img',img)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()

회전된 이미지 올바로 세우기

img = cv2.imread('poker.jpg')

width, height = 530, 710 #가로 530, 세로 710으로 결과물 출력

# 좌상, 우상, 우하, 좌하 (좌표 위치 : 시계 방향으로 4지점 정의)
src = np.array([[702,189],[1133,414],[726,1007],[276,700]], dtype=np.float32) #input 4개 지점
dst = np.array([[0,0],[width,0],[width,height],[0,height]], dtype=np.float32) #output 4개 지점

matrix = cv2.getPerspectiveTransform(src,dst) #Matrix 얻어옴
result = cv2.warpPerspective(img, matrix, (width,height)) #matrix대로 변환을 함

cv2.imshow('img',img)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()

미니 프로젝트 : 반자동 문서 스캐너

  • 지점을 그림판에서 직접 얻는 게 아니라 이미지상에서 마우스 클릭으로 좌표를 얻을 수 있게끔 해보자
  1. 마우스 이벤트 등록
def mouse_handler(event, x, y, flags, params):
    if event == cv2.EVENT_LBUTTONDOWN: #마우스 왼쪽 버튼 누르기
        print('왼쪽 버튼 Down')
        print(x,y)
    elif event == cv2.EVENT_LBUTTONUP : #마우스 왼쪽 버튼 떼기
        print('왼쪽 버튼 Up')
        print(x,y)
    elif event == cv2.EVENT_LBUTTONDBLCLK: #마우스 왼쪽 더블클릭
        print('왼쪽 버튼 Double Click')
#    elif event == cv2.EVENT_MOUSEMOVE: #마우스 이동
#        print('마우스 이동')
    elif event == cv2.EVENT_RBUTTONDOWN: #마우스 오른쪽 버튼 누르기
        print('오른쪽 버튼 Down')

cv2.namedWindow('img') #img란 이름의 윈도우를 먼저 만들어두는 것, 여기에 마우스 이벤트를 처리하기위한 핸들러 적용할 거임
cv2.setMouseCallback('img', mouse_handler)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
왼쪽 버튼 Down
702 146
왼쪽 버튼 Up
702 146
  1. 미니 프로젝트 코드 완성
src_img = cv2.resize(cv2.imread('poker.jpg'), None, fx=0.5, fy=0.5)



point_list = []
COLOR = (255,0,255) #핑크색

def mouse_handler(event, x, y, flags, params):
    if event == cv2.EVENT_LBUTTONDOWN: #마우스 왼쪽 버튼 누르기
        point_list.append((x,y))
    
    for point in point_list:
        cv2.circle(src_img, point, 5, COLOR, cv2.FILLED)
        
    if len(point_list) == 4:
        show_result() #결과 출력
    
    cv2.imshow('img',src_img) #왼쪽 버튼 누를 때마다 이미지 갱신
    
def show_result():
    width, height = 530, 710
    
    # 좌상, 우상, 우하, 좌하 (좌표 위치 : 시계 방향으로 4지점 정의)
    src = np.float32(point_list) #input 4개 지점
    dst = np.array([[0,0],[width,0],[width,height],[0,height]], dtype=np.float32) #output 4개 지점

    matrix = cv2.getPerspectiveTransform(src,dst) #Matrix 얻어옴
    result = cv2.warpPerspective(src_img, matrix, (width,height)) #matrix대로 변환을 함
    cv2.imshow('result',result)
    
cv2.namedWindow('img') #img란 이름의 윈도우를 먼저 만들어두는 것, 여기에 마우스 이벤트를 처리하기위한 핸들러 적용할 거임
cv2.setMouseCallback('img', mouse_handler)
cv2.imshow('img',src_img) #img라는 윈도우에 src_img를 띄움
cv2.waitKey(0)
cv2.destroyAllWindows()

14.이미지 변형(이진화)

  • 흰색과 검정색만 가지는 이미지로 변형시키는 것
img = cv2.resize(cv2.imread('book.jpg', cv2.IMREAD_GRAYSCALE),None, fx=0.1, fy=0.1)

# 127보다 크다고 하면 흰색(255)으로 아니면 검정색(0)으로
ret, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

cv2.imshow('img',img)
cv2.imshow('binary', binary)
cv2.waitKey(0)
cv2.destroyAllWindows()

Trackbar(값 변화에 따른 변형 확인)

def empty(pos):
    #print(pos)
    pass

name = 'Trackbar'
cv2.namedWindow(name) #Trackbar라는 이름의 윈도우를 먼저 생성함

cv2.createTrackbar('threshold', name, 127, 255, empty) #bar이름, 창의 이름, 초기값, 최대값, 이벤트 처리

while True:
    thresh = cv2.getTrackbarPos('threshold',name) #bar 이름, 창 이름 (0~255값을 차례로 반환해줌)
    ret, binary = cv2.threshold(img, thresh, 255, cv2.THRESH_BINARY)
    
    if not ret:
        break
    cv2.imshow(name, binary)
    if cv2.waitKey(1) == ord('q'):
        break

cv2.destroyAllWindows()    

Adaptive Threshold

  • 이미지를 작은 영역으로 나누어서 임계치 적용하는 방법
def empty(pos):
    #print(pos)
    pass

name = 'Trackbar'
cv2.namedWindow(name)

#bar이름, 창의 이름, 초기값, 최대값, 이벤트 처리
cv2.createTrackbar('block_size', name, 25, 100, empty) # block_size는 홀수만 가능, 1보다는 큰 값
cv2.createTrackbar('c', name, 3, 10, empty) # c의 경우 일반적으로 양수의 값을 사용


while True:
    block_size = cv2.getTrackbarPos('block_size',name) #bar 이름, 창 이름
    c = cv2.getTrackbarPos('c',name)
    
    if block_size <= 1:
        block_size = 3
        
    if block_size % 2 == 0:
        block_size += 1
    
    binary = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, block_size, c)
    
    cv2.imshow(name, binary)
    if cv2.waitKey(1) == ord('q'):
        break

cv2.destroyAllWindows()    

오츠 알고리즘

  • 최적의 threshold값을 찾아주는 알고리즘
  • Bimodal Image(쌍봉분포)에 사용하기에 적합함
img = cv2.resize(cv2.imread('book.jpg', cv2.IMREAD_GRAYSCALE),None, fx=0.1, fy=0.1)

# 127보다 크다고 하면 흰색(255)으로 아니면 검정색(0)으로
ret, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, otsu = cv2.threshold(img, -1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print('otsu threshold', ret)

cv2.imshow('img',img)
cv2.imshow('binary', binary)
cv2.imshow('otsu', otsu)
cv2.waitKey(0)
cv2.destroyAllWindows()
otsu threshold 101.0

15.경계선 검출

Canny Edge Detection(Trackbar를 이용한)

img = cv2.resize(cv2.imread('snowman.png'),None,fx=0.5,fy=0.5)

def empty(pos):
    pass

name = 'Trackbar'
cv2.namedWindow(name)
cv2.createTrackbar('threshold1', name, 0, 255, empty) #하위임계값
cv2.createTrackbar('threshold2', name, 0, 255, empty) #상위임계값

while True:
    threshold1 = cv2.getTrackbarPos('threshold1', name)
    threshold2 = cv2.getTrackbarPos('threshold2', name)

    #대상 이미지, threshold1(하위임계값), threshold2(상위임계값)
    canny = cv2.Canny(img, threshold1, threshold2) 

    cv2.imshow('img',img)
    cv2.imshow(name, canny)
    
    if cv2.waitKey(1) == ord('q'):
        break
    
cv2.waitKey(0)
cv2.destroyAllWindows()

16.윤곽선 검출

  • 윤곽선 : 경계선을 연결한 선
  • 정확하게 하기 위해 전처리 해줌 (binary image로 변환 – 흰색 검정색만 있는 이미지)
img = cv2.imread('card.png')
target_img = img.copy() #사본 이미지

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #GRAY이미지로 변환
ret, otsu = cv2.threshold(gray, -1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) #오츠알고리즘을 이용한 이진화

#반환되는 값 : 윤관선 정보, 계층구조
contours, hierarchy = cv2.findContours(otsu, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) #윤곽선 검출
#파라미터 : 이미지, 윤곽선 찾는 모드, 윤곽선 찾을 때 사용하는 근사치 방법(cv2.CHAIN_APPROX_SIMPLE or cv2.CHAIN_APPROX_NONE)

#윤곽선 그리기
COLOR = (0,200,0)
cv2.drawContours(target_img, contours, -1, COLOR, 2) 
# 파라미터 : 대상 이미지, 윤곽선 정보, 인덱스(-1이면 전체), 색깔, 두께


cv2.imshow('img',img)
cv2.imshow('gray', gray)
cv2.imshow('otsu', otsu)
cv2.imshow('contour', target_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

윤곽선 찾기 모드

  1. cv2.RETR_EXTERNAL : 가장 외곽의 윤곽선만 찾음
  2. cv2.RETR_LIST : 모든 윤곽선 찾음 (계층 정보 없음)
  3. cv2.RETR_TREE : 모든 윤곽선 찾음 (계층 정보를 트리 구조로 생성)

경계 사각형

  • 윤곽선의 경계면을 둘러싸는 사각형

    boundingRect()

img = cv2.imread('card.png')
target_img = img.copy() #사본 이미지

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, otsu = cv2.threshold(gray, -1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

#반환되는 값 : 윤관선 정보, 계층구조
contours, hierarchy = cv2.findContours(otsu, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) #윤곽선 검출
#파라미터 : 이미지, 윤곽선 찾는 모드, 윤곽선 찾을 때 사용하는 근사치 방법

COLOR = (0,200,0)

for cnt in contours:
    if cv2.contourArea(cnt) > 100: #윤곽선의 면적이 100보다 클 때만 도형 그림
        x,y,width,height = cv2.boundingRect(cnt) #윤곽선의 경계면을 둘러싸는 사각형에 대한 정보 제공
        cv2.rectangle(target_img,(x,y),(x+width,y+height), COLOR, 2) #제공 받은 정보로 사각형 그리기 (두께2)

cv2.imshow('img',img)
cv2.imshow('gray', gray)
cv2.imshow('otsu', otsu)
cv2.imshow('contour', target_img)
cv2.waitKey(0)
cv2.destroyAllWindows()


카테고리:

업데이트: