얼굴인식과 같은 이미지 프로세싱이 필요한 경우라면 가장 먼저 떠오르는게 OpenCV 일껍니다.

그런데 OpenCV 는 설치도 쉽지 않고 이걸 제대로 활용해서 원하는 인식 결과를 얻기란 더더욱 어려운 작업이죠. 실시간으로 영상을 처리하는데는 여전히 가장 유용한 도구임에는 분명 합니다만, 적어도 빠른 속도가 요구되지 않는 상황 – 정적인 이미지를 처리한 결과가 필요한 상황이라면 훨씬 구현하기 쉬운 대안이 있습니다. Google Cloud Vision API(이하 구글 비전 API) 입니다.

구글 비전 API 를 사용해서 이미지와 몇 가지 설정을 구글 서버에 던져주면 알아서 이미지 인식을 해서 결과를 알려줍니다. 인터넷 연결만 되어 있다면 프로세싱 파워가 낮은 기기에서도 충분히 이미지 처리를 할 수 있고 사용방법도 쉬운게 장점입니다. 또한 자바, 파이썬 등 주요 언어에서 사용할 수 있도록 라이브러리를 제공하기 때문에 코드 몇 줄로 구현이 가능합니다. 대신 서버에 이미지 보내서 결과를 받아오기까지 딜레이가 있기 때문에 많은 프레임을 실시간으로 처리하는데는 적합치 않아 보입니다.

테스트를 위해 라즈베리 파이를 준비하고 개발환경부터 만들어 보겠습니다.

구글 클라우드 비전 API 개발환경 셋업

구글 클라우드 비전 API 의 가장 좋은 참고자료는 공식 문서입니다. 이 문서에서도 아래 내용을 바탕으로 작성 했습니다.

계정 세팅

구글 비전 API 사용을 위해서는 먼저 클라우드 비전 API 사용 요청을 하고 인증 방식을 설정해야 합니다. 아래 링크에서 [View my console] 버튼을 클릭하면 .

google_vision_1

[cloud vision api] 검색해서 해당 API를 활성화 해줍니다.

google_vision_2

활성화가 되었다면 이제 인증 방식을 설정해줘야 합니다. 인증 방식은 API key, OAuth, Service account key 방식이 있는데 어떤걸 사용해도 무방하지만 이 문서에서는 Service account key 방식을 사용할겁니다.

화면 왼쪽 메뉴를 보면 열쇠모양(사용자 인증 정보) 메뉴가 있습니다. 클릭. 여기서 [사용자 인증 정보 만들기 – 서비스 계정 키]를 선택합니다.

credentials-service-account.png

새로운 서비스 계정의 이름과 ID를 입력하고

new-service-account.png

Key type 은 JSON 으로 선택합니다. 서비스 계정이 생성되면 downloaded-filename.json 링크 눌러서 JSON 파일을 다운로드 받아둡니다. 이 파일은 구글이 제공하는 API에서 사용할 인증 파일이므로 자신의 서버에 올려두고 사용해야 합니다.

downloaded-json.png

이제 JSON 파일의 이름을 원하는대로 바꾸고 라즈베리 파이에 저장합니다. 제 경우엔 라즈베리 파이 아래 경로에 올려뒀는데 원하시는 경로를 사용하면 됩니다.

  • /home/pi/temp/vision/gac.json

구글 라이브러리는 환경 변수 GOOGLE_APPLICATION_CREDENTIALS 를 이용해서 인증 파일의 위치를 인식합니다. 따라서 console 창에서 아래와 같이 명령을 실행해서 환경변수를 인식시켜 줍니다.

  • export GOOGLE_APPLICATION_CREDENTIALS=/home/pi/temp/vision/gac.json

로그인을 다시하면 위 작업을 또 해줘야 하니까 .bashrc 파일 마지막에 위 명령어를 추가해 두는 것이 좋습니다.

파이썬 설치

구글 비전 API는 파이썬 2.x, 3.x 를 모두 지원하므로 자신이 라즈베리 파이가 가진 파이썬 환경을 그대로 사용하면 됩니다.

구글 클라우드 비전 API 라이브러리 설치

구글에서 배포하는 클라우드 비전 API – 파이썬 라이브러리를 설치해야 합니다. 구글이 제공하는 비전 API 예제 파일을 먼저 다운로드 받아두세요.

위 링크에서 다운로드 받아 올려도 되고, 라즈베리 파이에서 아래 명령어를 실행해도 됩니다.

  • git clone https://github.com/GoogleCloudPlatform/cloud-vision.git

그리고 label 폴더에 들어가 보세요. requirements.txt 파일이 있는데 여기에 예제를 실행하는데 필요한 라이브러리들이 기재되어 있습니다. 여기 기재된 라이브러리들을 아래 명령어로 설치하면 됩니다.

  • cd cloud-vision/python/label
  • sudo pip install -r requirements.txt

구글에서 제공하는 각 예제들은 필요로 하는 라이브러리들이 틀립니다. 따라서 예제를 테스트 해보기 전에 위 명령어로 라이브러리 설치 작업을 해줘야 합니다.

이제 테스트 해볼 준비가 모두 끝났습니다.

이미지 인식 예제

구글 비전 API는 인식하고자 하는 대상에 따라 카테고리(Feature)가 구분되어 있습니다. 아래와 같은 인식 방법을 제공합니다.

  • LABEL_DETECTION : 이미지에 포함된 사물 인식
  • FACE_DETECTION : 얼굴 인식
  • TEXT_DETECTION : 문자 인식 (OCR, Optical Character Recognition)
  • LANDMARK_DETECTION : 지형, 지물 인식
  • LOGO_DETECTION : 회사 로고 인식
  • SAFE_SEARCH_DETECTION : 19금 이미지 인식?
  • IMAGE_PROPERTIES : 이미지의 주요 특성 인식 (주요 색상 등)

이 값들을 중복해서 사용할 수 있습니다. 구글 비전 API 를 사용해서 구글 서버로 이미지 인식 요청(request)을 보낼 때 base64 인코딩 된 이미지를 포함한 JSON 형식의 요청을 보냅니다. 아래와 같은 형식으로 request가 전송됩니다.

{
  "requests":[
    {
      "image":{
        "content":"/9j/7QBEUGhvdG9zaG9...image contents...fXNWzvDEeYxxxzj/Coa6Bax//Z"
      },
      "features":[
        {
          "type":"FACE_DETECTION",
          "maxResults":10
        },
        {
          "type":"LABEL_DETECTION",
          "maxResults":10
        }
      ]
    }
  ]
}

image 항목 뒤에 base64 인코딩 된 이미지 데이터가 붙고 features 항목에는 원하는 인식 방법을 기재합니다. 따라서 인식하고자 하는 목적에 따라 features 항목의 값을 적절히 조절해주면 됩니다.

구글 서버로 요청이 전송되면 잠시 후 결과(response) 값이 JSON 형태로 도착합니다. 아래와 유사한 형식으로 옵니다.

{
  "responses": [
    {
      "labelAnnotations": [
        {
          "mid": "/m/0bt9lr",
          "description": "dog",
          "score": 0.89208293
        }
      ]
    }
  ]
}

위 값은 어떤 인식을 했느냐에 따라 바뀌며, 훨씬 복잡한 형태를 가지기도 합니다. 구글에서 제공한 예제들을 실행해서 결과를 보면 어떤 결과값이 오는지 알 수 있습니다.

Label 인식

Label 인식 예제는 label 디렉토리 안에 있습니다. 먼저 인식할 이미지가 하나 필요하니 다운로드 받습니다. 스핑크스 이미지를 하나 받아서 이름을 sphinx.jpg 로 변경했습니다. 인식할 이미지는 500KB 이내여야 합니다.

224163_9213_4430

  • wget http://schools.academytravel.com.au/media/Image/cache/Cc730385-Ancient_Egypt_OneLine_Itineraries_AI_Egypt.jpg
  • mv Cc730385-Ancient_Egypt_OneLine_Itineraries_AI_Egypt.jpg sphinx.jpg

인식을 하기 전에 소스코드를 약간 수정해 줍니다. 인식한 결과(response JSON)를 console 창에 출력하도록 하기 위해서 입니다.

  • nano label.py

열어본 김에 소스코드를 보면… 모든 작업이 아래 코드에서 다 이루어집니다.

    with open(photo_file, 'rb') as image:
        image_content = base64.b64encode(image.read())
        service_request = service.images().annotate(body={
            'requests': [{
                'image': {
                    'content': image_content.decode('UTF-8')
                },
                'features': [{
                    'type': 'LABEL_DETECTION',
                    'maxResults': 1
                }]
            }]
        })
        # [END construct_request]
        # [START parse_response]
        response = service_request.execute()
        label = response['responses'][0]['labelAnnotations'][0]['description']
        print('Found label: %s for %s' % (label, photo_file))
        print response
        return 0
        # [END parse_response]

label.py 를 실행할 때 파라미터로 이미지 파일 이름을 넘길겁니다. 그러면 이미지 파일을 열어서 base64 인코딩을 해줍니다.

  • image_content = base64.b64encode(image.read())

이걸 구글 서버로 보낼 request 에 붙이는 겁니다. 바로 이어지는 코드가 구글 서버로 보낼 JSON 문자열을 작성하는 코드입니다. 만약 label 인식 외에 face 인식 등등을 더 추가하고 싶다면 features 항목에 첨삭을 해주면 됩니다.

그리고 아래 코드를 이용해 실제 서버에 request를 보내는 작업을 합니다.

  • response = service_request.execute()

request가 정상적으로 처리되면 response를 받는데 JSON 형식의 데이터로 되어 있습니다. 이 데이터를 화면에 표시하고 원하는 작업을 해주면 됩니다. 일단 데이터가 어떻게 구성되어 도착하는지 확인하는게 목적이므로 화면에 출력하도록 코드를 약간 수정해 주겠습니다. return 0 코드 바로 윗 줄에 아래 코드를 추가해줍니다.

  • print response

수정된 코드를 저장하고 shell로 빠져 나옵니다. 그리고 label.py 파이썬 파일을 이미지와 함께 실행해 봅니다.

  • python label.py sphinx.jpg

모든 과정이 정상적으로 진행된다면 아래처럼 결과가 표시될겁니다.

{u'responses': [{u'labelAnnotations': [{u'score': 0.97231364, u'mid': u'/m/05xh2', u'description': u'pyramid'}]}]}

구글 서버에서 이미지를 인식해서 결과를 보내준겁니다. 이걸 좀 더 보기 좋게 펼치면

{
    u'responses': [
        {
            u'labelAnnotations': [
                {
                    u'score': 0.97231364, 
                    u'mid': u'/m/05xh2', 
                    u'description': u'pyramid'
                }
            ]
        }
    ]
}

u” 라고 표시된 건 문자열 인코딩 방식을 나타내므로 없다 생각하셔도 됩니다.

실제 우리가 원하는 항목은 ‘description’ : ‘pyramid’ 입니다. 스핑크스와 피라미드가 포함된 이미지를 보냈는데 피라미드로 인식했네요. 그래도 만족할만한 결과입니다. 이 값을 파이썬에서 읽기 위해서는 아래처럼 코드를 작성하면 됩니다.

  • response[‘responses’][0][‘labelAnnotations’][‘description’]

JSON 데이터를 가져오는 자세한 방법은 생략합니다;;;

얼굴 인식

얼굴 인식도 마찬가지의 과정으로 테스트 해보시면 됩니다. face_detection 폴더로 이동해서 이미지 파일 하나를 넣어준 뒤, faces.py 코드를 수정하고 테스트 하면 됩니다.

einstein

detect_face() 함수를 찾아서 return response[‘responses’][0][‘faceAnnotations’] 코드 앞에 아래 코드를 넣어주세요.

  • print response

그리고 얼굴인식을 실행해보면…

  • python faces.py xxx.jpg

아래처럼 결과가 나옵니다. JSON 결과 파일에서 response – faceAnnotations 항목 내부의 내용들만 표시한 겁니다.

{
    'joyLikelihood': 'VERY_UNLIKELY', 
    'sorrowLikelihood': 'VERY_UNLIKELY', 
    'surpriseLikelihood': 'VERY_UNLIKELY', 
    'angerLikelihood': 'VERY_UNLIKELY', 
    
    'headwearLikelihood': 'VERY_UNLIKELY', 
    'underExposedLikelihood': 'VERY_UNLIKELY', 
    'blurredLikelihood': 'VERY_UNLIKELY', 

    'landmarkingConfidence': 0.54563558, 
    'detectionConfidence': 0.71568185, 
	
    'panAngle': 27.359901, 
    'tiltAngle': -2.6616335, 
    'rollAngle': 14.754687, 

    'boundingPoly': {
        'vertices': [
            {'y': 8, 'x': 4}, 
            {'y': 8, 'x': 191}, 
            {'y': 224, 'x': 191}, 
            {'y': 224, 'x': 4}
        ]
    }, 
    'fdBoundingPoly': {
        'vertices': [
            {'y': 69, 'x': 53}, 
            {'y': 69, 'x': 189}, 
            {'y': 204, 'x': 189}, 
            {'y': 204, 'x': 53}
        ]
    },
	
    'landmarks': [
        {
            'position': {'y': 108.6227, 'x': 105.59975, 'z': 0.00067580346}, 
            'type': 'LEFT_EYE'
        }, 
        {
            'position': {'y': 119.75568, 'x': 148.54039, 'z': 23.859083}, 
            'type': 'RIGHT_EYE'
        }, 
        ......
    ]
}

꽤 긴 결과가 나옵니다.

joyLikelihood, sorrowLikelihood, surpriseLikelihood, angerLikelihood 이 값들은 표정(기쁨, 슬픔, 놀람, 화)을 인식한 결과입니다. headwearLikelihood 는 모자를 썼는지 표시하는 항목인 것 같네요. 결과값은 아래 항목 중 하나의 값을 가집니다.

  • UNKNOWN : 인식 실패
  • VERY_UNLIKELY : 매우 해당되지 않음
  • UNLIKELY : 해당되지 않음
  • POSSIBLE : 가능성이 있음
  • LIKELY : 해당됨
  • VERY_LIKELY : 매우 해당됨

boundingPoly 얼굴 전체의 윤곽을 몇 개의 점으로 근사한 것입니다. 점들을 연결하면 얼굴 부분을 포함하는 다각형이 됩니다. fdBoundingPoly 는 얼굴 윤곽을 boundingPoly 보다 더 타이트하게 피부 경계 부분으로 잡은 것입니다. landmarks 는 얼굴의 각 부분 – 눈, 코, 입, 귀 등의 위치 데이터를 포함하고 있습니다.

랜드마크 인식

랜드마크 예제는 landmark_detection 디렉토리에 있습니다. 특이하게 이 예제는 GCS(Google Cloud Store) 상의 파일을 읽어와서 이미지 인식을 하네요. 아래 링크로 접속해서 Google Cloud Platform – Storage 로 들어갑니다.

  • https://cloud.google.com/storage/

아래처럼 좌측 브라우저 탭을 누르고 [버킷]을 하나 생성해야 합니다. 버킷 이름을 넣고 기억해둬야 합니다.

google_cloud_storage

그리고 랜드마크 이미지를 하나 올려두세요. 전 타지마할 이미지를 landmark.jpg 로 올려뒀습니다.

A view of the Taj Mahal on September 30, 2010 in Agra, India.

이제 라즈베리 파이로 돌아와서 landmark_detection 디렉토리로 이동합니다. detect_landmark.py 파이선 파일이 있는데 이걸 수정해 줍니다. identify_landmark() 함수를 찾아서 return 바로 앞 줄에 아래 코드를 넣어줍니다.

  • print response

그리고 아래 명령어로 테스트 해보세요.

  • python detect_landmark.py gs://버킷이름/파일이름

이미지 인식 결과는 아래처럼 옵니다.

{
    'responses': [
        {
            'landmarkAnnotations': [
                {
                    'locations': [
                        {
                            'latLng': {
                                'latitude': 27.174698469698683, 
                                'longitude': 78.042073
                            }
                        }
                    ], 
                    'score': 0.86202425, 
                    'mid': '/m/0l8cb', 
                    'boundingPoly': {
                        'vertices': [
                            {'y': 103, 'x': 204}, 
                            {'y': 103, 'x': 741}, 
                            {'y': 324, 'x': 741}, 
                            {'y': 324, 'x': 204}
                        ]
                    }, 
                    'description': 'Taj Mahal'
                }, 
                ......
                ......
            ]
        }
    ]
}

landmarkAnnotations 항목 안에 인식 결과가 1개 이상 포함되어 있을겁니다. 전 타지마할 이미지를 올렸는데 예상대로 ‘description’: ‘Taj Mahal’ 이라는 응답을 보내주네요.

기타 예제들

이 외에도 몇 가지 예제들이 더 있으므로 직접 테스트 해보시기 바랍니다. 특히 Text 인식은 조금 더 복잡하고 라이브러리를 몇 개 더 설치해야 하긴 하지만 여러모로 유용하게 사용될 수 있습니다.

참고자료