[OpenCV] 기본 영상 처리

메르센고수·2023년 12월 25일
0

OpenCV

목록 보기
3/9
post-thumbnail

오늘은 화이트 크리스마스 기념?으로 2개의 글을 올릴까한다.
어제는 노량 오늘은 서울의 봄을 보고 왔는데 이순신 장군님으로 국뽕을 치사량 만큼 채워놨더니 두광이가 채워놓은 국뽕을 싹 다 배출시킨 느낌이였다ㅎㅎ

실패하면 반역 성공하면 혁명 아닙니까!!

이 한마디가 가장 인상적이였다.


이번 Chapter는 영상처리에 대한 내용이다.

영상의 밝기 조절

화소 처리 (Point processing)

: 입력 영상의 특정 좌표 픽셀 값을 변경하여 출력 영상의 해당 좌표 픽셀 값으로 설정하는 연산

  • 조건
    - 결과 영상의 픽셀 값이 정해진 범위에 있어야 한다.
    - 반전, 밝기 조절, 명암비 조절 등

밝기 조절

: 영상을 전체적으로 더욱 밝거나 어둡게 만드는 연산

  • Saturate : 255보다 큰 값이나 0보다 작은 값을 각각 255와 0으로 고정시켜주는 연산

영상의 밝기 조절을 위한 영상의 덧셈 연산

cv2.add(src1, src2, dst=None, mask=None, dtype=None) -> dst

1) src1 : (입력) 첫 번째 영상 또는 스칼라
2) src2 : (입력) 두 번째 영상 또는 스칼라
3) dst : (출력) 덧셈 연산의 결과 영상
4) mask : 마스크 영상
5) dtype : 출력 영상 (dst)의 타입
6) 참고 사항
- 스칼라는 실수 값 하나 또는 실수 값 4개로 구성된 튜플
- dst를 함수 인자로 전달하려면 dst 크기가 src1, src2와 같아야 하며, 타입이 적절해야함

예시 코드

import sys
import numpy as np
import cv2


# 그레이스케일 영상 불러오기
src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

dst = cv2.add(src, 100)
#dst = np.clip(src + 100., 0, 255).astype(np.uint8)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

# 컬러 영상 불러오기
src = cv2.imread('lenna.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()

dst = cv2.add(src, (100, 100, 100, 0))
#dst = np.clip(src + 100., 0, 255).astype(np.uint8)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

영상의 산술 및 논리 연산

영상의 산술 연산

  • 두 영상의 같은 위치에 존재하는 픽셀 값을 더하여 결과 영상의 픽셀 값으로 설정
  • 덧셈 결과가 255보다 크면 픽셀 값을 255로 설정
cv2.add(src1, src2, dst=None, mask=None, dtype=None) -> dst

덧셈 연산

1) src1 : (입력) 첫 번째 영상 또는 스칼라
2) src2 : (입력) 두 번째 영상 또는 스칼라
3) dst : (출력) 덧셈 연산의 결과 영상
4) mask : 마스크 영상
5) dtype : 출력 영상 (dst)의 타입
6) 참고 사항
- 스칼라는 실수 값 하나 또는 실수 값 4개로 구성된 튜플
- dst를 함수 인자로 전달하려면 dst 크기가 src1, src2와 같아야 하며, 타입이 적절해야함

가중치 합

  • 두 영상의 같은 위치에 존재하는 픽셀 값에 대하여 가중합을 계산하여 결과 영상의 픽셀 값으로 설정
  • 보통 a+b=1이 되도록 설정 - 두 입력 영상의 평균 밝기를 유지
cv2.assWeighted(src1, alpha, src2, beta, gamma, dst=None, dtype=None) -> dst

1) src1 : 첫 번째 영상
2) alpha : 첫 번째 영상 가중치
3) src2 : 두 번째 영상. src1과 같은 크기 & 같은 타입
4) beta : 두 번째 영상 가중치
5) gamma : 결과 영상에 추가적으로 더할 값
6) dst : 가중치 합 결과 영상
7) dtype : 출력 영상의 타입

평균 연산

: 가중치를 a=b=0.5로 설정한 가중치 합

뺄셈 연산

  • 두 영상의 같은 위치에 존재하는 픽셀 값에 대하여 뺄셈 연산을 수행하여 결과 영상의 픽셀 값으로 설정
  • 뺄셈 결과가 0보다 작으면 픽셀 값을 0으로 설정 (saturate)
cv2.subtract(src1, src2, dst=None, mask=None, dtype=None) -> dst

1) src1 : 첫 번째 영상 또는 스칼라
2) src2 : 두 번째 영상 또는 스칼라
3) dst : 뺄셈 연산 결과 영상
4) mask : 마스크 영상
5) dtype : 출력 영상의 타입

차이 연산

  • 두 영상의 같은 위치에 존재하는 픽셀 값에 대하여 뺄셈 연산을 수행한 후, 그 절댓값을 결과 영상의 픽셀 값으로 설정
  • 뺄셈 연산과 달리 입력 영상의 순서에 영향을 받지 않음
cv2.absdiff(src1, src2, dst=None) -> dst

1) src1 : 첫 번째 영상 또는 스칼라
2) src2 : 두 번째 영상 또는 스칼라
3) dst : 차이 연산 결과 영상

예시 코드

import sys
import numpy as np
import cv2
from matplotlib import pyplot as plt


src1 = cv2.imread('lenna256.bmp', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('square.bmp', cv2.IMREAD_GRAYSCALE)

if src1 is None or src2 is None:
    print('Image load failed!')
    sys.exit()

dst1 = cv2.add(src1, src2, dtype=cv2.CV_8U)
dst2 = cv2.addWeighted(src1, 0.5, src2, 0.5, 0.0)
dst3 = cv2.subtract(src1, src2)
dst4 = cv2.absdiff(src1, src2)

plt.subplot(231),plt.axis('off'),plt.imshow(src1, 'gray'),plt.title('src1')
plt.subplot(232),plt.axis('off'),plt.imshow(src2, 'gray'),plt.title('src2')
plt.subplot(233),plt.axis('off'),plt.imshow(dst1, 'gray'),plt.title('add')
plt.subplot(234),plt.axis('off'),plt.imshow(dst2, 'gray'),plt.title('addWeighted')
plt.subplot(235),plt.axis('off'),plt.imshow(dst3, 'gray'),plt.title('subtract')
plt.subplot(236),plt.axis('off'),plt.imshow(dst4, 'gray'),plt.title('absdiff')
plt.show()

matplotlib 라이브러리를 이용하여 출력하는 코드인데, 위의 코드를 돌리면 다음과 같은 이미지가 출력된다.

영상의 논리 연산

  • 비트 단위 AND, OR, XOR, NOT 연산
cv2.bitwise_and(src1, src2, dst=None, mask=None) -> dst
cv2.bitwise_or(src1, src2, dst=None, mask=None) -> dst
cv2.bitwise_xor(src1, src2, dst=None, mask=None) -> dst
cv2.bitwise_not(src1, dst=None, mask=None) -> dst

1) src1 : 첫 번째 영상 또는 스칼라
2) src2 : 두 번째 영상 또는 스칼라
3) dst : 출력 영상
4) mask : 마스크 영상
5) 참고 사항
- 각각의 픽셀 값을 이진수로 표현하고 bit 단위 논리 연산을 수행

기본적인 영상 처리 기법

컬러 영상과 색 공간

: 이전에 언급한 적이 있지만, 빛의 3원색을 보통 RGB라고 알고 있지만, OpenCV에서는 BGR순서를 기본으로 사용한다. (내 생각엔 RGB는 무지개색 순서이고, BGR은 알파벳 순서인듯 싶다.)

img1 = cv2.imread('lenna.bmp', cv2.IMREAD_COLOR)

img2=np.zeros((480,640,3), np.uint8)

img3=cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)
img4=cv2.cvtColor(img3, cv2.COLOR_GRAY2BGR)
# img4의 경우 각 픽셀은 B,G,R 색 성분이 모두 같게 설정됨

(색상) 채널 분리

cv2.split(m, mv=None) -> dst

1) m : 다채널 영상 (B,G,R)로 구성된 컬러 영상
2) mv : 출력 영상
3) dst : 출력 영상의 리스트

(색상) 채널 결합

cv2.merge(mv, dst=None) -> dst

1) mv : 입력 영상 리스트 또는 튜플
2) dst : 출력 영상

RGB 색상 평면 나누기

import sys
import numpy as np
import cv2


# 컬러 영상 불러오기
src = cv2.imread('candies.png', cv2.IMREAD_COLOR)

if src is None:
    print('Image load failed!')
    sys.exit()

# 컬러 영상 속성 확인
print('src.shape:', src.shape)  # src.shape: (480, 640, 3)
print('src.dtype:', src.dtype)  # src.dtype: uint8

# RGB 색 평면 분할
b_plane, g_plane, r_plane = cv2.split(src)

#b_plane = src[:, :, 0]
#g_plane = src[:, :, 1]
#r_plane = src[:, :, 2]

cv2.imshow('src', src)
cv2.imshow('B_plane', b_plane)
cv2.imshow('G_plane', g_plane)
cv2.imshow('R_plane', r_plane)
cv2.waitKey()

cv2.destroyAllWindows()

색 공간 변환

: 영상 처리에서는 특정한 목적을 위해 RGB 색 공간을 HSV, YCrCb, GrayScale 등의 다른 색 공간으로 변환하여 처리한다.

cv2.cvtColor(src, code, dst=None, dstCn=None) -> dst

1) src : 입력 영상
2) code : 색 변환 코드

3) dstCn : 결과 영상의 채널 수, 0이면 자동 결정
4) dst : 출력 영상

  • RGB 색상을 그레이 스케일로 변환

    1) 장점 : 데이터 저장 용량 감소, 데이터 처리 속도 향상
    2) 단점 : 색상 정보 손실

HSV 색공간

  • Hue : 색상, 색의 종류
  • Saturation : 채도, 색의 탁하고 선명한 정도
  • Value : 명도, 빛의 밝기


YCrCb 색공간

  • PAL, NTSC, SECAM 등의 컬러 비디오 표준에 사용되는 색 공간
  • 영상의 밝기 정보와 색상 정보를 따로 분리하여 부호화 (흑백 TV 호환)
  • Y : 밝기 정보
  • Cr, Cb : 색차

히스토그램 분석

히스토그램

  • 영상의 픽셀 값 분포를 그래프의 형태로 표현한 것
  • 예를 들어 그레이스케일 영상에서 각 그레이스케일 값에 해당하는 픽셀의 개수를 구하고, 이를 막대 그래프의 형태로 표현

정규화된 히스토그램

  • 각 픽셀의 개수를 영상 전체의 픽셀 개수로 나누어 준 것
  • 해당 그레이스케일 값을 갖는 픽셀이 나타날 확률

히스토그램 구하기

cv2.calcHist(images, channels, mask, histSize, ranges, hist=None, accumulate=None) -> hist

1) images : 입력 영상 리스트
2) channels : 히스토그램을 구할 채널을 나타내는 리스트
3) mask : 마스크 영상, 입력 영상 전체에서 히스토그램을 구하려면 None 지정
4) histSize : 히스토그램 각 차원의 크기 (bin의 개수)를 나타내는 리스트
5) ranges : 히스토그램 각 차원의 최솟값과 최댓값으로 구성된 리스트
6) hist : 계산된 히스토그램
7) accumulate : 기존의 hist 히스토그램에 누적하려면 True, 새로 만드려면 False

import sys
import numpy as np
import matplotlib.pyplot as plt
import cv2


# 그레이스케일 영상의 히스토그램
src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

hist = cv2.calcHist([src], [0], None, [256], [0, 256])

cv2.imshow('src', src)
cv2.waitKey(1)

plt.plot(hist)
plt.show()

# 컬러 영상의 히스토그램
src = cv2.imread('lenna.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()

colors = ['b', 'g', 'r']
bgr_planes = cv2.split(src)

for (p, c) in zip(bgr_planes, colors):
    hist = cv2.calcHist([p], [0], None, [256], [0, 256])
    plt.plot(hist, color=c)

cv2.imshow('src', src)
cv2.waitKey(1)

plt.show()

cv2.destroyAllWindows()

영상의 명암비 조절

명암비

: 밝은 곳과 어두운 곳 사이에 드러나는 밝기 정도의 차이

  • 명암비 조절 함수
  • 효과적인 명암비 조절 함수
import sys
import numpy as np
import cv2


src = cv2.imread('lenna.bmp', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

alpha = 1.0
dst = np.clip((1+alpha)*src - 128*alpha, 0, 255).astype(np.uint8)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

  • 영상의 자동 명암비 조절
    <히스토그램 스트레칭>
    : 영상의 히스토그램이 그레이스케일 전 구간에서 걸쳐 나타나도록 변경하는 선형 변환 기법
  • 정규화 함수
cv2.normalize(src, dst, alpha=None, beta=None, norm_type=None,
							dtype=None, mask=None) -> dst

1) src : 입력 영상
2) dst : 결과 영상
3) alpha : (노름 정규화인 경우) 목표 노름 값, (원소 값 범위 정규화인 경우) 최솟값
4) beta : (원소 값 범위 정규화인 경우) 최댓값
5) norm_type : 정규화 타입. NORM_INF, NORM_L1, NORM_L2, NORM_MINIMAX
6) dtype : 결과 영상의 타입
7) mask : 마스크 영상

히스토그램 평활화

: 히스토그램이 그레이스케일 전체 구간에서 균일한 분포로 나타나도록 변경하는 명암비 향상 기법
=> 히스토그램 균등화, 균일화, 평탄화

  • 히스토그램 평활화를 위한 변환 함수 구하기
cv2.equalizeHist(src, dst=None) -> dst

1) src : 입력 영상. 그레이스케일 영상
2) dst : 결과 영상

-> 밝기 성분에 대해서만 히스토그램 평활화 수행 (색상 성분은 불변)

src=cv2.imread('field.bmp')

src_ycrcb=cv2.ctvColor(src,cv2.COLOR_BGR2YCrCb)
ycrcb_planes=cv2.split(src_ycrcb)

# 밝기 성분에 대해서만 히스토그램 평활화 수행
ycrcb_planes[0]=cv2.equalizeHist(ycrcb_planes[0])

dst_ycrcb=cv2.merge(ycrcb_planes)
dst=cv2.cvtColor(dst_ycrcb, cv2.COLOR_YCrCb2BGR)

특정 색상 영역 추출

  • 특정 범위 안에 있는 행렬 원소 검출
cv2.inRange(src, lowerb, upperb, dst=None) -> dst

1) src : 입력 행렬
2) lowerb : 하한값 행렬 또는 스칼라
3) upperb : 상한값 행렬 또는 스칼라
4) dst : 입력 영상과 같은 크기의 마스크 영상. 범위 안에 들어가는 픽셀은 255, 나머지는 0으로 설정

트랙바를 이용한 특정 색상 영역 추출

import sys
import numpy as np
import cv2


src = cv2.imread('candies.png')

if src is None:
    print('Image load failed!')
    sys.exit()

src_hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)

def on_trackbar(pos):
    hmin = cv2.getTrackbarPos('H_min', 'dst')
    hmax = cv2.getTrackbarPos('H_max', 'dst')

    dst = cv2.inRange(src_hsv, (hmin, 150, 0), (hmax, 255, 255))
    cv2.imshow('dst', dst)


cv2.imshow('src', src)
cv2.namedWindow('dst')

cv2.createTrackbar('H_min', 'dst', 50, 179, on_trackbar)
cv2.createTrackbar('H_max', 'dst', 80, 179, on_trackbar)
on_trackbar(0)

cv2.waitKey()

cv2.destroyAllWindows()

히스토그램 역투영

  • 영상의 각 픽셀이 주어진 히스토그램 모델에 얼마나 일치하는지를 검사하는 방법
  • 임의의 색상 영역을 검출할 때 효과적
  • 히스토그램 역투영 함수
cv2.calcBackProject(images, channels, hist, ranges, scale, dst=None) -> dst

1) images : 입력 영상 리스트
2) channels : 역투영 계산에 사용할 채널 번호 리스트
3) hist : 입력 히스토그램
4) ranges : 히스토그램 각 차원의 최솟값과 최댓값으로 구성된 리스트
5) scale : 출력 역투영 행렬에 추가적으로 곱할 값
6) dst : 출력 역투영 영상. 입력 영상과 동일 크기

예시 코드

import sys
import numpy as np
import cv2


# 입력 영상에서 ROI를 지정하고, 히스토그램 계산

src = cv2.imread('cropland.png')

if src is None:
    print('Image load failed!')
    sys.exit()

x, y, w, h = cv2.selectROI(src)

src_ycrcb = cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb)
crop = src_ycrcb[y:y+h, x:x+w]

channels = [1, 2]
cr_bins = 128
cb_bins = 128
histSize = [cr_bins, cb_bins]
cr_range = [0, 256]
cb_range = [0, 256]
ranges = cr_range + cb_range

hist = cv2.calcHist([crop], channels, None, histSize, ranges)
hist_norm = cv2.normalize(cv2.log(hist+1), None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)

# 입력 영상 전체에 대해 히스토그램 역투영

backproj = cv2.calcBackProject([src_ycrcb], channels, hist, ranges, 1)
dst = cv2.copyTo(src, backproj)

cv2.imshow('backproj', backproj)
cv2.imshow('hist_norm', hist_norm)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()


실습

크로마키 합성 - 녹색 영역에 다른 배경 영상을 합성

import sys
import numpy as np
import cv2


# 녹색 배경 동영상
cap1 = cv2.VideoCapture('woman.mp4')

if not cap1.isOpened():
    print('video open failed!')
    sys.exit()

# 비오는 배경 동영상
cap2 = cv2.VideoCapture('raining.mp4')

if not cap2.isOpened():
    print('video open failed!')
    sys.exit()

# 두 동영상의 크기, FPS는 같다고 가정
w=round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
h=round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
print('w x h: {} x {}'.format(w,h))
print('frame_cnt1:', frame_cnt1)
print('frame_cnt2:', frame_cnt2)

fps = cap1.get(cv2.CAP_PROP_FPS)
delay = int(1000 / fps)

# 출력 동영상 객체 생성
fourcc=cv2.VideoWriter_fourcc(*'DIVX')
out=cv2.VideoWriter('output.avi',fourcc,fps,(w,h))
# 합성 여부 플래그
do_composit = False

# 전체 동영상 재생
while True:
    ret1, frame1 = cap1.read()

    if not ret1:
        break
    
    # do_composit 플래그가 True일 때에만 합성
    if do_composit:
        ret2, frame2 = cap2.read()

        if not ret2:
            break
        
        frame2=cv2.resize(frame2,(w,h))

        # HSV 색 공간에서 녹색 영역을 검출하여 합성
        hsv = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, (50, 150, 0), (70, 255, 255))
        cv2.copyTo(frame2, mask, frame1)

    out.write(frame1);

    cv2.imshow('frame', frame1)
    key = cv2.waitKey(delay)

    # 스페이스바를 누르면 do_composit 플래그를 변경
    if key == ord(' '):
        do_composit = not do_composit
    elif key == 27:
        break

cap1.release()
cap2.release()
cv2.destroyAllWindows()

오늘은 여기까지이다. 진짜진짜 메리크리스마스

profile
블로그 이전했습니다 (https://phj6724.tistory.com/)

0개의 댓글

관련 채용 정보