머신러닝/영상인식

영상인식 응용 resize, read, hsv, mask, morphology operation, hough, arctan2

Olivia-BlackCherry 2025. 3. 19. 18:49
#Steps
#1. Reading and Resizing the Video
#2. Convert Colors from BGR to HSV color space
#3. Detecting the Pool Table Boundary
#4. Cleaning Up the Mask - Morphogical Operations
#5. Detecting Lines
#6. Classifying Lines
#7. Finding Table Corners
#8. Defining Regions of Interest
#9. Creating a Mask
#10. Detecting Balls
#11. Detecting and Marking Patterns
#12. Analyzing Circle Positions and Cue Line
#13. Collision and Reflection Prediction
#14. Timelimits and check Condition
#15. Condition for Writing Frames

 
 
1번 Resize & Read
읽어들이는 동영상의 크기가 크기 때문에 원하는 비율에 맞춰 사이즈를 조절한다. 
cap = cv2.VideoCapture("이름") 동영상 파일 열기
cap.read() 프레임을 하나씩 가져오기
cap.get(cv2.CAP_PROP_FRAME_WIDTH) 동영상의 너비 가져오기
cap.get(cv2.CAP_PROP_FPS) 초당 프레임수 가져오기
cv2.resize(원본, 새로운 가로 세로)
cv2.imshow("이름", 프레임) 영상 출력
cv2.waitKey(1) 1ms 동안 키 입력을 기다림 ord('1') 1키를 누르면 종료
cap.release() 동영상 파일 닫기
cv2.destroyAllWindows() 모든 창 닫기

mport cv2
import math
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw, ImageFont

cap = cv2.VideoCapture("video.mp4")
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
frame_rate = int(cap.get(cv2.CAP_PROP_FPS))

resize_width = 800
resize_height = int(frame_height*(resize_width/frame_width))

while True:
    ret, frame = cap.read()
    if ret:
        resized_frame = cv2.resize(frame, (resize_width, resize_height))
        cv2.imshow("Video", resized_frame)
        if cv2.waitKey(1) & 0xFF ==ord('1'):
            break
    else:
        break

cap.release()
cv2.destroyAllWindows()

 
 
2번 BGR → HSV 변환
BGR blue green red : open cv의 기본 색상 형식으로 카메라나 이미지에서 읽어온 원본 색상이다. 
HSV 는 hue, saturation, value 색상 채도 밝기로 표현한다. 
BGR은 빛의 영향을 많이 받아 색상이 일정하지 않지만, HSV에서는 색상 값만으로 특정한 색을 쉽게 찾을 수 있다. 또한 특정 색을 검출하기 쉽다.

        hsv_space = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

 
 
 
3~Mask
당구대에서 공의 움직임을 예측하려면, 먼저 당구대 자체와 공을 정확하게 감지해야 한다. 이를 위해 배경과 필요 없는 요소를 제거하고 관심 있는 부분만 남겨야 하는데, 이때 마스크를 사용한다. 
cv2.inRange(프레임, 시작범위, 마지막범위) ---> 특정 색깔 필터링
시작범위: 초록색의 최소 HSV값
마지막범위: 초록색의 최대 HSV값
이 범위에 해당하는 픽셀만 흰색(255)이고 나머지는 검은색(0)으로 마스크를 생성한다.

        mask_green = cv2.inRange(hsv_space, np.array([56,161,38]), np.array([71,255,94]))

 
 
4~ Morphology Operation
cv2.morphologyEx 모폴로지연산 --> 노이즈 제거

imageopen = cv2.morphologyEx(mask_green, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)))
imageclose= cv2.morphologyEx(imageopen, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (8,8)))

MOTPH_OPEN 열기 연산 : 작은 잡은 노이즈를 제거한다. 
cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) : (5,5) 크기의 사각형 커널을 이용해 연산 수행한다. 
작은 점이나 노이즈를 제거(Erosion)하고, 남은 부분을 팽창(Dilation)한다.
 
MORPH_CLOSE 닫기 연산 : 객체 내부의 빈 구멍을 메워 경계를 더 선명하게 만든다.
cv2.getSTructuringElement(cv2.MORPH_RECT,(8,8)): (8,8) 커널을 사용해 연산수행
객체의 경계를 확장해서 구멍을 Dilation팽창하고, 원래 크기로 다시 Erosion 침식=조정한다. 
 
 
5~ Hough 변환
Detecting Lines
직선을 검출하는 코드이다. 여기서 허프 변환Hough Transform을 이용해 직선을 검출한다. 점들의 패턴을 분석해 선을 찾는 방법이다. 해당 함수를 활용하면 당구대의 쿠션 라인이나 가장자리 선 검출이 용이하다.

# 5. Detecting Lines
        lines = cv2.HoughLinesP(imageclose, 1, np.pi/180, threshold=100, minLineLength=100, maxLineGap=10)

threshold=100 : 100개 이상의 점이 일직선상에 있으면 선으로 인정한다. 
minLineLength=100 : 100픽셀 이상이어야만 선으로 검출한다.
maxLineGap=10 : 10픽셀 이내의 끊어진 선은 같은 직선으로 연결
 
 
6~ 선 분류하기
np.arctan2(y2-y1, x2-x1)  : 선의 기울기 계산 - 직선의 기울기에 대한 아크탄젠트값(라디안) 반환
*180 /np.pi 라디안을 degree로 변환
abs() 각도를 절댓값으로 반환(음수각도 안나오도록)
angle<0 : 0도에 가까운 선은 수평선  ---> 위 아래의 쿠션벽
90-threshold < angle < 90+threshold : 90도에 가까운 선은 수직선 ----> 왼쪽 오른쪽의 쿠션벽

#Horizontal : Angle close tp 0 degrees
        #Vertical : angle near 90 degrees
        angle_threshold = 1
        horizontal_lines = []
        vertical_lines = []
        for line in lines:
            x1,y1,x2,y2 = line[0]
            angle = np.abs(np.arctan2(y2 - y1, x2-x1)*180/np.pi)
            if angle <angle_threshold:
                horizontal_lines.append(line)
            elif angle > 90 - angle_threshold and angle < 90 + angle_threshold:
                vertical_lines.append(line)

 
 
7~모서리 찾기
Finding Table Corners
수평선과 수직선을 분류해서 top, bottom, left, right 경계 찾기

        # 7. Finding Table Corners
        middle_y = frame.shape[0]/2
        top_lines = [line for line in horizontal_lines if line[0][1] < middle_y]
        bot_lines = [line for line in horizontal_lines if line[0][1] > middle_y]

        if len(bot_lines) ==0 or len(top_lines) ==0:
            out.write(frame)
            continue
        top_line = sorted(top_lines, key= lambda x: x[0][1])[0]
        bot_line = sorted(bot_lines, key= lambda x: x[0][1])[-1]

        middle_x = frame.shape[1]/2
        left_lines = [line for line in vertical_lines if line[0][0] < middle_x]
        right_lines = [line for line in vertical_lines if line[0][0] > middle_x]

        if len(left_lines) == 0 or len(right_lines) == 0:
            out.write(frame)
            continue
        left_line = sorted(left_lines, key=lambda x: x[0][1])[0]
        right_line = sorted(right_lines, key=lambda x: x[0][1])[-1]

 
테이블의 코너 4군데 검출하기

        top_left = [left_line[0][0], top_line[0][1]]
        bot_left = [left_line[0][0], bot_line[0][1]]
        top_right =  [right_line[0][0], top_line[0][1]]
        bot_right =  [right_line[0][0], bot_line[0][1]]

        corners = [top_left, bot_left, bot_right, top_right]
        print(corners)