no image
[MediaPipe] MediaPipe를 이용한 팔굽히기 모션 인식하기
사람을 인식하고 해당 신체의 모든 부위 얼굴, 팔, 다리, 몸통의 움직임을 추적하는MediaPipe 라이브러리를 사용하여 팔 운동 트레이닝 로직(AI 이두컬 트레이너)을 만들어보자.  1. MediaPipe, OpenCV 설치!pip install mediapipe opencv-python이번 작업에서 가장 중요한 두 가지 라이브러리를 설치한다. 2. 해당 라이브러리 임포트import cv2 # openCVimport mediapipe as mpimport numpy as npmp_drawing = mp.solutions.drawing_utilsmp_pose = mp.solutions.posemp_drawings: 신체를 인식하여 여러 부위를 감지 및 추적하는 것을 시각화 할 때 사용하는 유틸리티mp_p..
2024.06.24
no image
[OpenCV] 이미지의 특정 위치 좌표 추출
OpenCV로 이미지의 원하는 위치의 좌표 추출하는 법을 알아보자 좌표를 추출하여 해당 위치의 이미지 색상을 바꾸는 등의 응용까지 해볼 수 있다. 해당 이미지를 가지고 현재 회색인 신체 윤곽선 부분을 초록색으로 바꿔보자 1. 윤곽선 이미지 불러오기 import cv2 person = cv2.imread('person.png') 검은색 배경에 회식 라인의 윤곽선 이미지다. 2. 이미지 회색 처리 gray = cv2.cvtColor(resized_person, cv2.COLOR_BGR2GRAY) cv2.imshow('result', gray) cv2.waitKey(0) cv2.destroyAllWindows() 해당 이미지의 좌표를 추출하기 위해 이미지 이진화 작업이 꼭 필요한데 그 전에 회색 처리를 먼저 ..
2024.02.21
no image
[OpenCV] 이미지 합성
OpenCV로 이미지 끼리 합성을 하는 방법을 알아보자. 이미지 합성을 하기 위해서는 OpenCV에서 제공하는 비트 연산 기능을 이용하면 된다. 비트 연산 기능을 이용하여 배경 사진에 특정 로고 사진을 합성해보자 1. 로고 이미지 이진화 img_logo = cv2.imread('logo.png') img_gray = cv2.cvtColor(img_logo, cv2.COLOR_BGR2GRAY) # 이진화 전 회색 처리 필수 ret, img_mask = cv2.threshold(img_gray, 200, 255, cv2.THRESH_BINARY) img_mask_inv = cv2.bitwise_not(img_mask) cv2.imshow('img_mask', img_mask) cv2.imshow('img_m..
2024.02.08
no image
[OpenCV] 이미지 윤곽선 검출
사람의 인체 모양을 인식하고 어떠한 판단을 내리기 위해서는 신체에 윤곽선을 그리고, 해당 윤곽선에 대한 좌표 데이터를 획득해야 한다. 이러한 목표를 성취하기 위해서 첫 번째로 해야 할 작업은 이미지 안에 특정 사물의 윤곽선을 그리는 것부터 시작한다. 이 글은 파이썬의 이미지 처리 라이브러리인 OpenCV를 통해서 이미지 내 윤곽선을 검출하는 방법을 기술한다. 앞서 작성한 OpenCV의 설명을 보자면 OpenCV는 이미지를 흑백으로 변경을 하거나 이미지의 특정 위치의 좌표값을 얻는 등 여러 처리를 할 수 있는 라이브러리다. 해당 라이브러리를 사용하여 윤곽선을 검출하기 위해 과정을 단계별로 나누자면 이미지의 윤곽선을 검출하는 과정 이미지 회색 처리 이미지 바이너리 처리 위 모든 과정을 거친 이미지의 윤곽선 ..
2024.02.07
[Flask] Flask 웹 소켓 서버 구축
웹 소켓이란? 본래 서버와 클라이언트가 통신하기 위해서는 HTTP 통신을 통해 클라이언트의 요청과 서버의 응답 형식으로 통신이 이루어진다. HTTP 통신은 무조건 클라이언트의 요청이 있어야 서버에서 데이터를 송신할 수 있는데, 이 과정이 이루어지고 나면 클라이언트와 서버 간의 연결이 종료된다. 이를 개선하여 클라이언트와 서버의 연결을 유지하면서 서로 원할 때마다 데이터를 송수신 하기 위해 등장한 것이 웹 소켓이다. 1. SocketIO 라이브러리 설치 pip install flask-socketio 에디터 터미널에 입력해준다. 2. 해당 라이브러리 임포트 from flask_socketio import SocketIO 3. Flask 서버에 웹 소켓 서버 적용 app = Flask(__name__) so..
2024.02.05
[Flask] Flask 서버를 이용한 카메라 웹 스트리밍
파이썬의 OpenCV를 이용한 카메라를 웹에 표출하기 위해서는 Flask 서버를 구축해야 한다. 1. Flask 설치 pip install Flask Flask를 설치하기 위해 해당 명령어를 입력한다. 2. 라이브러리 Import from flask import Flask, Response, render_template import cv2 Flask를 포함하여 여러 라이브러리를 임포트한다. 3. 카메라 표출 함수 생성 camera = cv2.VideoCapture(0) def generate_frames(): while True: success, frame = camera.read() # 프레임 읽기 if not success: break else: ret, buffer = cv2.imencode('.j..
2024.02.01
[OpenCV] OpenCV 미니 창에 마우스 이벤트 처리하기
OpenCV의 기본적인 기능에는 이미지를 처리하기 위해 해당 이미지를 표시하는 창을 띄운다. 더불어, 해당 창을 특별한 기능이 담긴 프로젝트로 제작하기 위해 마우스 이벤트를 처리하는 기능을 제공하고 있다. 1. 마우스 이벤트 핸들러 함수 정의 - 마우스의 이벤트에 따라 원하는 동작을 수행하도록 하는 말그대로 이벤트 핸들러 함수를 정의한다. import cv2 def mouse_event(event, x, y, flags, param) : if event == cv2.EVENT_LBUTTONDOWN: print('왼쪽 마우스 클릭') elif event == cv2.EVENT_LBUTTONUP: print('왼쪽 마우스 클릭 해제 ') elif event == cv2.EVENT_LBUTTONDBLCLK: ..
2023.12.30
no image
[OpenCV] OpenCV를 이용하여 동영상 출력
OpenCV (Computer Vision)란? - 다양한 영상 (이미지) / 동영상 처리에 사용되는 오픈소스 라이브러리 => ex) 이미지를 흑백으로 변경, 이미지의 특정 영역을 잘라내기 - OpenCV의 기능을 이용하여 여러 응용 프로젝트를 제작할 수 있다 => ex) 문서 스캐너, 얼굴 인식, 모션 인식 1. OpenCV 설치 - 파이썬을 실행하는 에디터에 맞는 프롬프트에서 해당 명령어를 입력한다. (Jupyter Notebook: Anaconda Prompt, PyCharm: 에디터 터미널) 2. OpenCV 라이브러리 Import - OpenCV를 설치 했으면 파이썬 코드 내에서 cv2라는 이름으로 OpenCV를 임포트 할 수 있다. import cv2 3. 동영상 파일 읽어오기 1) cv2.V..
2023.12.19
728x90

사람을 인식하고 해당 신체의 모든 부위 얼굴, 팔, 다리, 몸통의 움직임을 추적하는

MediaPipe 라이브러리를 사용하여 팔 운동 트레이닝 로직(AI 이두컬 트레이너)을 만들어보자.

 

 

1. MediaPipe, OpenCV 설치

!pip install mediapipe opencv-python
  • 이번 작업에서 가장 중요한 두 가지 라이브러리를 설치한다.

 

2. 해당 라이브러리 임포트

import cv2 # openCV
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose
  • mp_drawings: 신체를 인식하여 여러 부위를 감지 및 추적하는 것을 시각화 할 때 사용하는 유틸리티
  • mp_pose: 실제 신체를 감지함과 동시에 감지 및 추적된 신체 부위의 위치 등을 추출해준다.

 

3. 감지 및 추적 될 신체 부위의 라벨링 값 확인

mp_pose.POSE_CONNECTIONS 
  • MediaPipe의 자체 알고리즘이 예측 결과로 신체에 그림을 그리기 위해 설정한 라벨링 값을 출력해보자.
frozenset({(0, 1),
           (0, 4),
           (1, 2),
           (2, 3),
           (3, 7),
           (4, 5),
           (5, 6),
           (6, 8),
           (9, 10),
           (11, 12),
           (11, 13),
           (11, 23),
           (12, 14),
           (12, 24),
           (13, 15),
           (14, 16),
           (15, 17),
           (15, 19),
           (15, 21),
           (16, 18),
           (16, 20),
           (16, 22),
           (17, 19),
           (18, 20),
           (23, 24),
           (23, 25),
           (24, 26),
           (25, 27),
           (26, 28),
           (27, 29),
           (27, 31),
           (28, 30),
           (28, 32),
           (29, 31),
           (30, 32)})
  • 출력해서 나온 결과를 아래 그림과 비교하면 각 숫자가 어떤 역할을 하는지 알 수 있다. 예를 들어, 코(0)는 눈(1, 4)와 연결된 것을 확인해 볼 수 있다.

 

4. 카메라 내 신체를 감지하여 그림을 그려보기

cap = cv2.VideoCapture(0) # 웹캠 불러오기
## 미디어파이프 인스턴스 설정 (신뢰도는 0.5, 연속 프레임 신뢰도 0.5)
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose: 
    # 미디어파이프를 효율적으로 사용하기 위해 with as 구문 사용

    while cap.isOpened():
        ret, frame = cap.read()
        
        # 이미지 다시 칠하기: 미디어 파이프에 전달하기 위해 BGR -> RGB로 변경
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False # 이미지 픽셀 값 수정 막기
				# pose.process()의 인자로 이미지를 넘겨주면 해당 이미지의 픽셀값이 수정 될 수도 있음
      
        # 신체를 감지함, 이미지 속의 신체 부위의 위치를 감지 및 움직임을 추적
        results = pose.process(image)
    
        # Recolor back to BGR
        # 이미지를 다시 RGB 에서 BGR 로 변경
        image.flags.writeable = True # 이미지 픽셀 값 수정 허용
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        
        # Render detections
        # 이미지나 상에 감지된 랜드마크를 그린다
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2), 
                                mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2) 
                                 )     
        # mp_drawing.draw_landmarks(이미지, 이미지의 감지된 신체부위 위치 및 관절, 연결할 랜드마크 선들, 선 스타일)
        
        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()
  • 요약 하자면 카메라를 불러와서 각 프레임 단위로 신체를 인식하고 여러 신체 부위를 감지 및 탐지하여 해당 이미지(프레임)에 관절라인, 즉 그림을 그린다.
  • min_detection_confidence:신체가 감지될 때 필요한 최소 신뢰도 설정.
  • min_tracking_confidence: 신체가 한번 감지 된 후 연속 프레임에서 해당 신체를 추적 할 때 필요한 최소 신뢰도 설정
  • MediaPipe의 신체 감지 알고리즘이 특정 부위를 50% 이상의 신뢰도(정확도)로 측정 되었을 때 해당 부위를 유효하게 간주함. 이 값을 너무 낮게 설정하면 잘못된 감지가 증가할 수 있으며, 너무 높게 설정하면 유효한 포즈를 놓칠 수 있다.
  • results = pose.process(image): 해당 프레임을 가져와 실제로 신체 감지 및 추적이 이루어진다. result 변수에는 감지 및 추적에 대해 다양한 정보가 들어간다.
  • mp_drawing.draw_landmarks(이미지, 이미지의 감지된 신체부위 위치 및 관절, 연결할 랜드마크 선들, 선 스타일): 프레임에 본격적으로 그림을 그린다.

 

5. 왼쪽 어깨, 팔꿈치, 팔목에 대한 좌표 추출

with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()
        
        results = pose.process(image)

        try:
            landmarks = results.pose_landmarks.landmark
						# 각 신체부위에 대한 좌표 추출
            print(landmarks)
        except:
            pass
# 왼쪽 어깨 x,y 좌표
print(landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x)
print(landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y)
0.7106555700302124
0.8390206694602966
  • 팔을 굽혔다 폈다 하는 각도를 계산하기 위해서는 어깨, 팔꿈치, 팔목 이 3가지의 좌표가 필요하다.
shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
[0.7106555700302124, 0.8390206694602966],
 [0.781584620475769, 1.1789385080337524],
 [0.796806812286377, 1.4664262533187866]

 

6. 팔 각도 계산

def calculate_angle(a,b,c):
    # 각 값을 받아 넘파이 배열로 변환
    a = np.array(a) # First
    b = np.array(b) # Mid
    c = np.array(c) # End
    
    # 세 좌표를 가지고 각도로 변환하는 코드
    radians = np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0])
    angle = np.abs(radians*180.0/np.pi)
    
    # 180도가 넘으면 360에서 뺀 값을 계산한다.
    if angle > 180.0:
        angle = 360-angle
        
    return angle
  • 해당 함수에 어깨, 팔꿈치, 손목의 좌표를 넣어보자.
calculate_angle(shoulder, elbow, wrist)
156.64536266620797

 

7. 팔 운동 카운트 기능 삽입

shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
            elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
            wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
            # 왼쪽 어깨, 팔꿈치, 팔목 등 감지된 랜드마크의 좌표 값들을 계산
            
            # 해당 좌표들에 대해 각도를 계산
            angle = calculate_angle(shoulder, elbow, wrist)
            
            # 이미지에 각도를 팔꿈치 위치에 표시한다.
            # cv2.putText(): 이미지 위에 텍스트를 넣는 함수
            cv2.putText(image, str(angle), 
                           tuple(np.multiply(elbow, [640, 480]).astype(int)), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA
                                )
            
            # 만약 각도가 160도가 넘으면, down 상태
            # 각도가 30도 보다 작고 down이면 count 증가, up 상태
            if angle > 160:
                stage = "down"
            if angle < 30 and stage =='down':
                stage="up"
                counter +=1
                print(counter)
  • 카메라의 프레임마다 팔의 각도를 가져오는 angle 변수를 가지고 if문을 작성하여 카운트를 올려주는 코드를 작성한다.
  • 각도가 160도라면 팔을 구부리지 않고 쭉 펴져 있는 상태이기 때문에 운동 횟수를 늘리지 않고, 30도처럼 팔을 다 굽힌 상태라면 횟수를 늘린다.

 

※ 전체 코드

def calculate_angle(a,b,c):
    # 각 값을 받아 넘파이 배열로 변환
    a = np.array(a) # First
    b = np.array(b) # Mid
    c = np.array(c) # End
    
    # 세 좌표를 가지고 각도로 변환하는 코드
    radians = np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0])
    angle = np.abs(radians*180.0/np.pi)
    
    # 180도가 넘으면 360에서 뺀 값을 계산한다.
    if angle >180.0:
        angle = 360-angle
        
    return angle





cap = cv2.VideoCapture(0)

# Curl counter variables
counter = 0 
stage = None

## Setup mediapipe instance
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()
        
        # Recolor image to RGB
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False
      
        # Make detection
        results = pose.process(image)
    
        # Recolor back to BGR
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        
        # Extract landmarks
        try:
            landmarks = results.pose_landmarks.landmark
            
            # Get coordinates
            shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
            elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
            wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
            # 왼쪽 어깨, 팔꿈치, 팔목 등 감지된 랜드마크의 좌표 값들을 계산
            # x,y,z까지 총 3개의 값이 있 는데 2차원의 값만 얻음
            
            # Calculate angle
            # 해당 좌표들에 대해 각도를 계산
            angle = calculate_angle(shoulder, elbow, wrist)
            
            # Visualize angle
            # 이미지에 각도를 팔꿈치 위치에 표시한다.
            # cv2.putText(): 이미지 위에 텍스트를 넣는 함수
            cv2.putText(image, str(angle), 
                           tuple(np.multiply(elbow, [640, 480]).astype(int)), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA
                                )
            
            # Curl counter logic
            # 만약 각도가 160도가 넘으면, down 상태
            # 각도가 30도 보다 작고 down이면 count 증가, up 상태
            if angle > 160:
                stage = "down"
            if angle < 30 and stage =='down':
                stage="up"
                counter +=1
                print(counter)
                       
        except:
            pass
        
        # Render curl counter
        # Setup status box
        cv2.rectangle(image, (0,0), (225,73), (245,117,16), -1)
        
        # Rep data
        cv2.putText(image, 'REPS', (15,12), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,0), 1, cv2.LINE_AA)
        cv2.putText(image, str(counter), 
                    (10,60), 
                    cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255), 2, cv2.LINE_AA)
        
        # Stage data
        cv2.putText(image, 'STAGE', (65,12), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,0), 1, cv2.LINE_AA)
        cv2.putText(image, stage, 
                    (60,60), 
                    cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255), 2, cv2.LINE_AA)
        
        
        # Render detections
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=2), 
                                mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2) 
                                 )               
        
        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

 

출처1: https://giveme-happyending.tistory.com/202

 

mediapipe 사용하여 팔굽히기 모션 인식하기

공부에 참고한 링크 https://youtu.be/06TE_U21FK4 개발환경 운영체제: Window 10 64 bit 개발언어: Python 3.11 개발 툴: Jupyter Lab 추가 패키지: mediapipe, opencv-python 라이브러리 & 모듈 설치 MediaPipe, opencv-python 설치

giveme-happyending.tistory.com

출처2: https://www.youtube.com/watch?v=06TE_U21FK4&t=642s

 

728x90
728x90

OpenCV로 이미지의 원하는 위치의 좌표 추출하는 법을 알아보자

좌표를 추출하여 해당 위치의 이미지 색상을 바꾸는 등의 응용까지 해볼 수 있다.

해당 이미지를 가지고 현재 회색인 신체 윤곽선 부분을 초록색으로 바꿔보자

 

1. 윤곽선 이미지 불러오기

import cv2

person = cv2.imread('person.png')
  • 검은색 배경에 회식 라인의 윤곽선 이미지다.

 

 

2. 이미지 회색 처리

gray = cv2.cvtColor(resized_person, cv2.COLOR_BGR2GRAY)

cv2.imshow('result', gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • 해당 이미지의 좌표를 추출하기 위해 이미지 이진화 작업이 꼭 필요한데 그 전에 회색 처리를 먼저 확실하게 해준다.

 

 

3. 이미지 이진화

ret, otsu = cv2.threshold(gray, -1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

cv2.imshow('result', otsu)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • 오츠 알고리즘을 통해 자동으로 이미지 이진화를 진행한다.

  • 좌표 추출을 원하는 위치가 검정색(픽셀 값: 0)이 되어야 한다. cv2.bitwise_not(otsu) 를 실행하여 반전 이진화를 진행한다.

 

 

4. 해당 윤곽선 좌표 추출

import numpy as np
np.where(contour == 0)
  • np.where() 는 Numpy 자료형 array 에 특정 조건을 만족하는 위치의 인덱스를 가져올 수가 있다.

  • 이미지를 OpenCV로 불러오게 되면 Numpy 자료형으로 불러오게 된다.

 

 

5. 회색 이미지에서 컬러 이미지로 변환

contour_color = cv2.cvtColor(contour, cv2.COLOR_GRAY2BGR)
  • 이미지를 가지고 회색 처리나 이진화를 하게 되면 색상 값이 하나만 존재하게 된다.
  • 이미지는 Numpy 자료형이기 때문에 배열로 치자면 컬러는 R,G,B 3가지의 색상 값을 가지고 있기 때문에 3차원 배열이고, 무채색 이미지는 검은색 계열 한가지의 색상만 존재하기 때문에 2차원 배열이다.

  • 따라서, 컬러 이미지로 변환을 해주면 2차원에서 3차원으로 변환된다.

  • 이미지의 검색 부분을 초록색으로 변경해줄 것이기 때문에 이와 같이 컬러 이미지로 변환한다.

 

 

6. 이미지 내 검정색 부분 초록색으로 변환

contour_color[np.where(contour == 0)] = [0,255,0] 
# 검은색=[0,0,0] 초록색=[0,255,0]
  • np.where(contour==0) 를 통해 윤곽선 이미지의 검은색 부분을 찾는다.
  • contour_color[np.where(contour == 0)] = [0,255,0] 컬러로 변환된 이미지에 찾은 좌표를 매칭시켜 초록색 RGB값을 삽입한다.

※ 결과

728x90

[OpenCV] 이미지 합성

태이니즘
|2024. 2. 8. 18:04
728x90

OpenCV로 이미지 끼리 합성을 하는 방법을 알아보자.

이미지 합성을 하기 위해서는 OpenCV에서 제공하는 비트 연산 기능을 이용하면 된다.

비트 연산 기능을 이용하여 배경 사진에 특정 로고 사진을 합성해보자

배경 이미지
로고 이미지

 

 

1. 로고 이미지 이진화

img_logo = cv2.imread('logo.png')
img_gray = cv2.cvtColor(img_logo, cv2.COLOR_BGR2GRAY) # 이진화 전 회색 처리 필수
ret, img_mask = cv2.threshold(img_gray, 200, 255, cv2.THRESH_BINARY)
img_mask_inv = cv2.bitwise_not(img_mask)
cv2.imshow('img_mask', img_mask)
cv2.imshow('img_mask_inv', img_mask_inv)
cv2.waitKey()
cv2.destroyAllWindows()
  • cv.threshold(img_gray, 200, 255, cv.THRESH_BINARY)를 사용하여 이진화를 진행한다. 해당 코드를 보면 픽셀이 200을 넘는 부분은 흰색인 배경만 존재하기 때문에 배경을 제외하고는 검은색으로 변환 될 것이다. *** 흰색 픽셀: 255, 검정색 픽셀: 0**
  • img_mask_inv = cv2.bitwise_not(img_mask) 는 위 바이너리 이미지를 반전 시키는 코드이다. 해당 이미지도 이미지 합성 시에 꼭 필요하다.

 

 

2. 로고 이미지 크기로 배경 이미지 잘라내기

img_background = cv2.imread('background.png')
height, width = img_logo.shape[:2]
img_roi = img_background[0:height, 0:width]
cv2.imshow('imgroi', img_roi)
cv2.waitKey()
cv2.destroyAllWindows()
  • 로고 이미지를 배경 이미지의 원하는 위치에 삽입을 하려면 해당 부분의 로고 이미지 크기 만큼 잘라낸다.
  • 잘라낸 배경 이미지에 로고 이미지와 비트 연산을 진행해야 한다.

 

 

3. 로고 이미지와 배경 이미지 합성 연산

img1 = cv2.bitwise_and(img_logo, img_logo, mask = img_mask_inv) 
img2 = cv2.bitwise_and(img_roi, img_roi, mask=img_mask)
cv2.imshow('img1', img1)
cv2.imshow('img2', img2)
cv2.waitKey()
cv2.destroyAllWindows()
  • cv2.bitwise_and(img_logo, img_logo, mask = img_mask_inv) 를 통해 로고 이미지 및 배경 이미지와 앞서 이진화를 진행한 바이너리 이미지와 AND 연산한다.

합성할 로고 이미지
합설할 배경 이미지

  • 위 AND 연산 결과로 나온 로고 이미지(img1)와 배경 이미지(img2) 자체를 더한다.
dst = cv2.add(img1, img2)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

 

 

4. 합성이 완료된 이미지 원본 이미지에 덮어 씌우기

img_background[0:height, 0:width] = dst
cv2.imshow('result', img_background)
cv2.waitKey()
cv2.destroyAllWindows()
  • 합성이 완료된 이미지 부분을 원본 이미지의 부분과 일치 시켜 덮어 씌운다.

※ 결과

728x90
728x90

사람의 인체 모양을 인식하고 어떠한 판단을 내리기 위해서는 신체에 윤곽선을 그리고, 해당 윤곽선에 대한 좌표 데이터를 획득해야 한다.

 

이러한 목표를 성취하기 위해서 첫 번째로 해야 할 작업은 이미지 안에 특정 사물의 윤곽선을 그리는 것부터 시작한다.

 

이 글은 파이썬의 이미지 처리 라이브러리인 OpenCV를 통해서 이미지 내 윤곽선을 검출하는 방법을 기술한다.

 

앞서 작성한 OpenCV의 설명을 보자면 OpenCV는 이미지를 흑백으로 변경을 하거나 이미지의 특정 위치의 좌표값을 얻는 등 여러 처리를 할 수 있는 라이브러리다.

 

해당 라이브러리를 사용하여 윤곽선을 검출하기 위해 과정을 단계별로 나누자면

  • 이미지의 윤곽선을 검출하는 과정
    1. 이미지 회색 처리
    2. 이미지 바이너리 처리
    3. 위 모든 과정을 거친 이미지의 윤곽선 검출
    4. 해당 윤곽선 이미지에 그리기

위와 같은 단계를 거친다.

 

1. 이미지 회색 처리

img = cv2.imread('card.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  • cv2.cvtColor() 는 이미지를 특정 색상으로 변환해준다.
  • 첫 번째 파라미터로 이미지를 삽입하고 두 번째 파라미터로 색상 변환 방법을 삽입한다.
  • cv2.COLOR_BGR2GRAY 는 기존 BGR 방식의 색상에서 회색계열로 이미지를 변환한다는 의미이다.

 

 

2. 이미지 바이너리 처리

ret, otsu = cv2.threshold(gray, -1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
  • 이미지를 바이너리(이진화) 시키면 픽셀 값은 0과 255만 갖게 할 수 있기 때문에 흰색과 검은색만 존재하게 할 수 있다.
  • 흰색과 검은색만 이루어진 이미지를 윤곽선 검출하기가 수월하기 때문에 이 과정을 꼭 거쳐야 한다.
  • cv2.threshold() 함수는 여러 개의 숫자 값을 특정한 임계값을 기준으로 두 가지 값으로 나누어 정의하는 함수이다.
  • cv2.threshold(image, threshold, value, type_flag)

      => image: 변환할 이미지, 이미지가 픽셀 값으로 변환되어 들어간다.

      => threshold: 임계값, 우리는 해당 임계값을 오츠 알고리즘이라는 것을 이용하여 자동으로 탐색할 것이기 때문에

            -1을 넣어주면 된다.

      => value: threshold에 기입한 임계값의 특정 조건에 만족하면 변환할 값

      => type_flag: 이진화 적용 방법, cv2.THRESH_BINARY는 픽셀 값이 임계값을 넘으면 value로 지정하고,

           넘지 못하면 0으로 지정하는 방법이다.

           해당 파라미터로 OR 조건으로 cv2.THRESH_OTSU 도 함께 기입할 시 오츠 알고리즘이 적용된다.

  • 오츠 알고리즘이란?

      => 이미지의 픽셀을 보고 이미지의 색상이 두 부류로 나뉘었을 때

            그림의 명암 분포가 가장 균일할 때의 임계값을 자동으로 계산하여 선택하는 알고리즘이다.

 

 

3. 위 모든 과정을 거친 이미지의 윤곽선 검출

contours, hierarchy = cv2.findContours(otsu, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
  • 앞서 변환한 바이너리 이미지에 대해 윤곽선을 검출한다.
  • contours 에 검출한 윤곽선의 좌표값들이 들어가기에, 윤곽선에 대한 작업 진행 시 이 변수를 적극 활용하면 된다.
  • cv2.findContours(image, mode, method)

      => image: 입력 이미지

      => mode: 윤곽선 검출 모드

        - cv2.RETR_EXTERNAL : 가장 외곽의 윤곽선만 찾음

        - cv2.PETR_LIST : 모든 윤곽선 찾음

      => method: 윤곽선 좌표값 반환 방법

        - APPROX_NONE: 윤곽선의 모든 좌표를 반환

        - APPROX_SIMPLE: 윤곽선의 꼭짓점 좌표만 반환

        - 윤곽선이 단순한 경우, 예를 들어 사각형인 경우에는 APPROX_SIMPLE을 사용하는 것이 메모리 성능에도

          효율적일 것이다.

 

 

4. 해당 윤곽선 이미지에 그리기

COLOR = (0, 200, 0) # 녹색
cv2.drawContours(img, contours, -1, COLOR, 2)
# cv2.drawContours(이미지, 윤곽선 정보, 인덱스, 색상, 두께)
# 인덱스가 -1이면 전체
  • 초록색에 두께 2로 전체 윤곽선을 이미지에 그린다.

 

※ 결과

728x90
728x90

웹 소켓이란?

  • 본래 서버와 클라이언트가 통신하기 위해서는 HTTP 통신을 통해 클라이언트의 요청과 서버의 응답 형식으로 통신이 이루어진다.
  • HTTP 통신은 무조건 클라이언트의 요청이 있어야 서버에서 데이터를 송신할 수 있는데, 이 과정이 이루어지고 나면 클라이언트와 서버 간의 연결이 종료된다.
  • 이를 개선하여 클라이언트와 서버의 연결을 유지하면서 서로 원할 때마다 데이터를 송수신 하기 위해 등장한 것이 웹 소켓이다.

 

1. SocketIO 라이브러리 설치

pip install flask-socketio
  • 에디터 터미널에 입력해준다.

 

2. 해당 라이브러리 임포트

from flask_socketio import SocketIO

 

 

3. Flask 서버에 웹 소켓 서버 적용

app = Flask(__name__)
socketio = SocketIO(app)
  • 변수로 생성한 Flask 객체를 SocketIO 생성자에 삽입하여 웹 소켓 서버로 적용한다.

 

4. 서버 실행

socketio.run(app, port=5000, debug=True)
  • 기존 Flask 서버를 실행할 때는 app.run() 함수를 실행시켰지만, 웹소켓을 적용한 서버를 실행할 때는 socketio.run() 함수를 실행한다.

 

5. 데이터를 송수신하는 방법

socketio.emit('sendEventName', data) # 데이터 송신

@socketio.on('receiveEventName') # 데이터 수신
def handle_message(data):
    print('받은 데이터: {}'.format(data))
  • 클라이언트와 데이터를 송수신하기 위해서는 각 작업이 이루어지는 이벤트 이름을 클라이언트와 통일한다.
  • 첫 번째 파라미터에는 그에 해당하는 이벤트 이름을 적고, 두 번째 파라미터에는 보내거나 받는 데이터가 사용된다.
728x90

'Python > Flask' 카테고리의 다른 글

[Flask] Flask 서버를 이용한 카메라 웹 스트리밍  (0) 2024.02.01
728x90

파이썬의 OpenCV를 이용한 카메라를 웹에 표출하기 위해서는 Flask 서버를 구축해야 한다.

 

1. Flask 설치

pip install Flask
  • Flask를 설치하기 위해 해당 명령어를 입력한다.

 

2. 라이브러리 Import

from flask import Flask, Response, render_template
import cv2
  • Flask를 포함하여 여러 라이브러리를 임포트한다.

 

3. 카메라 표출 함수 생성

camera = cv2.VideoCapture(0)
def generate_frames():
    while True:
        success, frame = camera.read()  # 프레임 읽기
        if not success:
            break
        else:
            ret, buffer = cv2.imencode('.jpg', frame)
            frame = buffer.tobytes()
            yield (b'--frame\\r\\n'
                   b'Content-Type: image/jpeg\\r\\n\\r\\n' + frame + b'\\r\\n')  # 프레임 스트리밍
  • 카메라 영상의 프레임 단위 이미지를 반환해주는 함수를 작성한다.
  • 일단 IP 카메라가 존재하지 않아서 camera = cv2.VideoCapture(0)을 통해 웹캠을 불러온다.

 

4. 카메라 표출 함수 실행 매핑 작성

@app.route('/camera')
def getCamera():
    return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
  • 카메라 프레임을 반환해주는 매핑 함수를 작성한다.
  • 해당 함수는 html 내에서 이용하면 된다.

 

5. 카메라 표출 페이지 출력 매핑 작성

@app.route('/cameraPage')
def cameraPage():
    return render_template('cameraPage.html')
  • 카메라 영상을 보여줄 페이지를 렌더링는 매핑을 작성한다.

 

6. 카메라를 최종적으로 출력하는 HTML 작성

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Camera</title>
</head>
<body>
    <div>
        <img src="/camera" style="height:100vh; width: 100%"/>
    </div>
</body>
</html>
  • <img src="/camera" style="height:100vh; width: 100%"/> 해당 코드를 통해 Flask 내 카메라 표출 함수를 이용하여 최종적으로 카메라를 표출한다.

 

※ 전체 코드

from flask import Flask, Response, render_template
import cv2

app = Flask(__name__)
camera = cv2.VideoCapture(0)  # 웹캠에 연결

@app.route('/')
@app.route('/home')
def home():
    return '메인 페이지'

def generate_frames():
    while True:
        success, frame = camera.read()  # 프레임 읽기
        if not success:
            break
        else:
            ret, buffer = cv2.imencode('.jpg', frame)
            frame = buffer.tobytes()
            yield (b'--frame\\r\\n'
                   b'Content-Type: image/jpeg\\r\\n\\r\\n' + frame + b'\\r\\n')  # 프레임 스트리밍

@app.route('/cameraPage')
def cameraPage():
    return render_template('cameraPage.html')

@app.route('/camera')
def getCamera():
    return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
    app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Camera</title>
</head>
<body>
    <div>
        <img src="/camera" style="height:100vh; width: 100%"/>
    </div>
</body>
</html>
728x90

'Python > Flask' 카테고리의 다른 글

[Flask] Flask 웹 소켓 서버 구축  (0) 2024.02.05
728x90

OpenCV의 기본적인 기능에는 이미지를 처리하기 위해 해당 이미지를 표시하는 창을 띄운다.

 

더불어, 해당 창을 특별한 기능이 담긴 프로젝트로 제작하기 위해 마우스 이벤트를 처리하는 기능을 제공하고 있다.

 

1. 마우스 이벤트 핸들러 함수 정의

  - 마우스의 이벤트에 따라 원하는 동작을 수행하도록 하는 말그대로 이벤트 핸들러 함수를 정의한다. 

import cv2

def mouse_event(event, x, y, flags, param) :
    if event == cv2.EVENT_LBUTTONDOWN:
        print('왼쪽 마우스 클릭')
    elif event == cv2.EVENT_LBUTTONUP:
        print('왼쪽 마우스 클릭 해제 ')
    elif event == cv2.EVENT_LBUTTONDBLCLK:
        print('왼쪽 마우스 더블 클릭')
    elif event == cv2.EVENT_MOUSEMOVE:
        print('마우스 이동')
    elif event == cv2.EVENT_RBUTTONDOWN:
        print('오른쪽 버튼 클릭')

  - 이벤트 핸들러 내의 함수는 5개의 파라미터를 받을 수 있다. 주로 사용하는 것은 아래 3가지다.

    1) event: 창 내에서 발생하는 마우스 이벤트 자체

    2) x: 이벤트가 발생한 마우스의 X좌표

    3) y: 이벤트가 발생한 마우스의 Y좌표

  - 마우스의 동작에 따라 각 이벤트 변수가 아래와 같이 지정되어 있다.

cv2.EVENT_LBUTTONDOWN #왼쪽 마우스 클릭
cv2.EVENT_LBUTTONUP #왼쪽 마우스 클릭 해제 
cv2.EVENT_LBUTTONDBLCLK #왼쪽 마우스 더블 클릭
cv2.EVENT_MOUSEMOVE #마우스 이동
cv2.EVENT_RBUTTONDOWN #오른쪽 버튼 클릭

 

2. 이미지 창 출력 및 해당 창에 이벤트 핸들러 적용

  - 위에 정의한 이벤트 핸들러 함수를 이미지 창에 적용해보자.

img = cv2.imread('dog.jpg')
cv2.namedWindow('dog')
cv2.setMouseCallback('dog', mouse_event)
cv2.imshow('dog', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

  - 해당 코드 내 중요한 부분은 2가지다.

    1) cv2.namedWindow('dog'): 'dog' 라는 타이틀의 이미지 창을 생성한다.

    2) cv2.setMouseCallback('dog', mouse_event): 앞서 생성한 'dog' 라는 이미지 창에

        위에서 정의한 핸들러 함수를 적용시킨다.

728x90
728x90

OpenCV (Computer Vision)란?

  - 다양한 영상 (이미지) / 동영상 처리에 사용되는 오픈소스 라이브러리

    => ex) 이미지를 흑백으로 변경, 이미지의 특정 영역을 잘라내기

  - OpenCV의 기능을 이용하여 여러 응용 프로젝트를 제작할 수 있다

    => ex) 문서 스캐너, 얼굴 인식, 모션 인식

 

1. OpenCV 설치

  - 파이썬을 실행하는 에디터에 맞는 프롬프트에서 해당 명령어를 입력한다. (Jupyter Notebook: Anaconda Prompt, PyCharm: 에디터 터미널)

 

2. OpenCV 라이브러리 Import

  - OpenCV를 설치 했으면 파이썬 코드 내에서 cv2라는 이름으로 OpenCV를 임포트 할 수 있다.

import cv2

 

3. 동영상 파일 읽어오기

  1) cv2.VideoCapture() 함수를 사용하여 읽어올 수 있다.

  2) 읽어온 영상 자체를 cap이라는 변수에 저장한다.

cap = cv2.VideoCapture('video.mp4')

  

4. 동영상 출력

  1) cap.isOpened() 함수를 사용하여 동영상 파일 업로드 성공 여부를 판단하여 반복문을 시작한다.

    => 동영상을 출력하기 위해 영상 내 프레임 단위의 이미지를 순차적으로 출력해야 하므로 반복문을 사용한다.

  2) cap.read() 함수를 사용하여 영상의 프레임 존재 여부와 프레임 자체를 가져온다.

  3) cv2.imshow() 함수 내 프레임을 삽입하여 해당 프레임 이미지를 출력한다.

  - 위 3단계가 프레임이 존재하지 않을 때까지 반복되어 연속된 프레임을 출력, 결국 영상이 출력된다.

while cap.isOpened(): # 동영상 파일이 올바르게 열렸는지?
    ret, frame = cap.read() # ret: 프레임 존재 여부, frame: 받아온 이미지 (프레임)
    if not ret:
        print('더 이상 가져올 프레임이 없어요')
        break
        
    cv2.imshow('video', frame) # 'video' 라는 이름의 창에 영상이 출력된다

 

5. 동영상 종료

   - 영상이 종료되었으므로 영상을 재생하는 메모리를 해제하여 자원을 반납하고, 해당 영상 창을 닫는다.

cap.release() #자원 해제
cv2.destroyAllWindows()

 

※ 전체 코드

import cv2
cap = cv2.VideoCapture('video.mp4')

while cap.isOpened(): # 동영상 파일이 올바르게 열렸는지?
    ret, frame = cap.read() # ret: 성공 여부, frame: 받아온 이미지 (프레임)
    if not ret:
        print('더 이상 가져올 프레임이 없어요')
        break
        
    cv2.imshow('video', frame)
        
cap.release() #자원 해제
cv2.destroyAllWindows()
728x90