DeepSort란 가장 널리 사용되고 객체 추적 프레임워크 중 하나로, SORT(Simple Online and Realtime Tracking - 예시: IOU)을 보완 확장한 기술이다. 이전 KhuCv는 SORT만을 사용하여 오직 IOU로만 id를 부여하여 object를 추적하였는데, 이는 object를 Detection하지 못 할 경우 IOU추적을 어렵게 만들어 객체에 새로운 id를 부여하는 오류를 범한다. 또한 객체가 다른 객체에 의해 가려지면 다른 객체에 FaceRect가 옮겨져가는 오류도 범한다. 이러한 오류들을 해결하고 Tracking 성능을 높이기 위해 DeepSORT프레임 워크를 사용한다.
사용할 DeepSORT프레임워크는에는 다음의 3가지 특징을 포함한다.
1. IOU.
2. FaceSimilarity(Cosine Similarity - 참고).
3. 이동 속도를 측정하여 다음 FaceRect위치 예상하기.
현재 Detection된 FaceRect를 이전 Frame에서 Detection된 FaceRect와 비교하여 측정한다. 먼저, 이전 FaceRect와 현재 FaceRect의 IOU(또는 FaceSimilarity)를 측정하고, 측정이 안 될 경우 FaceSimilarity(또는 IOU)로 측정을 한다. 측정이 된다면 Tracker객체의 Rect의 좌표와 이외에 필요한 정보들을 업데이트한다.
이 두 가지 모두 측정이 안 될 경우, 이전 FaceRect의 이동 속도를 가지고 다음 FaceRect의 위치를 추적하도록 한다. 만약 15~20번 정도 추적이 안 될 경우, Frame에서 사라진 것으로 간주하고 해당 FaceRect에 대해 추적하는 Tracker객체를 삭제한다.
심층 신경망(Deep Neural Network, DNN)은 입력층(input layer)과 출력층(output layer) 사이에 여러 개의 은닉층(hidden layer)들로 이뤄진 인공신경망(Artificial Neural Network, ANN)이다. 심층 신경망은 일반적인 인공신경망과 마찬가지로 복잡한 비선형 관계(non-linear relationship)들을 모델링할 수 있다.
예를 들어, 사물 식별 모델을 위한 심층 신경망 구조에서는 각 객체가 이미지 기본 요소들의 계층적 구성으로 표현될 수 있다. 이때, 추가 계층들은 점진적으로 모여진 하위 계층들의 특징들을 규합시킬 수 있다. 심층 신경망의 이러한 특징은, 비슷하게 수행된 인공신경망에 비해 더 적은 수의 유닛(unit, node)들 만으로도 복잡한 데이터를 모델링할 수 있게 해준다.
출처 : 심층 신경망(Deep Neural Network, DNN)
컴퓨터 비전에서는 활용되는 DNN은 미리 몇 천장의 사진으로 훈련되어 input layers로 받은 이미지에 있는 동물이 각각 어떤 동물인지 판별할 수 있다. 여기서 동물을 판별할 때, input layers에서 동물을 감지하고 각각의 특징들을 잡아내는데, 우린 이 특징을 잡아내는 기능을 이용하여 FaceRect의 특징을 찾아낼 것이다.
이후 FaceRect의 특징을 해당 Frame에서 Detection된 FaceRect와 cosine similarity를 이용하여 해당 Rect가 일치하는 비율을 확인할 것이다.
// Project.cpp
CProject::CProject() {
strcat(m_ExePt, "/mobilenetv2-7.onnx");// m_ExePt는 현재 작업 경로
m_MobileNet = cv::dnn::readNetFromONNX(m_ExePt);
}
// Project.h
class CProject{
cv::dnn::Net m_MobileNet;
}
for(auto list : detected_faceList) {
cv::Mat Roi = Input(cv::Rect(cv::Point(list.x1, list.y1), cv::Point(list.x2, list.y2)));
cv::Mat DnnInput;
cv::resize(Roi, DnnInput, cv::Size(224, 224), 0, 0, cv::INTER_AREA);
cv::Mat inputBlob = cv::dnn::blobFromImage(DnnInput, 1 / 255., cv::Size(224, 224), cv::Scalar(128, 128, 128), false);
m_MobileNet.setInput(inputBlob);
// features cv::Mat 받아들이기
#ifndef __APPLE__
cv::Mat features = m_MobileNet.forward("mobilenetv20_features_pool0_fwd");
#else
cv::Mat features = m_MobileNet.forward("onnx_node!mobilenetv20_features_pool0_fwd");
#endif
cv::Mat cvFeature(40, 32, CV_32FC1);
memcpy(cvFeature.data, features.data, 1280 * sizeof(float));
}
각 FaceRect의 특징이 담긴 cvFeature에 CosineSimilarity를 적용하여 Face Similarity를 검사한다.
#pragma once
#include "cv_dnn_ultraface.h"
#include <iostream>
#include <sstream>
#include <algorithm>
#include <vector>
class CProject
{
cv::Mat m_PreviousImage;
public:
char m_ExePath[256];
wchar_t m_ExePathUnicode[256];
CProject();
~CProject();
void GetExecutionPath();
void Run(cv::Mat Input, cv::Mat& Output, bool bFirstRun, bool bVerbose);
UltraFace *m_pUltraface;
cv::dnn::Net m_MobileNet;
};
class Point{
public:
float x,y;
Point(){}
Point(float a, float b): x(a), y(b){}
Point operator-(Point other){return {x-other.x, y-other.y};}
Point operator+(Point other){return {x+other.x, y+other.y};}
Point operator*(float a){return {x * a, y * a};}
bool operator==(Point other){return (other.x==x && other.y==y) ? true : false;}
};
class Rect{
public:
Point LT,RB;
Rect(){}
Rect(Point lt, Point rb): LT(lt), RB(rb){}
float iou(Rect other){ // iou가 0을 가질 경우, 이 사각형은 inter을 갖지 않음. 즉, 겹치지 않음.
Rect inter = this->intersection(other);
float thisArea = this->width() * this->height();
float otherArea = other.width() * other.height();
float interArea = inter.width() * inter.height();
return interArea/(thisArea+otherArea-interArea);
}
Rect intersection(Rect other){
float x1 = std::max(LT.x, other.LT.x);
float y1 = std::max(LT.y, other.LT.y);
float x2 = std::min(RB.x, other.RB.x);
float y2 = std::min(RB.y, other.RB.y);
return {{x1,y1},{x2,y2}};
}
float width(){
return RB.x-LT.x >= 0 ? RB.x-LT.x : 0;
}
float height(){
return RB.y-LT.y >= 0 ? RB.y-LT.y : 0;
}
Point center(){
return {(LT.x + RB.x)/2, (LT.y + RB.y)/2};
}
};
class Tracker{
public:
static int cnt;
int T_id;
int UnTracked = 0;
Point offset;
Rect rt;
std::vector<cv::Mat> featureList;
Tracker(){}
Tracker(Rect current, cv::Mat cvFeature):T_id(cnt++), rt(current), offset(0,0){
featureList.push_back(cvFeature);
matched = true;
}
double GetCosineSimilarity(cv::Mat feature){
double maxSimilarity = 0;
for(int i = featureList.size() - 1; i >= 0 && i >= featureList.size() - 5;--i){
double Similarity = 0;
double A = 0, B = 0;
for(int row = 0; row < featureList[i].rows; ++row)
for(int col = 0; col < featureList[i].cols; ++col){
Similarity += featureList[i].at<float>(row,col) * feature.at<float>(row,col); // 내적
A += featureList[i].at<float>(row, col) * featureList[i].at<float>(row, col); // A크기
B += feature.at<float>(row, col) * feature.at<float>(row,col); // B크기
}
if(sqrt(A) * sqrt(B) > 0) Similarity /= sqrt(A) * sqrt(B);
else Similarity = 0;
if(Similarity > maxSimilarity) maxSimilarity = Similarity;
}
return maxSimilarity;
}
};
: 맨 앞의 남자를 보면 ID 2번과 4번이 겹쳐지는 One Person Two faceRect Error가 발생함을 볼 수 있다. 해당 오류는 현재 Frame의 Detected된 Rect에 대해서, 이전 Frame의 조건을 만족하는 모든 Rect에 현재 Rect를 부여해줌으로써 발생한 문제이다.
: Project.cpp - CProject::Run()
-> 밑의 <문제 발생 코드> 참조
// <문제 발생 코드>
// tracking vector initialize
std::vector<Tracker> m_idTrackers;
int Tracker::cnt = 0;
void CProject::Run(cv::Mat Input, cv::Mat& Output, bool bFirstRun, bool bVerbose) {
std::vector<FaceInfo> faceList;
m_pUltraface->detect(Input, faceList);
cv::Mat OutImage = Input.clone();
for(auto list : faceList) {
cv::Mat Roi = Input(cv::Rect(cv::Point(list.x1, list.y1), cv::Point(list.x2, list.y2)));
cv::Mat DnnInput;
cv::resize(Roi, DnnInput, cv::Size(224, 224), 0, 0, cv::INTER_AREA);
cv::Mat inputBlob = cv::dnn::blobFromImage(DnnInput, 1 / 255., cv::Size(224, 224), cv::Scalar(128, 128, 128), false);
m_MobileNet.setInput(inputBlob);
// features cv::Mat 받아들이기
#ifndef __APPLE__
cv::Mat features = m_MobileNet.forward("mobilenetv20_features_pool0_fwd");
#else
cv::Mat features = m_MobileNet.forward("onnx_node!mobilenetv20_features_pool0_fwd");
#endif
cv::Mat cvFeature(40, 32, CV_32FC1);
memcpy(cvFeature.data, features.data, 1280 * sizeof(float));
Rect currentRt({list.x1,list.y1},{list.x2,list.y2});
bool has_id = false;
for(int i = 0; i < m_idTrackers.size(); ++i){
bool identified = false;
if(m_idTrackers[i].GetCosineSimilarity(cvFeature) > 0.98) identified = true;
else if(m_idTrackers[i].rt.iou(currentRt) > 0.15) identified = true;
if(identified){
has_id = true;
m_idTrackers[i].featureList.push_back(cvFeature);
m_idTrackers[i].UnTracked = (m_idTrackers[i].UnTracked > 1) ? -1 : 0;
m_idTrackers[i].offset = m_idTrackers[i].offset * 0.5 + (currentRt.center() - m_idTrackers[i].rt.center()) * 0.5;
m_idTrackers[i].rt = currentRt;
}
}
if(!has_id){
Tracker tracker(currentRt, cvFeature);
m_idTrackers.push_back(tracker);
}
}
for(int i = 0; i < m_idTrackers.size(); ++i){
// 삭제
if(m_idTrackers[i].UnTracked > 15) m_idTrackers.erase(m_idTrackers.begin() + i);
// 그리기
cv::Scalar color;
std::stringstream m_id;
m_id << m_idTrackers[i].T_id << "-" << m_idTrackers[i].UnTracked;
if(m_idTrackers[i].UnTracked == -1) {
color = cv::Scalar(255,0,255);
m_idTrackers[i].UnTracked = 1;
}
else if(m_idTrackers[i].UnTracked == 0) {
color = cv::Scalar(0,0,255);
m_idTrackers[i].UnTracked = 1;
}
else {
color = cv::Scalar(255,255,0);
Rect currentRt = m_idTrackers[i].rt;
m_idTrackers[i].rt.LT = m_idTrackers[i].rt.LT + m_idTrackers[i].offset;
m_idTrackers[i].rt.RB = m_idTrackers[i].rt.RB + m_idTrackers[i].offset;
m_idTrackers[i].offset = m_idTrackers[i].offset * 0.5 + (m_idTrackers[i].rt.center()-currentRt.center()) * 0.5;
m_idTrackers[i].UnTracked++;
}
cv::rectangle(OutImage, cv::Point(m_idTrackers[i].rt.LT.x,m_idTrackers[i].rt.LT.y), cv::Point(m_idTrackers[i].rt.RB.x,m_idTrackers[i].rt.RB.y),color, 2);
cv::putText(OutImage, m_id.str(), cv::Point(m_idTrackers[i].rt.LT.x,m_idTrackers[i].rt.LT.y-10),1,2,color,3);
}
/// m_idTrackers Vector의 capacity 맞춰주기
m_idTrackers.shrink_to_fit();
if(bVerbose)
DisplayImage(OutImage, Input.cols, 0, false, true);
}
: 현재 Detected된 FaceRect에 대하여 이전 프레임에서 검출된 FaceRect와의 Iou혹은 similarity가 일치할 경우, 비교 과정을 계속 진행하지 않고 break를 넣어 줌으로써 끝내어 하나의 Detected FaceRect에 한 개의 Rect만 대입하도록 한다. 해당 오류는 밑의 코드처럼 break를 작성함으로써 조건을 만족하는 하나의 Rect를 인지할 경우, FaceRect의 비교 과정을 끝낸다.
: Project.cpp - parts of CProject::Run()
-> 밑의 코드 참조
// 수정 코드 (parts of CProject::Run())
if(identified){
has_id = true;
m_idTrackers[i].featureList.push_back(features);
m_idTrackers[i].UnTracked = (m_idTrackers[i].UnTracked > 1) ? -1 : 0;
m_idTrackers[i].offset = m_idTrackers[i].offset * 0.5 + (currentRt.center() - m_idTrackers[i].rt.center()) * 0.5;
m_idTrackers[i].rt = currentRt;
break; // break 작성
}
: 3-1 one person two faceRect의 문제를 해결한 뒤, late Detection문제에 직면하였다. 해당 그림의 3-0 여성을 보면, 1-0의 남성의 뒤를 지난 후 ID 3번이 제 때에 FaceDetection을 하지 못하여 ID 5번이 할당된 후, 뒤늦게 FaceDetection하여 3번으로 교체되는 late Tracking현상이 발생하였다.
이 오류는 3번 여성이 가려졌다가 나타났을 때 밑의 코드에서처럼 인물의 유사도를 판별하는 유사도가 0.98을 넘지 못하여 발생한다.
// 오류 설명 참조 코드
if(m_idTrackers[i].GetCosineSimilarity(cvFeature) > 0.98) identified = true;
else if(m_idTrackers[i].rt.iou(currentRt) > 0.15) identified = true;
: late Tracking을 해결하기 위해선 유사도 판별의 임계치를 0.98 밑으로 설정을 해줘야 하는데, 그렇게 되면 임계치 유사도를 만족하는 FaceRect가 여러 개가 되어 제대로 된 Tracking을 못할 수 있다. 따라서 가장 유사도가 높은 FaceRect를 선정하여 원하는 FaceRect가 매칭되도록 해준다. 이 때, FaceRect가 화면에서 사라질 경우를 대비하여 유사도의 임계치를 0.945로 설정해준다. 또한, FaceRect가 5번 동안 matched되지 않았다면 유사도 값이 더 낮을 것이므로 임계치를 0.93으로 설정해주어 match되도록 한다.
: Project.cpp - CProject::Run()
-> 밑의 코드 참조
// 수정 코드 (Project.cpp - CProject::Run())
// tracking vector initialize
std::vector<Tracker> m_idTrackers;
int Tracker::cnt = 0;
void CProject::Run(cv::Mat Input, cv::Mat& Output, bool bFirstRun, bool bVerbose) {
if(bFirstRun){
Tracker::cnt = 0;
m_idTrackers.clear();
for(auto layer : m_MobileNet.getLayerNames()){
DlgPrintf("%s", layer.c_str());
}
}
std::vector<FaceInfo> faceList;
m_pUltraface->detect(Input, faceList);
cv::Mat OutImage = Input.clone();
for(auto list : faceList) {
cv::Mat Roi = Input(cv::Rect(cv::Point(list.x1, list.y1), cv::Point(list.x2, list.y2)));
cv::Mat DnnInput;
cv::resize(Roi, DnnInput, cv::Size(224, 224), 0, 0, cv::INTER_AREA);
cv::Mat inputBlob = cv::dnn::blobFromImage(DnnInput, 1 / 255., cv::Size(224, 224), cv::Scalar(128, 128, 128), false);
m_MobileNet.setInput(inputBlob);
// features cv::Mat 받아들이기
#ifndef __APPLE__
cv::Mat features = m_MobileNet.forward("mobilenetv20_features_pool0_fwd");
#else
cv::Mat features = m_MobileNet.forward("onnx_node!mobilenetv20_features_pool0_fwd");
#endif
cv::Mat cvFeature(40, 32, CV_32FC1);
memcpy(cvFeature.data, features.data, 1280 * sizeof(float));
Rect currentRt({list.x1,list.y1},{list.x2,list.y2});
bool has_id = false;
bool identified = false;
double maxSimilarity = 0;
Tracker *maxTracker;
for(int i = 0; i < m_idTrackers.size(); ++i){
if(m_idTrackers[i].rt.iou(currentRt) > 0.15) {
identified = true;
maxTracker = &m_idTrackers[i];
break;
}
else{
double similarity = m_idTrackers[i].GetCosineSimilarity(cvFeature);
if(similarity > maxSimilarity) {
maxSimilarity = similarity;
maxTracker = &m_idTrackers[i];
}
}
}
if(maxSimilarity > 0.945) identified = true;
else if(maxSimilarity > 0.93 && maxTracker->UnTracked > 5) identified = true;
if(identified){
has_id = true;
maxTracker->featureList.push_back(cvFeature);
maxTracker->UnTracked = (maxTracker->UnTracked > 1) ? -1 : 0;
maxTracker->offset = maxTracker->offset * 0.5 + (currentRt.center() - maxTracker->rt.center()) * 0.5;
maxTracker->rt = currentRt;
}
if(!has_id){
Tracker tracker(currentRt, cvFeature);
m_idTrackers.push_back(tracker);
}
}
for(int i = 0; i < m_idTrackers.size(); ++i){
// 삭제
if(m_idTrackers[i].UnTracked > 20 || (m_idTrackers[i].offset == Point{0,0} && m_idTrackers[i].UnTracked > 5)) m_idTrackers.erase(m_idTrackers.begin() + i);
// id 부여
cv::Scalar color;
std::stringstream m_id;
m_id << m_idTrackers[i].T_id << "-" << m_idTrackers[i].UnTracked;
// 초기화
if(m_idTrackers[i].UnTracked == -1) {
color = cv::Scalar(255,0,255);
m_idTrackers[i].UnTracked = 1;
}
else if(m_idTrackers[i].UnTracked == 0) {
color = cv::Scalar(0,0,255);
m_idTrackers[i].UnTracked = 1;
}
else {
color = cv::Scalar(255,255,0);
Rect currentRt = m_idTrackers[i].rt;
m_idTrackers[i].rt.LT = m_idTrackers[i].rt.LT + m_idTrackers[i].offset;
m_idTrackers[i].rt.RB = m_idTrackers[i].rt.RB + m_idTrackers[i].offset;
m_idTrackers[i].offset = m_idTrackers[i].offset * 0.5 + (m_idTrackers[i].rt.center()-currentRt.center()) * 0.5;
m_idTrackers[i].UnTracked++;
}
// rect, id 그리기
cv::rectangle(OutImage, cv::Point(m_idTrackers[i].rt.LT.x,m_idTrackers[i].rt.LT.y), cv::Point(m_idTrackers[i].rt.RB.x,m_idTrackers[i].rt.RB.y),color, 2);
cv::putText(OutImage, m_id.str(), cv::Point(m_idTrackers[i].rt.LT.x,m_idTrackers[i].rt.LT.y-10),1,2,color,3);
}
/// m_idTrackers Vector의 capacity 맞춰주기
m_idTrackers.shrink_to_fit();
if(bVerbose)
DisplayImage(OutImage, Input.cols, 0, false, true);
}
기존의 UnTracked시에 FaceRect를 추적하여 다음 위치를 예상하기 위한 이동 속도를 조절해주었다. 기존의 FaceRect 추적 속도는 현재 offset과 이전 프레임의 offset의 비율이 5대 5였으나 개선 후에 그 비율을 6.5대 3.5로 변경해주었다.
// 변경 전 = 5:5
maxTracker->offset = maxTracker->offset * 0.5 + (currentRt.center() - maxTracker->rt.center()) * 0.5;
m_idTrackers[i].offset = m_idTrackers[i].offset * 0.5 + (m_idTrackers[i].rt.center()-currentRt.center()) * 0.5;
// 변경 후 = 3.5: 6.5
maxTracker->offset = maxTracker->offset * 0.35 + (currentRt.center() - maxTracker->rt.center()) * 0.65;
m_idTrackers[i].offset = m_idTrackers[i].offset * 0.35 + (m_idTrackers[i].rt.center()-currentRt.center()) * 0.65;
// 1. Project.cpp
// Project.cpp: implementation of CProject (main project class you will write)
// Dept. Software Convergence, Kyung Hee University
// Prof. Daeho Lee, nize@khu.ac.kr
//
#include "KhuCvApp.h"
#include "Project.h"
#ifdef _MSC_VER
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
#endif
CProject::CProject() {
GetExecutionPath();
char m_ExePt[256];
strcat(m_ExePt, m_ExePath);
strcat(m_ExePath, "/version-RFB-320_without_postprocessing.onnx");
strcat(m_ExePt, "/mobilenetv2-7.onnx");
m_pUltraface = new UltraFace(m_ExePath, 320, 240);
m_MobileNet = cv::dnn::readNetFromONNX(m_ExePt);
}
CProject::~CProject() {
delete m_pUltraface;
}
void CProject::GetExecutionPath() {
wxFileName f(wxStandardPaths::Get().GetExecutablePath());
wxString appPath(f.GetPath());
wcscpy(m_ExePathUnicode, appPath);
strcpy(m_ExePath, appPath.c_str());
}
// tracking vector initialize
std::vector<Tracker> m_idTrackers;
int Tracker::cnt = 0;
void CProject::Run(cv::Mat Input, cv::Mat& Output, bool bFirstRun, bool bVerbose) {
if(bFirstRun){
Tracker::cnt = 0;
m_idTrackers.clear();
for(auto layer : m_MobileNet.getLayerNames()){
DlgPrintf("%s", layer.c_str());
}
}
std::vector<FaceInfo> faceList;
m_pUltraface->detect(Input, faceList);
cv::Mat OutImage = Input.clone();
for(auto list : faceList) {
cv::Mat Roi = Input(cv::Rect(cv::Point(list.x1, list.y1), cv::Point(list.x2, list.y2)));
cv::Mat DnnInput;
cv::resize(Roi, DnnInput, cv::Size(224, 224), 0, 0, cv::INTER_AREA);
cv::Mat inputBlob = cv::dnn::blobFromImage(DnnInput, 1 / 255., cv::Size(224, 224), cv::Scalar(128, 128, 128), false);
m_MobileNet.setInput(inputBlob);
// features cv::Mat 받아들이기
#ifndef __APPLE__
cv::Mat features = m_MobileNet.forward("mobilenetv20_features_pool0_fwd");
#else
cv::Mat features = m_MobileNet.forward("onnx_node!mobilenetv20_features_pool0_fwd");
#endif
cv::Mat cvFeature(40, 32, CV_32FC1);
memcpy(cvFeature.data, features.data, 1280 * sizeof(float));
Rect currentRt({list.x1,list.y1},{list.x2,list.y2});
bool has_id = false;
bool identified = false;
double maxSimilarity = 0;
Tracker *maxTracker;
for(int i = 0; i < m_idTrackers.size(); ++i){
if(m_idTrackers[i].rt.iou(currentRt) > 0.15) {
identified = true;
maxTracker = &m_idTrackers[i];
break;
}
else{
double similarity = m_idTrackers[i].GetCosineSimilarity(cvFeature);
if(similarity > maxSimilarity) {
maxSimilarity = similarity;
maxTracker = &m_idTrackers[i];
}
}
}
if(maxSimilarity > 0.945) identified = true;
else if(maxSimilarity > 0.93 && maxTracker->UnTracked > 5) identified = true;
if(identified){
has_id = true;
maxTracker->featureList.push_back(cvFeature);
maxTracker->UnTracked = (maxTracker->UnTracked > 1) ? -1 : 0;
maxTracker->offset = maxTracker->offset * 0.65 + (currentRt.center() - maxTracker->rt.center()) * 0.35;
maxTracker->rt = currentRt;
}
if(!has_id){
Tracker tracker(currentRt, cvFeature);
m_idTrackers.push_back(tracker);
}
}
for(int i = 0; i < m_idTrackers.size(); ++i){
// 삭제
if(m_idTrackers[i].UnTracked > 20 || (m_idTrackers[i].offset == Point{0,0} && m_idTrackers[i].UnTracked > 5)) m_idTrackers.erase(m_idTrackers.begin() + i);
// id 부여
cv::Scalar color;
std::stringstream m_id;
m_id << m_idTrackers[i].T_id << "-" << m_idTrackers[i].UnTracked;
// 초기화
if(m_idTrackers[i].UnTracked == -1) {
color = cv::Scalar(255,0,255);
m_idTrackers[i].UnTracked = 1;
}
else if(m_idTrackers[i].UnTracked == 0) {
color = cv::Scalar(0,0,255);
m_idTrackers[i].UnTracked = 1;
}
else {
color = cv::Scalar(255,255,0);
Rect currentRt = m_idTrackers[i].rt;
m_idTrackers[i].rt.LT = m_idTrackers[i].rt.LT + m_idTrackers[i].offset;
m_idTrackers[i].rt.RB = m_idTrackers[i].rt.RB + m_idTrackers[i].offset;
m_idTrackers[i].offset = m_idTrackers[i].offset * 0.35 + (m_idTrackers[i].rt.center()-currentRt.center()) * 0.65;
m_idTrackers[i].UnTracked++;
}
// rect, id 그리기
cv::rectangle(OutImage, cv::Point(m_idTrackers[i].rt.LT.x,m_idTrackers[i].rt.LT.y), cv::Point(m_idTrackers[i].rt.RB.x,m_idTrackers[i].rt.RB.y),color, 2);
cv::putText(OutImage, m_id.str(), cv::Point(m_idTrackers[i].rt.LT.x,m_idTrackers[i].rt.LT.y-10),1,2,color,3);
}
/// m_idTrackers Vector의 capacity 맞춰주기
m_idTrackers.shrink_to_fit();
if(bVerbose)
DisplayImage(OutImage, Input.cols, 0, false, true);
}
해당 프레임에서 detection된 FaceRect에 대하여 먼저 이전 프레임의 FaceRect와의 IOU값을 판별하여 Tracking을 시도한다. 만약 IOU로 Tracking이 되지 않을 경우, FaceSimilarity로 Tracking을 시도한다. 임계치의 IOU 혹은 FaceSimilarity를 만족하는 FaceRect가 발견될 경우 Tracker의 Rect, offset, UnTracked 등을 업데이트 해준다.
그러나 만약 OU 혹은 FaceSimilarity를 만족하는 FaceRect가 없을 경우, 새로운 객체로 판단하여 새로운 Tracker객체를 만들어준다.
판별 과정이 끝나면 각 Tracker의 UnTracked에 따라 색깔을 달리하여 Rectangle을 그려준다. UnTracked는 기본적으로 해당 FaceRect정보가 담긴 Tracker가 연속적으로 몇 번 Tracking하지 못하였는지를 의미한다. UnTracked가 0일 경우 이전 프레임에서 UnTracked된 적 없이 연속적으로 Tracking됐음을 뜻하며, -1일 경우 추적을 놓친 FaceRect를 찾았다는 것을 의미한다. 그리고 UnTracked가 1이상일 경우 이전 프레임에서 x번 이상 Tracking되지 못하였음을 뜻하고 이 UnTracked가 18번 이상이면 해당 Tracker는 삭제한다.
외부 FaceDetection DNN을 활용하여 FaceTracking하는 활동을 해보았다. 알고리즘을 짜는 데에만 일주일을 쓴 거 같다. 어떤 식으로 코드를 짜야 간결하고 잘 작동할까?라는 생각에 더 시간을 오래 쓴 거 같다. 솔직히 대략적인 기능을 어찌저찌 구현만 하면 이후에는 수월할 것이라 생각했는데, 위에 언급한 Error들에 직면했다.
3-1 Error는 한 3일간 고민을 했는데, 그 방법을 모르겠어서 교수님께 여쭤보았다. 교수님은 Tracking된 object는 따로 표시를 하여 2번 중복되지 않도록 하라고 하셨다. 그러나 나는 이 방법보다 더 간결한 방법이 있을 것이라 생각하였고, 3-1의 해결방법에 언급한 방법으로 해결하였다. 이후 3-2의 에러를 만났는데, 이 에러는 하루 정도 고민하여 해결하였다. 이전의 교수님이 설명해주신 maxSimilarity 아이디어에 조금 코드를 추가하여 에러를 해결할 수 있었다.
이번 프로젝트를 통해 원하는 기능을 내 방식대로 구현해보는 값진 경험을 할 수 있었으며, 아직은 부족하지만 코드를 간결하게 짜는 스킬도 조금은 터득할 수 있었다. 그리고 마주한 에러들을 혼자 생각해보고 (주변의 도움을 조금 받긴 했지만) 스스로 해결해보며, 문제를 마주했을 때 어떤 부분이 문제인지 정확히 인지하는 방식과 문제마다 어떤 식으로 해결해 나아가야 하는지에 대한 방식을 배울 수 있었다.
아직 Face Tracking 성능이 많이 부족하긴 하지만, 처음 한 것 치곤 만족스러운 결과를 낸 프로젝트였다. 추후, 컴퓨터 비전 및 딥러닝 관련 수업을 들으며, 이 성능을 계속 보완해나갈 계획이다.