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을 사용한다.
코드 내용
- CascadeClassifier을 선언
- 모델로 haarcascade_frontalface_alt.xml을 지정한다.
- 얼굴, 눈 코등을 인식하여 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의 크기를 검출하고 싶은 사람의 크기를 보고,
어떤 크기 이상만 검출, 어떤 크기 이하는 무시하는 규칙을 포함시키는 방법 고려