項目展示:基於ReID的行人計數

簡介

現實生活中有這樣的場景,攝像頭並不能覆蓋一個區域的所有地方,現在想不借助其他手段,僅通過攝像頭拍攝到的圖像,計算一個區域中的行人數目。

首先不能使用人羣計數的方法,因爲有些有人的地方沒有被攝像頭覆蓋。人羣計數或者人頭計數只能計算拍攝到的人數。

該項目使用的策略是:

  • 僅處理在入口處攝像頭拍攝的畫面
  • 因爲攝像頭是固定的,由人爲劃定入口位置,一條直線。
  • 行人檢測用來檢測行人位置。
  • 行人重識別用來判斷入口內的行人是否被加入到數據庫中,如果有則用來刷新信息,如果沒有則添加,同時計數加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幀保存目標形象來獲得更加可靠的目標特徵向量。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章