AI 개론

AI 개론 _ OpenCV를 이용한 컴퓨터 비전3 + OpenCV를 이용한 객체 검출, 동영상 처리 실습

Astero 2025. 4. 9. 23:07
728x90

1차 이산함수 미분

 

 

영상과 1차 미분

영상 1차 미분을 이용한 에지 검출

 

특징 검출 : Sobel edqe

 

Sobel 필터 연산결과

                                                                   dx                                                            dy

 

그래디언트 (Gradient)

from fastcampus

 

실제 영상에서 구한 그래디언트 크기와 방향

  • 그레디언트 크기 : 픽셀  값의 차이 정도, 변화량
  • 그레디언트 방향 : 픽셀 값이 가장 급격하게 증가하는 방향

 

Canny edge

https://towardsdatascience.com/canny-edge-detection-step-by-step-in-python-computer-vision-b49c3a2d8123

 

Canny edge criteria

 

canny edge

 

Hough transform : 직선 검출

 

 

 

 Hough transform: 직선 검출

 

 

 

Hough transform: 원 검출

 

 

 

연결 객체 검출: 레이블링

example

 

 외곽선 검출

 

 기하학적 모멘트

 

 Hu’s invariant moments

 

Match 비교

 

배경 차분 (Background subtraction)

 

이동평균 배경 차분 (Moving average)

 

 

OpenCV를 이용한 객체 검출, 동영상 처리

import numpy as np
import cv2
import sys
import matplotlib.pyplot as plt
from pathlib import Path

객체 검출

folder = "fig"

소벨 필터

kernel = np.array([[-1, 0, 1],
                   [-2, 0, 2],
                   [-1, 0, 1]], np.float32)

# src = cv2.imread("./fig/bamboo.jpg", cv2.IMREAD_GRAYSCALE)
src = cv2.imread(Path(folder, "bamboo.jpg"), cv2.IMREAD_GRAYSCALE)


# kernel


dst = cv2.filter2D(src, -1, kernel)

cv2.imshow("dst", src)

while True:
    if cv2.waitKey() == 27:
        break

cv2.destroyAllWindows()
src = cv2.imread(Path(folder, "bamboo.jpg"), cv2.IMREAD_GRAYSCALE)
# src = cv2.imread("./fig/plates.png", cv2.IMREAD_GRAYSCALE)


kernel = np.array([[-1, 0, 1],
                   [-2, 0, 2],
                   [-1, 0, 1]], np.float32)
# print(kernel)
# print(kernel.T)

dst1 = cv2.filter2D(src, -1, kernel)
dst2 = cv2.filter2D(src, -1, kernel.T)

cv2.imshow("bamboo", src)
cv2.imshow("dst1", dst1)
cv2.imshow("dst2", dst2)


while True:
    if cv2.waitKey() == 27:
        break

cv2.destroyAllWindows()
## Sobel filter
# cv2.Sobel(src, ddepth, dx, dy, dst, ksize, scale, delt, borderType) -> dst
# src : 입력영상
# ddepth : 출력영상의 데이터 타입 (-1)
# dx : x 방향 미분차수
# dy : x 방향 미분차수
# dst : 출력영상
# ksize : 커널의 크기
# scale : 연산결과에 추가적으로 곱할 값
# delta : 연산결과에 추가적으로 더할 값
# borderType : 가장자리 픽셀확장 방식

# magnitude(x, y, magnitude) -> magnitude
# src = cv2.imread("./fig/son.jpg", cv2.IMREAD_GRAYSCALE)
src = cv2.imread(Path(folder, "son.jpg"), cv2.IMREAD_GRAYSCALE)


dx = cv2.Sobel(src, cv2.CV_32F, 1, 0)
dy = cv2.Sobel(src, cv2.CV_32F, 0, 1)

mag = cv2.magnitude(dx, dy)
mag = np.clip(mag,  0, 255).astype(np.uint8)

ret, dst = cv2.threshold(mag, 150, 255, cv2.THRESH_BINARY)
background = np.zeros((mag.shape[0], mag.shape[1]), np.uint8)
background[mag > 150] = 255

cv2.imshow("src", src)
# cv2.imshow("dx", dx)
# cv2.imshow("dy", dy)
cv2.imshow("mag", mag)
cv2.imshow("dst", dst)
cv2.imshow("background", background)


# plt.imshow(mag, cmap = "gray")
# plt.show()

cv2.waitKey()
cv2.destroyAllWindows()

Canny 에지필터

## Canny edge
# Canny(image, threshold1, threshold2, edges, apertureSize, L2gradient) -> edges
# image : 입력 영상
# threshold1: 에지결정 하한값
# threshold1: 에지결정 상한값
# edges: None
# apertureSize: 커널사이즈
# L2gradient: gradient 크기 계산, False
src = cv2.imread(Path(folder, "son.jpg"), cv2.IMREAD_GRAYSCALE)
dst = cv2.Canny(src, 150, 180)

cv2.imshow("src", src)
cv2.imshow("dst", dst)

cv2.waitKey()
cv2.destroyAllWindows()

Hough 변환: 직선검출

## 직선 검출, 곡선 검출; 허프 변환 (Hough transform)
# HoughLinesP(image, rho, theta, threshold, lines, minLineLength, maxLineGap) -> lines
# image: 입력 에지영상
# rho: 축적배열에서 rho의 간격
# theta: 축적배열에서 theta의 간격
# threshold: 직선판단할 임계값
# lines: 선분의 끝좌표 (x1, y1, x2, y2)
# srn = None, stn = None
# minLineLength: 검출한 선분의 최소 길이
# maxLineGap: 직선으로 간주할 최대 에지 점 간격 (끝어진 점을 연결할 기준)
# img = cv2.imread("./fig/checkerboard.png", cv2.IMREAD_GRAYSCALE)
img = cv2.imread(Path(folder, "checkerboard.png"), cv2.IMREAD_GRAYSCALE)

edge = cv2.Canny(img, 100, 200)
# ret, edge = cv2.threshold(edge, )

lines = cv2.HoughLinesP(edge, 1, np.pi/360, 100, minLineLength = 10, maxLineGap = 30)
print(lines.shape[0]) # 라인갯수

dst = cv2.cvtColor(edge, cv2.COLOR_GRAY2BGR)

for i in range(lines.shape[0]):
    pt1 = (lines[i][0][0], lines[i][0][1])
    pt2 = (lines[i][0][2], lines[i][0][3])
    cv2.line(dst, pt1, pt2, (0, 0, 255), 1, cv2.LINE_AA)

cv2.imshow('img', img)
cv2.imshow('edge', edge)
cv2.imshow("dst", dst)

cv2.waitKey()
cv2.destroyAllWindows()
52

 

Hough 변환: 곡선검출

# HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]]) -> circles
# image: 입력영상
# method: cv2.HOUGH_GRADIENT,
# dp: 입력영상에 대한 실제 영상처리 배율, 1,  2 설정
# minDist: 검출된 원들간의 최소거리
# circles: 원좌표 (cx, cy, r), shape = (1, N, 3), dtype = np.float32
# param1: Canny edge max 값
# param2: 축척배열에서 원검출 임계값
# minRadius: 원 크기의 최소값
# maxRadius: 원 크기의 최대값
## Hough transform, 원 검출

# src = cv2.imread("./fig/plates.png")
src = cv2.imread(Path(folder, "plates.png"))

src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
# src_gray =  255 - src_gray
circles = cv2.HoughCircles(src_gray, cv2.HOUGH_GRADIENT, 1, 
                           50, param1=200, param2= 100, minRadius=50, maxRadius=150)

print(circles.shape[1])

for i in range(circles.shape[1]):
    cx, cy, radius = circles[0][i]
    cv2.circle(src, (int(cx), int(cy)), int(radius), (0, 0, 255), 2, cv2.LINE_AA)

cv2.imshow('src', src)
cv2.imshow("src-gray", src_gray)

cv2.waitKey()
cv2.destroyAllWindows()
6

레이블링 (labeling)

## 레이블링 (labeling)

# connectedComponentsWithStats(image[, labels[, stats[, centroids[, connectivity[, ltype]]]]]) -> retval, labels, stats, centroids
# src = cv2.imread("./fig/symbols.png")
src = cv2.imread(Path(folder, "symbols.png"))

# src = cv2.GaussianBlur(src, (0, 0), 1)

src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(src_gray, 100, 255, cv2.THRESH_BINARY)
# mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, None)


cnts, labels, stats, centroids = cv2.connectedComponentsWithStats(mask)
print(stats[1])

for i in range(1, cnts):
    (x, y, w, h, area) = stats[i]

    if area <= 50:
        continue

    cv2.rectangle(src, (x, y, w, h), (0, 0, 255), 2)

print("count = ", cnts)

cv2.imshow("src", src)
# cv2.imshow("gray", src_gray)
cv2.imshow("mask", mask)

cv2.waitKey()
cv2.destroyAllWindows()
[     0      0    512    512 217198]
count =  2

 

외곽선 검출

## 외곽선 검출
# findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> contours, hierarchy
    # image: 입력 영상. non-zero 픽셀을 객체로 간주함.
    # mode: 외곽선 검출 모드. cv2.RETR_로 시작하는 상수. 
    #     (cv2.RETR_EXTERNAL, cv2.RETR_LIST,cv2.RETR_CCOMP, cv2.RETR_TREE)
    # method: 외곽선 근사화 방법. cv2.CHAIN_APPROX_로 시작하는 상수.
    # contour: 검출된 외곽선 좌표. numpy.ndarray로 구성된 리스트. 
    # contours[i].shape=(K, 1, 2). contours[i].dtype=numpy.int32.
    # hierarchy: 외곽선 계층 정보. numpy.ndarray. shape=(1, N, 4). dtype=numpy.int32.
    # hierarchy[0, i, 0] ~ hierarchy[0, i, 3]이 순서대로 next, prev, child, parent
    # 외곽선 인덱스를 가리킴. 해당 외곽선이 없으면 -1.
    # offset: 좌표 값 이동 옵셋. 기본값은 (0, 0).
    
# drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image
# src = cv2.imread("./fig/shape.png")
src = cv2.imread(Path(folder, "shape.png"))

src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(src_gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)
contours, hierachy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
for i in range(len(contours)):
    cv2.drawContours(src, contours, i, (0, 0, 255), 1)
    cv2.putText(src, str(i), contours[i][0][0], cv2.FONT_HERSHEY_COMPLEX, 1,
                (0, 0, 255), 1, cv2.LINE_AA)

cv2.imshow('src', src)
cv2.imshow('src_gray', src_gray)
cv2.imshow("mask", mask)
cv2.waitKey()
cv2.destroyAllWindows()

기하학적 모멘트

## 기하학적 모멘트 (Hu 불변 모멘트)
# obj = cv2.imread("./fig/spades.png", cv2.IMREAD_GRAYSCALE)
# src = cv2.imread("./fig/symbols.png", cv2.IMREAD_GRAYSCALE)
obj = cv2.imread(Path(folder, "spades.png"), cv2.IMREAD_GRAYSCALE)
src = cv2.imread(Path(folder, "symbols.png"), cv2.IMREAD_GRAYSCALE)



_, obj_bin = cv2.threshold(obj, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)
obj_contours, _ = cv2.findContours(obj_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
obj_pts = obj_contours[0]
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)
src_contours, _ = cv2.findContours(src_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
dst = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)

for pts in src_contours:
    if cv2.contourArea(pts) < 1000:
        continue
    rc = cv2.boundingRect(pts)
    cv2.rectangle(dst, rc, (255, 0, 0), 1)
    dist = cv2.matchShapes(obj_pts, pts, cv2.CONTOURS_MATCH_I3, 0)
    cv2.putText(dst, str(round(dist, 3)), (rc[0], rc[1] - 3), cv2.FONT_HERSHEY_COMPLEX, 0.8,
                (255, 0, 0), 1, cv2.LINE_AA)
    
    if dist < 0.1:
        cv2.rectangle(dst, rc, (0, 0, 255), 2)
        cv2.putText(dst, str(round(dist, 3)), (rc[0], rc[1] - 3), cv2.FONT_HERSHEY_COMPLEX, 0.8,
                (0, 0, 255), 2, cv2.LINE_AA)
        
cv2.imshow("obj", obj)
cv2.imshow("src", src)
cv2.imshow("dst", dst)
# cv2.imshow("obj_bin", obj_bin)

cv2.waitKey()
cv2.destroyAllWindows()

동영상 처리

동영상 객체 검출

# cv2.VideoCapture(index/filename, apiPreference=None) -> retval
# index: camera_id or filename
# apiPreference=None

# cv2.VideoWriter(filename, fourcc, fps, framesize, isColor=None) -> retval
# filename: 저장할 이름
# fourcc: cv2.VideoWriter_fourcc(*'DIVX') 를 사용
# fps: 초당 프레임 수 e.g. 30
# frameSize: 프레임 크기 e.g., [640, 480])
# isColor: Color 영상

# cap = cv2.VideoCapture("./fig/PETS2000.avi")
cap = cv2.VideoCapture(Path(folder, "PETS2000.avi"))

if not cap.isOpened():
    print("Video open failed")
    sys.exit()

ret, background = cap.read()
background_gray = cv2.cvtColor(background, cv2.COLOR_BGR2GRAY)
background_gray_G = cv2.GaussianBlur(background_gray, (0, 0), 1.) 
# cv2.imshow("background", background)
# cv2.waitKey()
while True:
    ret, frame = cap.read() # fps: frame per second

    if not ret:
        break
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    frame_gray_G = cv2.GaussianBlur(frame_gray, (0, 0), 1.)
    diff_G = cv2.absdiff(frame_gray_G, background_gray_G)
    ret_g, mask_g = cv2.threshold(diff_G, 50, 255, cv2.THRESH_BINARY)
    cnts, labels, stats, centroids = cv2.connectedComponentsWithStats(mask_g)
    for i in range(1, cnts):
        x, y, w, h, area = stats[i]
        if area <= 200:
            continue
        cv2.rectangle(frame, (x, y, w, h), (0, 0, 255), 2)

    cv2.imshow("frame", frame)
    cv2.imshow("diff", diff_G)
    cv2.imshow("mask_g", mask_g)

    if cv2.waitKey(20) == 27:
        break

cv2.destroyAllWindows()
cap.release()

이동 평균 배경 차분

# accumulateWeighted(src, dst, alpha, mask) -> dst
# src: 입력영상
# dis: 출력영상 (32bit, 64bit)
# alpha : 축적가중치
# mask: 마스트 영상
## 이동 평균 배경 차분
cap = cv2.VideoCapture(Path(folder, "PETS2000.avi"))

if not cap.isOpened():
    print("Video open failed")
    sys.exit()
ret, back = cap.read()
back = cv2.cvtColor(back, cv2.COLOR_BGR2GRAY)
back = cv2.GaussianBlur(back, (0, 0), 1.)
fback = back.astype(np.float32)
while True:
    ret, frame = cap.read()
    if not ret:
        print("frame is None")
        break
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    frame_gray = cv2.GaussianBlur(frame_gray, (0, 0), 1)
    cv2.accumulateWeighted(frame_gray, fback, 0.01)
    back = fback.astype(np.uint8)
    diff = cv2.absdiff(frame_gray, back)
    ret, mask = cv2.threshold(diff, 50, 255, cv2.THRESH_BINARY)
    cnts, labels, stats, centroids = cv2.connectedComponentsWithStats(mask)
    for i in range(1, cnts):
        x, y, w, h, area = stats[i]
        if area < 100:
            continue
        cv2.rectangle(frame, (x, y, w, h), (0, 0, 255), 2)

    cv2.imshow("frame",frame)
    cv2.imshow("mask",mask)
    cv2.imshow("back",back)
    
    if cv2.waitKey(20) == ord("q"):
        break

cv2.destroyAllWindows()
cap.release()

Webcam 열기

# 카메라 열기
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
# cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
# cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# cap.set(cv2.CAP_PROP_FPS, 30)
# cap = cv2.VideoCapture('raining.mp4')

if not cap.isOpened(): #True or Falose
    print("Camera open failed")
    cap.release()
    sys.exit()


# 카메라 프레임 크기 출력
print('Frame width:', int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
print('Frame height:', int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))


# 카메라 프레임 처리
while True:
    ret, frame = cap.read()

    if not ret:
        
        break

    edge = cv2.Canny(frame, 50, 150)       
    # inversed = ~frame  # 반전
    
    cv2.imshow('frame', frame)
    cv2.imshow('frame1', edge)
#     cv2.imshow('inversed', inversed)

    if cv2.waitKey(10) == 27:
        break

cv2.destroyAllWindows()
Frame width: 640
Frame height: 480

 

동영상 저장

# cv2.VideoWriter(filename, fourcc, fps, framesize, isColor=None) -> retval
# filename: 저장할 이름
# fourcc: cv2.VideoWriter_fourcc(*'DIVX') 를 사용
# fps: 초당 프레임 수 e.g. 30
# frameSize: 프레임 크기 e.g., [640, 480])
# isColor: Color 영상
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

if not cap.isOpened():
    print("Camera open failed!")
    sys.exit()

# cap.get(cv2.CAP_PROP_FRAME_WIDTH) -> float type 반환
w = round(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

# fps = cap.get(cv2.CAP_PROP_FPS) # Frame per second
fps = 30


# fourcc = cv2.VideoWriter_fourcc(*'MJPG')
fourcc = cv2.VideoWriter_fourcc(*'DIVX') 
# fourcc = cv2.VideoWriter_fourcc('D', 'I', 'V', 'X') # *'DIVX' 


delay = round(1000 / fps) #frame 간 시간 간격, ms 단위

out = cv2.VideoWriter('output.avi', fourcc, fps, (w, h), 
                      isColor = True)

if not out.isOpened():
    print('File open failed!')
    cap.release()
    sys.exit()

while True:
    ret, frame = cap.read()

    if not ret:
        break

#     inversed = ~frame
#     edge = cv2.Canny(frame, 50, 150)
#     edge_color = cv2.cvtColor(edge, cv2.COLOR_GRAY2BGR)

    out.write(frame) #소리는 capture가 안됨
#     out.write(inversed)
#     out.write(edge_color)

    cv2.imshow('frame', frame)
#     cv2.imshow('inversed', inversed)
#     cv2.imshow('edge', edge)
    if cv2.waitKey(delay) == 27:
        break


out.release()
cv2.destroyAllWindows()

카툰 필터 카메라

# 카툰 필터 카메라
import sys 
import numpy as np
import cv2


def cartoon_filter(img):
    h, w = img.shape[:2]
#     img2 = cv2.resize(img, (w//2, h//2))

    blr = cv2.bilateralFilter(img, -1, 20, 7)
    edge = 255 - cv2.Canny(img, 80, 120)
    edge = cv2.cvtColor(edge, cv2.COLOR_GRAY2BGR)

    dst = cv2.bitwise_and(blr, edge)
    dst = cv2.resize(dst, (w, h), interpolation=cv2.INTER_NEAREST)

    return dst


def pencil_sketch(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blr = cv2.GaussianBlur(gray, (0, 0), 3)
    dst = cv2.divide(gray, blr, scale=255)
    return dst


cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

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

cam_mode = 0

while True:
    ret, frame = cap.read()

    if not ret:
        break

    if cam_mode == 1:
        frame = cartoon_filter(frame)
    elif cam_mode == 2:
        frame = pencil_sketch(frame)
        frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)

    cv2.imshow('frame', frame)
    key = cv2.waitKey(1)

    if key == 27:
        break
    elif key == ord(' '):
        cam_mode += 1
        if cam_mode == 3:
            cam_mode = 0


cap.release()
cv2.destroyAllWindows()
728x90