라즈베리파이,웹카메라에서 OpenCV-Python을 사용한 카드 판독

라즈베리파이의 PiCamera 비디오 피드에서 카드 플레이를 감지하고 식별하기 위해 OpenCV를 사용하는 파이썬 프로그램을 설명합니다. 출처

시작하기

 깁프트허브의 저장소에서 다운로드하고 CardDetector.py를 실행합니다. 카드를 감지하기 위해서는 카드를 어두운 배경에 놓아야 합니다. 프로그램을 끝내려면 키보드의 ‘q’를 누르세요.

이 프로그램은 원래 리눅스 운영체제가 설치된 라즈베리파이에서 실행되도록 설계되었지만 Windows 7/8/10에서도 실행할 수 있습니다. Windows에서 실행하려면 Anaconda (https://www.anaconda.com/download/, Python 3.6 버전)를 다운로드하여 설치하고 Anaconda Prompt를 시작한 다음 IDLE를 실행하고 IDLE에서 CardDetector.py 파일을 열고 실행합니다. Anaconda 환경에는 opencv 및 numpy 패키지가 설치되어 있으므로 따로 설치할 필요가 없습니다. Windows에서 이 프로그램을 실행하는 경우 아래 설명에 따라 USB 카메라를 사용하도록 프로그램을 변경해야 합니다.

카드 판독에  PiCamera 또는 USB 카메라를 사용할 수 있습니다. USB 카메라를 사용하는 경우 CardDetector.py의 38 행을 다음과 같이 변경하세요. VideoStream 함수의 파라미터 숫자 1을 2로 변경하면 됩니다. 

videostream = VideoStream.VideoStream((IM_WIDTH,IM_HEIGHT),FRAME_RATE,2,0).start()

카드 판독이 잘 작동하기 위해서는 테스트하는 카드에서 만들어진 이미지를 사용하는 것이 좋습니다. 이미지를 생성하려면 Rank_Suit_Isolator.py를 실행하여 카드 사진을 찍으세요. 카드의 에이스, 2, 3…. 순으로 모든 카드의 사진을 찍으면 됩니다. 이렇게 카드를 찰영하면 Card_Imgs 디렉토리에 저장되는데 기존 이미지를 덮어 쓰기 때문에 주의하세요.

파일 설명

  • CardDetector.py : 카드를 판독하는 메인 프로그램입니다.
  • Cards.py : CardDetector.py에서 사용하는 클래스와 함수가 있습니다.
  • PiVideoStream.py : PiCamera에서 비디오 스트림을 생성하며 CardDetector.py에서 사용합니다.
  • Rank_Suit_Isolator.py : 훈련(학습)용 이미지를 만들기 위해 카드 세트에서 카드 순번등 카드를 분류하는데 사용할 수 있는 독립 스크립트입니다.
  • Card_Imgs : 디렉토리안에 훈련용 이미지가 있습니다.

윈도우즈 설치시 프로그램 오류

다음의 에러는 노트북의 카메라를 사용하기 위해 파이 카메라를 사용하는 기존의 코드를 수정해야 합니다.

videostream = VideoStream.VideoStream((IM_WIDTH,IM_HEIGHT),FRAME_RATE,2,0).start()

다음 에러는 프로그램에서 dummy를 삭제해야 합니다. dummy는 필요한 변수가 아니고 cv2.findContours 함수 설명에 보면 리턴값이 contours, hierarchy 2개입니다. 프로그램의 여러곳에서 cv2.findContours함수를 사용하고 있습니다.

*Rank_Suit_Isolator.py은 picamera 모듈이 필요합니다. 윈도우에서 해당 모듈 설치에 오류가 있습니다. 대신에 USB 카메라를 사용한다면 24번 라인의 PiOrUSB 값을 2로 변경하세요.

프로그램 소스 설명

CardDetector.py

############## Python-OpenCV Playing Card Detector ###############
#
# Author: Evan Juras
# Date: 9/5/17
# Description: Python script to detect and identify playing cards
# from a PiCamera video feed.
#

# 카드 판독 관련 파이썬 라이브러리와 커스텀 라이브러리 가져오기 
import cv2
import numpy as np
import time
import os
import Cards
import VideoStream


### ---- 초기화 ---- ###
# Define constants and initialize variables 

## 카메라 설정(화면의 높이와 폭, 프레임)
IM_WIDTH = 1280
IM_HEIGHT = 720 
FRAME_RATE = 10

## Initialize calculated frame rate because it's calculated AFTER the first time it's displayed
## 처음에 디스플레이되고 계산되기 때문에 측정한 프레임 속도를 초기화한다.
frame_rate_calc = 1
freq = cv2.getTickFrequency() # 이 함수는 초당 틱 수를 반환합니다.

## Define font to use 사용할 폰트를 정의
font = cv2.FONT_HERSHEY_SIMPLEX # 보통 크기의 산스세리프 폰트

# 카메라 객체와 비디오 피드를 초기화합니다. 
# 비디오 스트림은 카메라 피드에서 프레임을 지속적으로 얻는 별도의 스레드로 설정됩니다.
# VideoStream 클래스 정의는 VideoStream.py 참조하세요.
##  PICAMERA 대신에 USB 카메라를 사용하는 경우,
## 다음 라인에서 세 번째 변수를 1에서 2로 변경하세요.
videostream = VideoStream.VideoStream((IM_WIDTH,IM_HEIGHT),FRAME_RATE,2,0).start()
time.sleep(1) # 카메라가 준비될 시간

# 훈련용 이미지(순번,슈트) 로드하기. 
path = os.path.dirname(os.path.abspath(__file__))
train_ranks = Cards.load_ranks( path + '/Card_Imgs/') #1,2,3,,,,J,Q,K
train_suits = Cards.load_suits( path + '/Card_Imgs/') #클럽,다이아몬드,하트,스페이드


### ---- 메인 루프(반복문) ---- ###
# 메인 루프는 비디오 스트림에서 반복적으로 프레임을 가져옵니다.
# 그리고 카드를 찾고 식별하기 위해 처리합니다.

cam_quit = 0 # 반복문 제어 변수

# 프레임 캡처 시작
while cam_quit == 0:

    # 비디오스트림에서 프레임 가져오기
    image = videostream.read()

    # 시작 타이머(프레임 속도 계산용)
    t1 = cv2.getTickCount()

    # Pre-process camera image (gray, blur, and threshold it)
    pre_proc = Cards.preprocess_image(image)
	
    # Find and sort the contours of all cards in the image (query cards)
    cnts_sort, cnt_is_card = Cards.find_cards(pre_proc)

    # If there are no contours, do nothing
    if len(cnts_sort) != 0:

        # Initialize a new "cards" list to assign the card objects.
        # k indexes the newly made array of cards.
        cards = []
        k = 0

        # For each contour detected:
        for i in range(len(cnts_sort)):
            if (cnt_is_card[i] == 1):

                # Create a card object from the contour and append it to the list of cards.
                # preprocess_card function takes the card contour and contour and
                # determines the cards properties (corner points, etc). It generates a
                # flattened 200x300 image of the card, and isolates the card's
                # suit and rank from the image.
                cards.append(Cards.preprocess_card(cnts_sort[i],image))

                # Find the best rank and suit match for the card.
                cards[k].best_rank_match,cards[k].best_suit_match,cards[k].rank_diff,cards[k].suit_diff = Cards.match_card(cards[k],train_ranks,train_suits)

                # Draw center point and match result on the image.
                image = Cards.draw_results(image, cards[k])
                k = k + 1
	    
        # Draw card contours on image (have to do contours all at once or
        # they do not show up properly for some reason)
        if (len(cards) != 0):
            temp_cnts = []
            for i in range(len(cards)):
                temp_cnts.append(cards[i].contour)
            cv2.drawContours(image,temp_cnts, -1, (255,0,0), 2)
        
        
    # Draw framerate in the corner of the image. Framerate is calculated at the end of the main loop,
    # so the first time this runs, framerate will be shown as 0.
    cv2.putText(image,"FPS: "+str(int(frame_rate_calc)),(10,26),font,0.7,(255,0,255),2,cv2.LINE_AA)

    # Finally, display the image with the identified cards!
    cv2.imshow("Card Detector",image)

    # Calculate framerate
    t2 = cv2.getTickCount()
    time1 = (t2-t1)/freq
    frame_rate_calc = 1/time1
    
    # Poll the keyboard. If 'q' is pressed, exit the main loop.
    key = cv2.waitKey(1) & 0xFF
    if key == ord("q"):
        cam_quit = 1
        

# Close all windows and close the PiCamera video stream.
cv2.destroyAllWindows()
videostream.stop()