YOLO目标识别
1. 神经网络与深度学习
神经网络与深度学习
1.人工智能技术演变
不管你关不关注技术,近两年一定被一个词儿深刻地改变了,那就是AI 。但你有没有发现啊,AI圈的新词越来越多了,从DeepSeek、ChatGPT、MidJourney到Transformer、RAG、Agent、MCP,一个个技术术语层出不穷。

整个AI知识体系实在是太庞大了,接下来我争取用最通俗、最易懂、最直白的语言,给同学们梳理人工智能大模型领域的关键概念、基本原理、底层逻辑,给面向应用的同学做一些必要的技术知识补充。

我们从现代人工智能的分水岭——大模型的基础架构深度学习 讲起,扒一扒它的前世今生。相信大家学完以后,会对人工智能、机器学习、神经网络以及深度学习这几个概念有更深的理解。

我们现在所热议的大模型,它实际上是人工智能 的一种。在早期人工智能开始的时候啊,实际上根本没啥智能,更多的就是一个听话的、严格按照规则来走的机器人。

后来随着数据量开始增多,我们的机器学习、神经网络,包括后面的深度学习出现,这些算法才显得越来越智能。等到后面大模型,这个智能的层次一下子就提升了。
接下来,回到早期的人工智能时代,盘盘这一路怎么就从规则识别、机器学习、神经网络,最后演化到深度学习,它每一类算法擅长什么,最后为啥又不行了,我给大家从头说起。

2.早期的人工智能
早期的人工智能,比较机械化。工程师想让他干活,得写非常明确的规则。比如说什么条件下做什么事,做这件事总共需要几步,都要完完全全写明白,一步都不能少。

比如:早年让机器做垃圾邮件识别,工程师就要逐条写规则:
- 含“中奖”算垃圾,
- 提“转账”要标记风险。 🚨
有了这些规则呢,程序就能帮我们抓一部分坏人了。👮

但是要是有另外一些骗子,比如说今天让你去领补贴,明天说有免费体检,那机器还是识别不出来。那么这个规则永远赶不上套路 ,这就是早期的人工智能,虽然能替人做事了,但是举一反一,非常死板。😭

3. 机器学习
这时候机器学习就站起来了,说别写规则了,让我自己学。他的本事就是从数据里面自己找规律。💪 💪 💪
给他一堆垃圾邮件和正常邮件的数据,他就能够总结出哪些特征的邮件是垃圾邮件,哪些是好邮件、正常邮件,那比人写规则可灵活多了,一下子是不是就智能多了? 😍😍

机器学习的本质就是从海量数据里面要学出一个输入到输出的预测公式。 👈 👈

拿演唱会门票价格预测举个例子。比如说你想蹲一蹲你偶像的演唱会的二手票,但是又怕被黄牛坑。就想知道这个内场后排、开场前三天、个人粉丝转手的票 大概多少钱?。

这里就有一些影响票价的信息,比如说座位区域是内场还是看台,距离开场是剩一天、三天还是一周,卖家是个人粉丝还是黄牛,那么输出就是这张票的一个合理的价格。
机器学习就是去广泛地搜集数据,扒遍过去几年同类型演唱会的几万条数据,包括座位区域、剩余时间、卖家类型,以及每一张票最终的实际成交价格。
有了这些数据,它会自动地去算出这些信息和票价的关系。比如我们算出这样一个公式:👇

有了这个公式,把特征条件放进去以后,就能算一个大概价格。比如内场、剩余三天、个人粉丝,那么价格就是550。

4.有监督学习
这里给大家举的这个例子,实际上它还有另外一个称呼,叫有监督学习。机器学习一般分为两类:
- 有监督学习
- 无监督学习
那么它们有什么区别呢?
有监督学习最重要的特点就是有答案。

比如说我们搜集了很多花的特征,测量了很多鸢尾花的花萼长度、宽度,花瓣长度、宽度,然后呢还有这些花具体属于哪一种,比如山鸢尾、变色鸢尾、维吉尼亚鸢尾。那么我们这样的数据,它有特征,有真实结果,这就叫做有答案。那么基于这样的数据所训练出的算法,由于有真实的结果,我们就能够给每一个样本去计算一个很明确的误差。
有监督学习,因为有误差的指导,它在调参或者在参数的识别调整方面就非常高效,因为它有答案。

5.无监督学习
另外一个方法叫做无监督学习。相当于我们做思考题,没有答案。典型的无监督学习包括聚类、降维,还有关联规则识别。
举一个现实中能够用到无监督学习、切实地去产生商业价值的例子。
比如你楼下开了一家奶茶店,最近那个电商平台出了很多优惠措施,大家都喜欢去这个奶茶店买奶茶,这个奶茶店就积累到了一堆订单数据。然后他就发现:
- 有些人喜欢每周买三次,每次只点十五元,只点一杯珍珠奶茶;
- 那有另外一些人可能每个月只买一次,但每次就能点五十块钱的全家福套餐。
那机器人就会自动地把上面的两类消费行为归为两类人:
- 第一种叫“日常续命党”,
- 第二种叫“宿舍囤货党”。
那么老板就靠这个信息可以搞精准优惠:
- 比如说给日常续命党一个满十二减二的优惠券,让他们天天想来;
- 宿舍囤货党,有一个买二送一的套餐券,让他们一次就多带几杯,生意直接火到排队。
这就是无监督学习,是聚类分析在商业的一个非常经典的应用。

机器学习看起来够智能,但慢慢的他再见大世面就又不行了。传统的机器学习有一个明显的短板,就是它只能处理结构化数据。
结构化数据就是那种能放在Excel里面规规整整的数据,有横行有竖列,横行表示我们很多对象或者是很多观测,纵列来表示这个对象多个维度的特征。
随着图像数据、音频数据、视频、文本这些数据出现的时候,机器学习的这个短板就很明显了。比如一张猫的照片,全是密密麻麻的像素点啊;那么一段语音,都是一些波动的声波信号,就很难再去提取这特征一和特征二。
那要让我们传统的机器学习来处理这些数据,就得靠人手动提取特征,比如说我们用图像来识别猫,你得去自己告诉猫有没有尖耳朵,有没有圆眼睛,把这些特征再转换成数值。这种手动提取特征,不仅慢,而且容易漏掉关键信息,比如说猫的毛色深浅、胡须长短,我们人可能没注意,但对于机器认猫来说特别重要。
那这时候,传统的机器学习就非常局限 。

5.神经网络
此时大名鼎鼎的神经网络就出现了。它就是传统机器学习的一个升级版,主打让机器自己提取特征,不用人瞎掺和 。

顾名思义,就是模仿咱们人脑中的神经元结构去进行模式识别和特征提取。它自动能够从我们原始的图像数据中提取特征,最终能够输出对于这幅图像的一个判断。

那么具体它是怎么做的呢?我们用这样一幅图来为大家解释原理👇
首先我们会将猫或者狗的图像输入到神经网络里,图像在计算机里面一般会被转化为一种数值矩阵,比如像素值的矩阵,作为网络的初始输入数据。
然后中间会经过若干个隐藏层,自动地去把特征进行提取和转化。比如:
- 低层的隐藏层,可以提取图像的基础特征,例如猫或者狗的毛发、边缘、轮廓、线条、纹理等等;
- 中间的隐藏层,可以去在底层的基础上提取更复杂的特征,比如说猫的耳朵形状啊、狗的脸型轮廓等等;
- 高层的隐藏层,则一般进一步会整合中间层的特征,提取更抽象、更具有区分性的特征,例如猫的一个整体形态,或者是狗的一个外貌特征,例如这个狗的鼻子形状,或者是它的耳朵的一个下垂状态。
这些隐藏层通过大量的参数调整 ,就能不断的学习出怎么样区分出猫和狗的那些关键特征。
最后进入到输出层,就会基于前面提取的特征,给出是猫还是狗的一个判断结果。

6.单层感知机
现在的神经网络是非常的智能,但是早期的时候其实特别菜。最早叫单层感知机 ,也就只有一层神经元。这几个神经元工作就是把输进来的特征量通过一个线性加权做一个求和,然后输到一个激活函数里面,把这个线性求和最后转化成一个0 /1 的二元结果。

单层感知机其实更擅长的是做一个线性分类,也就说它只能解决线性可分的问题。
如果我们遇到异或问题 ,如下图👇,如果出现这样的聚类模式,我们线性就很难给它区分开了,用一条直线很难画出来。那怎么办呢?
有些人就想了,我能不能做一个变换,把这些难分的点呢去换一下它的位置,比如把红色点往上移,把蓝色的点往下移,这时候再用一条直线是不是就可以轻松分开了? 但是这个拉点 的操作,单层感知机是做不了,因为他没有处理点位置的中间环节。

这时候多层感知机就提出来了,因为他加了一个隐藏层。就专门做我们刚才说这个拉点 的活。在单层的感知机中间呢会加入一个隐藏层,把原始的特征做进一步的转化,然后才进入到最后的输出。比如把原始的x、y转换成x+y、x-y这样的新特征,那就相当于把它映射到了一个新坐标系下面,然后再用一条直线在新的坐标系去划分,完美!这就是多层感知机的一个雏形。
7.多层感知机
有了多层感知机的这个架构,现在想问的是,不同层级的权重怎么估?这个权重相当于什么呢?
可以理解为:做题时候的一个解题步骤啊,它要确定我们怎么样去变换原始的特征。比如说第一步要算x+y,第二步要x2,第三步要-1,
那么每个步骤的系数就都是权重,那要让模型算对,就得让这些权重刚好能凑出那个能分对异或点的步骤。s
早期的调权啊,全靠瞎蒙。比如说我们先随机设了一组权重,算出来发现没有分区分开,算错了。那我们并不知道是x加y这步错了,还是乘以二这步错了啊,那我只能再换一组权重。
就像学生做错题了,不知道哪一步,只能从头瞎改。哪怕只有几十组权重的一个简单多层感知机,要想能正确划分出结果,试出那个权重,也得耗费大量时间,就更别说后来几百几千组权重的模型了。大大地限制了这个多层感知机的一个应用。


8.BP算法
直到一个人的出现,号称我们深度学习之父的杰弗里·辛顿(Hinton),一九八六年提出重磅的反向传播,也就是BP算法
- 反复去做这个三步,直到误差小到可以接受,权重就固定了,我们整个模型就训练好了。

它的核心就是给调权重加了个导航,相当于给我们刚才那个步骤加了一个错题的批改指南。
简单点的说:
- 第一步要先随机设定一个权重,做正向计算。比如说我们要预测一个房价,你发现按照你这个随机的权重,预测出来是一百万,但真实的房价是一百二十万,那么我们第一步要算出这个误差是二十万。
- 第二步反向传播,把二十万的误差往输入层去传啊,就像我们老师批改作业,从后一题往回找错因,看看是哪个权重导致了这个误差。
- 第三步,就要调节权重,让权重去往这个能减少误差的方向去变。比如某个权重能让误差变大,那就调小它;某个权重能让误差变小,就调大它。

有了这个算法,神经网络的权重的效率就大大提升,能够训练的网络层数也越来越深。这让科学家们很兴奋,就网络层数给不断地上,网络节点给狠加加加,没别的,就是越整越复杂。
可是呢,随着训练的网络深度越来越深,诡异的事情发生了. =====> 当神经网络增加到一定层数的时候,会突然遇到一种叫做梯度消失或者梯度爆炸 的问题。
啥是梯度?简单理解就是调整我们权重的一个方向和幅度。刚才误差反向传播的时候,其实就是在传梯度。所谓梯度消失,就是梯度传着传着变成零了,最后的权重都变成零了,没法调,模型就摆烂了。
那梯度爆炸跟他正好相反,就是模型会变得特别不稳定,权重变得特别大,那预测结果也跟着乱跳。比如说预测房价,输入稍微变一点,输出就能从一百万跳到一个亿,那非常不准确,根本没法用。

9.深度信念模型
因为梯度消失和梯度爆炸的问题,科学家们又蔫了啊。因为如果我们网络深度训练不深的话,其实会大大地限制我们可以解决问题的难度。
那么时间又过了十年,还得是这位Hinton,提出了几乎拯救神经网络架构的算法——深度信念网络(现在叫Deep Learning)。这个训练算法的提出,让我们神经网络可以搭载的层级直线上升,彻底突破前面BP算法的一个复杂度限制 。
他是怎么估计的呢?简单来说,就是一个预训练加微调的技术。了解过一点大模型知识的同学,是不是对这两个词并不陌生?
具体是这么做的:
- 这是一个神经网络,要把训练过程分为两步。第一步,要先给神经网络做一个逐层预训练,那就像我们盖楼,要先把每一层架构单独搭稳。把深度网络拆成一个个小模块,比如说十层网络,我们先练输入层和第一层隐藏层,用无监督学习让这两层练会抓数据基础特征,比如说去认我们猫的头像,先去学它的边缘线条。然后把第一层练熟之后当做新输入,再和第二层模块接着练,一层一层垒地基,就避免一上来全部训练、全部层一起训练权重就乱掉。

等每一层都扎实了,再用BP算法去整体微调。那么此时,由于我们权重有了一个比较靠谱的初始值,梯度就不容易产生消失或者是爆炸的问题,能够精准调参。这个时候,就能让网络扛住十层、一百层的深度,深度学习这概念也正式诞生。👍
我们可以用它处理做人脸识别、语音识别,还有智能客服。那么我们现在这些比较普及的应用,其实背后全是深度学习在干活。

咱们已经把AI、人工智能、机器学习、深度学习的关系理得明明白白了。我们总结了一下:
- 人工智能 是一个最广泛的概念,它的目标是让机器人能够模拟人类的感知、推理和学习等智能行为;
- 传统的机器学习 是一类擅长处理规规整整的、有明确特征的结构化数据;
- 神经网络 是一种新型的机器学习方法,一种能够从图像、音频这种非结构化数据中提取特征的模型结构;
- 深度学习 是现在机器学习的一个主流方向,它的核心就是去构建一个深层的神经网络,层层递进去挖掘数据里面更复杂、更抽象的特征,让机器越来越智能。

10.擅长领域
深度学习这么强,最擅长的领域还是图像识别。有一个比赛大家可能听过,号称图像识别领域的奥林匹克大赛——ImageNet.
这个比赛用的数据集非常庞大,包括了超过一千四百万张图像和两万多个类别,而且它有一个非常高质量的标注,也就说它有一个很明确的监督信号,因此成为各种深度学习算法、机器学习算法所测试它性能的一个非常有名的比赛。二零一二年以前,大家所测试的各种算法在这个数据集上表现平平,这个模型的识别精度非常有限,误差率居高不下。直到二零一二年,Hinton带着他的学生团队,推出AlexNet,它作为一种深度卷积神经网络,在当年的大赛里面直接杀疯了,成绩比第二名高出41%,误差率大幅降低。这一个架构的提出,直接就让深度学习在图像领域C位出道。那么各大科技公司也开始重视深度学习,并广泛应用起来。

说到这儿,可能有同学有疑惑:既然早期深度学习在图像领域封神,怎么后来突然就能够读报纸、写报告,甚至能批改作业,在文本领域也爆火了呢?

我们有时间就带大家聊一聊深度学习是针对图像、文本的不同特点设置专属模型的,两大明星网络CNN、RNN分别是什么,后来又是怎么引发改变AI格局,也就是当今GPT的底层架构Transformer强势崛起的。

2. CNN和RNN
前言
1.引言
上一次我们学习了深度学习解决了非结构化数据的特征提取问题,开启了AI发展的新阶段。但大家有没有发现一个奇怪的现象,AI最早是在图像数据上火起来的,人脸识别、自动驾驶、以图搜物,这些应用已经不知不觉地融入了我们生活。可直到这几年,AI才突然变得会聊天了,写诗、写论文、写代码样样都行。
那问题来了,为什么图像AI早早就崛起了,而会写字说话的AI却来的这么晚呢?
接下来揭开这个谜底,带你从CNN、RNN一路走到Transformer,看看AI到底怎么一步一步学会听懂人话的。👍


2.CNN
先来看一张照片,它本质就是像素的排列,但我们人眼为什么一看就知道这是猫啊?因为我们能抓住关键特征,比如说我看到尖耳朵、圆眼睛、毛茸茸啊。

那么AI就也学会了这一招,于是CNN卷积神经网络就诞生了。首先他像一个抓特征的猎人,用一个个的小探测器在图片上划来划去,这些小探测器的学名叫卷积核 。比如有个探测器专门找边缘,有个探测器专门找眼睛,有个探测器专门去找毛发纹理。他每划到一个地方,就要打个分,比如说这里像耳朵吗?这里像眼睛吗?匹配度高的话呢,它就亮红灯,匹配度低呢它就灭灯。那这个过程就叫卷积 x,他用小窗口扫描全局,去抓局部特征。

卷积神经网络还有另外一个操作叫池化 ,简单说来就是压缩照片。比如我们把像素矩阵的四个格子缩小成一个,只保留那个最亮的值,这样呢图片变小了,计算也变快了,但关键信息还都还在。就这么2个步骤。

CNN的经典网络AlexNet在2012年的ImageNet大赛上,直接把错误率干到了15.3%,大家知道,这个错误率可是比我们人眼识别照片的错误率都还要低。手机里的美颜、扫码自动对焦,背后都是他在干活。

但是CNN有一个致命的问题:看不懂文字 。为什么?

因为我们的文字它不是局部特征的组合,而是顺序决定意义。比如说我们看“我吃苹果”和“苹果吃我”,词一模一样,但顺序一换,意思天差地别。前面就是一个正常的剧情,后面就是一个科幻甚至恐怖的剧情。

CNN是不管顺序,他会把一个句子当做一个词袋子来处理,自然就傻了。于是专门为序列设计的RNN ,循环神经网络上场了。

3.RNN
循环神经网络RNN的核心思想特别像人一边读一边记笔记。

比如他看到“我吃苹果”,那就先读“我”哎就记下来,就主语是我;
再看到“吃”,结合前面的笔记,就知道是我在吃;
最后看到“苹果”,他就继续推我吃的对象是苹果。
那么这个笔记的学名叫隐藏状态。

RNN在处理文本时,它会按照词的顺序一个一个算,每处理一个词呢,就把这个词的信息存到隐藏状态里,下一个词的计算必须要用到这个隐藏状态。所以我们的RNN可以干摘要、翻译、写摘要、判断情感,早期NLP就全靠它。

但是RNN也有两个大毛病。第一太慢了 ,它必须一个词儿一个词儿算,不能并行计算 。1000个字的文章,它必须得等前面999个词儿算完,才能推到第1000个,这扛不住大数据啊!!!

第二个特点就是记性差 ,这个太长的句子,它开头的信息传到结尾就忘了。比如这句话:“他昨天去超市买了牛奶,因为他孩子喜欢喝什么啊?”那他看到“喝”,等到要填的时候,他可能早就忘了孩子是谁,就填不出牛奶。
这个问题叫做长距离依赖问题 ,也就是说RNN,记不住前因,所以文本的AI就一直被卡在这里。

4.Transformer
直到2017年一个王炸出现,当年谷歌就甩出一篇论文《Attention is All You Need》,也是现在学大模型的一个奠基性的文章。那么他就提出了一个全新的架构Transformer ,直接解决了RNN的两大难题,还成了现在所有大语言模型的地基,相当于我们AI界的奠基石了。 666666

为什么这么牛?注意力 。Transformer提出了一种叫做注意力机制的这样一种设计。就我们人在看一幅图或者一段文字的时候,是不是会一眼就抓住关键部位,从而快速的进行模式识别,比如说认出这个是个兔子。而注意力机制让机器会像人一样抓重点,能够给更重要的词更高的关注度的权重。
比如说谈到一句话“我爱中国,你呢?”
比如说翻译“爱”这个词的时候,注意力会先计算“爱”和其他词的关联度。
比如说他看“爱”和“我”相关吗?相关还是主谓关系,就是高分;
那“爱”和“中国”相关吗?相关它是动宾关系,它是爱的对象,高分;
那“爱”和“你”相关吗?这关系不太大,所以低分。
这样一来,我们机器在翻译“爱”的时候,就会重点关注“我”和“中国”,然后给其他无关的词语它的权重就更小。

注意力机制还支持并行计算。
比如说处理“我爱中国”这四个词儿,他能一次性算出“我”和“爱”“中国”的关联度,“爱”和“我”“中国”的关联度,以及“中国”和“我”“爱”的关联度啊,不用等前一个词儿算完。
就好像是这个从单窗口办事,变成多窗口同时办公啊,这是计算机领域的最多跑一次,这个文本处理速度直接提升好几个量级。RNN这个无法并行的问题被Transformer解决了。

但又一个问题来了,如果所有的位置一起算,怎么知道顺序?
比如怎么样避免“我吃苹果”和“苹果吃我”这个没有办法区分的困境呢?
这样一来机器算的又快又准,直接就解决了RNN的痛点。

Transformer给的办法很巧妙,他会给每个词加一个座位号。比如说“我吃苹果”这个短语,他会给“我”编一个位置,给它一个编码,比如说0.01、0.9就是一串数字;“吃”处于位置二,给他另外一个编码,0.3、0.7,另外一个位置向量;“苹果”呢处于位置三,有一个新的位置编码。
他会给每一个词一个独特的位置编码,最后模型会把词的语义信息和位置编码打包一起,输入到网络里去训练。这样一来机器既能并行去算词的关联度,又能够通过位置标签知道---是我在吃苹果,不是苹果在吃我。

我们来总结一下:👇

5.BERT和GPT
Transformer怎么成为当前各大主流大模型的基础架构呢?
这来源于Transformer的整体架构设计。Transformer整体上可以分为编码器和解码器 两部分,各自负责不同的任务。现在大家使用的各种大模型,基本就是沿着其中的某一个分支演化的,也有部分少量的模型是两侧都考虑。

- 像BERT以及后续的一系列变体,它主要关注的就是编码器侧的优化,还在模型轻量化的方向上不断突破,就使得模型的这个语义理解能力越来越厉害。
- GPT系列主要选择的就是解码器的路线,它主要突破的是这个模型的规模,不断优化训练数据的质量,将文本的生成能力打磨到了出神入化的境界。像在咱们中国火出圈的DeepSeek,其实也是GPT系列的分支,完全基于的是Transformer的解码器的堆叠。

下面就具体结合BERT和这个GPT的介绍,让大家了解一下编码器和解码器在处理文本上思路上的区别。

BERT就像一个语言的理解大师,只用Transformer的编码器部分,特点就是在解析文本时可以双向看上下文。

比如说他要填空“他在哪里吃午餐?”,那么这个BERT呢就会同时看左边“他有”,在右边有“吃午饭”,那猜测他可能是在公司或者是家里。这就是双向看上下文功能。

有了这个功能,在这种大段文本的理解方面就很强,所以他很擅长文本分类,比如说看看一段文字是体育新闻,还是财经报道,然后关键词提取以及语义匹配方面就非常强。语义匹配就是比如说要区分“我是想买手机”和“推荐手机”它是不是一回事。

GPT主要则侧重于解码器侧,他更像一个文本生成的大师,特点是单向生成,不能看未来。

一方面它主要用的是单向注意力,也叫因果注意力去生成当前的词。那么它的特点就是,当你可生成当前这个词的时候,只能看前面已经生成的词,不能提前看后面。比如说你写一个句子“我今天要去啊”,那你只能根据“我今天要去”来推导下一个词是“图书馆”还是“操场”,总不能先知道后面要写“复习高数”,

另一方面,这个解码器还有一个掩码机制,专门去屏蔽未来的词儿,确保整个文本的生成它符合时序的逻辑 ,就不会出现说我先写“考了100分”,然后再写“我今天考试”这种颠倒因果的情况。

当然GPT这么厉害,还离不开这个预训练加微调的经典模式。
在预训练阶段,工程师会用海量的文本数据,比如说互联网的书籍呀、论文、网页去给GPT喂饭,他主要去学习一般性的词语搭配、语法规则、常规知识。通过训练知道,夏天经常和炎热、空调、西瓜一块出现,毕业论文可能就跟摘要、目录、参考文献一块出现。这个阶段给GPT就训练出来通用能力了,让他能够具备基本的知识储备和文本生成能力。
到了微调阶段,主要是针对具体任务给GPT在做专项训练。比如说你想让他去帮你写考研复习计划,你可能就用大量的这个考研复习贴、考研备考资料哈、研究生入学准备这样的数据去做微调;想让他改代码,就需要用GitHub上面的开源代码去做微调。
通过这种方式,GPT就能够快速适配不同的特殊场景,既保留了预训练的通用能力,又在特定任务上表现出色。

从GPT1到现在的GPT4,模型的进化是一个规模变大和能力变强的过程。
比如说早期GPT1的参数量只有1.17个亿,他就只能写简单的短句;
到GPT3参数量直接就突破1750个亿。奥特曼他们就发现,模型参数量的数量直接决定最终模型性能的一个表现,所以他就把参数量一下子堆到非常的高。那这个时候他不仅能够写完整的诗歌论文,还能够解数学题、去分析就这个段落的因果关系。
GPT4更厉害了,参数有2000多个亿,新增了多模态的能力,比如除了处理文本之外,还能看图片。

5.总结
AI不是一夜变聪明的,是靠CNN学会了看,再靠RNN学会了记,最后用Transformer的注意力机制,终于学会了理解和表达。
用AI我写个周报,完成作业,帮我解释一下量子力学,写首周杰伦风格的歌,就知道它背后,是一场长达十多年的技术接力,是无数工程师在模型结构上的精雕细琢、精益求精!!!

3. yolo介绍
前言
在学习yolo之前,我们先来了解一下YOLO之前的世界是怎样的。
如果我们现在有一个分类器:

现在我们的追求升级了,我们不仅仅想处理这种一张图片中只有一个物体的图片,我们现在想处理有多个物体的图片 。
我们该什么做呢?
有几点我们要实现想到:
首先,物体的位置是不确定的,你没办法保证物体一定在最中间;
其次,物体的大小是不确定的,有的物体比较大,也有的物体比较小,注意,这里不是说大象一定更大,猫咪一定更小,毕竟还有近大远小嘛;
然后,我们还没办法保证物体的种类,假设我们有一个可以识别100中物体的分类器,那么起码图片中出现了这100种物体我们都要识别出来。
比如说这样: 👇

是不是看上去很难?
最直接的方法是滑窗法,就是用滑动窗口去识别一个个物体

上图的框框就是所谓的滑窗。如果一个物体正好出现在一个滑窗中,那么我们就可以把它检测出来了,这个滑窗的位置也就是我们认为这个物体所在的位置。
如果物体没有正好出现在一个滑窗中呢?
我们管滑窗每次滑动的距离叫做步长,如果我们把步长设置的特别小,如果步长仅仅为一个像素点,那一定可以保证物体可以正好出现在某个窗口中了。
那如果某个物体特别大,或者特别小呢?
例如在上图中,每个窗口和汽车差不多大小,但是如果我们要识别一辆卡车,一个窗口可能就不够大了。
显然,我们可以设计不同大小的窗口,我们可以设计几十中不同大小的窗口,让他们按照最小的步长滑动,把窗口里的所有图片都放入分类器中。
但是这样太太太浪费时间了。
到这里R-CNN 出现了,用滑窗法可能最后得到了几十万个窗口,R-CNN可以提前扫描一下图片,得到2000个左右的Region(其实就是前面的窗口),这样不就节省了很多时间?
R-CNN(Region Proposal 中文意思:候选区域) ,并且提出了一个叫做Selective Search的算法。
好了,我们总结下:👇
在R-CNN之前,传统目标检测方法(如DPM)依赖手工设计特征(如HOG、SIFT),精度有限且泛化能力差。R-CNN的创新在于:用CNN自动提取图像特征,替代手工特征,大幅提升检测精度。R-CNN的核心是“先找候选区域,再用CNN分类”,但是R-CNN速度极慢,存储开销大,很快被YOLO系列取代。
在讲解YOLO之前,我们再重申一下我们的任务。我们的目的是在一张图片中找出物体,并给出它的类别和位置。目标检测是基于监督学习的,每张图片的监督信息是它所包含的N个物体,每个物体的信息有五个,分别是物体的中心位置(x,y)和它的高(h)和宽(w),最后是它的类别。
YOLO的名字和核心思想
在2016年之前,目标检测的主流是“两阶段”算法(如R-CNN、Fast R-CNN、Faster R-CNN):先花大量时间找“可能有物体的区域”,再对区域分类。这种方法精度高,但速度极慢(一张图检测需几秒到几十秒),完全无法满足实时需求(如自动驾驶、视频监控需要≥24帧/秒)。
2016年,Joseph Redmon(约瑟夫·雷德蒙)等人在论文《You Only Look Once: Unified, Real-Time Object Detection》中提出YOLO(You Only Look Once),核心思想颠覆传统:

“把目标检测从‘两步走’变成‘一步走’——让网络‘只看一次’图片,直接输出所有物体的位置和类别。”
这一创新让目标检测速度提升10-100倍,从此开启“实时检测 ”的新时代。
YOLO = You Only Look Once (你只看一次)
核心思想: 以往的很多目标检测方法,会把检测问题分成两步:第一步找出图片中可能有哪些物体(候选区域),第二步再对这些候选区域进行分类。这就像我们找东西,先乱指一堆地方,再一个个仔细看是什么。
YOLO的做法: 它将目标检测视为一个单一的回归问题。简单来说,就是一次性从图片中直接预测出所有物体的类别和它们的位置。就像我们一眼扫过去,就能把所有猫和狗都找出来并框上。
优势:
- 速度快: “只看一次”决定了它速度非常快,可以达到实时检测(比如视频每秒30帧以上),这也是它名字的由来和最大亮点之一。
- 端到端训练: 整个检测过程可以一气呵成地训练。
YOLO的大致工作流程 👇

想象YOLO看一张图片:
- 网格划分 : 它先把图片划分成一个个小的网格(比如19x19的网格)。
- 每个网格负责预测 : 每个网格负责预测那些中心点落在该网格内的物体。
- 预测内容(每个网格预测多个“边界框”) :
- 边界框的位置 : 框的中心坐标(x,y),宽度和高度(w,h)。
- 置信度 : 这个框里真的有一个物体的可能性有多大?以及这个框预测的准不准?
- 类别概率 : 如果框里有物体,它是猫的概率是多少?是狗的概率是多少?
- 筛选与输出 : 最后,YOLO会从所有网格预测的众多边界框中,筛选出最靠谱的几个(比如通过非极大值抑制算法去掉重复的框),并输出它们的类别和位置。
YOLO版本
| 版本 | 发布时间 | 核心创新/突破 | 关键性能(COCO mAP@0.5 / 速度) | 核心特点 | 典型应用 |
|---|---|---|---|---|---|
| YOLO v1 | 2016 | 首创单阶段检测(无候选区域,一次前向传播预测所有物体) | 63.4% / 45 FPS | 实时检测奠基,快但小物体/定位精度弱 | 早期实时监控原型 |
| YOLO v2 | 2017 | 锚框、多尺度训练、YOLO9000(跨数据集训练,支持9000+类) | 76.8%(VOC)/ 67 FPS | 速度与精度平衡,通用检测能力强 | 通用目标检测(如“万物检测”) |
| YOLO v3 | 2018 | Darknet-53骨干、多尺度特征金字塔(FPN)(3种尺度预测不同大小物体) | 57.9% / 51 FPS | 小物体检测大幅提升,工业界基准模型 | 安防监控、自动驾驶 |
| YOLO v4 | 2020 | 工程技巧集成(Mosaic增强、CIoU Loss、SPP/PANet) | 65.7% / 62 FPS | 易训练部署,工程优化集大成者 | 工业质检、智能交通 |
| YOLO v5 | 2020 | 模块化PyTorch实现、Focus模块、自适应锚框、多尺寸模型(n/s/m/l/x) | 66.7% / v5n=140 FPS | 用户友好,工业化落地首选(文档全) | 中小企业AI落地(零售/农业) |
| YOLO v6 | 2022 | RepVGG骨干、解耦头、自蒸馏训练 | 66.7% / 比v5快30% | 速度与精度兼顾,适合云端/边缘端 | 电商直播实时检测 |
| YOLO v7 | 2022 | E-ELAN骨干、辅助训练头、动态标签分配(SimOTA) | 68.2% / v7-tiny=161 FPS | 轻量化极强,速度与精度双优 | 无人机巡检、移动机器人 |
| YOLO v8 | 2023 | C2f模块、多任务支持(检测/分割/姿态估计)、动态标签分配(SimOTA 2.0) | 69.8% / v8n=150 FPS | 全能模型,精度新高,工业界主流 | 自动驾驶(检测+分割)、体育分析 |
现在Yolo11
FPS:每秒帧数,即每秒处理的帧数,是衡量视频播放流畅度的重要指标。
YOLO已成为工业界实时目标检测的“标配”,其开源生态和持续创新仍在推动AI视觉技术的普及。
YOLO11 由 Ultralytics 团队在 2024 年 9 月 30 日发布, 最新的 YOLOv11 模型在之前的 YOLO 版本引入了新功能和改进,以进一步提高性能和灵活性。YOLO11 在快速、准确且易于使用,使其成为各种目标检测和跟踪、实例分割、图像分类和姿态估计任务的绝佳选择。可以看出官网 YOLOv11 在COCO数据集上的性能表现,如下图所示:

4. yolo案例
1. 安装yolo
前言
打开Anaconda Prompt,输入以下命令安装PyTorch:

临时使用清华镜像源安装PyTorch
pip install torch torchvision torchaudio ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple然后在Jupyter中测试一下:
# Cell 5: 最终验证
def final_check():
"""最终检查和测试"""
print("🎯 最终验证:")
print("-" * 30)
# 检查所有必要的库
libraries = {
'ultralytics': 'from ultralytics import YOLO',
'torch': 'import torch',
'cv2': 'import cv2',
'matplotlib': 'import matplotlib.pyplot as plt'
}
success_count = 0
for lib_name, import_statement in libraries.items():
try:
exec(import_statement)
print(f"✅ {lib_name}: 正常")
success_count += 1
except ImportError:
print(f"❌ {lib_name}: 缺失")
print("-" * 30)
print(f"📊 成功率: {success_count}/{len(libraries)}")
if success_count == len(libraries):
print("🎉 所有库安装成功!可以开始YOLO检测了!")
return True
else:
print("⚠️ 还有库未安装成功,请重启内核后重试")
return False
# 运行最终检查
if final_check():
print("\n🚀 现在可以运行YOLO检测代码了!")结果:
🎯 最终验证:
------------------------------
✅ ultralytics: 正常
✅ torch: 正常
✅ cv2: 正常
✅ matplotlib: 正常
------------------------------
📊 成功率: 4/4
🎉 所有库安装成功!可以开始YOLO检测了!
🚀 现在可以运行YOLO检测代码了!上面安装了CPU版本的PyTorch,如果要安装GPU版本,可以自行查询安装指南,这里不再赘述。
2. yolo案例之猫狗识别
前言
在学习通上下载test的文件夹,放到Jupyter的工作目录下,里面有猫狗图片若干 👇

接下来我们将使用YOLOv5来进行猫狗识别。
代码如下:
from ultralytics import YOLO
import cv2
import matplotlib.pyplot as plt
print("🎯 开始YOLO猫狗检测...")
# 加载模型
model = YOLO('yolov8n.pt')
# 检测图片
results = model('./img/test/*.jpg') # 检测所有jpg图片第一次会自动下载 yolov8n.pt,下载完成后,会自动加载,速度会比较慢,请耐心等待。也可以在学习通上下载现成的yolov文件,导入到jupyter的工作目录下。

代码执行日志如下:👇
🎯 开始YOLO猫狗检测...
image 1/7 D:\javasoftware\jupyter_notebook\img\test\01.jpg: 416x640 1 cat, 91.9ms
image 2/7 D:\javasoftware\jupyter_notebook\img\test\02.jpg: 640x448 1 cat, 86.8ms
image 3/7 D:\javasoftware\jupyter_notebook\img\test\03.jpg: 640x480 1 cat, 137.3ms
image 4/7 D:\javasoftware\jupyter_notebook\img\test\04.jpg: 640x640 1 dog, 296.7ms
image 5/7 D:\javasoftware\jupyter_notebook\img\test\05.jpg: 640x320 1 dog, 113.7ms
image 6/7 D:\javasoftware\jupyter_notebook\img\test\06.jpg: 640x608 1 dog, 1 teddy bear, 145.0ms
image 7/7 D:\javasoftware\jupyter_notebook\img\test\07.jpg: 384x640 1 dog, 186.7ms
Speed: 5.1ms preprocess, 151.1ms inference, 1.1ms postprocess per image at shape (1, 3, 384, 640)对比发现,非常准,而且速度也很快。
3. yolo案例之车流量统计
前言

import cv2
from ultralytics import solutions
# 打开视频文件进行读取
cap = cv2.VideoCapture("img/cheliu.mp4")
# 断言视频成功打开,如果失败则抛出错误信息
assert cap.isOpened(), "视频文件出错!!!"
# 获取视频的基本属性,用于后续创建视频写入器
w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
# 创建一个视频写入器,用于将处理后的帧编码成新视频
video_writer = cv2.VideoWriter("heatmap_output.avi", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
# 定义用于物体计数的感兴趣区域(Region of Interest, ROI)
# 这些是可选参数,取消注释可以启用特定功能
# region_points = [(20, 400), (1080, 400)] # 一条计数线(例如,用于统计穿过某条线的车辆)
# region_points = [(20, 400), (1080, 400), (1080, 360), (20, 360)] # 一个矩形区域(例如,统计某个房间内的人数)
# region_points = [(20, 400), (1080, 400), (1080, 360), (20, 360), (20, 400)] # 一个多边形区域的点集(可以定义任意形状的区域)
# 初始化热力图对象
heatmap = solutions.Heatmap(
show=True, # 是否在屏幕上实时显示处理结果窗口
model="yolo11n.pt", # YOLO模型文件的路径,用于检测和跟踪物体
colormap=cv2.COLORMAP_PARULA, # 热力图所使用的颜色映射表,PARULA是一种蓝-绿-黄的渐变
# region=region_points, # 如果设置了此参数,热力图和计数将只在定义的区域内进行
# classes=[0, 2], # 只为特定的物体类别生成热力图,例如,只显示'人'(class 0)和'汽车'(class 2)
)
# 开始逐帧处理视频的主循环
while cap.isOpened():
# 从视频流中读取一帧
success, im0 = cap.read()
# 如果读取失败(例如视频已播放完毕),则退出循环
if not success:
print("视频帧为空或处理完成")
break
# 【核心步骤】将当前帧传入热力图对象进行处理
# 该函数会执行目标检测、更新热力图状态,并返回结果
results = heatmap(im0)
# print(results) # 可以取消注释来查看results对象中包含的其他信息
# 将处理后的帧(已叠加热力图)写入到输出视频文件中
video_writer.write(results.plot_im)
# 释放视频捕获对象
cap.release()
# 释放视频写入器对象
video_writer.release()
# 关闭所有由OpenCV创建的图形窗口
cv2.destroyAllWindows()

4. yolo案例之数量识别
前言

https://docs.ultralytics.com/zh/guides/instance-segmentation-and-tracking/
执行之后,会下载yolo11n-seg.pt, 速度回很慢,所以可以下载学习通上的资料,导入到Jupyter的文件夹中

import cv2
from ultralytics import solutions
# 打开视频文件
cap = cv2.VideoCapture("./img/cheliu.mp4")
assert cap.isOpened(), "Error reading video file(错误:无法读取视频文件)"
# 读取第一帧用于选择ROI
ret, frame = cap.read()
if not ret:
print("无法读取视频的第一帧!")
exit()
# 使用OpenCV内置的ROI选择器选择矩形区域
print("请在弹出的窗口中选择检测区域(拖拽鼠标绘制矩形,按SPACE或ENTER确认)")
roi = cv2.selectROI("Select ROI and press SPACE or ENTER", frame) # 弹出窗口选择ROI
cv2.destroyWindow("Select ROI and press SPACE or ENTER") # 关闭选择窗口
# 解析ROI坐标
x, y, w, h = roi
print(f"选择的ROI区域: x={x}, y={y}, width={w}, height={h}")
# 转换为ObjectCounter需要的多边形区域格式(四个顶点)
region_points = [
(x, y), # 左上角
(x + w, y), # 右上角
(x + w, y + h), # 右下角
(x, y + h) # 左下角
]
print(f"转换后的区域坐标: {region_points}")
# 重新设置视频到起始位置
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
# 初始化视频写入器
w_video, h_video, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
video_writer = cv2.VideoWriter("object_counting_output.avi", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w_video, h_video))
# 初始化物体计数器对象
counter = solutions.ObjectCounter(
show=True, # 显示处理结果
region=region_points, # 使用选择的区域
model="yolo11n.pt", # YOLO模型
# classes=[0, 2], # 可选:统计特定类别
# tracker="botsort.yaml", # 可选:跟踪器
)
print("开始处理视频...")
# 处理视频
frame_count = 0
while cap.isOpened():
success, im0 = cap.read()
frame_count += 1
if not success:
print(f"视频处理完成!共处理 {frame_count} 帧")
break
# 对当前帧进行物体计数处理
results = counter(im0)
# 显示进度(可选)
if frame_count % 100 == 0:
print(f"已处理 {frame_count} 帧...")
# 写入处理后的帧
video_writer.write(results.plot_im)
if cv2.waitKey(1) == ord('q'):
break # 7. 退出
# 释放资源
cap.release()
video_writer.release()
cv2.destroyAllWindows()
print("🎉 视频处理完成!输出文件:object_counting_output.avi")
print(f"使用的检测区域: {region_points}")
5. 区域内对象计数
前言
使用 Ultralytics YOLO11 在区域中进行对象计数涉及使用先进的计算机视觉精确确定指定区域内的对象数量。 这种方法对于优化流程、增强安全性以及提高各种应用程序的效率非常有价值。

区域内目标计数的优势
- 精确度和准确性 : 借助先进的计算机视觉技术,区域内对象计数可确保精确和准确的计数,从而最大限度地减少通常与手动计数相关的错误。
- 效率提升 : 自动化对象计数可提高运营效率,提供实时结果并简化不同应用中的流程。
- 多功能性与应用 : 区域内物体计数的多功能性使其适用于各种领域,从制造业和监控到交通监控,从而提高了其广泛的实用性和有效性。
使用 Ultralytics YOLO11 进行对象计数可以应用于许多实际场景:
- 零售分析 : 统计商店不同区域的顾客数量,以优化布局和人员配置。
- 交通管理 : 监控特定路段或交叉路口的车辆流量。
- 制造业: 跟踪在不同生产区域移动的产品。
- 仓库运营 : 盘点指定存储区域中的库存物品。
- 公共安全 : 监控活动期间特定区域的人群密度。
案例代码👇
import cv2
from ultralytics import solutions
cap = cv2.VideoCapture("./img/renliu.mp4")
assert cap.isOpened(), "Error reading video file"
# Pass region as list
# region_points = [(20, 400), (1080, 400), (1080, 360), (20, 360)]
# Pass region as dictionary
region_points = {
# "region-01": [(500, 500), (250, 500), (250, 250), (500, 250)],
"region-02": [(400, 350), (720, 350), (720, 600), (400, 600)],
}
# Video writer
w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
video_writer = cv2.VideoWriter("region_counting.avi", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
# Initialize region counter object
regioncounter = solutions.RegionCounter(
show=True, # display the frame
region=region_points, # pass region points
model="yolo11n.pt", # model for counting in regions, e.g., yolo11s.pt
)
# Process video
while cap.isOpened():
success, im0 = cap.read()
if not success:
print("Video frame is empty or processing is complete.")
break
results = regioncounter(im0)
# print(results) # access the output
video_writer.write(results.plot_im)
cap.release()
video_writer.release()
cv2.destroyAllWindows() # destroy all opened windows区域说明:
点坐标:
A(400, 350) —— B(720, 350)
| |
| |
D(400, 600) —— C(720, 600)
连接方式:A→B→C→D→A (按顺序连接)运行结果:👇

6. yolo案例之运动检测
前言
通过 Ultralytics YOLO11 的姿势估计来监控锻炼,可以通过实时准确地跟踪关键身体地标和关节来增强运动评估。这项技术可以提供关于运动姿势的即时反馈,跟踪锻炼程序,并测量性能指标,从而为用户和教练优化训练课程。
锻炼监控的优势 👇
优化性能 : 根据监控数据定制训练计划,以获得更好的结果。
目标达成 : 跟踪和调整健身目标,以衡量进度。
个性化: 根据个人数据定制锻炼计划,以提高有效性。
健康意识 : 早期检测表明健康问题或过度训练的模式。
明智的决策 : 用于调整日常安排和设定实际目标的数据驱动决策。

人体的关键点位图::👇

案例代码:👇
import cv2
from ultralytics import solutions
# 打开视频文件
cap = cv2.VideoCapture("./img/yundong2.mp4")
# 检查视频是否成功打开
assert cap.isOpened(), "无法读取视频文件"
# 创建视频写入器,用于保存处理后的视频
# 获取视频的宽度、高度和帧率信息
w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
# 创建视频写入对象
video_writer = cv2.VideoWriter("workouts_output.avi", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
# 初始化AI健身监测系统
gym = solutions.AIGym(
show=True, # 是否在屏幕上显示处理后的帧
kpts=[6, 8, 10], # 要监测的关键点索引:6=右肩, 8=右肘, 10=右手腕 (用于俯卧撑监测)
model="yolo11n-pose.pt", # YOLO11姿态估计模型文件路径
# line_width=2, # 可选项:调整边界框和文字显示的线条宽度
)
# 开始处理视频帧
while cap.isOpened():
# 读取一帧视频
success, im0 = cap.read()
# 如果读取失败(视频结束或出错)
if not success:
print("视频帧为空或处理完成")
break
# 使用AI健身系统进行姿态分析和运动监测
results = gym(im0)
# print(results) # 可取消注释查看详细的分析结果数据
# 将处理后的帧写入输出视频文件
video_writer.write(results.plot_im)
# 释放资源
cap.release() # 关闭视频文件
video_writer.release() # 关闭视频写入器
cv2.destroyAllWindows() # 销毁所有OpenCV创建的窗口关键参数详解:
kpts=[6, 8, 10] 的含义:
6: 右肩 (right_shoulder)
8: 右肘 (right_elbow)
10: 右手腕 (right_wrist)
这三个点构成右臂俯卧撑监测:
右肩(6) —— 右肘(8) —— 右手腕(10)
测量: 肘关节的弯曲角度,用于判断俯卧撑的标准性
更多的kpts参数详解:
# 不同的运动检测需要不同的关键点组合
# 以下是一些常见的运动检测关键点组合
#引体向上 (Pull-up)
kpts=[6, 8, 10] # 右肩、右肘、右手腕
# 监控上肢运动
# 深蹲检测
kpts=[11, 13, 15] # 左臀、左膝、左脚踝
# 或者
kpts=[12, 14, 16] # 右臀、右膝、右脚踝
# 监控下肢运动
# 俯卧撑检测
kpts=[6, 8, 10] # 右肩、右肘、右手腕
# 或者
kpts=[5, 7, 9] # 左肩、左肘、左手腕7. Yolo之火柴人
前言

如果需要连接更多的点, 变成火柴人,可以使用下面的代码:
import cv2
import numpy as np
from ultralytics import YOLO
# 使用正确的视频路径
video_path = "./img/yundong.mp4"
cap = cv2.VideoCapture(video_path)
assert cap.isOpened(), f"Error reading video file: {video_path}"
# 获取视频信息
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
print(f"视频信息: 宽度={w}, 高度={h}, FPS={fps}")
# Video writer
output_path = "multi_pose_output.avi"
video_writer = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
# 加载模型
try:
model = YOLO("yolo11n-pose.pt")
print("模型加载成功")
except Exception as e:
print(f"模型加载失败: {e}")
exit()
# 定义多种运动的关键点组合
POSE_CONFIGS = {
'pushup_left': {'kpts': [5, 7, 9], 'color': (255, 0, 0), 'name': 'Left Pushup'}, # 蓝色
'pushup_right': {'kpts': [6, 8, 10], 'color': (0, 255, 0), 'name': 'Right Pushup'}, # 绿色
'squat_left': {'kpts': [11, 13, 15], 'color': (0, 0, 255), 'name': 'Left Squat'}, # 红色
'squat_right': {'kpts': [12, 14, 16], 'color': (255, 255, 0), 'name': 'Right Squat'} # 青色
}
def calculate_angle(point1, vertex, point2):
"""计算三个点形成的角度"""
try:
a = np.array(point1)
b = np.array(vertex)
c = np.array(point2)
ba = a - b
bc = c - b
# 避免除零错误
norm_ba = np.linalg.norm(ba)
norm_bc = np.linalg.norm(bc)
if norm_ba == 0 or norm_bc == 0:
return 0.0
cosine_angle = np.dot(ba, bc) / (norm_ba * norm_bc)
angle = np.arccos(np.clip(cosine_angle, -1.0, 1.0))
return np.degrees(angle)
except:
return 0.0
def draw_multiple_poses(frame, results):
"""在帧上绘制多个姿态分析"""
annotated_frame = frame.copy()
# 如果没有检测到姿态,直接返回原帧
if results[0].keypoints is None:
cv2.putText(annotated_frame, "No pose detected", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
return annotated_frame
keypoints = results[0].keypoints.data.cpu().numpy()
# 如果没有检测到关键点
if len(keypoints) == 0:
cv2.putText(annotated_frame, "No keypoints detected", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
return annotated_frame
# 显示检测到的人数
cv2.putText(annotated_frame, f"Persons detected: {len(keypoints)}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# 遍历检测到的每个人
for person_id, person_kpts in enumerate(keypoints):
if len(person_kpts) == 0:
continue
# 为每个人绘制不同颜色的骨架
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0),
(255, 0, 255), (0, 255, 255), (128, 128, 128)]
person_color = colors[person_id % len(colors)]
# 绘制完整骨架(火柴人)
SKELETON_CONNECTIONS = [
[15, 13], [13, 11], [16, 14], [14, 12], # 腿部和脚部
[11, 12], [5, 11], [6, 12], [5, 6], # 躯干和臀部
[5, 7], [6, 8], [7, 9], [8, 10], # 肩部和手臂
[1, 2], [0, 1], [0, 2], [1, 3], [2, 4] # 头部和面部
]
# 绘制骨架连线
valid_connections = 0
for connection in SKELETON_CONNECTIONS:
start_idx, end_idx = connection
if (start_idx < len(person_kpts) and end_idx < len(person_kpts) and
person_kpts[start_idx][2] > 0.3 and person_kpts[end_idx][2] > 0.3): # 降低置信度阈值
start_pt = (int(person_kpts[start_idx][0]), int(person_kpts[start_idx][1]))
end_pt = (int(person_kpts[end_idx][0]), int(person_kpts[end_idx][1]))
cv2.line(annotated_frame, start_pt, end_pt, person_color, 2)
valid_connections += 1
# 绘制关键点
for j, kpt in enumerate(person_kpts):
if kpt[2] > 0.3: # 降低置信度阈值
center = (int(kpt[0]), int(kpt[1]))
cv2.circle(annotated_frame, center, 3, (255, 255, 255), -1)
# 计算并显示多个关节角度
angles_info = []
angle_positions = [] # 存储角度文字显示位置
# 左臂俯卧撑角度
if (len(person_kpts) > 9 and
person_kpts[5][2] > 0.3 and person_kpts[7][2] > 0.3 and person_kpts[9][2] > 0.3):
angle = calculate_angle(
[person_kpts[5][0], person_kpts[5][1]], # 左肩
[person_kpts[7][0], person_kpts[7][1]], # 左肘
[person_kpts[9][0], person_kpts[9][1]] # 左手腕
)
angles_info.append(f"L-Arm: {angle:.1f}")
elbow_pos = (int(person_kpts[7][0]), int(person_kpts[7][1]))
cv2.circle(annotated_frame, elbow_pos, 8, (255, 0, 0), 2)
angle_positions.append((elbow_pos[0] + 10, elbow_pos[1] - 10))
# 右臂俯卧撑角度
if (len(person_kpts) > 10 and
person_kpts[6][2] > 0.3 and person_kpts[8][2] > 0.3 and person_kpts[10][2] > 0.3):
angle = calculate_angle(
[person_kpts[6][0], person_kpts[6][1]], # 右肩
[person_kpts[8][0], person_kpts[8][1]], # 右肘
[person_kpts[10][0], person_kpts[10][1]] # 右手腕
)
angles_info.append(f"R-Arm: {angle:.1f}")
elbow_pos = (int(person_kpts[8][0]), int(person_kpts[8][1]))
cv2.circle(annotated_frame, elbow_pos, 8, (0, 255, 0), 2)
angle_positions.append((elbow_pos[0] + 10, elbow_pos[1] - 10))
# 左腿深蹲角度
if (len(person_kpts) > 15 and
person_kpts[11][2] > 0.3 and person_kpts[13][2] > 0.3 and person_kpts[15][2] > 0.3):
angle = calculate_angle(
[person_kpts[11][0], person_kpts[11][1]], # 左臀
[person_kpts[13][0], person_kpts[13][1]], # 左膝
[person_kpts[15][0], person_kpts[15][1]] # 左脚踝
)
angles_info.append(f"L-Leg: {angle:.1f}")
knee_pos = (int(person_kpts[13][0]), int(person_kpts[13][1]))
cv2.circle(annotated_frame, knee_pos, 8, (0, 0, 255), 2)
angle_positions.append((knee_pos[0] + 10, knee_pos[1] - 10))
# 右腿深蹲角度
if (len(person_kpts) > 16 and
person_kpts[12][2] > 0.3 and person_kpts[14][2] > 0.3 and person_kpts[16][2] > 0.3):
angle = calculate_angle(
[person_kpts[12][0], person_kpts[12][1]], # 右臀
[person_kpts[14][0], person_kpts[14][1]], # 右膝
[person_kpts[16][0], person_kpts[16][1]] # 右脚踝
)
angles_info.append(f"R-Leg: {angle:.1f}")
knee_pos = (int(person_kpts[14][0]), int(person_kpts[14][1]))
cv2.circle(annotated_frame, knee_pos, 8, (255, 255, 0), 2)
angle_positions.append((knee_pos[0] + 10, knee_pos[1] - 10))
# 显示角度信息
base_y = 60 + person_id * 120 # 为每个人错开显示位置
for i, info in enumerate(angles_info):
y_pos = base_y + i * 25
cv2.putText(annotated_frame, info, (10, y_pos),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# 显示人数标签
head_pos = (int(person_kpts[0][0]) if person_kpts[0][2] > 0.3 else (50, 50))
label_pos = (head_pos[0] - 20, head_pos[1] - 20) if isinstance(head_pos, tuple) else (50, 50)
cv2.putText(annotated_frame, f"P{person_id + 1}", label_pos,
cv2.FONT_HERSHEY_SIMPLEX, 0.8, person_color, 2)
return annotated_frame
# 处理视频
frame_count = 0
while cap.isOpened():
success, frame = cap.read()
if not success:
print(f"视频读取完成,共处理 {frame_count} 帧")
break
frame_count += 1
# 每10帧打印一次进度
if frame_count % 10 == 0:
print(f"处理进度: {frame_count} 帧")
try:
# 姿态估计
results = model(frame, pose=True, conf=0.5) # 添加置信度阈值
# 绘制多个姿态
annotated_frame = draw_multiple_poses(frame, results)
# 显示结果
cv2.imshow("Multi-Pose Analysis - Press Q to quit", annotated_frame)
# 写入视频
video_writer.write(annotated_frame)
except Exception as e:
print(f"处理第 {frame_count} 帧时出错: {e}")
# 写入原始帧
video_writer.write(frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
print("用户手动停止")
break
cap.release()
video_writer.release()
cv2.destroyAllWindows()
print(f"处理完成!输出视频保存为: {output_path}")"L-Arm"→ "左臂角度"
"R-Arm"→ "右臂角度"s
"L-Leg"→ "左腿角度"
"R-Leg"→ "右腿角度"
可以根据这些角度来设定运动类型:
- 俯卧撑:左臂和右臂的角度都小于90度
- 深蹲:左腿和右腿的角度都小于90度
- 其他:其他情况

