250910 [ Day 47 ] - OpenCV (3)

TaeHyun·2025년 9월 10일

TIL

목록 보기
49/184

시작하며

오늘은 어제에 이어서 Pillow라는 라이브러리를 이용해서 OpenCV에 한글을 그리는 방법부터 블러, 이진화 등 약간 이론적으로 이해가 필요한 내용이 있었지만 크게 어려운 내용은 없었다. 오늘부터 어려운 내용들이 시작된다고 했는데 아직까지는 할 만한 것 같다.

텍스트 그리기(한글)

  • pip install pillow 사용
from PIL import Image, ImageDraw, ImageFont

img = np.zeros((460, 640, 3), dtype=np.uint8)

def putKorText(img, text, position, font_size, font_color):
    img_pil = Image.fromarray(img)
    draw = ImageDraw.Draw(img_pil)
    font = ImageFont.truetype("/System/Library/Fonts/AppleSDGothicNeo.ttc", font_size)
    draw.text(position, text, font=font, fill=font_color)
    return np.array(img_pil)

text = putKorText(img, "안녕하세요", (220,300), 30, (255,255,255))

cv.imshow("KOR", text)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

블러

  • 이미지를 흐리게 하는 효과
  • cv2.blur(img, kernel) : 단순 평균 방식
  • cv2.GaussianBlur(img, kernel, 표준편차) : 가우시안 평균 방식
  • cv2.medianBlur(img, kernel) : 중앙값 사용 방식
  • kernel : 블러값을 계산하는 영역 (홀수값 사용)
  • 표준편차에 0 넣으면 자동 계산
img = cv.imread(DOG_PATH)

# 평균 블러
blur_avg = cv.blur(img, (5,5))

# 가우시안 블러
blur_gaussian = cv.GaussianBlur(img, (5,5), 0)

cv.imshow("Blur", blur_avg)
cv.imshow("Gaussian", blur_gaussian)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

  • 커널 사이즈 변화에 따른 차이
img = cv.imread(DOG_PATH)

kernel_3 = cv.GaussianBlur(img, (3,3), 0)
kernel_5 = cv.GaussianBlur(img, (5,5), 0)
kernel_7 = cv.GaussianBlur(img, (7,7), 0)

cv.imshow("kernel_3", kernel_3)
cv.imshow("kernel_5", kernel_5)
cv.imshow("kernel_7", kernel_7)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

  • 표준편차 변화에 따른 차이
img = cv.imread(DOG_PATH)

sigma_1 = cv.GaussianBlur(img, (0,0), 1)
sigma_2 = cv.GaussianBlur(img, (0,0), 2)
sigma_3 = cv.GaussianBlur(img, (0,0), 3)

cv.imshow("sigma_1", sigma_1)
cv.imshow("sigma_2", sigma_2)
cv.imshow("sigma_3", sigma_3)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

이진화 (Binarization)

  • 특정 값을 기준으로 픽셀의 값을 0(검은색)또는 255(흰색)로 분류하는 것
    • 분석을 단순화하기 위해 사용
    • 특정 관심 영역을 분리하는데 용이함

Threshold : 임계값, 문턱값

  • 기준값의 역할을 함
  • 빛의 밝기를 기준으로 픽셀 값을 변환
  • cv2.threshold(img, threshold, maxValue, type)
    • return / binary 값 반환
    • return : threshold 값
    • binary : 이미지
img = cv.imread(BOOK_PATH, cv.IMREAD_GRAYSCALE)

ret, binary = cv.threshold(img, 127, 255, cv.THRESH_BINARY)

cv.imshow("img", img)
cv.imshow("binary", binary)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

트랙바(Track bar)

  • cv2.createTrackbar(trackbarName, name, startValue, maxValue, callback) : 트랙바 생성
  • cv2.getTrackbarPos(trackbarName, name) : 트랙바의 값을 가져옴
img = cv.imread(BOOK_PATH, cv.IMREAD_GRAYSCALE)

# 트랙바 생성 이전에 창이 만들어져 있어야함
name = "Threshold"
cv.namedWindow(name)

# 콜백 함수는 필수
def trackbar_func(x):
    pass    

# 트랙바 생성
trackbar_name = "threshold"
cv.createTrackbar(trackbar_name, name, 127, 255, trackbar_func)

while True:
    threshold = cv.getTrackbarPos(trackbar_name, name)
    ret, binary = cv.threshold(img, threshold, 255, cv.THRESH_BINARY)

    cv.imshow("name", binary)

    if cv.waitKey(1) == ord("q"):
        break

cv.destroyAllWindows()
cv.waitKey(1)

  • 콜백 함수 자리에 람다 함수 활용 가능
img = cv.imread(DOG_PATH)

name = "Resize"
cv.namedWindow(name)

trackbar_name = "scale(%)"
cv.createTrackbar(trackbar_name, name, 100, 200, lambda x:x)

while True:
    scale = cv.getTrackbarPos(trackbar_name, name)
    
    if scale == 0:
        scale = 1

    width = int(img.shape[1] * scale / 100)
    height = int(img.shape[0] * scale / 100)
    resized = cv.resize(img, (width, height))

    cv.imshow(name, resized)

    if cv.waitKey(1) == ord("q"):
        break

cv.destroyAllWindows()
cv.waitKey(1)
img = np.zeros((460, 640, 3), dtype=np.uint8)

name = "COLOR"
cv.namedWindow(name)

cv.createTrackbar("B", name, 0, 255, lambda x:x)
cv.createTrackbar("G", name, 0, 255, lambda x:x)
cv.createTrackbar("R", name, 0, 255, lambda x:x)

trackbar_name = "0:OFF\n1:ON"
cv.createTrackbar(trackbar_name, name, 1, 1, lambda x:x)

while True:
    B = cv.getTrackbarPos("B", name)
    G = cv.getTrackbarPos("G", name)
    R = cv.getTrackbarPos("R", name)

    switch = cv.getTrackbarPos(trackbar_name, name)
    if switch == 1:
        img[:] = (B,G,R)

    cv.imshow(name, img)

    if cv.waitKey(1) == ord("q"):
        break

cv.destroyAllWindows()
cv.waitKey(1)

적응형 이진화

  • 영역별로 서로 다른 임계값을 적용하는 이진화
  • cv2.adaptiveThreshold(img, maxValue, adaptiveMethod, thresholdType, blockSize, C)
    • adaptiveMethod : 적응형 임계값 알고리즘
    • blockSize : 픽셀의 임계값을 계산하는 데 사용되는 픽셀 근처의 크기(1보다 큰 홀수)
    • C : 평균 또는 가중 평균에서 뺀 상수 (임계값 미세 조정)
img = cv.imread(BOOK_PATH, cv.IMREAD_GRAYSCALE)

name = "Adaptive Threshold"
cv.namedWindow(name)

cv.createTrackbar("block_size", name, 25, 100, lambda x:x) 
cv.createTrackbar("C", name, 1, 10, lambda x:x)

while True:
    block_size = cv.getTrackbarPos("block_size", name)
    C = cv.getTrackbarPos("C", name)

    if block_size <= 1:
        block_size = 3
    
    if block_size % 2 == 0:
        block_size += 1

    binary = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, block_size, C)

    cv.imshow(name, binary)

    if cv.waitKey(1) == ord("q"):
        break

cv.destroyAllWindows()
cv.waitKey(1)

오츠 알고리즘

  • 최적의 threshold를 찾는 알고리즘
  • 각 그룹 내부의 분산(값의 퍼짐)을 최소화하는 임계값을 찾는 것을 목표로 함
img = cv.imread(DOG_PATH, cv.IMREAD_GRAYSCALE)

ret_1, binary = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
ret_2, otsu = cv.threshold(img, -1, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)

print(ret_1, ret_2)

cv.imshow("img", binary)
cv.imshow("otsu", otsu)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

이미지 회전

rotate

  • cv2.rotate(img, rotateCode)
  • rotateCode
    • cv2.ROTATE_90_CLOCKWISE : 시계방향 90도 회전
    • cv2.ROTATE_180 : 180도 회전
    • cv2.ROTATE_90_COUNTERCLOCKWISE : 반시계방향 90도 회전
img = cv.imread(DOG_PATH)

dst_90 = cv.rotate(img, cv.ROTATE_90_CLOCKWISE)
dst_180 = cv.rotate(img, cv.ROTATE_180)
dst_90_counter = cv.rotate(img, cv.ROTATE_90_COUNTERCLOCKWISE)

cv.imshow("original", img)
cv.imshow("90", dst_90)
cv.imshow("180", dst_180)
cv.imshow("90 counter", dst_90_counter)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

아핀 변환

  • 이미지를 이동, 회전, 크기 조정, 반사, 기울이기(뒤틀기)와 같은 변환을 수학적으로 처리하는 방법
    • 행렬 연산을 사용하여 정의
  • math 모듈로 직접 구현
import math

img = cv.imread(DOG_PATH)

rad = 45 * math.pi / 180
affine = np.array([[math.cos(rad), -math.sin(rad), 0], [math.sin(rad), math.cos(rad), 0]])
scale = (img.shape[1], img.shape[0])

dst = cv.warpAffine(img, affine, scale)

cv.imshow("img", img)
cv.imshow("rotate", dst)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

  • getRotationMatrix2D 이용
img = cv.imread(DOG_PATH)

center = (int(img.shape[1]/2), int(img.shape[0]/2))
scale = (img.shape[1], img.shape[0])
affine = cv.getRotationMatrix2D(center, 45, 1)

dst = cv.warpAffine(img, affine, scale)

cv.imshow("img", img)
cv.imshow("rotate", dst)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

원근 변환

  • 원근법의 원리를 이용해 이미지에서 특정 부분의 기하학적 왜곡을 수정하거나 시점을 변경하는 것
  • cv2.getPerspectiveTransform(src, dst) -> mat
  • cv2.warpPerspective(img, mat, (width, height))
  • 행렬 생성시 시계 방향 순서대로 생성
img = cv.imread(CARD_PATH)

width, height = 600, 350

src = np.array([[54,261], [981,128], [1213,560], [194,735]], dtype=np.float32)
dst = np.array([[0,0], [width,0], [width,height], [0,height]], dtype=np.float32)

mat = cv.getPerspectiveTransform(src, dst)
result = cv.warpPerspective(img, mat, (width, height))

cv.imshow("img", img)
cv.imshow("warped", result)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

마치며

OpenCV 용어들이 전에 음향 일을 하면서 배웠던 용어들이나 영상 관련 용어들이 많이 나와서 이해하기가 한결 수월한 것 같다. 나중에는 OpenCV를 사용해서 어떤 사물인지 인식하는 등의 기능도 다뤄본다고 해서 빨리 배워보고 싶다.

NOTION

MY NOTION (OpenCV. 02)

profile
Hello I'm TaeHyunAn, Currently Studying Data Analysis

0개의 댓글