영상 처리 4주차 - Faced recognition based on brightness & contrast

DatQueue·2022년 10월 12일
0
post-thumbnail

3주차는 휴식입니다....😃

4주차 방향성

지난 1주차엔 파이썬과 opencv, dlib를 이용하여 간단한 2D 얼굴인식이란 무엇이고 어떤 형식으로 진행되는지 알아보았고, 2주차땐 2D 얼굴인식의 문제점을 파악한 뒤 Javascript와 tensorflow를 이용하여 웹을 통하여 3D 입체 구현을 진행해 보았다.

하지만 위의 단계는 단지 "체험" 정도였다면 이번 주차 부터 우리 조는 각 주마다 다른 컨셉을 통해 2D 얼굴인식 구현의 문제점을 개선해보기로 하였다. 이를 통해 이번 주차에서 본인(작성자)은 밝기와 명암에 따라 얼굴 인식 과정에서 어떠한 문제점이 생기고, 이를 어떻게 해결하는 것이 좋은가에 관해 파이썬과 opencv를 통해 알아보기로 하였다.

코드 진행은 영상처리 1주차의 코드를 베이스로 한다.

1주차 코드 - base code

import cv2
import dlib
import sys
import numpy as np
from matplotlib import pyplot as plt

scaler = 0.3

detector = dlib.get_frontal_face_detector()

# 머신 러닝으로 학습된 모델
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

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

while True:
    ret, img = cap.read()
    if not ret:
        break

    img = cv2.resize(
        img, (int(img.shape[1] * scaler), int(img.shape[0] * scaler)))

    ori = img.copy()

    # detect faces
    faces = detector(img)
    face = faces[0]

    dlib_shape = predictor(img, face)
    shape_2d = np.array([[p.x, p.y] for p in dlib_shape.parts()])

    # compute center of face
    # 얼굴의 좌상단
    top_left = np.min(shape_2d, axis=0)
    # 얼굴의 우하단
    bottom_right = np.max(shape_2d, axis=0)
    # 얼굴의 중심점
    center_x, center_y = np.mean(shape_2d, axis=0).astype(np.int)

    # visualize
    img = cv2.rectangle(img, pt1=(face.left(), face.top()), pt2=(face.right(
    ), face.bottom()), color=(255, 255, 255), thickness=2, lineType=cv2.LINE_AA)

    # 얼굴 특징점 표시 (marked on face)
    for s in shape_2d:
        cv2.circle(img, center=tuple(s), radius=1, color=(
            255, 255, 255), thickness=2, lineType=cv2.LINE_AA)

    # 얼굴의 좌상단 우하단에 파랑색 특징점 표시
    cv2.circle(img, center=tuple(top_left), radius=1, color=(
        255, 0, 0), thickness=2, lineType=cv2.LINE_AA)
    cv2.circle(img, center=tuple(bottom_right), radius=1,
               color=(255, 0, 0), thickness=2, lineType=cv2.LINE_AA)

    # 얼굴의 중심에 빨간점 표시
    cv2.circle(img, center=tuple((center_x, center_y)), radius=1,
               color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA)

    cv2.imshow('img', img)
    cv2.waitKey(1)

밝기에 따른 얼굴 인식 --- only brightness


처음으로 진행할 것은 밝기에 따른 이미지 내 얼굴 인식이다.

포화 연산 (Saturation Arithmetic)

그 전에 먼저 바탕으로 알고 가면 좋을 키워드는 바로 "포화 연산"이다.

일반적 이미지 값의 타입은 uint8, 8비트 숫자 255까지만 표현이 가능하기 때문에 256은 이진수로 100000000(2) 9비트가 필요하여 uint8 형식에서는 다시 0이 된다. 이렇게 처리되면 아주 밝은 부분이 아주 어둡게, 또는 아주 어두운 부분이 아주 밝게 반대로 표현되어 이미지가 변질된다.

그래서 픽셀값이 255를 넘으면 255로, 0 미만이면 0으로 처리하는 것이 포화연산이다.

간단히 코드를 통해 알아보자.

def saturate_whole_brightness(p, num):
	pic = p.copy()
    pic = pic.astype('int64')
    pic = np.clip(pic*num, 0, 255)
    pic = pic.astype('uint8')
    return pic

img = saturate_whole_brightness(img, 1)

실제로 이번 테스트에 사용하게 될 코드이다. 참고로 img는 실제로 테스트에쓰기 위해 reading과 resize와 같은 작업을 마친 후의 img여야 할 것이다.

먼저 간단한 saturate_whole_brightness()라는 함수를 정의하였고 파라미터로 p,num이 두가지를 받았다. p는 우리가 사용할 img이다.

여기서 주목해야 할 것은 pic.astype('int64')이다. 이렇게 타입 변환을 해주지 않고 그냥 아래의 np.clip()을 진행할 경우 아무 의미가 없다. 만약 그렇게 했을 경우, 결국 8비트 uint8인 것은 그대로 이므로 픽셀이 255를 넘으면 또 다시 0이 될 것이다. 즉, 선 작업으로 형변환을 해주는 것이 필요하다. 그 후 함수 호출부를 통해 원하는 색상값을 주입 후 다시 8비트로 다시 형변환 해준다.

  • np.clip()
    array 내의 element들에 대해서 min 값 보다 작은 값들을 min값으로 바꿔주고 max 값 보다 큰 값들을 max값으로 바꿔주는 함수.

이 때, saturate_whole_brightness()를 호출하는 과정에서 두번째 파라미터를 통해 우리는 이미지의 포화색을 조절할 수 있는 것이다. 위 코드에서 작성된 "1"이 베이스이다.

num값을 1보다 크게 주면 전체적으로 밝아지고, 1보다 작게 주면 어두워진다.


그럼 한번 코드에 적용시켜 보자 !!

코드에 적용 및 문제점 파악

위에서 다루었던 포화 연산 코드를 제외하고는 모두 동일하다. 주의해야할 점은 포화 연산 함수를 정의하고 호출하는 부분이다. 파라미터명과 호출부의 위치를 우리 테스트와 알맞게 주입시켜주어야한다.

import cv2
import dlib
import sys
import numpy as np
from matplotlib import pyplot as plt

scaler = 0.3

detector = dlib.get_frontal_face_detector()

# 머신 러닝으로 학습된 모델
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

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


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

    if not ret:
        break

    img = cv2.resize(
        img, (int(img.shape[1] * scaler), int(img.shape[0] * scaler)))

    #ori = img.copy()
	
    
    # 포화 연산 함수 선언
    def saturate_whole_brightness(p, num):
        pic = p.copy()
        pic = pic.astype('int64')
        pic = np.clip(pic*num, 0, 255)
        pic = pic.astype('uint8')
        return pic
	
    # 포화 연산 함수 호출
    img = saturate_whole_brightness(img, 6.6)

    # detect faces
    faces = detector(img)
    face = faces[0]

    dlib_shape = predictor(img, face)
    shape_2d = np.array([[p.x, p.y] for p in dlib_shape.parts()])

    # compute center of face
    # 얼굴의 좌상단
    top_left = np.min(shape_2d, axis=0)
    # 얼굴의 우하단
    bottom_right = np.max(shape_2d, axis=0)
    # 얼굴의 중심점
    center_x, center_y = np.mean(shape_2d, axis=0).astype(np.int)

    # visualize
    img = cv2.rectangle(img, pt1=(face.left(), face.top()), pt2=(face.right(
    ), face.bottom()), color=(255, 255, 255), thickness=2, lineType=cv2.LINE_AA)

    # 얼굴 특징점 표시 (marked on face)
    for s in shape_2d:
        cv2.circle(img, center=tuple(s), radius=1, color=(
            255, 255, 255), thickness=2, lineType=cv2.LINE_AA)

    # 얼굴의 좌상단 우하단에 파랑색 특징점 표시
    cv2.circle(img, center=tuple(top_left), radius=1, color=(
        255, 0, 0), thickness=2, lineType=cv2.LINE_AA)
    cv2.circle(img, center=tuple(bottom_right), radius=1,
               color=(255, 0, 0), thickness=2, lineType=cv2.LINE_AA)

    # 얼굴의 중심에 빨간점 표시
    cv2.circle(img, center=tuple((center_x, center_y)), radius=1,
               color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA)

    cv2.imshow('img', img)

    cv2.waitKey(1)

우리는 포화 연산 함수의 호출부의 두 번째 매개변수만 제어하면 된다. 먼저 0.5로 낮추어보자.

img = saturate_whole_brightness(img, 0.5)

이전에 비해 어두워지긴 하였지만 얼굴 인식에 아직 무리는 없다. 조금 더 낮춰보자. 이번엔 0.2이다.

아직도 인식엔 이상이 없었다.

마지막으로 num값을 0.1로 낮추어 보았더니, 비디오 파일 자체가 열리지도 않았다. 얼굴인식을 수행가능한 그 경계값을 대략 찾아본결과, 0.17정도가 마지노선이었고, 그 아래로 내려가면 인식 코드가 실행이 되지 않았다.

사실, 아직 정확한 이유를 찾진 못하였지만, 코드 진행상 img를 디텍팅하지 못하였기 때문이라 추측된다.


이미지를 어둡겐 해보았으니 밝게도 간단히 알아보자. num값을 4로 높여보자 !

4로 높였더니 확연히 눈에 띄는 부분이 보인다. 오른쪽 사람의 얼굴이 기존과 다르게 인식되지 않는다. 어두울때는 그래도 인식은 되었는데, 밝기를 크게 높이니 인식이 불가하였다.

또한 num값이 5이상되면 해당 비디오 파일이 디텍팅의 문제로 빠르게 꺼지고, 최대 마지노선의 num값은 6.5 정도였다.


명암에 따른 얼굴 인식 -- with contrast


contrast control (명암 조절)

위에서 알아본 "밝기"에 따른 얼굴 인식 조절은 함수 명에서도 알 수 있듯이 이미지 전체의 밝기를 키웠다가 줄였다 하는 경우였다. 즉, 밝은 부분을 더 밝게, 어두운 부분을 더 어둡게 처리하는 "명암"측면에선 해당치 않는다.

명암 차이를 크게 만들기 위해서는 밝은 부분은 더 밝게 해주고, 어두운 부분은 더 어둡게 해줘야 한다. 밝고 어두움의 기준으로 0~255의 중간 값인 128을 사용하고, 128보다 더 큰 값은 더 밝게 만들고 128보다 작은 값은 더 어둡게 만들어 대비를 더 크게 만들어주면 된다.

해당 코드는 "밝기"에 따른 이미지 처리 코드 방식과 유사하다.

#declaration
def saturate_contrast(p, num):
	pic = p.copy()
    pic = pic.astype('int32')
    pic = np.clip(pic+(pic-128)*num, 0, 255) #달라진 부분
    pic = pic.astype('uint8')
    return pic
#call
img = saturate_contrast(img, 2)

saturate_contrast()함수의 선언 구문 내에 3번째 pic 정의에 주목하자.

pic = np.clip(pic+(pic-128)*num, 0, 255)

해당 구문이 바로 명암 차이를 가능케 해준다. num값을 키울 수록 첫 번째 인자인 pic + (pic-128) * num부분이 극단적으로 음수로 가거나 극단적으로 255를 넘기게 된다. 그리고 해당 수들을 clip()메서드를 통해 0과 255로 정의해준다. 그 변화 양상을 값 대입을 통해 간단히 알아보자.

코드에 적용 및 문제점 파악

우리 코드에 해당 로직을 적용시켜보자.

# 생략 

def saturate_whole_brightness(p, num):
	pic = p.copy()
    pic = pic.astype('int64')
    pic = np.clip(pic*num, 0, 255)
    pic = pic.astype('uint8')
    return pic

def saturate_contrast(p, num):
	pic = p.copy()
    pic = pic.astype('int32')
    pic = np.clip(pic+(pic-128)*num, 0, 255)
    pic = pic.astype('uint8')
    return pic

#img = saturate_whole_brightness(img, 4)

img = saturate_contrast(img, 2)

# 생략

명암 조절 함수의 선언및 호출부는 앞전의 밝기 조절 함수와 동일한 선상에 위치시켰다. 그 후 밝기 조절 함수 saturate_whole_brightness()의 호출을 주석처리 해 줌으로써 잠시 막아둔다.

먼저 num값에 2를 대입한뒤 이미지를 확인해보자.

기존의 비디오 이미지에 비해 확연히 명암 대비가 보인다. 그럼 조금 더 올려보자. 이번엔 num값에 5를 대입해보겠다.

더욱 확연한 차이가 보인다. 명암의 차이가 더 확실히 일어남과 동시에, 오른쪽 사람의 얼굴은 디텍팅 가능한 시간이 확연히 줄어듬이 보인다.

그 후, num값을 7이상 올리면 비디오 파일 자체가 열리지 않으며 에러가 뜬다. 이 역시, 확실하진 않지만 얼굴을 디텍팅하지 못하였기 때문에 전체 구문에서 충돌을 일으킨 것으로 보인다. 간단히 말하자면 detector()메서드를 통해 imgface값을 읽어오는 과정이 이루어지지 못하였기 때문이라 할 수 있다.


다음 포스팅 예고

우린 이번 포스팅에서 1주차때 진행한 비디오를 통한 얼굴인식 단계에서 밝기와 명암이란 장애물을 두어 인식의 가능 범위를 간단히 알아보았다. 확연히 2D 이미지 처리에선 어느정도의 유효값을 벗어나면 인식의 오류가 있다는 것을 직접 확인함으로써 인지하였다.

포스팅이 길어질 것을 고려해 다음 포스팅에서 해당 문제점들의 해결 방안에 대해 알아보고자 한다.

profile
You better cool it off before you burn it out / 티스토리(Kotlin, Android): https://nemoo-dev.tistory.com

0개의 댓글