(위의 사진은 SORT의 흐름도이다.) SORT는 실시간 추적을 위해 object들을 효율적으로 연관지어주는 MOT(Multi Object Tracking)이다. 이 때 MOT란 다수의 객체들을 추적하기 위해 detection 결과 간 연관(association)을 수행하는 과정이다.
SORT는 Simple Online and Realtime Tracking의 약자이다. 여기서 나타나는 Online Tracking 방식은 미래 프레임에 대한 정보 없이 과거와 현재 프레임의 객체 detection 정보만을 사용하여 연관 관계에 대한 Tracking을 수행하는 방식이다. 따라서 과거 혹은 현재 프레임에 대한 detection이 부족하여 제대로된 association이 이뤄지지 못하면, 제대로된 Tracking이 이루어지지 못한다는 단점을 가지고 있다.
IOU(Intersection over Union)란 두 겹치는 사각형의 교집합 사각형의 넓이를 두 사각형의 합집합 사각형의 넓이로 나눈 값. 즉, 겹치는 두 사각형의 교집합과 합집합의 비율. 겹치는 영역이 커질 수록 IOU 값이 높다.
Target Association은 MOT 방법을 기반으로 한 tracking-by-detection의 핵심 단계이다. IOU를 Metric으로 사용하여 그림에서는 IOU Match라고 나타낸다. 이 단계에서는 IOU 유사도를 구한 후, 추적되고 있던 개체와 아닌 개체를 분류한다. 이 때 추적되고 있지 않던 개체는 사라진 개체, 새로 등장한 개체 이 두 가지로 나뉠 수 있다.
실제 상황에서 발생하는 Occulusion이나 Different View Point, ID switching은 지속적인 추적을 방해하며, 한 번 사라진 개체(추적을 놓친 개체)로 하여금 추적 불가능하게 만든다. SORT는 한 번 추적을 놓치면, 다시 동일한 객체가 나타났을 때 새로운 객체로 인식하는 한계를 가지므로 추후 DeepSORT방식을 이용하여 SORT의 한계를 보완해야 한다.
[참고] : IOU란?
Project.cpp의 Run함수를 살펴보면, 현재 프레임에서 detection된 FaceRect객체들은 FaceInfo객체로 나타남을 볼 수 있다. FaceInfo는 얼굴을 인식한 Rect(사각형)의 좌표를 저장한 객체로써 (x1,y1)은 LeftTop, (x2,y2)는 RightBottom의 좌표를 의미한다.
현재 프레임에서 detection된 각 FaceRect와 이전 프레임의 FaceRect의 IOU를 판별하여 특정 IOU값 이상을 만족하는 Rect가 있다면 즉, 겹치는 사각형이 있다면, 이는 동일한 인물이 움직여서 생긴 Rect의 움직임으로 판단할 수 있으므로 별도의 Vector(m_idTrackers)에 넣어준다. 화면상에 존재하는 Rect에 대한 IOU판별을 끝내면 m_idTrackers Vector내의 객체 각각에 대하여 FaceRect와 id를 화면에 그려준다.
이 과정을 반복하면 오직 IOU를 이용하여 해당 FaceRect의 움직임을 추적할 수 있다.
// tracking
class Point{
public:
float x,y;
Point(){}
Point(float a, float b): x(a), y(b){}
};
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;
}
};
class Tracker{
public:
static int cnt;
int T_id;
Rect rt;
int time = 0;
Tracker(){}
Tracker(Rect current):T_id(cnt++), rt(current){}
void update_rt(Rect other){
rt = other;
}
void drawText(cv::Mat OutImage){
std::stringstream m_id;
m_id << T_id;
cv::putText(OutImage, m_id.str(), cv::Point(rt.LT.x,rt.LT.y-10),1,2,cv::Scalar(0,0,255),3);
}
};
현재 프레임의 FaceRect를 추적하기 위해 Tracker라는 class를 만든다. 이 Tracker객체는 추적하는 각 개체마다 id를 부여하여 구분하기 위해 m_id멤버 변수를 static으로 선언하였으며, 각 객체가 생성될 때마다 1씩 증가된 id가 부여된다. 이 외에도 추적하는 Rect를 저장하기 위한 Rect, 그리고 화면 상에 몇 번 연속적으로 나타나지 않았는지를 세는 time instance를 갖는다.
#include <sstream>
// 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::rectangle(OutImage, cv::Point(list.x1, list.y1), cv::Point(list.x2, list.y2), cv::Scalar(0, 0, 255), 2);
/// tracking - start
Rect currentRt({list.x1,list.y1},{list.x2,list.y2});
bool has_id = false;
for(auto &tracker : m_idTrackers){
float c_iou = currentRt.iou(tracker.rt);
if(c_iou >= 0.25){
tracker.update_rt(currentRt);
has_id = true;
tracker.time = 0;
break;
}
}
if(!has_id){ /// 현재 Rect가 vector안에 없다면
Tracker tracker(currentRt);
m_idTrackers.push_back(tracker);
}
}
for(int i = 0; i < m_idTrackers.size(); ++i)
if(m_idTrackers[i].time > 4) m_idTrackers.erase(m_idTrackers.begin() + i);
if(m_idTrackers[i].time == 0) m_idTrackers[i].drawText(OutImage);
m_idTrackers[i].time++;
}
/// m_idTrackers Vector의 capacity 맞춰주기
m_idTrackers.shrink_to_fit();
/// tracking - end
if(bVerbose)
DisplayImage(OutImage, Input.cols, 0, false, true);
}
현재 프레임의 FaceRect마다 이전 프레임의 FaceRect정보가 담긴 m_idTrackers(vector)안의 모든 Tracker객체와 IOU를 비교한다. 만약 임계치 이상의 IOU값을 만족하는 갖는 Tracker가 있다면, Tracker의 Rect를 업데이트 해주고 time = 0으로 초기화해준다. 만약 m_idTrackers 안에 IOU를 만족하는 Tracker Rect가 없다면, 이 FaceRect를 새로운 객체로 인식하고 해당 Rect를 갖는 Tracker를 m_idTrackers에 추가한다.
화면의 모든 Rect에 대한 IOU판별이 끝나면, m_idTrackers에 존재하는 Tracker의 id를 화면에 그련준다(drawText). 이 때, 화면에 3번 이상 나타나지 않는 Rect Tracker는 m_idTrackers에서 제거한다. 이후, m_idTrackers vector의 capacity를 줄여주기 위해 shrink_to_fit()함수를 이용한다.
위의 과정을 반복함으로써, IOU를 이용하여 얼굴 Tracking이 가능해진다. 하지만 얼굴이 장애물에 가려짐으로써 3번 이상 나타나지 않을 경우 동일한 사람에 대한 Tracking이 불가능해지며, 다른 사람에게 FaceRect가 옮겨가는 현상까지 발생한다.
이는 추후 얼굴 특징을 이용하여 DeepSORT Tracking 방식을 이용함으로써 어느 정도 극복 가능할 것으로 보인다.