OpenCV(2)

현서·2025년 8월 7일

컴퓨터 비전

목록 보기
5/16
post-thumbnail

ROI (Region of Interest) : 관심 영역

17_roi.py

import cv2
img = cv2.imread('images/sun.jpg')

x = 182
y = 22
w = 119
h = 108

roi = img[y:y+h, x:x+w]  # y:y+h는 세로 방향, x:x+w는 가로 방향
roi_copy = roi.copy()  # ROI를 복사
img[y:y+h, x+w:x+w+w] = roi

# 두 태양을 박스로 감싸기
cv2.rectangle(img, (x, y), (x+w+w, y+h), (0, 255, 0), 3)

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


18_roiselector.py

import cv2

oldx = oldy = w = h = 0
color = (255, 0, 0)
isDrag = False
img_copy = None

def on_mouse(event, x, y, flags, param):
    global oldx, oldy, w, h, isDrag, img_copy

    if event == cv2.EVENT_LBUTTONDOWN:
        isDrag = True
        oldx = x
        oldy = y
    elif event == cv2.EVENT_MOUSEMOVE:
        if isDrag:
            img_copy = img.copy()
            cv2.rectangle(img_copy, (oldx, oldy), (x, y), color, 3)
            cv2.imshow('img', img_copy)
    elif event == cv2.EVENT_LBUTTONUP:
        if isDrag:
            isDrag = False
            if x > oldx and y > oldy:
                w = x - oldx
                h = y - oldy
                if w > 0 and h > 0:
                    cv2.rectangle(img_copy, (oldx, oldy), (x, y), color, 3)
                    cv2.imshow('img', img_copy)
                    roi = img[oldy:oldy+h, oldx:oldx+w]
                    cv2.imshow('roi', roi)

        else:
            cv2.imshow('img', img)
            print('영역이 설정되지 않음')

img = cv2.imread('./images/sun.jpg')
cv2.namedWindow('img')
cv2.setMouseCallback('img', on_mouse)
cv2.imshow('img', img)
cv2.waitKey()

이렇게 박스를 그리면 박스 안 영역 이미지만 추출해낼 수 있다.


영상의 이진화

이진화(Binary Thresholding)

  • 영상을 흑백(0 또는 255)으로 변환하여
    특정 임계값(threshold) 이상인 픽셀을 흰색(255)으로, 이하인 픽셀을 검은색(0)으로 변환하는 과정.

  • OpenCV에서는 cv2.threshold() 함수를 사용하여 고정 임계값 이진화, 적응형 이진화, Otsu의 이진화 등을 적용할 수 있다.

  • 기본적인 이진화 방법 : cv2.THRESH_BINARY

  • 조명이 균일하지 않은 경우에도 효과적인 이진화 방법 : cv2.ADAPTIVE_THRESH_GAUSSIAN_C


오츠 이진화(Otsu's Binarization)

  • OpenCV에서 제공하는 자동 임계값 결정 기법
  • 영상의 히스토그램을 분석하여 객체와 배경을 가장 잘 구분할 수 있는 최적의 임계값을 자동으로 찾는 방법이다.
  • cv2.THRESH_OTSU 옵션을 사용하여 cv2.threshold() 함수에서 적용할 수 있다.
  • 모든 픽셀 값의 분포를 기반으로 클래스 내 분산(intra-class variance)을 최소화하는 임계값을 자동으로 선택하여 수동으로 임계값을 설정하는 불편함을 줄여준다.
  • 일반적으로 cv2.THRESH_BINARY + cv2.THRESH_OTSU를 함께 사용하여 이진화를 수행하며, 그레이스케일 변환이 선행되어야 한다.

19_binarization.py

# 이진화
# 이미지의 픽셀 값을 0과 1(0과 255) 두 가지 값만 가지도록 만드는 영상 처리 기법
# OCR, 윤곽 검출, 객체 분할, 문서 스캔 등 작업에 유리

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('./images/cells.png', cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([img], [0], None, [256], [0, 255])

# 픽셀값이 임계값을 넘으면 최대값으로 설정하고, 넘지 못하면 0으로 설정
a, dst1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY) 
print(a) # 임계값
b, dst2 = cv2.threshold(img, 210, 255, cv2.THRESH_BINARY) 
print(b) # 임계값
c, dst3 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print(c) # Otsu 임계값

cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
plt.plot(hist)
plt.show()
cv2.waitKey()

100.0 # dst1 임계값
210.0 # dst2 임계값
206.0 # dst3 (Otsu 임계값)


적응형 이진화(Adaptive Thresholding)

  • 조명 변화가 심한 영상에서도 적절한 임계값을 적용하여 이진화를 수행하는 방법
  • 일반적인 cv2.threshold()는 하나의 고정된 임계값을 사용하지만, cv2.adaptiveThreshold()는 영상의 작은 영역마다 서로 다른 임계값을 계산하여 적용한다.
    → 명암 차이가 고르지 않은 이미지에서도 효과적으로 객체와 배경을 분리할 수 있다.
  • OpenCV에서는 평균값을 이용하는 cv2.ADAPTIVE_THRESH_MEAN_C와 가우시안 가중치를 적용하는 cv2.ADAPTIVE_THRESH_GAUSSIAN_C 두 가지 방식을 제공하며, 블록 크기(blockSize)와 상수 값(C)을 조절하여 최적의 결과를 얻을 수 있다.

20_localbinary.py

import cv2
import numpy as np

img = cv2.imread('./images/sudoku.jpg', cv2.IMREAD_GRAYSCALE)

# 전역 자동 이진화
a, dst1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

# 지역 자동 이진화
dst2 = np.zeros(img.shape, dtype=np.uint8)
bw = img.shape[1] // 4
bh = img.shape[0] // 4

# 적응형 이진화
# ADAPTIVE_THRESH_MEAN_C: 해당 픽셀 주변의 평균값을 기준으로 임계값 설정
# 9: 이웃 블록 크기. 현재 픽셀 주변에서 얼마만큼의 영역을 고려할지 설정. 클수록 더 넓은 영역을 평균 -> 조명에 덜 민감하지만 부드러움
# 5: 임계값 보정 상수, 평균 또는 가중 평균에서 얼마나 빼줄지 결정. 값이 클수록 픽셀이 어두워야 흰색이 됨(임계값을 낮추거나 높이기 위한 튜닝용 파라미터)
dst3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 5)
# ADAPTIVE_THRESH_GAUSSIAN_C: 주변 픽셀에 가중치를 곱한 평균값으로 임계값을 설정
# 조명이 불균일한 이미지에서 효과적, 부드러운 결과
dst4 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 5)

for y in range(4):
    for x in range(4):
        img_ = img[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
        dst_ = dst2[y*bh:(y+1)*bh, x*bw:(x+1)*bw]
        cv2.threshold(img_, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU, dst_)

cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.imshow('dst4', dst4)

cv2.waitKey()

이렇게 조명에 따라 밝기가 다르게 찍힌 이미지에서 전역 자동 이진화를 하게 되면 검게 출력되는 부분이 있음을 확인했다.
조명에 따라 밝기가 다르게 찍힌 이미지에서는 지역 자동 이진화나 적응형 이진화를 해야 함을 확인할 수 있었다.


영상의 변환 (Image Transformation)

  • 영상의 형태, 크기, 밝기, 색상 등을 변경하여 새로운 영상으로 변환하는 과정.

  • 컴퓨터 비전에서 다양한 전처리 및 후처리에 활용된다.

  • 기하학적 변환(Geometric Transformation)
    확대/축소(Scaling), 회전(Rotation), 이동(Translation), 투시 변환(Perspective Transform) 등
    cv2.warpAffine(), cv2.getPerspectiveTransform()

  • 강도 변환(Intensity Transformation)
    픽셀 값의 변화를 조정하는 방법
    명암 조절, 히스토그램 평활화, 이진화(Thresholding) 등
    cv2.equalizeHist(), cv2.threshold(), cv2.adaptiveThreshold()


21_translate.py

import cv2
import numpy as np

img = cv2.imread('./images/dog.bmp')
aff = np.array([
    [1, 0, 150],
    [0, 1, 100]
], dtype=np.float32) # # 원본 이미지를 오른쪽으로 150픽셀, 아래로 100픽셀 이동시키는 변환

# 어파인 변환
# 이미지의 위치나 모양을 변경하는 선형 변환
# (0, 0) : 원본 이미지 크기를 그대로 전달
dst1 = cv2.warpAffine(img, aff, (0, 0))

# interpolation(보간법): 픽셀을 어떻게 채울지 결정
# INTER_NEAREST : 최근법 이웃 보간(속도 빠름, 품질 낮음), 가까운 픽셀 값을 그대로 복사. 계단 현상이나 노이즈가 생길 수 있음
dst2 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_NEAREST)
# INTER_CUBIC : 4차 보간법(속도 느림, 품질 좋음), 주변 16개 픽셀을 사용하여 곡선으로 예측, 이미지를 부드럽게 확대/축소
dst3 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_CUBIC)

cp = (img.shape[1] / 2, img.shape[0] / 2) # (가로 중앙, 세로 중앙) = 이미지의 중심 좌표(center point)
rot = cv2.getRotationMatrix2D(cp, 30, 0.7)
dst4 = cv2.warpAffine(img, rot, (0, 0))

cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.imshow('dst4', dst4)
cv2.waitKey()

cv2.getRotationMatrix2D(center, angle, scale)
- center: 회전 기준점 (여기서는 이미지의 중앙)
- angle: 회전 각도 (양수는 반시계 방향 회전)
- scale: 크기 조절 계수 (< 1은 축소)
angle=30, scale=0.7이면
중심 기준으로 30도 반시계 방향 회전
70% 크기로 축소


22_perspective.py

import cv2
import numpy as np

img = cv2.imread('./images/pic.jpg')

w, h = 600, 400

srcQuad = np.array(
    [[370, 173], [1224, 157], [1417, 830], [209, 849]], np.float32
)

dstQuad = np.array(
    [[0, 0], [w, 0], [w, h], [0, h]], np.float32
)

# 투시 변환(Perspective Transform)
# 영상에서 원근감을 조절하거나, 사각형을 반듯하게 펴주는 변환
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(img, pers, (w, h))

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

이미지 좌표는 그림판에서 확인해볼 수 있다.


23_namecard.py

import cv2
import numpy as np
import sys

img = cv2.imread('./images/namecard.jpg')
h, w = img.shape[:2]
dh = 500
# A4용지 크기: 210mm*297mm
dw = round(dh * 297 / 210)

srcQuad = np.array([
    [30, 30], [30, h-30], [w-30, h-30], [w-30, 30]
], np.float32)

dstQuad = np.array([
    [0, 0], [0, dh], [dw, dh], [dw, 0]
], np.float32)

dragSrc = [False, False, False, False]
ptOld = 0

def drawROI(img, corners):
    cpy = img.copy()
    c1 = (192, 192, 255)
    c2 = (128, 128, 255)

    for pt in corners:
        cv2.circle(cpy, tuple(pt.astype(int)), 25, c1, -1)
        cv2.line(cpy, tuple(corners[0].astype(int)), tuple(corners[1].astype(int)), c2, 2)
        cv2.line(cpy, tuple(corners[1].astype(int)), tuple(corners[2].astype(int)), c2, 2)
        cv2.line(cpy, tuple(corners[2].astype(int)), tuple(corners[3].astype(int)), c2, 2)
        cv2.line(cpy, tuple(corners[3].astype(int)), tuple(corners[0].astype(int)), c2, 2)

    return cpy

def onMouse(event, x, y, flags, param):
    global srcQuad, dragSrc, ptOld
    if event == cv2.EVENT_LBUTTONDOWN:
        for i in range(4):
            if cv2.norm(srcQuad[i] - (x, y)) < 25:
                dragSrc[i] = True
                ptOld = (x, y)
                break

    if event == cv2.EVENT_MOUSEMOVE:
        for i in range(4):
            if dragSrc[i]:
                srcQuad[i] = (x, y)
                cpy = drawROI(img, srcQuad)
                cv2.imshow('img', cpy)
                ptOld = (x, y)
                break

    if event == cv2.EVENT_LBUTTONUP:
        for i in range(4):
            dragSrc[i] = False

disp = drawROI(img, srcQuad)
cv2.namedWindow('img')
cv2.setMouseCallback('img', onMouse)
cv2.imshow('img', disp)

while True:
    key = cv2.waitKey()
    if key == 27: # ESC 키 누르면 종료
        sys.exit()
    elif key == 13: # Enter 키 누르면 결과 확인
        break

pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(img, pers, (dw, dh), flags=cv2.INTER_CUBIC)
# cv2.INTER_CUBIC : 4x4 주변 픽셀을 이용한 고급 보간법 (느리지만 부드러움)
cv2.imshow('dst', dst)
cv2.waitKey()


영상의 필터링 연산(Image Filtering Operation)

  • 영상의 특성을 강조하거나 잡음을 제거하기 위해 커널(필터)을 활용하여 픽셀 값을 변환하는 과정
  • OpenCV에서는 필터링을 통해 노이즈 제거, 엣지 검출, 블러 효과, 샤프닝 등의 처리를 수행할 수 있다.
  • 평균 블러링(Averaging), 가우시안 블러링(Gaussian Blur), 미디언 블러링(Median Blur) 등
    cv2.blur(), cv2.GaussianBlur(), cv2.medianBlur() 함수를 통해 적용할 수 있다.
  • 고급 필터링 기법 : 소벨 필터(Sobel Filter), 라플라시안 필터(Laplacian Filter) → 엣지 검출 필터
    cv2.Sobel(), cv2.Laplacian()을 사용하여 적용할 수 있다.

24_blurring.py

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

img = cv2.imread('./images/dog.bmp')
# img = cv2.imread('./images/noise.bmp')
# img = cv2.imread('./images/gaussian_noise.jpg')

dst1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
dst2 = cv2.blur(img, (7, 7))
# GaussianBlur
# 픽셀 주변 값들을 가우시안 분포에 따라 가중 평균에서 흐리게 만듬
# 커널 크기가 클수록 넓은 범위를 흐리게 함
# 전체적으로 흐림 처리만 하고 싶을 때
dst3 = cv2.GaussianBlur(img, (0, 0), 2)

# 픽셀 주변의 값 중에서 중간값을 선택해서 새로운 픽셀 값으로 바꾸는 필터
# 이미지의 각 픽셀을 기준으로 주변 픽셀을 확인한 후 그 중에서 가장 중간값(순서대로 정렬)을 가져와서 그 픽셀을 새로운 값으로 바꿔 블러 처리 함 
dst4 = cv2.medianBlur(img, 7)

# 양방향 필터(bilateralFilter)
# 엣지(윤곽선) 보존하면서 부드럽게 처리할 수 있는 필터
# 공간 정보 + 픽셀 색상 정보를 함께 고려
# 색상 거리 시그마, 공간 거리 시그마
# 노이즈를 줄이고, 윤곽선을 유지하고 싶을 때
dst5 = cv2.bilateralFilter(img, 12, 100, 100)

# Canny 엣지 검출
# 엣지를 찾기 위한 임계값을 자동으로 조절하여 엣지를 찾아주는 알고리즘
# lower threshold: 약한 엣지를 설정
# upper threshold: 강한 엣지를 설정
med_val = np.median(img)
lower = int(max(0, 0.7 * med_val))
upper = int(min(255, 1.3*med_val))
print(lower)
print(upper)
dst6 = cv2.GaussianBlur(img, (3, 3), 0)
dst6 = cv2.Canny(dst6, lower, upper, 3)


cv2.imshow('img', img)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.imshow('dst4', dst4)
cv2.imshow('dst5', dst5)
cv2.imshow('dst6', dst6)

plt.figure(figsize=(10, 5))
for i, k in enumerate([5, 7, 9]):
    # k * k 크기 커널 생성: 모든 원소가 1/k**2 예) k=5 1/25 로 채워진 5*5 행렬
    kernel = np.ones((k, k)) / k ** 2
    # dst1: 영상, -1: 출력 이미지의 깊이(채널 수 등)를 입력 이미지와 동일하게 유지
    filtering = cv2.filter2D(dst1, -1, kernel)
    plt.subplot(1, 3, i+1)
    plt.imshow(filtering)
    plt.title('kernel size: {}'.format(k))
    plt.axis('off')

plt.show()
cv2.waitKey()


cv2.GaussianBlur(src, ksize, sigmaX)

인자의미
src원본 이미지 (img)
ksize블러 처리에 사용할 커널 크기(width, height)
sigmaXX방향 표준편차 (σ), 즉 블러 강도
반환값블러가 적용된 이미지

ksize를 (0, 0)으로 설정하면, OpenCV가 sigmaX 값(여기선 2)을 기준으로 적절한 커널 크기를 자동으로 계산한다.


cv2.medianBlur(img, 7)

  • img: 원본 이미지
  • 7: 커널 크기 (홀수만 가능, 예: 3, 5, 7, 9...)
  • 7×7 영역 내에서 중간값 계산

"소금-후추 노이즈(salt-and-pepper noise)" 제거에 특히 강하다.


cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace [, dst [, borderType]]) [] : 생략 가능

  • src: 소스 이미지
  • dst: 대상 이미지
  • d: 각 픽셀 이웃 직경.
  • σColor: 색 공간의 표준 편차
  • σSpace: 좌표 공간의 표준 편차(픽셀 단위)

profile
The light shines in the darkness.

0개의 댓글