簡介
現實生活中有這樣的場景,攝像頭並不能覆蓋一個區域的所有地方,現在想不借助其他手段,僅通過攝像頭拍攝到的圖像,計算一個區域中的行人數目。
首先不能使用人羣計數的方法,因爲有些有人的地方沒有被攝像頭覆蓋。人羣計數或者人頭計數只能計算拍攝到的人數。
該項目使用的策略是:
- 僅處理在入口處攝像頭拍攝的畫面
- 因爲攝像頭是固定的,由人爲劃定入口位置,一條直線。
- 行人檢測用來檢測行人位置。
- 行人重識別用來判斷入口內的行人是否被加入到數據庫中,如果有則用來刷新信息,如果沒有則添加,同時計數加1;同時判斷入口外的行人,是否在數據庫中,如果有,則從數據庫中刪除,計數減1。
項目使用當前sota的EANet作爲ReID模型,使用YoloV3作爲行人檢測網絡。
項目部分代碼展示
YoloV3使用tensorflow所支持的pb模型,然後使用TF C++ API調用,EANet使用LibTorch調用pth文件。
Yolov3模型類定義
class YoloV3 : public Model_Loader_Base {
public:
YoloV3() {};
bool predict(vector<BBoxes>&);
std::string output_node = "post_processing/result";
Tensor t_in = Tensor(DT_FLOAT, TensorShape({ 1,544,544,3 }));
void preProcessing(const Mat &ori);
void drawBBoxes(Mat&, vector<BBoxes>&);
vector<std::pair<string, Tensor> > inputs_for_yolo;
};
EANet模型類定義
class EANet {
public:
EANet(string path) { model_path = path; calDistacneModel = new CalCosDistance("calculateCosHead.pt");
calDistacneModel->InitModel();
};
...
...
void preprocessing(const Mat &img);
void cropPerson(const Mat &, const vector<TfModel::BBoxes> &bboxes, vector<Mat> &personContainer);
at::Tensor predict();
vector<int> countPersonNumber(vector<Mat> &query);
void calFunction(Point p1, Point p2);
bool calDirection( Point center, int);
vector<float> lineFunction;
vector<int> countPersonNumberByRegion(vector<Mat> &query, Point &p1, Point &p2, int direction, vector<TfModel::BBoxes> &bboxes);
void drawInfo(Mat &frame, vector<int> &recorder, vector<TfModel::BBoxes> bboxes);
vector<int> countPersonNumberByRegionV2(vector<Mat> &query, Point &p1, Point &p2, int direction, vector<TfModel::BBoxes> &bboxes);
int delPersonByRegion(const Mat &query);
void writeGalleryForEachFrame();
};
我省略了一些變量,僅僅展示幾個比較關鍵的函數功能。
-
經過行人檢測得到行人位置,如何得知行人是在入口內還是入口外?
用戶指定一個直線(非平行)作爲入口,同時還需給出正方向的位置。
void calFunction(Point p1, Point p2) 函數計算兩個點代表的直線方程。 行人的位置用點表示(點可取在中點偏下位置)。該點帶入方程,如果點在直線上方,計算得到正值,如果點在直線下方,計算得到負值。用戶指定正值還是負值爲正方向,正方向即點在門內。以此得知行人和門的關係。
其中 bool calDirection( Point center, int)就是用來判斷行人和門的關係。 -
行人信息如何保存
行人一旦進入入口,EANet會通過餘弦相似度判斷該人和數據庫中其他人的相似度,從而得到該人是否本來就在門內。如果不是則計數加一,同時把該人這一幀的形象以及對應的特徵向量(經過EANet)保存下來(內存中)。 -
行人首次被保存的形象也許不夠表達這個人的全面信息
我們不僅僅保存行人出現的第一個形象,連續6幀保存他的形象。用一個長度爲6的隊列,保存距離當前幀前的6幀的形象。這6個形象的特徵向量會按照比例加權,得到更加能代表行人的特徵向量。
//如果 min_index不爲-1,則說明當前query和gallery中有一個目標相同,且min_index指向距離最小的gallery。
if (min_index != -1) {
count.push_back(0);
// 如果當前的query和某一個gallery的距離小於0.1,非常確信這是同一個人,則把query添加進gallery
if (galleryVectorMap[min_index].size() < 6 && min_distance < 0.12)
{
galleryVectorMap[min_index].push_back(queryVector);
galleryMap[min_index].push_back(query[i]);
}
else if(galleryVectorMap[min_index].size() == 6 && min_distance < 0.12){
// 保持每一個人的gallery最多有6張。
galleryVectorMap[min_index].push_back(queryVector);
galleryVectorMap[min_index].pop_front();
galleryMap[min_index].push_back(query[i]);
galleryMap[min_index].pop_front();
}
else if(galleryVectorMap[min_index].size() > 6){
std::cerr << "max size of gallery vector container is 6" << endl;
exit(-1);
}
}
//如果 min_index爲-1,則說明當前query和gallery中的任何一個目標都不同,則把這個query加入gallery中。
else if (min_index == -1)
{
int new_index = galleryMap.size();
assert(new_index == galleryVectorMap.size());
if (priority_index.empty()) {
// 空
}
else {
new_index = priority_index.front();
priority_index.pop();
}
count.push_back(1);
galleryMap[new_index].push_back(query[i]);
galleryVectorMap[new_index].push_back(queryVector);
}
結果展示
該視頻場景非實際應用場景。黃色的線代表入口位置。綠色框代表在入口外。紅色代表這個人已經被計數,在數據庫中保存。藍色代表目標第一次進門內。
隨着目標漸變移動,他的形象會和之前的不一樣,所以我們採用連續6幀保存目標形象來獲得更加可靠的目標特徵向量。