计算机视觉——P2PNet基于点估计的人群计数原理与C++模型推理

简介

人群计数是计算机视觉领域的一个核心任务,旨在估算静止图像或视频帧中的行人数量。在过去几十年中,研究人员在这个领域投入了大量的精力,并在提高现有主流基准数据集性能方面取得了显著进展。然而,训练卷积神经网络需要大规模且高质量的标记数据集,而标记像素级别的行人位置成本昂贵,令人望而却步。

此外,由于数据分布之间存在领域转移,即在标签丰富的数据领域(源领域)上训练的模型无法很好地泛化到另一个标签稀缺的数据领域(目标领域),这严重限制了现有方法的实际应用。

《Rethinking Counting and Localization in Crowds: A Purely Point-Based Framework》提出了一个全新的基于点的框架,可以同时用于人群计数和个体定位。与传统的基于定位的方法不同,该框架完全依赖于点级别的表示,避免了中间表示(如密度图或伪目标框)可能引入的误差,并提出了一种新的性能评价指标,称为密度归一化平均精度,以更全面、更准确地评估模型性能。

研究团队还提出了一个名为点对点网络(P2PNet)的示例模型,该模型直接预测一系列人头点的集合来定位图像中的人群个体,避免了冗余步骤,并实现了与真实人工标注一致的定位。通过深入分析,研究者发现了实现该方法的核心策略,即为预测的候选点分配最优的学习目标,并通过基于匈牙利算法的一对一匹配策略来实现。实验证明,P2PNet在人群计数基准上显著超越了现有的最先进方法,并取得了非常高的定位精度。
在这里插入图片描述

网络结构

在这里插入图片描述
P2PNet的网络结构并不复杂。它建立在VGG16的基础上,并引入了一个上采样路径来获取细粒度的深度特征图,类似于特征金字塔网络(FPN)。然后,它利用两个分支来同时预测一组点及其置信度分数。在我们的流程中,关键步骤是确保预测点和真实点之间的一对一匹配,这决定了这些预测点的学习目标。

预测

在这里插入图片描述
Point proposals的初始化有两种方式,一种是全部初始化在中心点,另一种是网格式分布。Feature Map上的一个pixel对应着原图上的一个patch(sxs),并在这上面初始化K个Point proposal。
在这里插入图片描述
这些point proposals的坐标加上回归头分支得到的偏置就可以得到预测点的坐标。

匹配与损失计算

在这里插入图片描述
预测点与真实点之间的匹配用的是匈牙利算法,代价矩阵的计算方式如上图,它是坐标偏差与置信度分数的一个综合的考量。
在这里插入图片描述
分类损失函数是交叉熵损失,回归损失函数是欧氏距离。

在这里插入图片描述
文章还提出了一种新的度量指标nAP。nAP是根据平均精度计算出来的,平均精度是精度-召回率(PR)曲线下的面积。具体来说,给定所有预测的头部点ˆP,我们首先将其置信度得分从高到低进行排序。然后,根据预定义的密度感知标准,依次确定所调查的点是TP或FP。密度感知标准如上左图所示。

实验结果

在这里插入图片描述
研究者考虑了从ShanghaiTech Part A到Trancos的实验,如上表所示。显然,所提出的方法比现有的适应方法提高了2.9%。
在这里插入图片描述
由双重鉴别器生成的不同级别(分别为像素、补丁像素、补丁、图像)级别分数的可视化。图中的正方形代表一个标量。注意白色方块代表1,黑色方块代表0。

实现代码

训练代码可以参考:https://github.com/TencentYoutuResearch/CrowdCounting-P2PNet

推理代码可以参考下面的代码:

#include <sstream>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>

using namespace cv;
using namespace dnn;
using namespace std;

struct CrowdPoint
{
	cv::Point pt;
	float prob;
};

static void shift(int w, int h, int stride, vector<float> anchor_points, vector<float>& shifted_anchor_points)
{
	vector<float> x_, y_;
	for (int i = 0; i < w; i++)
	{
		float x = (i + 0.5) * stride;
		x_.push_back(x);
	}
	for (int i = 0; i < h; i++)
	{
		float y = (i + 0.5) * stride;
		y_.push_back(y);
	}

	vector<float> shift_x((size_t)w * h, 0), shift_y((size_t)w * h, 0);
	for (int i = 0; i < h; i++)
	{
		for (int j = 0; j < w; j++)
		{
			shift_x[i * w + j] = x_[j];
		}
	}
	for (int i = 0; i < h; i++)
	{
		for (int j = 0; j < w; j++)
		{
			shift_y[i * w + j] = y_[i];
		}
	}

	vector<float> shifts((size_t)w * h * 2, 0);
	for (int i = 0; i < w * h; i++)
	{
		shifts[i * 2] = shift_x[i];
		shifts[i * 2 + 1] = shift_y[i];
	}

	shifted_anchor_points.resize((size_t)2 * w * h * anchor_points.size() / 2, 0);
	for (int i = 0; i < w * h; i++)
	{
		for (int j = 0; j < anchor_points.size() / 2; j++)
		{
			float x = anchor_points[j * 2] + shifts[i * 2];
			float y = anchor_points[j * 2 + 1] + shifts[i * 2 + 1];
			shifted_anchor_points[i * anchor_points.size() / 2 * 2 + j * 2] = x;
			shifted_anchor_points[i * anchor_points.size() / 2 * 2 + j * 2 + 1] = y;
		}
	}
}
static void generate_anchor_points(int stride, int row, int line, vector<float>& anchor_points)
{
	float row_step = (float)stride / row;
	float line_step = (float)stride / line;

	vector<float> x_, y_;
	for (int i = 1; i < line + 1; i++)
	{
		float x = (i - 0.5) * line_step - stride / 2;
		x_.push_back(x);
	}
	for (int i = 1; i < row + 1; i++)
	{
		float y = (i - 0.5) * row_step - stride / 2;
		y_.push_back(y);
	}
	vector<float> shift_x((size_t)row * line, 0), shift_y((size_t)row * line, 0);
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < line; j++)
		{
			shift_x[i * line + j] = x_[j];
		}
	}
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < line; j++)
		{
			shift_y[i * line + j] = y_[i];
		}
	}
	anchor_points.resize((size_t)row * line * 2, 0);
	for (int i = 0; i < row * line; i++)
	{
		float x = shift_x[i];
		float y = shift_y[i];
		anchor_points[i * 2] = x;
		anchor_points[i * 2 + 1] = y;
	}
}
static void generate_anchor_points(int img_w, int img_h, vector<int> pyramid_levels, int row, int line, vector<float>& all_anchor_points)
{
	vector<pair<int, int> > image_shapes;
	vector<int> strides;
	for (int i = 0; i < pyramid_levels.size(); i++)
	{
		int new_h = floor((img_h + pow(2, pyramid_levels[i]) - 1) / pow(2, pyramid_levels[i]));
		int new_w = floor((img_w + pow(2, pyramid_levels[i]) - 1) / pow(2, pyramid_levels[i]));
		image_shapes.push_back(make_pair(new_w, new_h));
		strides.push_back(pow(2, pyramid_levels[i]));
	}

	all_anchor_points.clear();
	for (int i = 0; i < pyramid_levels.size(); i++)
	{
		vector<float> anchor_points;
		generate_anchor_points(pow(2, pyramid_levels[i]), row, line, anchor_points);
		vector<float> shifted_anchor_points;
		shift(image_shapes[i].first, image_shapes[i].second, strides[i], anchor_points, shifted_anchor_points);
		all_anchor_points.insert(all_anchor_points.end(), shifted_anchor_points.begin(), shifted_anchor_points.end());
	}
}

class P2PNet
{
public:
	P2PNet(const float confThreshold = 0.5)
	{
		this->confThreshold = confThreshold;
		this->net = readNet("SHTechA.onnx");
	}
	void detect(Mat& frame);
private:
	float confThreshold;
	Net net;
	Mat preprocess(Mat srcimgt);
	const float mean[3] = { 0.485, 0.456, 0.406 };
	const float std[3] = { 0.229, 0.224, 0.225 };
	vector<String> output_names = { "pred_logits", "pred_points" };
};


Mat P2PNet::preprocess(Mat srcimg)
{
	int srch = srcimg.rows, srcw = srcimg.cols;
	int new_width = srcw / 128 * 128;
	int new_height = srch / 128 * 128;
	Mat dstimg;
	cvtColor(srcimg, dstimg, cv::COLOR_BGR2RGB);
	resize(dstimg, dstimg, Size(new_width, new_height), INTER_AREA);
	dstimg.convertTo(dstimg, CV_32F);
	int i = 0, j = 0;
	for (i = 0; i < dstimg.rows; i++)
	{
		float* pdata = (float*)(dstimg.data + i * dstimg.step);
		for (j = 0; j < dstimg.cols; j++)
		{
			pdata[0] = (pdata[0] / 255.0 - this->mean[0]) / this->std[0];
			pdata[1] = (pdata[1] / 255.0 - this->mean[1]) / this->std[1];
			pdata[2] = (pdata[2] / 255.0 - this->mean[2]) / this->std[2];
			pdata += 3;
		}
	}
	return dstimg;
}

void P2PNet::detect(Mat& frame)
{
	const int width = frame.cols;
	const int height = frame.rows;
	Mat img = this->preprocess(frame);
	const int new_width = img.cols;
	const int new_height = img.rows;
	Mat blob = blobFromImage(img);
	this->net.setInput(blob);
	vector<Mat> outs;
	//this->net.forward(outs, this->net.getUnconnectedOutLayersNames());
	this->net.forward(outs, output_names);

	vector<int> pyramid_levels(1, 3);
	vector<float> all_anchor_points;
	generate_anchor_points(img.cols, img.rows, pyramid_levels, 2, 2, all_anchor_points);
	const int num_proposal = outs[0].cols;
	int i = 0;
	float* pscore = (float*)outs[0].data;
	float* pcoord = (float*)outs[1].data;
	vector<CrowdPoint> crowd_points;
	for (i = 0; i < num_proposal; i++)
	{
		if (pscore[i] > this->confThreshold)
		{
			float x = (pcoord[i] + all_anchor_points[i * 2]) / (float)new_width * (float)width;
			float y = (pcoord[i + 1] + all_anchor_points[i * 2 + 1]) / (float)new_height * (float)height;
			crowd_points.push_back({ Point(int(x), int(y)), pscore[i] });
		}
		pcoord += 2;
	}
	cout << "have " << crowd_points.size() << " people" << endl;
	for (i = 0; i < crowd_points.size(); i++)
	{
		cv::circle(frame, crowd_points[i].pt, 2, cv::Scalar(0, 0, 255), -1, 8, 0);
	}
}

int main()
{
	P2PNet net(0.3);
	string imgpath = "2.jpeg";
	Mat srcimg = imread(imgpath);
	net.detect(srcimg);

	static const string kWinName = "dst";
	namedWindow(kWinName, WINDOW_NORMAL);
	imshow(kWinName, srcimg);
	waitKey(0);
	destroyAllWindows();
}

实现结果:
在这里插入图片描述
在这里插入图片描述
工程源码下载:https://download.csdn.net/download/matt45m/88936724?spm=1001.2014.3001.5503

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/444632.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

.net6Api后台+uniapp导出Excel

之前的这个是vue3写法&#xff0c;后端是.net6Api.net6Api后台VUE3前端实现上传和下载文件全过程_vue3 下载文件-CSDN博客 在现在看来似乎搞的复杂了&#xff0c;本次记录一下.net6Api后台uniapp导出Excel。 后端和之前的不一样&#xff0c;前端也和之前的不一样&#xff0c;…

鸿蒙培训开发:就业市场的新热点~

金三银四在即&#xff0c;随着春节假期结束&#xff0c;各行各业纷纷复工复产&#xff0c;2024年的春季招聘市场也迎来了火爆的局面。最近发布的《2024年春招市场行情周报&#xff08;第一期&#xff09;》显示&#xff0c;尽管整体就业市场仍处于人才饱和状态&#xff0c;但华…

IPsec VPN之安全联盟

一、何为安全联盟 IPsec在两个端点建立安全通信&#xff0c;此时这两个端点被称为IPsec对等体。安全联盟&#xff0c;即SA&#xff0c;是指通信对等体之间对某些要素的约定&#xff0c;定义了两个对等体之间要用何种安全协议、IP报文的封装方式、加密和验证算法。SA是IPsec的基…

Linux高级IO之poll

(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e;嗨&#xff01;你好这里是ky233的主页&#xff1a;这里是ky233的主页&#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ 目录 一、poll函数接口 1.接口 2.poll做了什么工…

2024 年中国高校大数据挑战赛赛题 C:用户对博物馆评论的情感分析完整思路以及源代码分享

博物馆是公共文化服务体系的重要组成部分。国家文物局发布&#xff0c; 2021 年我国新增备案博物馆 395 家&#xff0c;备案博物馆总数达 6183 家&#xff0c;排 名全球前列&#xff1b;5605 家博物馆实现免费开放&#xff0c;占比达 90%以上&#xff1b;全国 博物馆举办展览 3…

docker 子网

当需要给容器分配指定 ip &#xff0c;为避免ip 冲突&#xff0c;指定容器子网处理 创建 subnet 子网 docker network create --subnet 10.0.0.0/24 --gateway 10.0.0.1 subnet-testdocker network ls NETWORK ID NAME DRIVER SCOPE ... f582ecf297bc sub…

【杂记】IDEA和Eclipse如何查看GC日志

1.Eclipse查看GC日志 1.1 右击代码编辑区 -> Run As -> Run Configurations 1.2 点击Arguments栏 -> VM arguments:区域填写XX参数 -> Run 1.3 控制台输出GC详细日志 2.IDEA查看GC日志 2.1 鼠标右击代码编辑器空白区域&#xff0c;选择Edit 项目名.main()... 2.…

Mysql命令行客户端

命令行客户端 操作数据库操作数据表 操作数据库 mysql> create database mike charsetutf8; Query OK, 1 row affected (0.01 sec) mysql> show databases; -------------------- | Database | -------------------- | information_schema | | mike …

【C语言】linux内核tcp_write_xmit和tcp_write_queue_purge

tcp_write_xmit 一、讲解 这个函数 tcp_write_xmit 是Linux内核TCP协议栈中的一部分&#xff0c;其基本作用是发送数据包到网络。这个函数会根据不同情况推进发送队列的头部&#xff0c;确保只要远程窗口有空间&#xff0c;就可以发送数据。 下面是对该函数的一些主要逻辑的中…

实验二(一):IPV4编址及IPV4路由基础实验

一实验介绍 1.关于本实验 IPv4( Internet Protocol Version 4)是 TCP/IP 协议族中最为核心的协议之一。 它工作在 TCP/IP参考模型的网际互联层&#xff0c;该层与 OSI参考模型的网络层相对应。 网络层提供了无连接数据传输服务&#xff0c;即网络在发送分组时不需要先建立连…

算法设计与分析(超详解!) 第一节 算法概述

1.算法的定义 算法的非形式化定义&#xff1a;算法是规则的有限集合&#xff0c;是为解决特定问题而规定的一系列操作。 可以理解为&#xff1a;算法&#xff08;algorithm&#xff09;是指在解决问题时&#xff0c;按照某种机械的步骤一定可以得到问题的结果&#xff08;有的…

数学建模-动态规划(美赛运用)

动态规划模型的要素是对问题解决的抽象&#xff0c;其可分为&#xff1a; 阶段。指对问题进行解决的自然划分。例如&#xff1a;在最短线路问题中&#xff0c;每进行走一步的决策就是一个阶段。 状态。指一个阶段开始时的自然状况。例如&#xff1a;在最短线路问题中&#xff…

嵌入式Qt 制作一个登录对话框

一.登录对话框需求分析 二.代码实现 main.c&#xff1a; #include <QtGui/QApplication> #include "widget.h"int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); }Widget.h&#xff1a; #ifndef _WIDGET_H_…

EDA软件

EDA软件 EDA概念IC类EDA&#xff08;芯片EDA软件&#xff09;数字芯片和模拟芯片的区别模拟芯片产品种类IC设计类数字电路设计模拟电路设计 IC制造类IC封装类 PCB类EDA&#xff08;板级EDA软件&#xff09;Mentor公司板级EDACadence公司板级EDAAltium公司&#xff08;已被日本瑞…

bug_java

文章目录 1.创建Maven时&#xff1a; idea报错为&#xff1a;java&#xff1a;错误&#xff1a;不支持发行版本52. Springbot启动报错-类文件具有错误的版本 61.0, 应为 52.0 1.创建Maven时&#xff1a; idea报错为&#xff1a;java&#xff1a;错误&#xff1a;不支持发行版本…

贪吃蛇(C语言实现)

贪食蛇&#xff08;也叫贪吃蛇&#xff09;是一款经典的小游戏。 —————————————————————— 本博客实现使用C语言在Windows环境的控制台中模拟实现贪吃蛇小游戏。 实行的基本功能&#xff1a; • 贪吃蛇地图的绘制 • 蛇吃食物的功能&#xff08;上、…

【重新定义matlab强大系列十七】Matlab深入浅出长短期记忆神经网络LSTM

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

Netty Review - 探究Netty服务端主程序无异常退出的背后机制

文章目录 概述故障场景尝试改进问题分析铺垫&#xff1a; Daemon线程Netty服务端启动源码分析逻辑分析 如何避免Netty服务端意外退出最佳实践 概述 在使用Netty进行服务端程序开发时&#xff0c;初学者可能会遇到各种问题&#xff0c;其中之一就是服务端意外退出的问题。这种问…

基于机器学习的工业用电量预测完整代码数据

视频讲解: 毕业设计:算法+系统基于机器学习的工业用电量预测完整代码数据_哔哩哔哩_bilibili 界面展示: 结果分析与展示: 代码: from sklearn import preprocessing import random from sklearn.model_selection import train_test_split from sklearn.preprocessing…

oracle基础-多表关联查询 备份

一、概述 在实际应用系统开发中会设计多个数据表&#xff0c;每个表的信息不是独立存在的&#xff0c;而是若干个表之间的信息存在一定的关系&#xff0c;当用户查询某一个表的信息时&#xff0c;很可能需要查询关联数据表的信息&#xff0c;这就是多表关联查询。SELECT语句自身…