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

news2025/2/28 0:11:58

简介

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

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

《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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

书与我

和书深深结缘&#xff0c;始于需求&#xff0c;得益于通勤时间长。 读什么书 一直没有停止过编码&#xff0c;工作性质也要求我必须了解很多的新技术&#xff0c;从踏上工作岗位后&#xff0c;就需要不停的看书。从《JAVA编程思想》、《java与模式》、《TCP/IP详解》、《深入…

131.分割回文串

// 定义一个名为Solution的类 class Solution {// 声明一个成员变量&#xff0c;用于存储所有满足条件的字符串子序列划分结果List<List<String>> lists new ArrayList<>(); // 声明一个成员变量&#xff0c;使用LinkedList实现的双端队列&#xff0c;用于临…

Windows下安装pip

一、下载pip 官网地址&#xff1a;https://pypi.org/project/pip/#files 1.1、pip工具查找方法 单击官网首页“PyPi”选项 在弹出来的搜索框中输入“pip” 选择最新的pip版本&#xff0c;点进去 下载pip安装包包 二、安装pip 解压“pip-24.0.tar.gz”&#xff0c;进…

【深度学习笔记】6_5 RNN的pytorch实现

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.5 循环神经网络的简洁实现 本节将使用PyTorch来更简洁地实现基于循环神经网络的语言模型。首先&#xff0c;我们读取周杰伦专辑歌词…

b站小土堆pytorch学习记录—— P23-P24 损失函数、反向传播和优化器

文章目录 一、损失函数1.简要介绍2.代码 二、优化器1.简要介绍2.代码 一、损失函数 1.简要介绍 可参考博客&#xff1a; 常见的损失函数总结 损失函数的全面介绍 pytorch学习之十九种损失函数 损失函数&#xff08;Loss Function&#xff09;是用来衡量模型预测输出与实际…

开发指南002-前后端信息交互规范-概述

前后端之间采用restful接口&#xff0c;服务和服务之间使用feign。信息交互遵循如下平台规范&#xff1a; 前端&#xff1a; 建立api目录&#xff0c;按照业务区分建立不同的.js文件&#xff0c;封装对后台的调用操作。其中qlm*.js为平台预制的接口文件&#xff0c;以qlm_user.…

离线数仓(五)【数据仓库建模】

前言 今天开始正式数据仓库的内容了, 前面我们把生产数据 , 数据上传到 HDFS , Kafka 的通道都已经搭建完毕了, 数据也就正式进入数据仓库了, 解下来的数仓建模是重中之重 , 是将来吃饭的家伙 ! 以及 Hive SQL 必须熟练到像喝水一样 ! 第1章 数据仓库概述 1.1 数据仓库概念 数…

【stm32 外部中断】

中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 中断优先级&#xff1a;当有多个中…

mybatis-plus整合spring boot极速入门

使用mybatis-plus整合spring boot&#xff0c;接下来我来操作一番。 一&#xff0c;创建spring boot工程 勾选下面的选项 紧接着&#xff0c;还有springboot和依赖我们需要选。 这样我们就创建好了我们的spring boot&#xff0c;项目。 简化目录结构&#xff1a; 我们发现&a…

未来城市:探索数字孪生在智慧城市中的实际应用与价值

目录 一、引言 二、数字孪生与智慧城市的融合 三、数字孪生在智慧城市中的实际应用 1、智慧交通管理 2、智慧能源管理 3、智慧建筑管理 4、智慧城市管理 四、数字孪生在智慧城市中的价值 五、挑战与展望 六、结论 一、引言 随着科技的飞速发展&#xff0c;智慧城市已…

R统计学2 - 数据分析入门问题21-40

往期R统计学文章&#xff1a; R统计学1 - 基础操作入门问题1-20 21. 如何对矩阵按行 (列) 作计算&#xff1f; 使用函数 apply() vec 1:20 # 转换为矩阵 mat matrix (vec , ncol4) # [,1] [,2] [,3] [,4] # [1,] 1 6 11 16 # [2,] 2 7 12 17 # [3,] …

前端框架的发展历史介绍

前端框架的发展历史是Web技术进步的一个重要方面。从最初的简单HTML页面到现在的复杂单页应用程序&#xff08;SPA&#xff09;&#xff0c;前端框架和库的发展极大地推动了Web应用程序的构建方式。以下是一些关键的前端框架和库&#xff0c;以及它们的发布年份、创建者和主要特…

UnicodeDecodeError: ‘gbk‘和Error: Command ‘pip install ‘pycocotools>=2.0

今天重新弄YOLOv5的时候发现不能用了&#xff0c;刚开始给我报这个错误 subprocess.CalledProcessError: Command ‘pip install ‘pycocotools&#xff1e;2.0‘‘ returned non-zero exit statu 说这个包安装不了 根据他的指令pip install ‘pycocotools&#xff1e;2.0这个根…

从零开始:神经网络(2)——MP模型

声明&#xff1a;本文章是根据网上资料&#xff0c;加上自己整理和理解而成&#xff0c;仅为记录自己学习的点点滴滴。可能有错误&#xff0c;欢迎大家指正。 神经元相关知识&#xff0c;详见从零开始&#xff1a;神经网络——神经元和梯度下降-CSDN博客 1、什么是M-P 模型 人…

CorelDRAW Graphics Suite2024专业图形设计软件Windows/Mac最新25.0.0.230版

CorelDRAW Graphics Suite 2024是一款专业的图形设计软件&#xff0c;它集成了CorelDRAW Standard 2024和其他高级图形处理工具&#xff0c;为用户提供了全面的图形设计和编辑解决方案。 该软件拥有强大的矢量编辑功能&#xff0c;用户可以轻松创建和编辑矢量图形&#xff0c;…

数字化转型导师坚鹏:科技金融政策、案例及数字化营销

科技金融政策、案例及数字化营销 课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚科技金融有哪些利好政策&#xff1f; 不知道科技金融有哪些成功案例&#xff1f; 不知道科技金融如何数字化营销&#xff1f; 课程特色&#xff1a; 以案例的方式解读原…

聚类简单讲解

聚类任务 聚类任务是指将一组数据分成多个不同的组&#xff08;或簇&#xff09;&#xff0c;使得同一组内的数据点彼此相似&#xff0c;而不同组之间的数据点尽可能不相似的过程。聚类任务的目标是发现数据中的固有结构&#xff0c;而不需要事先知道数据的类别信息。聚类算法…

IntelliJ IDEA Dev 容器

​一、dev 容器 开发容器&#xff08;dev 容器&#xff09;是一个 Docker 容器&#xff0c;配置为用作功能齐全的开发环境。 IntelliJ IDEA 允许您使用此类容器来编辑、构建和运行您的项目。 IntelliJ IDEA 还支持多个容器连接&#xff0c;这些连接可以使用 Docker Compose …

多种方法求解数组排序

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary_walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

Day29:安全开发-JS应用DOM树加密编码库断点调试逆向分析元素属性操作

目录 JS原生开发-DOM树-用户交互 JS导入库开发-编码加密-逆向调试 思维导图 JS知识点&#xff1a; 功能&#xff1a;登录验证&#xff0c;文件操作&#xff0c;SQL操作&#xff0c;云应用接入&#xff0c;框架开发&#xff0c;打包器使用等 技术&#xff1a;原生开发&#x…