카테고리 없음

파이썬 데이터분석 실무 테크닉 100 9장

오늘도 가보자잇 2024. 11. 2. 04:24

9장 : 잠재고객을 파악하기 위한 이미지 인식 테크닉 10

영상을 통한 고객의 모습을 통해 상품 선택과 이어주기 위해선 고성능, 고가의 이미지 인식 기술이 필요하지만, 이는 '어느 정도' 성능에도 만족할 수 있다면 무료 라이브러리를 이용해 충분히 실현할 수 있다.

카메라에서 얻은 이미지를 이용해 인식과 얻는 과정을 배움으로써 이미지 인식을 현장에서 응용하는 흐름을 파악한다.

 

동영상 : mov folder, 

이미지 : img folder에 저장돼 있다.

 

81. 이미지 데이터를 불러오기

 

import cv2
img = cv2.imread("img/img01.jpg")
height, width = img.shape[:2]
print("이미지 가로: " + str(width))
print("이미지 세로: " + str(height))
cv2.namedWindow("img",cv2.WINDOW_NORMAL)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • 1행 : cv2 라이브러리를 불러온다.
  • 2행 : img 폴더에 포함된 이미지 img01.jpg을 불러와서 img에 저장한다.
  • 3행 : img에 포함된 이미지 정보를 shape로 추출한다.
  • 불러온 이미지는 imshow를 사용하여 표시한다.
  • waitKey를 실행하면 몇 초 동안 이미지를 표시할지를 밀리초 단위로 지정한다.
    • 인수가 1000인 경우는 1초간 표시된다.
    • 0인 경우는 윈도우가 닫을 때까지 계속해서 표시한다.

82. 동영상 데이터를 불러오기

import cv2

# 정보 취득 #
cap = cv2.VideoCapture("mov/mov01.avi")
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
fps = cap.get(cv2.CAP_PROP_FPS)
print("가로: " + str(width))
print("세로: " + str(height))
print("총 프레임수: " + str(count))
print("FPS: " + str(fps))

# 출력 #
while(cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        cv2.imshow("frame", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
cap.release()
cv2.destroyAllWindows()

동영상 불러오기는 OpenCV의 VideoCapture를 사용했다.

불러온 동영상 정보를 cap에 저장하고 함수 get으로 정보를 취득한다.

while에서 cap에 저장된 동영상 정보를 프레임마다 처리하고, 각 프레임의 정보를 함수 read로 읽어 들입니다.

frame에 저장된 정보는 이미지 정보이기에 imshow로 표시할 수 있다.

while 내용 : 각 프레임을 1밀리초 동안 표시하고 다음 프레임으로 이동한다.

종료 : 모든 프레임 처리나 'q'를 클릭 시 종료하도록 한다.

 

83. 동영상을 이미지로 나누고 저장하자

import cv2
cap = cv2.VideoCapture("mov/mov01.avi")
num = 0
while(cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        cv2.imshow("frame", frame)
        filepath = "snapshot/snapshot_" + str(num) + ".jpg"
        cv2.imwrite(filepath,frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    num = num + 1
cap.release()
cv2.destroyAllWindows()

imwrite : 이미지 저장 함수

저장할 폴더명을 포함한 경로를 지정한 후에 프레임 정보를 이미지로 저장한다.

 

84. 이미지 속에 사람이 어디에 있는지 검출해 보자

사람을 간단하게 인식하기 위해 "HOG 특징량"이라는 것을 사용한다.

HOG : Histogram of Oriented Gradients의 약자로 '휘도의 기울기'라고 설명할 수 있으며, '사람 실루엣 형태의 특징을 위치나 각도로 표현'

import cv2

# 준비 #
hog = cv2.HOGDescriptor()
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
hogParams = {'winStride': (8, 8), 'padding': (32, 32), 'scale': 1.05, 'hitThreshold':0, 'finalThreshold':5}

# 검출 #
img = cv2.imread("img/img01.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
human, r = hog.detectMultiScale(gray, **hogParams)
if (len(human)>0):
    for (x, y, w, h) in human:
        cv2.rectangle(img, (x, y), (x + w, y + h), (255,255,255), 3)

cv2.namedWindow("img",cv2.WINDOW_NORMAL)
cv2.imshow("img",img)
cv2.imwrite("temp.jpg",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

HOGDescriptor를 선언하고 사람 모델 지정

모델은 함수 setSVMDetector를 사용하고 인수로 cv2.HOGDescriptor_getDefaultPeopleDetector()를 지정하면 된다.

읽어 들인 이미지를 cvtColor를 이용해 흑백으로 변환한 후 detectMultiScale로 사람을 검출한다.

검출된 사람의 위치 정보는 human에 저장한다.

rectangle : 이 정보와 이미지에 사각형을 그리는 함수- 어느 위치의 사람인지 알 수 있다.

 

85. 이미지 속 얼굴을 검출해 보자

 

import cv2

# 준비
cascade_file = "haarcascade_frontalface_alt.xml"
cascade = cv2.CascadeClassifier(cascade_file)

# 검출
img = cv2.imread("img/img02.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
face_list = cascade.detectMultiScale(gray, minSize=(50, 50))

# 검출한 얼굴 표시하기
for (x, y, w, h) in face_list:
    color = (0, 0, 225)
    pen_w = 3
    cv2.rectangle(img, (x, y), (x+w, y+h), color, thickness = pen_w)

cv2.namedWindow("img",cv2.WINDOW_NORMAL)
cv2.imshow("img",img)
cv2.imwrite("temp.jpg",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

얼굴 검출에는 CascadeClassifier을 사용한다.

 

코드 내용

  1. CascadeClassifier을 선언
  2. 모델로 haarcascade_frontalface_alt.xml을 지정한다.
  3. 얼굴, 눈 코등을 인식하여 detectMultiScale을 이용하면 얼굴의 위치를 검출하고, 위치파악이 가능하다.

 

86. 이미지 속 사람의 얼굴이 어느 쪽을 보고 있는지 검출해보자.

 

import cv2
import dlib
import math

# 준비 #
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
detector = dlib.get_frontal_face_detector()

# 검출 #
img = cv2.imread("img/img02.jpg")
dets = detector(img, 1)

for k, d in enumerate(dets):
    shape = predictor(img, d)

    # 얼굴 영역 표시
    color_f = (0, 0, 225)
    color_l_out = (255, 0, 0)
    color_l_in = (0, 255, 0)
    line_w = 3
    circle_r = 3
    fontType = cv2.FONT_HERSHEY_SIMPLEX
    fontSize = 1
    cv2.rectangle(img, (d.left(), d.top()), (d.right(), d.bottom()), color_f, line_w)
    cv2.putText(img, str(k), (d.left(), d.top()), fontType, fontSize, color_f, line_w)

    # 중심을 계산할 사각형 준비
    num_of_points_out = 17
    num_of_points_in = shape.num_parts - num_of_points_out
    gx_out = 0
    gy_out = 0
    gx_in = 0
    gy_in = 0
    for shape_point_count in range(shape.num_parts):
        shape_point = shape.part(shape_point_count)
        #print("얼굴 랜드마크No.{} 좌표 위치: ({},{})".format(shape_point_count, shape_point.x, shape_point.y))
        #얼굴 랜드마크마다 그리기
        if shape_point_count<num_of_points_out:
            cv2.circle(img,(shape_point.x, shape_point.y),circle_r,color_l_out, line_w)
            gx_out = gx_out + shape_point.x/num_of_points_out
            gy_out = gy_out + shape_point.y/num_of_points_out
        else:
            cv2.circle(img,(shape_point.x, shape_point.y),circle_r,color_l_in, line_w)
            gx_in = gx_in + shape_point.x/num_of_points_in
            gy_in = gy_in + shape_point.y/num_of_points_in

    # 중심 위치 표시
    cv2.circle(img,(int(gx_out), int(gy_out)),circle_r,(0,0,255), line_w)
    cv2.circle(img,(int(gx_in), int(gy_in)),circle_r,(0,0,0), line_w)

    # 얼굴 방향 계산
    theta = math.asin(2*(gx_in-gx_out)/(d.right()-d.left()))
    radian = theta*180/math.pi
    print("얼굴 방향:{} (각도:{}도)".format(theta,radian))

    # 얼굴 방향 표시
    if radian<0:
        textPrefix = "   left "
    else:
        textPrefix = "   right "
    textShow = textPrefix + str(round(abs(radian),1)) + " deg."
    cv2.putText(img, textShow, (d.left(), d.top()), fontType, fontSize, color_f, line_w)


# cv2.imshow("img",img)
# cv2.imwrite("temp.jpg",img)
# cv2.waitKey(0)

cv2.namedWindow("img",cv2.WINDOW_NORMAL)
cv2.imshow("img",img)
cv2.imwrite("temp.jpg",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

dlib : 이 라이브러리는 얼굴을 랜드마크라고 부르는 눈, 코, 입, 윤곽의 68개의 특징점으로 표현할 수 있다.

이것으로 사람이 어느 쪽으로 돌리고 있는 지와 같은 세세한 정보를 검출할 수 있다.

 

코드 내용

  • shape_predictor로 68개의 얼굴 기관 모델을 불러온다.
  • get_frontal_face_detector로 68개의 정면 얼굴 모델을 불러온다.
  • 윤관 중심과 안쪽 중심의 차이로 얼굴 방향을 계산한다.
  • 몇도(deg) 틀어졌는지 표시된 것을 알 수 있다.

그 외에도 dlib를 이용하면 표정의 변화 같은 것도 검출할 수 있다.

 

87. 검출한 정보를 종합해서 타임랩스를 만들어보자

장시간의 정보 >> 간단하게 경향을 파악하는 방법

"빠르게 재생하기"인 타임랩스가 적당하다.

이는 데모영상으로도 가능하다.

import cv2

print("타임랩스 생성 시작")

# 동영상 읽어오기 #
cap = cv2.VideoCapture("mov/mov01.avi")
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

# hog 선언 #
hog = cv2.HOGDescriptor()
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
hogParams = {'winStride': (8, 8), 'padding': (32, 32), 'scale': 1.05, 'hitThreshold':0, 'finalThreshold':5}

# 타임랩스 작성 #
movie_name = "timelapse.avi"
fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
video = cv2.VideoWriter(movie_name,fourcc, 30, (width,height))

num = 0
while(cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        if (num%10==0):
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            human, r = hog.detectMultiScale(gray, **hogParams)
            if (len(human)>0):
                for (x, y, w, h) in human:
                    cv2.rectangle(frame, (x, y), (x + w, y + h), (255,255,255), 3)

            video.write(frame)
    else:
        break
    num = num + 1
video.release()
cap.release()
cv2.destroyAllWindows()
print("타임랩스 생성 완료")

코드 내용

  • VideoWriter_fourcc : 타임랩스와 같은 동영상 파일을 만들기 위한 함수
  • FourCC라는 4개의 동영상 데이터 포맷을 지정함으로써 동영상 파일을 작성할 수 있다.
    • 여기서 x,v,i,d로 4개의 파라미터를 지정해서 동영상 파일을 AVI 형식으로 작성한다.
  • 저장하고 싶은 프레임을 write로 저장
  • release를 실행하면 동영상 생성 완료

88 전체 모습을 그래프로 가시화

동영상에서 사람을 검출한 후에 그 시계열 변화를 가시화하자.

 

import cv2
import pandas as pd

print("분석 시작")
# 동영상 읽어오기 #
cap = cv2.VideoCapture("mov/mov01.avi")
fps = cap.get(cv2.CAP_PROP_FPS)

# hog 선언 #
hog = cv2.HOGDescriptor()
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
hogParams = {'winStride': (8, 8), 'padding': (32, 32), 'scale': 1.05, 'hitThreshold':0, 'finalThreshold':5}

num = 0
list_df = pd.DataFrame( columns=['time','people'] )
while(cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        if (num%10==0):
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            human, r = hog.detectMultiScale(gray, **hogParams)
            if (len(human)>0):
                for (x, y, w, h) in human:
                    cv2.rectangle(frame, (x, y), (x + w, y + h), (255,255,255), 3)
            tmp_se = pd.Series( [num/fps,len(human) ], index=list_df.columns )
            list_df = list_df.append( tmp_se, ignore_index=True )       
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
    else:
        break
    num = num + 1
cap.release()
cv2.destroyAllWindows()
print("분석 종료")

 

import matplotlib.pyplot as plt
plt.plot(list_df["time"], list_df["people"])
plt.xlabel('time(sec.)')
plt.ylabel('population')
plt.ylim(0,15)
plt.show()

 

시계열 데이터 저장에는 판다스의 데이터프레임을 이용하면 편리하다. 

프레임 번호를 FPS로 정의하면 경과시간을 계산할 수 있기 때문에 시간과 사람의 수를 저장할 수 있다.

이 정보를 Matplotlib으로 그래프화 할 수 있다.

 

89. 거리의 변화를 그래프로 확인해보자

import cv2
import pandas as pd

print("분석 시작")
# 동영상 읽어오기 #
cap = cv2.VideoCapture("mov/mov02.avi")
fps = cap.get(cv2.CAP_PROP_FPS)

# hog 선언 #
hog = cv2.HOGDescriptor()
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
hogParams = {'winStride': (8, 8), 'padding': (32, 32), 'scale': 1.05, 'hitThreshold':0, 'finalThreshold':5}

num = 0
list_df2 = pd.DataFrame( columns=['time','people'] )
while(cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        if (num%10==0):
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            human, r = hog.detectMultiScale(gray, **hogParams)
            if (len(human)>0):
                for (x, y, w, h) in human:
                    cv2.rectangle(frame, (x, y), (x + w, y + h), (255,255,255), 3)
            tmp_se = pd.Series( [num/fps,len(human) ], index=list_df.columns )
            list_df2 = list_df2.append( tmp_se, ignore_index=True )       
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
    else:
        break
    num = num + 1
cap.release()
cv2.destroyAllWindows()
print("분석 종료")
import matplotlib.pyplot as plt
plt.plot(list_df2["time"], list_df2["people"])
plt.xlabel('time(sec.)')
plt.ylabel('population')
plt.ylim(0,15)
plt.show()

코드 결과

결과를 보면 mov01.avi와 비교해서 사람 수가 늘어난 것 같은 느낌을 받는다.

실제로 mov02.avi를 확인하면 카메라 근처에 사람이 몰려있어 사람 수가 많이 카운트될 것처럼 보인다.

하지만 HOG로 분석한 데이터는 노이즈가 많고, 오검출로 변동이 많은 그래프가 되어 그 차이를 확인하는 것이 어렵다.

 

90. 이동평균을 계산해서 노이즈를 제거하자.

노이즈 : 계산하지 않아도 될 것을 계산하여 생기는 오차이다.

이것은 확률적으로 생긴다.

 

이 문제는 시간의 평균(이동 평균)을 계산하면 갑자기 사람의 숫자가 늘어나거나 줄어들어서 생기는 오차를 줄일 수 있을 거다. 함수 moving_average를 사용해서 이동 평균을 계산해 보자.

 

import numpy as np
def moving_average(x, y):
    y_conv = np.convolve(y, np.ones(5)/float(5), mode='valid')
    x_dat = np.linspace(np.min(x), np.max(x), np.size(y_conv))
    return x_dat, y_conv

 

plt.plot(list_df["time"], list_df["people"], label="raw")
ma_x, ma_y = moving_average(list_df["time"], list_df["people"])
plt.plot(ma_x,ma_y, label="average")
plt.xlabel('time(sec.)')
plt.ylabel('population')
plt.ylim(0,15)
plt.legend()
plt.show()

이렇게 이동평균은 좀 더 정확하게 비교할 수 있다. 테크닉 89에서 설명한 것처럼 실제 mov2.avi쪽이 카메라 근처에 사람들이 모여 있습니다.

이 모습을 좀 더 정확하게 비교하기 위해서는

검출할 HOG의 크기를 검출하고 싶은 사람의 크기를 보고,

어떤 크기 이상만 검출, 어떤 크기 이하는 무시하는 규칙을 포함시키는 방법 고려