ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于MATLAB的人体姿态识别:特征设计与SVM分类实践

基于MATLAB的人体姿态识别:特征设计与SVM分类实践 简介一份面向高校课程设计场景的MATLAB人体行为识别项目资源围绕“坐、蹲、躺、站立”四种静态姿势展开借助图像预处理、特征提取与机器学习分类完成多姿态判别并配备可直接运行的GUI界面。适合需要快速搭建完整视觉识别Demo、理解SVM/HOG等基础思路的初学者。压缩包共15个文件涵盖.m源码、.fig界面文件、.mat数据、测试图片、运行效果图及说明文档整体约1.01MB轻量易用。其中invmoments.m与shuyeshibie.m构成核心识别流程配合运行方法.docx和说明.txt可降低上手门槛GUI设计图与效果图方便对照界面布局。目前已有941人学习对于正在做计算机视觉或模式识别课程设计、希望参考完整项目结构并快速复现的同学这份资源能提供从数据准备到界面交互的整套参考实现。1. MATLAB人体行为识别为什么先定特征再搭GUI视频行为分析里坐、蹲、躺、站是四类最基础的静态姿态养老看护、跌倒预警、工位行为统计都要先把它们判准。这类需求样本少、现场环境杂、还要能当场给人演示用 MATLAB 做是最顺的取帧、特征计算、模型训练、GUI 界面都在同一个环境里改一处就能立刻看到效果。常见误区是一上来堆深度网络普通电脑跑不动出错也分不清是分割问题还是分类问题。我一般反过来做背景建模抠出人体轮廓算宽高比、填充率、质心高度用多分类 SVM 区分姿态再用 App Designer 包一层 GUI 界面。中间每一步都能打印结果排错路径短。这篇文章从特征设计写到分类器训练再把摄像头接进 GUI 界面最后给一套让输出更稳的多数投票方案。2. 姿态识别的特征设计从视频帧里把人抠出来再算几何量2.1 用 vision.ForegroundDetector 把运动前景分离出来特征提取前先解决一个问题画面里除了人还有椅子、桌子、地面纹理这些都会干扰后续的分类。常见做法是用前景检测器把运动的人从背景里分离出来MATLAB 的 Computer Vision Toolbox 提供了vision.ForegroundDetector本质是混合高斯背景建模对缓慢变化的背景比单纯帧差法稳得多。%% 读取视频并初始化前景检测器 vidReader VideoReader(sample.mp4); fgd vision.ForegroundDetector(... NumTrainingFrames, 50, ... AdaptLearningRate, 0.01, ... InitialVariance, 30); % 前50帧只用于建立背景模型不参与识别 for i 1:50 frame readFrame(vidReader); fgd(frame); end % 从第51帧开始取真实识别帧 frame readFrame(vidReader); fgMask fgd(frame); fgMask medfilt2(fgMask, [5 5]); % 中值滤波去掉椒盐噪声这段代码先让检测器用前 50 帧学习背景再对后续帧输出二值掩膜前景像素为 1背景为 0。NumTrainingFrames决定背景建模要花多少帧场景里有窗帘晃动或光线渐变时可以加到 80 到 100太小背景没学全静止的人会被判成背景。AdaptLearningRate控制背景更新速度调大能让模型跟上光线变化但人如果长时间不动也会慢慢融进背景识别就会断掉我一般控制在 0.005 到 0.01。InitialVariance是初始背景方差数值越小前几帧的像素越容易被当成前景直接调 30 起步就行。前景掩膜通常带着大量孤立噪点所以要先过一遍中值滤波。接下来还要做一步后处理用凸包填补人体轮廓里的空洞并只保留面积最大的连通域。fgMask bwconvhull(fgMask, union); % 把非连通的人形区域补成完整轮廓 labels bwlabel(fgMask); % 给所有连通域编号 stats regionprops(fgMask, Area, ... BoundingBox, FilledArea, ... Centroid, Orientation, Extent); [~, idx] max([stats.Area]); % 找到面积最大的目标 fgMask ismember(labels, idx); % 只保留画面中最大的目标bwconvhull的union模式会生成包住整片前景的凸包适合把被椅子扶手、桌腿切碎的人形轮廓重新粘起来。bwlabel给每个连通区域编一个整数号regionprops返回一个结构数组每个元素对应该区域的几何量。max([stats.Area])找到面积最大的编号再用ismember把掩膜中其他区域全部置零。这里只取面积最大的对象是假设画面里只有一个人形目标多人同时出现时需要用跟踪器给每个人单独分配掩膜否则宽高比会把两个人一起包进去得到毫无意义的结果。提示bwconvhull会把人腿部打开的区域一并填满宽高比会略微变大对坐姿与蹲姿这类“收缩型”姿态影响不小可以先做形态学腐蚀再取凸包让轮廓更贴合真实人体。2.2 四个特征量把坐蹲躺站分开轮廓提取出来后真正有判别力的是人体区域的空间分布。我做这类项目时通常只保留四个特征量够用且易于排查特征量计算方式站立坐 / 蹲躺宽高比外接矩形宽/高小于 0.60.6~1.2大于 1.3质心高度比质心 Y 坐标 / 矩形高度大于 0.60.35~0.60.4~0.6填充率FilledArea / 包围盒面积0.45~0.70.6~0.85大于 0.7主轴倾斜角Orientation 的绝对值接近 0接近 0接近 90站立时身体竖直外接矩形明显高大于宽质心位置靠上坐下和蹲下都是身体在垂直方向缩短区别在于蹲姿更矮、包围盒更饱满躺下时宽大于高主轴方向接近水平。这个表里的区间只是初始参考值不同摄像头安装高度和焦距下要重测。把单个特征变成函数方便后面在训练脚本和 GUI 回调里共用function feat extractPoseFeature(fgMask) props regionprops(fgMask, BoundingBox, Centroid, ... Area, FilledArea, Orientation, Extent); if isempty(props) feat zeros(1, 4); return; end bb props.BoundingBox; w bb(3); h bb(4); feat(1) w / max(h, eps); % 宽高比 feat(2) props.Centroid(2) / h; % 质心相对高度 feat(3) props.Extent; % 填充率 feat(4) abs(props.Orientation); % 主轴与垂直轴的夹角单位度 end函数返回一个 1×4 的特征向量顺序固定为宽高比、质心高度比、填充率、主轴夹角。eps用来防止高度为 0 时除零max(h, eps)保证分母总是正小数。质心高度比用的是图像坐标系的 Y 值在 MATLAB 里图像 Y 轴向下所以质心 Y 越大表示位置越低和常识相反算特征时不需要改成物理坐标分类器会自己学习这个关系。2.3 分辨不出的姿势正面坐姿与蹲姿几何特征不是万能的。人正面朝向摄像头坐下时外接矩形高度被压缩、填充率升高和蹲姿在 2D 画面里的表现几乎一模一样。如果只在纯几何特征上做文章怎么调阈值都压不住这两类的混淆。常见的补齐方案有两类。一类是增加样本形态差异把拍摄机位固定在斜上方 30 到 45 度坐姿的腿部会向前伸出宽高比明显大于蹲姿。另一类是引入骨架关键点MATLAB 的 Deep Learning Toolbox 可以加载预训练姿态估计网络把双肩连线与髋部连线的夹角、肩髋垂直距离作为附加维度和几何特征拼成一个更长的向量再喂给分类器。第二种方案效果好但对显卡和 CPU 要求高做原型验证时我通常先用第一种固定机位采集几天数据看混淆矩阵再决定要不要上骨架。3. 训练分类器用 fitcecoc 把坐蹲躺站做成多分类模型3.1 样本怎么组织按姿势归档的 CSV 比零散 .mat 更好用开机位前要先把训练样本准备好。特征提取脚本会批量遍历视频对每一帧计算extractPoseFeature输出的特征存成 CSV每种姿势建一个文件。这样训练和采集流程解耦模型换场景时只要重新生成 CSV不用改分类器代码。文件名行数每行内容sit.csvN1宽高比, 质心高度比, 填充率, 主轴角squat.csvN2同上lie.csvN3同上stand.csvN4同上这个表格的意义在于约束采集数量四种姿势的样本量尽量接近尤其是蹲姿现场人员蹲的次数最少很容易出现只有几十条、模型被其他类带跑的情况。我的经验是每类不低于 200 帧且把同一个人的连续帧隔几帧取一条避免相邻帧高度相关。读取 CSV 并组装成训练集files {sit.csv, squat.csv, lie.csv, stand.csv}; labels categorical({sit, squat, lie, stand}); X []; Y []; for k 1:numel(files) data readmatrix(fullfile(features, files{k})); X [X; data]; Y [Y; repmat(labels(k), size(data, 1), 1)]; endreadmatrix在读取数值型 CSV 时会把文本表头忽略掉所以文件名里的纯数字文件可以直接读。repmat把标签重复到和特征矩阵行数一致再把四个矩阵纵向拼接得到完整的 X 与 Y。categorical标签在分类器里比整数标签更安全后续confusionchart能直接显示类名。3.2 用 fitcecoc 训练多分类 SVM 并做交叉验证四分类问题在 MATLAB 里有标准套路fitcecoc把多分类拆成多个二分类任务底层用 SVM 作为每个二元学习器分类决策面比单个多类 SVM 更稳定。rng(2024); % 固定随机种子保证可复现 template templateSVM(KernelFunction, linear, ... Standardize, true); cvmdl fitcecoc(X, Y, ... Learners, template, ... Coding, onevsone, ... KFold, 5); fprintf(交叉验证错误率: %.2f%%\n, kfoldLoss(cvmdl) * 100); finalMdl fitcecoc(X, Y, Learners, template); save(postureModel.mat, finalMdl);rng(2024)固定随机种子否则KFold切分数据的随机性会让每次训练的错误率不一样。templateSVM里的KernelFunction选linear时配合Standardize对 0~1 和 0~90 两种量纲的混合特征很友好换成rbf能拟合更复杂的边界但需要额外调KernelScale样本少时容易过拟合。Coding的onevsone把问题拆成六对二分类每对只在两个类别上训练比onevsall慢一些但在类别不平衡时对少数类更宽容。上面的KFold在fitcecoc里会返回一个分好折的交叉验证模型kfoldLoss给出平均错误率先拿它决定特征是否靠谱再重新训练完整的finalMdl用于 GUI。3.3 单帧预测与置信度输出GUI 里每一帧调用一次预测代码只有三行feat extractPoseFeature(fgMask); % 从前景掩膜计算特征向量 [label, score] predict(finalMdl, feat);predict的第一个返回值是模型判定的类别第二个返回值是一个 1×4 的分数向量数值越大表示模型对改类越有把握。四个分数之和为 1但分数不能直接理解为概率更多是类别间的相对比较。GUI 上显示姿态时我会把分数排序后只取前两名比如“蹲下 0.62坐下 0.31”这样在使用者眼里不是不明不白地跳号而是能看到模型在犹豫。3.4 分类器常见翻车现场这类项目最容易翻车的不是算法调参而是把“旧场景的模型”直接用来跑“新场景的视频”。换摄像头高度、换房间、换人的体型特征的数值分布都会漂移。常见做法是保留训练脚本把新视频切出几分钟重新生成 CSV 后重训一次再比较交叉验证错误率不要相信第一次训练的模型可以一直用。另一个问题是特征向量里混入大量人体区域面积很小的帧此时质心高度比完全没有意义采集样本时要把“人太小、面积低于阈值”的帧直接丢弃我的阈值是外接矩形高度小于整个画面高度十分之一时不计入样本。4. App Designer 搭 GUI把视频画面、识别结果和调试参数放进同一个窗口4.1 为什么用 App Designer 而不是 GUIDEGUI 界面在 MATLAB 里的老主流是 GUIDE但它基于 Figure 回调控件代码和界面布局文件分离改一个控件 Tag 后面代码就要跟着找半天。现在新项目我一般用 App Designer理由很直接回调函数自动带上app对象所有控件都能用app.控件名访问代码在同一个文件里编辑运行前还能直接预览窗口布局对做演示原型来说效率高得多。4.2 控件清单与布局界面布局按“左画面、右信息”的结构排左侧放视频显示区右侧放识别结果和操作按钮这样演示时观众眼睛自然落在结果上。这套布局需要的最小控件如下控件Tag作用坐标区UIAxes显示当前帧与人体轮廓按钮StartButton启动/停止识别循环标签PostureLabel显示当前姿态中文名标签ScoreLabel显示最高置信度数值下拉框ModelSelect切换已保存的模型文件布局细节UIAxes 的宽高比设成 16:9 与摄像头输出一致不然画面会被拉伸变形PostureLabel 的字体大小设为 20 磅以上颜色用深绿表示识别正常分数低于 0.5 时用橙色提醒“低置信度”这比一堆仪表盘更直观。4.3 回调里用 timer 跑识别循环新手最容易踩的坑是在按钮回调里写while true读视频。这个循环会占住主线程窗口拖动、按钮点击全部失去响应整个 GUI 界面像卡死了一样。常见做法是创建一个timer对象每个周期处理一帧主循环让位给事件回调。function startDetection(app) if ~isempty(app.t) isvalid(app.t), stop(app.t); end app.t timer(... TimerFcn, (~, ~) stepOnce(app), ... Period, 0.15, ... ExecutionMode, fixedRate, ... TasksToExecute, inf); start(app.t); end function stepOnce(app) if hasFrame(app.vid) frame readFrame(app.vid); else app.vid.CurrentTime 0; % 视频播完回到开头 return; end fgMask fgd(frame); feat extractPoseFeature(fgMask); [label, score] predict(app.model, feat); labelNames [站立; 坐下; 蹲下; 躺下]; app.PostureLabel.Text char(labelNames(label)); [~, maxIdx] max(score); app.ScoreLabel.Text sprintf(置信度 %.2f, score(maxIdx)); imshow(frame, Parent, app.UIAxes); endTimerFcn在每个计时周期触发一次stepOncePeriod设为 0.15 秒即每秒处理约 6 帧这个帧率对结果展示足够也留给特征提取和处理足够时间。hasFrame判断视频是否还有下一帧避免readFrame在文件末尾抛错。predict返回的label是一个 categorical 类型直接用整数索引到labelNames字符串数组把英文类名映射成界面上的中文显示。imshow时参数Parent必须写否则图像会开一个新的 Figure 窗口。注意timer的回调里不要再用drawnow或pause强行刷新timer本身在事件队列里执行插入阻塞语句会让其他 UI 回调排队界面反而更卡。想要调试信息就用fprintf写到命令行。4.4 摄像头输入与文件回放的统一接口演示前经常要在现场摄像头和录好的视频之间切换。把视频源统一封装成一个句柄摄像头用webcam文件用VideoReader两者的读取接口略有差异我在 App Designer 里的做法是在启动时判断传入路径是否为文件if ~isempty(app.videoPath) % 文件模式 app.vid VideoReader(app.videoPath); else % 摄像头模式 cam webcam; app.vid struct(hasFrame, () true, ... readFrame, () cam.snapshot); app.isWebcam true; end这样stepOnce里只要写hasFrame(app.vid)和readFrame(app.vid)就行不用在回调里到处判断当前是摄像头还是文件模式。文件模式的readFrame返回 RGB 图像摄像头snapshot返回的也是 RGB 图像类型统一后面所有处理逻辑共用。5. 验证与交付技巧混淆矩阵、多数投票和低置信度提示5.1 用多数投票把单帧抖动压下去单帧分类结果会偶尔跳变比如坐姿下身体前倾一两帧被误判成蹲姿。多数投票是成本最低的平滑手段维护一个固定长度的标签缓冲最终输出取最新 N 帧的判断众数。function voted majorityVote(app, label) app.buf [app.buf; char(label)]; if size(app.buf, 1) 7 app.buf(1, :) []; end voted mode(categorical(cellstr(app.buf))); end窗口长度取 7 帧在 0.15 秒一帧的处理节奏下大约是 1 秒的平滑时间界面上姿态切换不会粘滞又能滤掉孤立误判。窗口太小没作用窗口太大则从站到坐的切换要延迟好几秒体验很差。注意缓冲用字符串数组按行追加超过窗口长度时删除第一行保持队列先进先出。5.2 把验证做成你能向别人证明的东西识别模型交付时光说“准确率百分之八十多”没有说服力我用 MATLAB 自带的confusionchart直接把测试集结果画出来能直观看到哪两类在互相认错。predicted predict(finalMdl, XTest); confusionchart(YTest, predicted);图表会自动按类名排布对角线亮说明该类别大部分判对非对称的暗点说明某两类存在系统性混淆。如果“蹲”与“坐”的格子偏亮就把这二分类单独拎出来画散点图看两个特征量的分布是否重叠再去决定调整机位还是追加骨架关键点维度。5.3 低置信度提示和模型重训闭环GUI 成品交付后维护成本最高的不是界面是模型带不动新现场。我会在界面上加一个低置信度逻辑当max(score)小于 0.5 时PostureLabel 颜色由深绿切到橙黄同时把当前帧保存到unlabeled/目录积累一段时间后人工标注并追加进 CSV重跑一遍第三节的训练脚本。这样模型每次更新都是在真实数据上加量不是凭感觉改参数。保存模型时把文件名带 上日期比如postureModel_20260412.mat下拉列表里保留历史版本现场效果变差时能一键回滚这个细节在交付给别人维护时尤其重要。本文还有配套的精品资源点击获取
返回列表