2024年7月

说明

新设计了1个KU040 FPGA板子,回来之后接上JTAG FPGA不识别。做如下检查:

1、电源测试点均正常;

2、查看贴片是否有漏焊,检查无异常,设计上NC的才NC;

3、反复检查JTAG接线是否异常,贴片是否异常;

上述检查均无问题,开始查看原理图,逐个对照XILINX手册进行研究。

其中发现 CFGBVS 在设计图中接了地,对照XILINX 手册
https://www.amd.com/content/dam/xilinx/support/documents/user_guides/ug570-ultrascale-configuration.pdf

In the Kintex UltraScale and Virtex UltraScale FPGAs, the configuration banks voltage select
(CFGBVS) pin must be set to High or Low to determine the I/O voltage support for the pins
in bank 0, and for the multi-function pins in bank 65 when they are used during
configuration. The CFGBVS is a logic input pin referenced between VCCO_0 and GND. When
the CFGBVS pin is connected to the VCCO_0 supply of 3.3V or 2.5V, the configuration I/O
support operation at 3.3V or 2.5V. When the CFGBVS pin is connected to GND, the
configuration I/O support operation at 1.8V or 1.5V. There is no CFGBVS pin in the Artix
UltraScale+, Kintex UltraScale+, and Virtex UltraScale+ FPGAs because their configuration
I/O only support operation at 1.8V or 1.5V. 

Configuration is not supported below the minimum recommended operating voltage for
1.5V as specified in the data sheet. The CFGBVS pin setting determines the I/O voltage
support for bank 0 at all times, before, during, and after configuration. CFGBVS similarly
controls the voltage tolerance on bank 65, but only during configuration.

也就是说,这里原理图 CFGBVS 接地肯定是不对的,必须拉高到3.3V,还好留了1个0欧姆电阻在这里 还可以飞线补救一下。

传统的基于边缘信息的模板匹配其计算得分的公式如下所示:

这是一个累加公式,对于原图的每一个有效像素位置,以其为中心或左上角起点(图像中的坐标一般是X方向从左向右,Y方向从上到下),在原图中覆盖模板宽度和高度大小的范围内,按照模板有效特征点的位置和梯度信息,逐点和原图对应位置的梯度信息进行上述累加符号内的计算,在进行完累加后,再次求平均值得到有效像素位置的实际得分。

我们抛开累加的过程,单独看看逐点的计算式。

为了表述和绘图方便,我们把上述公式用下面的符号简化下:

即x
1
和y
1
模板图的X和Y方向x
2
和y
2
梯度,x
2
和y
2
原图对应的X和Y方向梯度。

我们把x
1
、y
1
、x
2
、y
2
绘制到一个二维平面图中,如下图所示:

上述图中,红色线条表示x
1
和y
1
对应的向量,其长度用a表示,绿色线条表示x
2
和y
2
对应的向量,其长度用b表示。两个向量之间的夹角用θ表示。

另外,α表示红色向量和X轴之间的夹角,β表示绿色向量和Y轴之间的夹角。c表示红色和绿色向量终点之间的长度。

根据数学中的余弦定理,a、b、c以及θ之间有如下关系:

再根据勾股定理,我们进一步展开有:

比较公式(4)和公式(3),我们可以看到两者的结果完全相同,因此,求每个点的得分也等同于求对应的梯度向量的夹角余弦。

注意到,要获取θ,我们可以先获得α和β值,然后通过

Θ = β - α

获取,而α和β可以使用atan2函数获取。

通常,模板的信息都是离线计算的,因此,每个特征点的α(模板)值可以提前计算。但是β值需要通过类似于atan2之类的函数实时计算。

得到Θ值后,可以直接使用cos函数计算余弦值,即得到该点的得分。

实际上,无论是atan2函数也好,还是cos函数也好,其内部都是由很多浮点指令组合而成的,非常耗时,不利于程序的实现和效果。

这里提出一个加速的方案,我们称之为十六角度量化的夹角余弦匹配,她的核心还是基于信息论中的香农采样定理。

我们先说一个简单的事情。

在我们的匹配过程中,总得分是由m个特征点各自得分累加后求平均值获取的,因此,如果各自的得分有小幅度的偏差,对总得分的影响应该很小,这样,我们可以先这样想,如果我们把0到360角度分为360等份(cos是以360度一个周期震动的函数),即每等份的差距是1度,然后在计算α和β时,也把得到的角度四舍五入到最接近的等份,这样,我们可以提前建立起一个360*360的查找表,输入α和β的值,就能查到对应cos值了。

这种查表的精度其实还是相当高的,但是这个表太大,查表时的cachemiss相对来说有点严重。

当我们以22.5度为每等份的差距时,可以把360度量化为16等份,此时,对应的表只有16*16=256个元素,查表的效率就非常高了,不过精度损失相对来说就严重一些,但是,实际的验证表面这种损失对匹配的结果影响是完全在可接受范围内的。

这个构成相当于把0到22.5度的向量就直接标记为索引0,22.5到45之间的角度标为1,45到67.5之间的角度标为2,67.5到90度之间的角度标为3,依次类推。

以22.5为间距进行标记的过程的另外一个优势是,可以不用先使用耗时的atan2函数得到角度后再来计算索引值,而是可以根据有关x1和y1(图像数据中x1和y1通常是整数)的数值关系做直接的判断,这种判断也是整形的计算,效率相对来说比较高,具体的可以再代码里看到。

那么表格是如何建立的呢,比如α对应的索引是2,β对应的索引是4,那么表中的内容是什么呢?这个也很简单,就是直接使用

cos(4 * 22.5 - 2 * 22.5) =  0.707117987607051  得到。

为了进一步提高速度,尽量减少浮点的计算,我们可以把这个表的得分设计为整形值,比如将表的得分统一乘以一个较大的整数,而后舍去成绩得到的小数部分,仅仅保留整数部分,这样表格数据就可以完全用整数来表示了,这个时候,只需要在最后计算得分的时候统一除以刚刚放大的整数就可以了,比如刚才的浮点就可以用下面的整数代替:

0.707117987607051  * 63  =   44.5484332192442  四舍五入取整45。

再仔细的考虑下,刚刚建立的是二维表,实际上,这个过程还可以使用一维表进行,因为如果把0到360度角度量化为16个等份,那么模版和原图匹配时的不同的角度差异值只会有31种可能(-15、-14、-13..........0、1、2.......13、14、15),每种取值都有固定的得分,因此,只需要构建一个31个元素的表格,然后根据差异的索引在查表就可以了。注意考虑到索引必须为正值,这个差异的索引应该等于β - α+15。、

使用一维表的方案还可以进一步进行推广。因为二维表必须考虑表的维度,他和量化的等份数成平方关系,太大了这个表的元素太多,一方面占用内存,另外还存在较为严重的cachemiss,但是如果仅仅是一维表,则这方面的顾虑就少了很多,比如我们就以1度为划分间隔,把0到360度划分为360份,这样需要的一维表的大小为719个元素,这个大小无论从内存还是cachemiss角度来考虑都是可以接受的。这样做带来的好处就是计算出的每个位置的得分值和真正的得分是更为接近的。

当然,具体使用多少个等份量化,还和其他一些因素有关,比如使用8/16/32角度量化,在PC上可以使用某些特殊指令集加速查表的过程,而其他的角度则不行。使用二维表有的时候更易处理一些特殊情况,比如原图中不需要参与匹配的一些特殊点。而使用一维表可能需要使用分支语句处理,从来带来性能损伤。

关于余弦相似性,正好昨天博客园也有一篇文章有涉及,大家可以参考下:
十分钟搞懂机器学习中的余弦相似性

如果想时刻关注本人的最新文章,也可关注公众号:

Web1.0、Web2.0 和 Web3.0 的区别主要体现在以下几个关键方面:

内容创作与交互

  • Web1.0
    :内容主要由网站所有者或少数专业人员创建,用户大多只是被动接收信息。例如,早期的雅虎、新浪等门户网站,用户只能浏览编辑们发布的新闻、文章等,几乎没有互动功能。
  • Web2.0
    :用户成为内容的重要创作者和传播者,能够轻松地进行互动、分享和协作。典型的如微博、抖音等平台,用户可以发布自己的文字、图片、视频,并与其他用户进行评论、点赞、转发等互动。
  • Web3.0
    :基于区块链技术,用户对自己创作的内容拥有绝对的控制权和所有权,同时通过智能合约实现更公平的价值分配。比如,在去中心化的内容创作平台上,创作者可以直接获得收益,无需经过中心化平台的抽成。

数据所有权和隐私

  • Web1.0
    :数据主要由网站拥有和管理,用户对数据的掌控权有限。
  • Web2.0
    :大型互联网公司掌控着用户数据,并可能将其用于商业目的,用户隐私存在一定风险。像某些社交平台可能会分析用户数据来推送个性化广告。
  • Web3.0
    :利用区块链的加密和分布式账本技术,用户数据归个人所有,只有在用户授权的情况下才能被使用,极大地保障了用户的隐私和数据主权。

平台中心化程度

  • Web1.0
    :虽然内容由少数人创建,但平台的中心化程度相对较低。
  • Web2.0
    :高度中心化,平台拥有绝对的控制权和规则制定权。比如,某些社交媒体平台可以随意封禁用户账号或删除内容。
  • Web3.0
    :去中心化的架构,减少了对中心化机构的依赖,平台规则由社区共同制定和维护。

商业模式

  • Web1.0
    :主要依赖广告收入来维持运营。
  • Web2.0
    :除了广告,还有电商、会员服务、付费内容等多种盈利模式,但平台往往抽取较高的分成。
  • Web3.0
    :出现了新的商业模式,如通证经济、去中心化金融(DeFi)等,减少了中间环节的费用。

应用场景和潜力

  • Web1.0
    :适用于信息的单向传播,如新闻资讯的展示。
  • Web2.0
    :广泛应用于社交、娱乐、电子商务等领域,极大地丰富了人们的生活和工作方式。
  • Web3.0
    :有望在金融、供应链管理、数字身份认证、物联网等领域带来重大变革,实现更高效、安全和公平的价值交换。

Web1.0 到 Web3.0 的演进,反映了互联网在技术、用户参与、数据管理和商业模式等方面的不断创新和发展。

前言

又花了一块rmb玩玄机。。。啥时候才能5金币拿下一个应急靶机,只能说功底还没到家,唯有继续加油了。。。

简介

账号root密码linuxrz
ssh root@IP
1.有多少IP在爆破主机ssh的root帐号,如果有多个使用","分割
2.ssh爆破成功登陆的IP是多少,如果有多个使用","分割
3.爆破用户名字典是什么?如果有多个使用","分割
4.登陆成功的IP共爆破了多少次
5.黑客登陆主机后新建了一个后门用户,用户名是多少

应急开始

准备工作

下面我遇到比较多的且比较重要的都加深颜色了,其实都很重要,只是作者还没碰到过。

日志文件 说明
/var/log/cron 记录了系统定时任务相关的日志
/var/log/cups 记录打印信息的日志
/var/log/dmesg 记录了系统在开机时内核自检的信息,也可以使用dmesg命令直接查看内核自检信息
/var/log/mailog 记录邮件信息
/var/log/message 记录系统重要信息的日志。这个日志文件中会记录Linux系统的绝大多数重要信息,如果系统出现问题时,首先要检查的就应该是这个日志文件
/var/log/btmp 记录
错误登录日志
,这个文件是二进制文件,不能直接vi查看,而要
使用lastb命令查看
/var/log/lastlog 记录系统中
所有用户最后一次登录时间的日志
,这个文件是二进制文件,不能直接vi,而要
使用lastlog命令查看
/var/log/wtmp 永久记录所有用户的登录、注销信息,同时记录系统的启动、重启、关机事件。同样这个文件也是一个二进制文件,不能直接vi,而
需要使用last命令来查看
/var/log/utmp 记录当前已经登录的用户信息
,这个文件会随着用户的登录和注销不断变化,只记录当前登录用户的信息。同样这个文件不能直接vi,而要
使用w,who,users等命令来查询
/var/log/secure 记录验证和授权方面的信息
,只要涉及账号和密码的程序都会记录,比如SSH登录,su切换用户,sudo授权,甚至添加用户和修改用户密码都会记录在这个日志文件中
/var/log/auth.log 注明:这个有的Linux系统有,有的Linux系统没有,一般都是/var/log/secure文件来记录居多

查看auth.log文件

这里着重了解一下auth.log,因为这个文件给我坑了。。。


  • auth.log
    文件在本题目中存储了:

    登录成功和失败的信息,即认证过程,还有登录成功后系统账户的活动动作,比如添加用户等等(总之这一个文件就包含了我们整个做题的过程)

  • auth.log
    文件主要存储与系统认证和授权相关的日志信息。具体内容包括但不限于以下几类信息:


    • 1:
      登录和注销活动:
      成功和失败的登录尝试
      用户注销事件

    • 2:
      认证过程:
      SSH 登录尝试(成功和失败)
      本地控制台登录
      Sudo 提权事件(成功和失败)

    • 3:
      安全事件:
      无效的登录尝试
      错误的密码输入
      锁定和解锁屏幕事件

    • 4:
      系统账户活动:
      用户添加、删除和修改
      组添加、删除和修改

    • 5:PAM(Pluggable Authentication Modules)相关信息:
      各种 PAM 模块的日志输出,包括认证和会话管理

grep -a

注意,这个也把我坑了!!!
选项通常是为了处理可能包含二进制数据的文件,将它们视为文本文件进行处理,那么也就是说如果你直接cat 该文件没问题,但是你需要对cat出来的文件内容进行处理的话就会报二进制错误,那么也就是说该文件中存在二进制数据的。
为什么会存在二进制数据??可能就是为了坑你,而且我也学到了使用-a这个参数能够避免二进制查看报错。

步骤 1

1.有多少IP在爆破主机ssh的root帐号,如果有多个使用","分割 小到大排序 例如flag{192.168.200.1,192.168.200.2}

刚刚已经讲过我被文件坑了,想必如果在不知道auth.log是日志文件的情况下,大家应该会去找
/var/log/secure
这个文件吧。。。
ok ,我们现在明确了ssh爆破的日志记录都在auth.log中
(他这里又一个
auth.log.1
,查看了一下估计就是备份文件了,
那我们这里就是用备份文件

命令:
cat /var/log/auth.log.1 | grep -a "Failed password for root" | awk '{print $11}' | sort -n | uniq -c'

  • 解释:
    grep -a "Failed password for root"是过滤出来登录失败的记录
    awk '{print $11}'是将失败每条记录中的ip提取出来(这里就是经验多了就知道,或者你自己慢慢调试,看是第几列就打印第几列)
    sort -n是将ip进行排序,-n其实你加不加无所谓,反正你调试对了就行,我这个就是加-n就能从小到大排序。
    uniq -c就是去重且打印重复次数

  • flag为:
    flag{192.168.200.2,192.168.200.31,192.168.200.32}
    在这里插入图片描述

步骤 2

2.ssh爆破成功登陆的IP是多少,如果有多个使用","分割

命令:
grep -a "Accepted " /var/log/auth.log.1 | awk '{print $11}' | uniq -c

  • 解释
    grep -a "Accepted "就是过滤登录成功的记录
    awk '{print $11}' 获取登录ip
    uniq -c 去重

  • flag为:
    flag{192.168.200.2}
    在这里插入图片描述

步骤 3

3.爆破用户名字典是什么?如果有多个使用","分割

  • 错误示范:
    这里本人仅仅打印了第九列,这里其实是不对的,因为无效用户名会导致他出现invalid user xxxx,其实xxxx才是我们要的用户名,所以我下面这种方式就没有不同的用户名都列出来。
    在这里插入图片描述

  • 正确方式:
    这里用到了perl语言,我还没搞懂就不解释了,原理肯定是我想的那样,就是涉及到知识盲区了而已。
    命令:
    cat /var/log/auth.log.1 | grep -a "Failed password" |perl -e 'while($_=<>){ /for(.*?) from/; print "$1\n";}'|uniq -c|sort -nr

  • flag为:
    flag{root,user,hello,test3,test2,test1}
    在这里插入图片描述

步骤 4

4.成功登录 root 用户的 ip 一共爆破了多少次

  • 我们已知成功登录root的ip地址是:flag{
    192.168.200.2
    }
    命令:
    grep -a "192.168.200.2" /var/log/auth.log.1 | grep -a 'Failed password root' | awk '{print $11}' | uniq -c

  • flag为:
    flag{4}
    在这里插入图片描述

步骤 5

5.黑客登陆主机后新建了一个后门用户,用户名是多少

由于我们现在已经知道了auth.log文件能够记录系统账户活动,那我们直接grep该文件即可,添加用户的命令是
net user

  • 筛选 net user命令即可
    命令:
    grep -a 'net user' /var/log/auth.log.1

  • flag为:
    flag{test2}
    在这里插入图片描述

总结


成果:
flag{192.168.200.2,192.168.200.31,192.168.200.32}
flag{192.168.200.2}
flag{root,user,hello,test3,test2,test1}
flag{4}
flag{test2}


通过这日志分析题,了解到了日志文件其实还有一个
auth.log
的文件日志,不仅仅能够记录认证过程,还能知道系统账户执行了哪些账户操作,还有其他记录类型。
需要注意的是,该文件可能包含一些二进制内容,所以我们grep的时候需要用-a参数进行过滤操作,否则会报错。
总体体验还是挺好的,第二次因为grep报错原因浪费掉了好多时间,差几分钟就要重开第三次,玩的就是心跳啊。。。

【C++】使用ort推理yolov10

前言:由于笔者是编导专业,想玩玩yolo模型,搜来搜去全是python,所以在学会之后写一篇文章帮助和笔者同样情况的人

环境

Windows 10

C++17

onnxruntime18.1(DML版本)

opencv4.9

visual studio2022

1. 环境配置

1.1 OpenCV环境配置

1.1.1 OpenCV 配置

  1. 访问 OpenCV 的 GitHub 页面
    https://github.com/opencv/opencv
  2. 在 Releases 页面找到最新的 .exe 文件并下载
  3. 运行安装程序,选择默认安装路径

1.1.2
在 Visual Studio 中配置 OpenCV

  1. 打开 Visual Studio,新建一个控制台项目
  2. 右键点击项目,选择“属性”
  3. 在属性页中,选择“VC++ 目录”
  4. 在“包含目录”中添加 OpenCV 安装目录下的
    include
    文件夹路径,如:
    C:\opencv\build\include
  5. 在“库目录”中添加 OpenCV 安装目录下的
    x64\vc16\lib
    文件夹路径,如:
    C:\opencv\build\x64\vc16\lib

运行下面的代码,如果环境配置没有问题,则会正确显示图片

如果显示无法打开文件lib.obj,则把代码中的#pragma comment(lib,"opencv_world490.lib")去掉

改成在 "项目” → "链接器” → “输入” 附加依赖项中写opencv_world490.lib

#include <opencv2/opencv.hpp>
#pragma comment(lib,"opencv_world490.lib")
//注意!!我这里是opencv_world490.lib,不同版本的OpenCV跟的数字是不一样,如果是按照默认位置安装,则可以在这个文件夹看你的lib叫什么
//  C:\opencv\build\x64\vc16\lib
int main()
{
    cv::Mat image = cv::imread("path/to/your/image.jpg"); // 替换为你的图像路径
    if (image.empty())
    {
        std::cout << "无法加载图像!" << std::endl;
        return -1;
    }

    cv::imshow("图像", image);
    cv::waitKey(0);

    return 0;
}

1.2 onnxruntime1.18.1(DML版本)配置

使用 NuGet 安装 ONNX Runtime

  1. 在 Visual Studio 中,右键点击项目,选择“管理 NuGet 程序包”。
  2. 在搜索框中输入 “onnxruntime”。
  3. 选择 “Microsoft.ML.OnnxRuntime.DirectML”,点击“安装”。

1.3 把环境改成C++17

  1. 打开项目属性页: 右键点击项目,选择“属性”。
  2. 选择 C/C++ 选项卡: 在左侧菜单中选择“C/C++”。
  3. 选择“高级”选项: 在右侧菜单中选择“高级”。
  4. 更改编译器语言标准: ****在“编译器语言标准”下拉菜单中选择“ISO C++17 标准 (/std:c++17)”。

2. 需要了解的知识

2.1 onnxruntime基本认识

首先我们对onnxruntime建立一些基本认识,onnxruntime就是一个推理框架,可以把模型摆进去,输入数据,模型输出数据,不止可以跑yolo,还可以跑其他模型,同样是推理框架的有Pytorch,OpenVINO,tensorRT,NCNN(据说神中神,还没开始玩),关于为什么选择ORT,不知道,群友说ORT也神中神,所以就决定是他了

2.2 yolo模型基本认识

推理一个yolo模型,我们一般把他分为三个步骤:
1. 前处理 2.推理 3. 后处理

前处理要做什么
: 把不同宽高的图片处理成模型能推理的宽高,一般yolo模型都是支持(640,640)的图片的推理,如果输入的是(1920,1080)的图片就进行缩放+填充,把他变成(640,640)的图片

推理在做什么
: 推理就是拿推理框架推理,一般就是一行代码,也许就是session.run(),就推理完了

后处理在做什么
:模型输出的数据是非常杂乱的,因此需要对数据进行处理,比如说置信度过滤,nms,这是非常耗时的,但是在yolov10中,可以省去这一步,这也是yolov10效率高的原因

2.3 onnxruntime 是怎么进行推理的

onnxruntime执行推理依靠一行代码:
auto output_tensors = session->Run(...)

他需要传入的参数有这些

  • Ort::RunOptions{ nullptr }
    : 推理选项,这里使用默认选项。
  • input_node_names_cstr.data()
    : 输入节点名称数组。
  • &input_tensor, 1
    : 输入张量指针和数量。
  • output_node_names_cstr.data()
    : 输出节点名称数组。
  • output_node_names.size()
    : 输出节点数量。

按照这些参数我们再继续

首先
input_node_names_cstr.data()
这个参数的值怎么得到,笔者不知道,选择抄代码,代码如下

  session = std::make_unique<Ort::Session>(*env, model_path.c_str(), session_options);
  allocator = std::make_unique<Ort::AllocatorWithDefaultOptions>();

  auto num_input_nodes = session->GetInputCount();
  auto num_output_nodes = session->GetOutputCount();
  input_node_names.resize(num_input_nodes);
  output_node_names.resize(num_output_nodes);

  for (size_t i = 0; i < num_input_nodes; i++)
  {
      auto input_name = session->GetInputNameAllocated(i, *allocator);
      input_node_names[i] = input_name.get();
      input_node_names_cstr.push_back(input_node_names[i].c_str());
  }

紧接着
input_tensor
的值要怎么得到,这个笔者知道

  1. 我们要先处理图片,把图片处理成640*640
  2. 将图片转成RGB格式
  3. 把图片归一化到[0,1]
  4. 把图片用split函数分成三个通道,分别存进大小为(640,640,1)的Mat的数组中(mat就是OpenCV提供的,用来储存图片信息的结构)
  5. 用for循环把三个通道的值分别用memcpy存到
    input_tensor_value

  6. Ort::Value::CreateTensor<float>();

    input_tensor_value
    变成
    input_tensor

接下来是
output_node_names_cstr.data()
怎么得到,经典抄代码环节

    for (size_t i = 0; i < num_output_nodes; i++)
    {
        auto output_name = session->GetOutputNameAllocated(i, *allocator);
        output_node_names[i] = output_name.get();
        output_node_names_cstr.push_back(output_node_names[i].c_str());
    }

接下来是
output_node_names
,也是抄代码,就上面一段一起的,哦这些代码都是在官方仓库抄的

这些参数都有了,就可以推理了

3. 敲代码环节

3.1 预处理图片(把图片改成模型接受的shape,然后用灰度填充)

#include <opencv2/opencv.hpp>
// 我这里出现莫名奇妙的报错,然后去掉#pragma comment(lib,"opencv_world490.lib"),
// 在visual studio中的链接器中添加opencv_world490.lib就好了
void LetterBox(const cv::Mat& image, cv::Mat& outImage, cv::Vec4d& params, const cv::Size& newShape,
	bool autoShape, bool scaleFill, bool scaleUp, int stride, const cv::Scalar& color)
{

	cv::Size shape = image.size();
	float r = std::min((float)newShape.height / (float)shape.height,
		(float)newShape.width / (float)shape.width);
	if (!scaleUp)
		r = std::min(r, 1.0f);

	float ratio[2]{ r, r };
	int new_un_pad[2] = { (int)std::round((float)shape.width * r),(int)std::round((float)shape.height * r) };

	auto dw = (float)(newShape.width - new_un_pad[0]);
	auto dh = (float)(newShape.height - new_un_pad[1]);

	if (autoShape)
	{
		dw = (float)((int)dw % stride);
		dh = (float)((int)dh % stride);
	}
	else if (scaleFill)
	{
		dw = 0.0f;
		dh = 0.0f;
		new_un_pad[0] = newShape.width;
		new_un_pad[1] = newShape.height;
		ratio[0] = (float)newShape.width / (float)shape.width;
		ratio[1] = (float)newShape.height / (float)shape.height;
	}

	dw /= 2.0f;
	dh /= 2.0f;

	if (shape.width != new_un_pad[0] && shape.height != new_un_pad[1])
	{
		cv::resize(image, outImage, cv::Size(new_un_pad[0], new_un_pad[1]));
	}
	else {
		outImage = image.clone();
	}

	int top = int(std::round(dh - 0.1f));
	int bottom = int(std::round(dh + 0.1f));
	int left = int(std::round(dw - 0.1f));
	int right = int(std::round(dw + 0.1f));
	params[0] = ratio[0];
	params[1] = ratio[1];
	params[2] = left;
	params[3] = top;
	cv::copyMakeBorder(outImage, outImage, top, bottom, left, right, cv::BORDER_CONSTANT, color);
}

int main()
{
    auto img = cv::imread("./zidane.jpg");
    // 目标尺寸
    cv::Size newShape = cv::Size(640, 640);
	cv::Vec4d temp_param = { 1,1,0,0 };
    cv::Scalar color = cv::Scalar(114, 114, 114);
    cv::Mat outImage;
	LetterBox(img, outImage, temp_param, newShape, false, false, true, 32, color);
    return 0;
}

运行起来应该是下面这个效果

3.2紧接着为了要session.run(),我们需要先创建session,同时要创建session的参数

比如,打算使用几线程,打算用什么加速器,可以是cpu推理,也可以是cuda加速,也可以是dml加速,我们这里自然是选择dml加速,在选择加速器前,要判断加速器可不可用,使用的代码是
auto dml_it = std::find(providers.begin(), providers.end(), "DmlExecutionProvider");

std::vector<float> input_tensor_values;
input_tensor_values.resize(3 * width * height);
env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_ERROR, "YOLOv10Inference");
session_options.SetIntraOpNumThreads(1);
session_options.SetGraphOptimizationLevel(ORT_ENABLE_ALL);

std::vector<std::string> providers = Ort::GetAvailableProviders();
auto cuda_it = std::find(providers.begin(), providers.end(), "CUDAExecutionProvider");
auto dml_it = std::find(providers.begin(), providers.end(), "DmlExecutionProvider");

if (cuda_it != providers.end())
{
    std::cout << "CUDA Execution Provider is available" << std::endl;
}
else if (dml_it != providers.end())
{
    std::cout << "DirectML Execution Provider is available" << std::endl;
    OrtSessionOptionsAppendExecutionProvider_DML(session_options, 0);
}
else
{
    std::cout << "No suitable provider found, using default CPU execution" << std::endl;
}

session = std::make_unique<Ort::Session>(*env, model_path.c_str(), session_options);

加了这几行之后代码应该张这个样子,并且编译应该是能通过的,如果编译不能通过,自己排查一下吧~,我是边做边写的,如果你真的和我做的一样,应该也是可以编译通过的

// yolov10示例2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define NOMINMAX
#include <iostream>
#include <opencv2/opencv.hpp>
#include <onnxruntime_cxx_api.h>
#include <dml_provider_factory.h>
void LetterBox(const cv::Mat& image, cv::Mat& outImage, cv::Vec4d& params, const cv::Size& newShape,
	bool autoShape, bool scaleFill, bool scaleUp, int stride, const cv::Scalar& color)
{

	cv::Size shape = image.size();
	float r = std::min((float)newShape.height / (float)shape.height,
		(float)newShape.width / (float)shape.width);
	if (!scaleUp)
		r = std::min(r, 1.0f);

	float ratio[2]{ r, r };
	int new_un_pad[2] = { (int)std::round((float)shape.width * r),(int)std::round((float)shape.height * r) };

	auto dw = (float)(newShape.width - new_un_pad[0]);
	auto dh = (float)(newShape.height - new_un_pad[1]);

	if (autoShape)
	{
		dw = (float)((int)dw % stride);
		dh = (float)((int)dh % stride);
	}
	else if (scaleFill)
	{
		dw = 0.0f;
		dh = 0.0f;
		new_un_pad[0] = newShape.width;
		new_un_pad[1] = newShape.height;
		ratio[0] = (float)newShape.width / (float)shape.width;
		ratio[1] = (float)newShape.height / (float)shape.height;
	}

	dw /= 2.0f;
	dh /= 2.0f;

	if (shape.width != new_un_pad[0] && shape.height != new_un_pad[1])
	{
		cv::resize(image, outImage, cv::Size(new_un_pad[0], new_un_pad[1]));
	}
	else {
		outImage = image.clone();
	}

	int top = int(std::round(dh - 0.1f));
	int bottom = int(std::round(dh + 0.1f));
	int left = int(std::round(dw - 0.1f));
	int right = int(std::round(dw + 0.1f));
	params[0] = ratio[0];
	params[1] = ratio[1];
	params[2] = left;
	params[3] = top;
	cv::copyMakeBorder(outImage, outImage, top, bottom, left, right, cv::BORDER_CONSTANT, color);
	cv::imshow("1", outImage);
	cv::waitKey(0);
}

int main()
{
    auto img = cv::imread("./zidane.jpg");
    // 目标尺寸
	int width = 640;
	int height = 640;
	const std::wstring& model_path = L"./yolov10n.onnx";
    cv::Size newShape = cv::Size(width, height);
	cv::Vec4d temp_param = { 1,1,0,0 };
    cv::Scalar color = cv::Scalar(114, 114, 114);
    cv::Mat outImage;
	LetterBox(img, outImage, temp_param, newShape, false, false, true, 32, color);
	Ort::SessionOptions session_options;
	std::unique_ptr<Ort::Env> env;
	std::unique_ptr<Ort::Session> session;
	std::vector<float> input_tensor_values;
	input_tensor_values.resize(3 * width * height);
	env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_ERROR, "YOLOv10Inference");
	session_options.SetIntraOpNumThreads(1);
	session_options.SetGraphOptimizationLevel(ORT_ENABLE_ALL);

	std::vector<std::string> providers = Ort::GetAvailableProviders();
	auto cuda_it = std::find(providers.begin(), providers.end(), "CUDAExecutionProvider");
	auto dml_it = std::find(providers.begin(), providers.end(), "DmlExecutionProvider");

	if (cuda_it != providers.end())
	{
		std::cout << "CUDA Execution Provider is available" << std::endl;
	}
	else if (dml_it != providers.end())
	{
		std::cout << "DirectML Execution Provider is available" << std::endl;
		OrtSessionOptionsAppendExecutionProvider_DML(session_options, 0);
	}
	else
	{
		std::cout << "No suitable provider found, using default CPU execution" << std::endl;
	}

	session = std::make_unique<Ort::Session>(*env, model_path.c_str(), session_options);
	return 0;
}

3.3 获取输入输出节点名称输入输出节点数量(写完了报错不要怕,因为我也报错)

allocator = std::make_unique<Ort::AllocatorWithDefaultOptions>();

auto num_input_nodes = session->GetInputCount();
auto num_output_nodes = session->GetOutputCount();
input_node_names.resize(num_input_nodes);
output_node_names.resize(num_output_nodes);

for (size_t i = 0; i < num_input_nodes; i++)
{
    auto input_name = session->GetInputNameAllocated(i, *allocator);
    input_node_names[i] = input_name.get();
    input_node_names_cstr.push_back(input_node_names[i].c_str());
}

for (size_t i = 0; i < num_output_nodes; i++)
{
    auto output_name = session->GetOutputNameAllocated(i, *allocator);
    output_node_names[i] = output_name.get();
    output_node_names_cstr.push_back(output_node_names[i].c_str());
}

3.4 把图片转成浮点型,并且归一化到[0,1],把三个通道的数据分别储存到channels中(channels是三个一维的Mat组成的数组),并把他放到input_tensor_values中,并且CreateTensor,创建input_tensor

cv::Mat float_img;
std::vector<cv::Mat> channels(3);
outImage.convertTo(float_img, CV_32FC3, 1.0 / 255.0);
cv::split(float_img, channels);
for (int i = 0; i < 3; ++i)
{
    std::memcpy(input_tensor_values.data() + i * width * height, channels[i].data, width * height * sizeof(float));
}
std::vector<int64_t> input_node_dims = { 1, 3, height, width };
Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);

Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
    memory_info, input_tensor_values.data(), input_tensor_values.size(),
    input_node_dims.data(), input_node_dims.size()
);

3.5 推理!!获取推理结果

  auto output_tensors = session->Run(Ort::RunOptions{ nullptr },
      input_node_names_cstr.data(), &input_tensor, 1,
      output_node_names_cstr.data(), output_node_names.size()
  );

3.6 把现在的内容整合一下(这应该是编译成功的),看到那个
output_tensors ,模型输出就在那里面

// yolov10示例2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define NOMINMAX
#include <iostream>
#include <opencv2/opencv.hpp>
#include <onnxruntime_cxx_api.h>
#include <dml_provider_factory.h>
void LetterBox(const cv::Mat& image, cv::Mat& outImage, cv::Vec4d& params, const cv::Size& newShape,
	bool autoShape, bool scaleFill, bool scaleUp, int stride, const cv::Scalar& color)
{

	cv::Size shape = image.size();
	float r = std::min((float)newShape.height / (float)shape.height,
		(float)newShape.width / (float)shape.width);
	if (!scaleUp)
		r = std::min(r, 1.0f);

	float ratio[2]{ r, r };
	int new_un_pad[2] = { (int)std::round((float)shape.width * r),(int)std::round((float)shape.height * r) };

	auto dw = (float)(newShape.width - new_un_pad[0]);
	auto dh = (float)(newShape.height - new_un_pad[1]);

	if (autoShape)
	{
		dw = (float)((int)dw % stride);
		dh = (float)((int)dh % stride);
	}
	else if (scaleFill)
	{
		dw = 0.0f;
		dh = 0.0f;
		new_un_pad[0] = newShape.width;
		new_un_pad[1] = newShape.height;
		ratio[0] = (float)newShape.width / (float)shape.width;
		ratio[1] = (float)newShape.height / (float)shape.height;
	}

	dw /= 2.0f;
	dh /= 2.0f;

	if (shape.width != new_un_pad[0] && shape.height != new_un_pad[1])
	{
		cv::resize(image, outImage, cv::Size(new_un_pad[0], new_un_pad[1]));
	}
	else {
		outImage = image.clone();
	}

	int top = int(std::round(dh - 0.1f));
	int bottom = int(std::round(dh + 0.1f));
	int left = int(std::round(dw - 0.1f));
	int right = int(std::round(dw + 0.1f));
	params[0] = ratio[0];
	params[1] = ratio[1];
	params[2] = left;
	params[3] = top;
	cv::copyMakeBorder(outImage, outImage, top, bottom, left, right, cv::BORDER_CONSTANT, color);
	cv::imshow("1", outImage);
	cv::waitKey(0);
}

int main()
{
    auto img = cv::imread("./zidane.jpg");
    // 目标尺寸
	int width = 640;
	int height = 640;
	const std::wstring& model_path = L"./yolov10n.onnx";
    cv::Size newShape = cv::Size(width, height);
	cv::Vec4d temp_param = { 1,1,0,0 };
    cv::Scalar color = cv::Scalar(114, 114, 114);
    cv::Mat outImage;
	LetterBox(img, outImage, temp_param, newShape, false, false, true, 32, color);
	Ort::SessionOptions session_options;
	std::unique_ptr<Ort::Env> env;, 1.0 / 255.0
	std::unique_ptr<Ort::Session> session;
	std::unique_ptr<Ort::AllocatorWithDefaultOptions> allocator;
	std::vector<float> input_tensor_values;
	std::vector<std::string> input_node_names;
	std::vector<std::string> output_node_names;
	std::vector<const char*> input_node_names_cstr;
	std::vector<const char*> output_node_names_cstr;
	input_tensor_values.resize(3 * width * height);
	env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_ERROR, "YOLOv10Inference");
	session_options.SetIntraOpNumThreads(1);
	session_options.SetGraphOptimizationLevel(ORT_ENABLE_ALL);

	std::vector<std::string> providers = Ort::GetAvailableProviders();
	auto cuda_it = std::find(providers.begin(), providers.end(), "CUDAExecutionProvider");
	auto dml_it = std::find(providers.begin(), providers.end(), "DmlExecutionProvider");

	if (cuda_it != providers.end())
	{
		std::cout << "CUDA Execution Provider is available" << std::endl;
	}
	else if (dml_it != providers.end())
	{
		std::cout << "DirectML Execution Provider is available" << std::endl;
		OrtSessionOptionsAppendExecutionProvider_DML(session_options, 0);
	}
	else
	{
		std::cout << "No suitable provider found, using default CPU execution" << std::endl;
	}

	session = std::make_unique<Ort::Session>(*env, model_path.c_str(), session_options);
	allocator = std::make_unique<Ort::AllocatorWithDefaultOptions>();

	auto num_input_nodes = session->GetInputCount();
	auto num_output_nodes = session->GetOutputCount();
	input_node_names.resize(num_input_nodes);
	output_node_names.resize(num_output_nodes);

	for (size_t i = 0; i < num_input_nodes; i++)
	{
		auto input_name = session->GetInputNameAllocated(i, *allocator);
		input_node_names[i] = input_name.get();
		input_node_names_cstr.push_back(input_node_names[i].c_str());
	}

	for (size_t i = 0; i < num_output_nodes; i++)
	{
		auto output_name = session->GetOutputNameAllocated(i, *allocator);
		output_node_names[i] = output_name.get();
		output_node_names_cstr.push_back(output_node_names[i].c_str());
	}
	cv::Mat float_img;
	std::vector<cv::Mat> channels(3);
	outImage.convertTo(float_img, CV_32FC3, 1.0 / 255.0);
	cv::split(float_img, channels);
	for (int i = 0; i < 3; ++i)
	{
		std::memcpy(input_tensor_values.data() + i * width * height, channels[i].data, width * height * sizeof(float));
	}

	std::vector<int64_t> input_node_dims = { 1, 3, height, width };
	Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);

	Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
		memory_info, input_tensor_values.data(), input_tensor_values.size(),
		input_node_dims.data(), input_node_dims.size()
	);

	auto output_tensors = session->Run(Ort::RunOptions{ nullptr },
		input_node_names_cstr.data(), &input_tensor, 1,
		output_node_names_cstr.data(), output_node_names.size()
	);
	return 0;
}

3.7 后处理咯,相当之简单,因为yolov10就是没后处理,他要做的就是根据缩放函数(letterbox),把坐标缩放回去

	auto output_tensors = session->Run(Ort::RunOptions{ nullptr },
	input_node_names_cstr.data(), &input_tensor, 1,
	output_node_names_cstr.data(), output_node_names.size()
	);
	const float* raw_output = output_tensors[0].GetTensorData<float>();
	auto shape = output_tensors[0].GetTensorTypeAndShapeInfo().GetShape();
	int64_t num_boxes = shape[1];
	int64_t box_data_length = shape[2];
	detections.clear();
	for (int64_t i = 0; i < num_boxes; i++)
	{
		const float* detection = raw_output + i * box_data_length;
		float x1 = detection[0];
		float y1 = detection[1];
		float x2 = detection[2];
		float y2 = detection[3];
		float confidence = detection[4];
		int class_id = static_cast<int>(detection[5]);

		if (confidence > 0.8)
		{

			// Adjust coordinates to remove padding
			float x = (x1 + 0.5 * (x2 - x1)-temp_param[2])/temp_param[0];
			float y = (y1 + 0.5 * (y2 - y1) - temp_param[3]) / temp_param[1];

			float w = (x2 - x1) / temp_param[0];
			float h = (y2 - y1) / temp_param[1];

			int left = MAX(int(x - 0.5 * w + 0.5), 0);
			int top = MAX(int(y - 0.5 * h + 0.5), 0);

			cv::Rect rect(left, top, int(w + 0.5), int(h + 0.5));
			detections.emplace_back(rect, confidence, class_id);
		}
	}

3.8 根据rect绘制方框

for (const auto& detection : detections)
{
	const auto& [rect, confidence, class_id] = detection;
	cv::rectangle(img, rect, cv::Scalar(0, 255, 0), 2);
}
cv::imshow("1", img);
cv::waitKey(0);

4. 最终代码和实现效果

实现效果,注意!!你使用的模型yolov10n.onnx,要和你的cpp文件在一个目录下

// yolov10示例2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define NOMINMAX
#include <iostream>
#include <opencv2/opencv.hpp>
#include <onnxruntime_cxx_api.h>
#include <dml_provider_factory.h>
void LetterBox(const cv::Mat& image, cv::Mat& outImage, cv::Vec4d& params, const cv::Size& newShape,const cv::Scalar& color)
{

	cv::Size shape = image.size();
	float r = std::min((float)newShape.height / (float)shape.height,
		(float)newShape.width / (float)shape.width);

	float ratio[2]{ r, r };
	int new_un_pad[2] = { (int)std::round((float)shape.width * r),(int)std::round((float)shape.height * r) };

	auto dw = (float)(newShape.width - new_un_pad[0]);
	auto dh = (float)(newShape.height - new_un_pad[1]);

	dw /= 2.0f;
	dh /= 2.0f;

	if (shape.width != new_un_pad[0] && shape.height != new_un_pad[1])
	{
		cv::resize(image, outImage, cv::Size(new_un_pad[0], new_un_pad[1]));
	}
	else {
		outImage = image.clone();
	}

	int top = int(std::round(dh - 0.1f));
	int bottom = int(std::round(dh + 0.1f));
	int left = int(std::round(dw - 0.1f));
	int right = int(std::round(dw + 0.1f));
	params[0] = ratio[0];
	params[1] = ratio[1];
	params[2] = left;
	params[3] = top;
	cv::copyMakeBorder(outImage, outImage, top, bottom, left, right, cv::BORDER_CONSTANT, color);
}

int main()
{
    auto img = cv::imread("./bus.jpg");
    // 目标尺寸
	int width = 640;
	int height = 640;
	const std::wstring& model_path = L"./yolov10n.onnx";
    cv::Size newShape = cv::Size(width, height);
	std::vector<std::tuple<cv::Rect, float, int>> detections;
	cv::Vec4d temp_param = { 1,1,0,0 };
    cv::Scalar color = cv::Scalar(114, 114, 114);
    cv::Mat outImage;
	LetterBox(img, outImage, temp_param, newShape, color);
	Ort::SessionOptions session_options;
	std::unique_ptr<Ort::Env> env;
	std::unique_ptr<Ort::Session> session;
	std::unique_ptr<Ort::AllocatorWithDefaultOptions> allocator;
	std::vector<float> input_tensor_values;
	std::vector<std::string> input_node_names;
	std::vector<std::string> output_node_names;
	std::vector<const char*> input_node_names_cstr;
	std::vector<const char*> output_node_names_cstr;
	input_tensor_values.resize(3 * width * height);
	env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_ERROR, "YOLOv10Inference");
	session_options.SetIntraOpNumThreads(1);
	session_options.SetGraphOptimizationLevel(ORT_ENABLE_ALL);

	std::vector<std::string> providers = Ort::GetAvailableProviders();
	auto cuda_it = std::find(providers.begin(), providers.end(), "CUDAExecutionProvider");
	auto dml_it = std::find(providers.begin(), providers.end(), "DmlExecutionProvider");

	if (cuda_it != providers.end())
	{
		std::cout << "CUDA Execution Provider is available" << std::endl;
	}
	else if (dml_it != providers.end())
	{
		std::cout << "DirectML Execution Provider is available" << std::endl;
		OrtSessionOptionsAppendExecutionProvider_DML(session_options, 0);
	}
	else
	{
		std::cout << "No suitable provider found, using default CPU execution" << std::endl;
	}

	session = std::make_unique<Ort::Session>(*env, model_path.c_str(), session_options);
	allocator = std::make_unique<Ort::AllocatorWithDefaultOptions>();

	auto num_input_nodes = session->GetInputCount();
	auto num_output_nodes = session->GetOutputCount();
	input_node_names.resize(num_input_nodes);
	output_node_names.resize(num_output_nodes);

	for (size_t i = 0; i < num_input_nodes; i++)
	{
		auto input_name = session->GetInputNameAllocated(i, *allocator);
		input_node_names[i] = input_name.get();
		input_node_names_cstr.push_back(input_node_names[i].c_str());
	}

	for (size_t i = 0; i < num_output_nodes; i++)
	{
		auto output_name = session->GetOutputNameAllocated(i, *allocator);
		output_node_names[i] = output_name.get();
		output_node_names_cstr.push_back(output_node_names[i].c_str());
	}
	cv::Mat float_img;
	std::vector<cv::Mat> channels(3);
	//cv::cvtColor(img, img, cv::COLOR_BGR2RGB);

	outImage.convertTo(float_img, CV_32FC3, 1.0 / 255.0);
	
	cv::split(float_img, channels);
	for (int i = 0; i < 3; ++i)
	{
		std::memcpy(input_tensor_values.data() + i * width * height, channels[i].data, width * height * sizeof(float));
	}

	std::vector<int64_t> input_node_dims = { 1, 3, height, width };
	Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);

	Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
		memory_info, input_tensor_values.data(), input_tensor_values.size(),
		input_node_dims.data(), input_node_dims.size()
	);

	auto output_tensors = session->Run(Ort::RunOptions{ nullptr },
		input_node_names_cstr.data(), &input_tensor, 1,
		output_node_names_cstr.data(), output_node_names.size()
	);
	const float* raw_output = output_tensors[0].GetTensorData<float>();
	auto shape = output_tensors[0].GetTensorTypeAndShapeInfo().GetShape();
	int64_t num_boxes = shape[1];
	int64_t box_data_length = shape[2];
	detections.clear();
	for (int64_t i = 0; i < num_boxes; i++)
	{
		const float* detection = raw_output + i * box_data_length;
		float x1 = detection[0];
		float y1 = detection[1];
		float x2 = detection[2];
		float y2 = detection[3];
		float confidence = detection[4];
		int class_id = static_cast<int>(detection[5]);

		if (confidence > 0.8)
		{

			// Adjust coordinates to remove padding
			float x = (x1 + 0.5 * (x2 - x1)-temp_param[2])/temp_param[0];
			float y = (y1 + 0.5 * (y2 - y1) - temp_param[3]) / temp_param[1];

			float w = (x2 - x1) / temp_param[0];
			float h = (y2 - y1) / temp_param[1];

			int left = MAX(int(x - 0.5 * w + 0.5), 0);
			int top = MAX(int(y - 0.5 * h + 0.5), 0);

			cv::Rect rect(left, top, int(w + 0.5), int(h + 0.5));
			detections.emplace_back(rect, confidence, class_id);
		}
	}

	for (const auto& detection : detections)
	{
		const auto& [rect, confidence, class_id] = detection;
		cv::rectangle(img, rect, cv::Scalar(0, 255, 0), 2);
	}
	cv::imshow("1", img);
	cv::waitKey(0);
	return 0;
}

可以优化的地方

  1. OpenCV的mat速度好像是有点慢,自己实现一个MAT用,速度和占用都能好不少
  2. 如果确定要推理某个尺寸的图片,直接训练那个尺寸的图片,然后可以去掉letterbox,速度又能快点
  3. 如果追求极致效率,请转去ncnn,都说是神中神,快去试试,回来告诉我

最后

闷了,写的时候搜个半天搜不到,AI写出来的都是老代码,onnxruntime1.18.1都不适用,希望这篇文章能帮到你,如果文章有问题也欢迎斧正