[转载]win8快捷键大全分享,非常全 - 幕三少 - 博客园

mikel阅读(802)

[转载]win8快捷键大全分享,非常全 – 幕三少 – 博客园.

Windows 8全新的Metro操作体验,对于没有平板只能用快捷键来提高效率了。全面的Windows 8快捷键,请下载微软官方的Windows 8快捷键表格,快捷键全记完整个人都斯巴达了..其实常用的也就那么几个,例如:Win+X,Win+I等..Win键这回在Windows 8是发挥到极致了。现把我搜集到的快捷键与大家分享下

Windows 8 Consumer Preview常用快捷键

Windows 键 可在开始屏幕主菜单及最后一个应用程序间循环切换

Windows 键 + C 打开“超级按钮”

Windows 键 + I 打开当前程序的“设置”菜单

Windows 键 + F 搜索文件或文件夹

Windows 键 + Q 搜索应用

Windows 键 + 空格键 切换语言或输入法(如果启用了多种语言或输入法)

Windows 键 + Z 右键菜单(开始屏幕)

Windows 键 + L 锁定计算机或切换用户

Windows 键 + Tab 在已打开应用程序间循环切换(开始屏幕)

Windows 键 + P 选择演示显示模式

Windows 键 + E 打开计算机

Windows 键 + R 打开“运行”对话框

Windows 键 + D 显示桌面

Windows 键 + M 最小化所有窗口

Windows 键 + T 循环切换任务栏上的程序(传统桌面)

Windows 键 + 数字键 “启动锁定到任务栏中的由该数字所表示位置处的程序

如果该程序已在运行,则切换到该程序(传统桌面)”

Windows 键 + 向上键 最大化窗口(传统桌面)

Windows 键 + 向左键 将窗口最大化到屏幕的左侧(传统桌面)

Windows 键 + 向右键 将窗口最大化到屏幕的右侧(传统桌面)

Windows 键 + 向下键 最小化窗口(传统桌面)

Windows 键 + Home 最小化除活动窗口之外的所有窗口(传统桌面)

开始屏幕直接输入 开始屏幕下可直接搜索关键词

Alt+Tab 通过选择栏在所有已打开程序间切换(传统桌面)

其他 Windows 键相关快捷键

Windows 键 + G 循环切换小工具

Windows 键 + U 打开轻松访问中心

Windows 键 + X 打开 Windows 移动中心

Windows 键 + Pause 显示“系统属性”对话框

Windows 键 + Shift + V 反向切换系统通知信息

Windows 键 + Shift + 加号(+) 打开放大镜并放大桌面

Windows 键 + Shift + 减号(-) 打开放大镜并缩小桌面

Windows 键 + O 开启或关闭屏幕方向锁定(如果您的电脑支持屏幕方向自动感应)

Windows 键 + V 在屏幕上的通知中循环切换

Windows 键 + Page Up 在多监视器设置中将开始屏幕移动至左监视器

Windows 键 + Page Down 在多监视器设置中将开始屏幕移动至右监视器

Windows 键 + Enter 打开“讲述人”

Windows 键 + W 打开所选设置的“搜索”个性分类

Windows 键 + H 打开“共享”个性分类

Windows 键 + K 打开“设备”个性分类

Windows 键 + Shift + M 将最小化的窗口还原到桌面

Windows 键 + Shift + 向上键 将窗口拉伸到屏幕的顶部和底部

Windows 键 + Shift + 向左键或向右键 将窗口从一个监视器移动到另一个监视器

Windows 键 + ‘ 当您将应用程序向一侧对齐时,此热键将切换屏幕上应用程序的中心

Windows 键 + . 当您将应用程序向一侧对齐时,此热键会将拆分栏移动至右侧

Windows 键 + Shift + . 当您将应用程序向一侧对齐时,此热键会将拆分栏移动至左侧

Ctrl + Windows 键 + F 搜索计算机(如果已连接到网络)

Shift + Windows 键 + 数字 启动锁定到任务栏中的由该数字所表示位置处的程序的新实例

Ctrl + Windows 键 + 数字 切换到锁定到任务栏中的由该数字所表示位置处的程序的最后一个活动窗口

Alt + Windows 键 + 数字 打开锁定到任务栏中的由该数字所表示位置处的程序的跳转列表 (Jump List)

Ctrl + Windows 键 + Tab 通过 Aero Flip 3-D 使用箭头键循环切换任务栏上的程序

Ctrl + Windows 键 + B 切换到在通知区域中显示消息的程序

常规键盘快捷方式

F1 显示帮助

Ctrl+Shift+Esc 打开任务管理器

Ctrl+C(或 Ctrl+Insert) 复制选择的项目

Ctrl+X 剪切选择的项目

Ctrl+V(或 Shift+Insert) 粘贴选择的项目

Ctrl+Z 撤消操作

Ctrl+Y 重新执行某项操作

Delete(或 Ctrl+D) 删除所选项目并将其移动到“回收站”

Shift+Delete 不先将所选项目移动到“回收站”而直接将其删除

F2 重命名选定项目

Ctrl+向右键 将光标移动到下一个字词的起始处

Ctrl+向左键 将光标移动到上一个字词的起始处

Ctrl+向下键 将光标移动到下一个段落的起始处

Ctrl+向上键 将光标移动到上一个段落的起始处

Ctrl+Shift 加某个箭头键 选择一块文本

Shift 加任意箭头键 在窗口中或桌面上选择多个项目,或者在文档中选择文本

Ctrl 加任意箭头键+空格键 选择窗口中或桌面上的多个单个项目

Ctrl+A 选择文档或窗口中的所有项目

F3 搜索文件或文件夹

Alt+Enter 显示所选项的属性

Alt+F4 关闭活动项目或者退出活动程序

Alt+空格键 为活动窗口打开快捷方式菜单

Ctrl+F4 关闭活动文档(在允许同时打开多个文档的程序中)

Ctrl+Alt+Tab 使用箭头键在打开的项目之间切换

Ctrl+鼠标滚轮 更改桌面上的图标大小

Windows 键 + Tab 使用 Aero Flip 3-D 循环切换任务栏上的程序

Ctrl + Windows 键 + Tab 通过 Aero Flip 3-D 使用箭头键循环切换任务栏上的程序

Alt+Esc 以项目打开的顺序循环切换项目

F6 在窗口中或桌面上循环切换屏幕元素

F4 在 Windows 资源管理器中显示地址栏列表

Shift+F10 显示选定项目的快捷菜单

Ctrl+Esc 打开「开始」菜单

Alt+加下划线的字母 显示相应的菜单

Alt+加下划线的字母 执行菜单命令(或其他有下划线的命令)

F10 激活活动程序中的菜单栏

向右键 打开右侧的下一个菜单或者打开子菜单

向左键 打开左侧的下一个菜单或者关闭子菜单

F5(或 Ctrl+R) 刷新活动窗口

Alt+向上键 在 Windows 资源管理器中查看上一级文件夹

Esc 取消当前任务

插入 CD 时按住 Shift 阻止 CD 自动播放

左 Alt+Shift 在启用多种输入语言时切换输入语言

Ctrl+Shift 在启用多个键盘布局时切换键盘布局

右或左 Ctrl+Shift 更改从右到左阅读语言的文本阅读方向

“在 Windows 资源管理器或文件夹中使用的快捷键”

Ctrl+N 打开新窗口

Ctrl+W 关闭当前窗口

Ctrl+Shift+N 新建文件夹

End 显示活动窗口的底端

Home 显示活动窗口的顶端

F11 最大化或最小化活动窗口

Ctrl+句点 (.) 顺时针旋转图片

Ctrl+逗号 (,) 逆时针旋转图片

Num Lock+数字键盘上的星号 (*) 显示所选文件夹下的所有子文件夹

Num Lock+数字键盘上的加号 (+) 显示所选文件夹的内容

Num Lock+数字键盘上的减号 (-) 折叠选定的文件夹

向左键 折叠当前选项(如果它处于展开状态),或者选择其父文件夹

Alt+Enter 打开所选项目的“属性”对话框

Alt+P 显示预览窗格

Alt+向左键 查看上一个文件夹

Backspace 查看上一个文件夹

向右键 显示当前选项(如果它处于折叠状态),或者选择第一个子文件夹

Alt+向右键 查看下一个文件夹

Alt+向上键 查看父文件夹

Ctrl+Shift+E 显示所选文件夹上面的所有文件夹

Ctrl+鼠标滚轮 更改文件和文件夹图标的大小和外观

Alt+D 选择地址栏

Ctrl+E 选择搜索框

Ctrl+F 选择搜索框

在对话框中使用的快捷键

Ctrl+Tab 在选项卡上向前移动

Ctrl+Shift+Tab 在选项卡上向后移动

Tab 在选项上向前移动

Shift+Tab 在选项上向后移动

Alt+加下划线的字母 执行与该字母匹配的命令(或选择选项)

Enter 对于许多选定命令代替单击鼠标

空格键 如果活动选项是复选框,则选中或清除该复选框

箭头键 如果活动选项是一组选项按钮,则选择某个按钮

F1 显示帮助

F4 显示活动列表中的项目

Backspace 如果在“另存为”或“打开”对话框中选中了某个文件夹,则打开上一级文件夹

在任务栏项目中的快捷键

按住 Shift 并单击某个任务栏按钮 打开程序,或快速打开程序的另一个实例

按住 Ctrl+Shift 并单击某个任务栏按钮 以管理员身份打开程序

按住 Shift 并右键单击某个任务栏按钮 显示该程序的窗口菜单

按住 Shift 并右键单击某个分组的任务栏按钮 显示该组的窗口菜单

按住 Ctrl 并单击某个分组的任务栏按钮 循环切换该组的窗口

在放大镜中的快捷键

Windows 徽标键 + 加号 (+) 或减号 (-) 放大或缩小

Ctrl+Alt+空格键 以全屏模式预览桌面

Ctrl+Alt+F 切换到全屏模式

Ctrl+Alt+L 切换到镜头模式

Ctrl+Alt+D 切换到停靠模式

Ctrl+Alt+I 反色

Ctrl+Alt+箭头键 按箭头键的方向平移

Ctrl+Alt+R 调整镜头的大小

Windows 徽标键 + Esc 退出放大镜

在远程桌面连接中的快捷

Alt+Page Up 将程序从左侧移动到右侧

Alt+Page Down 将程序从右侧移动到左侧

Alt+Insert 按照程序启动的顺序循环切换程序

Alt+Home 显示「开始」菜单

Ctrl+Alt+Break 在窗口和全屏之间切换

Ctrl+Alt+End 显示“Windows 安全”对话框

Alt+Delete 显示系统菜单

Ctrl+Alt+数字键盘上的减号 (-) 将客户端当前活动窗口的副本放在终端服务器的剪贴板上(提供的功能与在本地计算机上按 Alt+PrtScn 相同)

Ctrl+Alt+数字键盘上的加号 (+) 将整个客户端窗口区域的副本放在终端服务器的剪贴板上(提供的功能与在本地计算机上按 PrtScn 相同)

Ctrl+Alt+向右键 从远程桌面控件“跳转”到主机程序中的控件(如按钮或文本框)将远程桌面控件嵌入到其他(主机)程序后,此功能非常有用

Ctrl+Alt+向左键 从远程桌面控件“跳转”到主机程序中的控件(如按钮或文本框)将远程桌面控件嵌入到其他(主机)程序后,此功能非常有用

在画图中的快捷键

Ctrl+N 创建新的图片

Ctrl+O 打开现有图片

Ctrl+S 将更改保存到图片

F12 将此图片另存为新文件

Ctrl+P 打印图片

Alt+F4 关闭图片及其画图窗口

Ctrl+Z 撤消更改

Ctrl+Y 恢复更改

Ctrl+A 选择整个图片

Ctrl+X 剪切选择内容

Ctrl+C 将选择内容复制到剪贴板

Ctrl+V 从剪贴板粘贴选择内容

向右键 将选择内容或活动图形向右移动一个像素

向左键 将选择内容或活动图形向左移动一个像素

向下键 将选择内容或活动图形向下移动一个像素

向上键 将选择内容或活动图形向上移动一个像素

Esc 取消某个选择

Delete 删除某个选择

Ctrl+B 粗体选择文本

Ctrl++ 将画笔、直线或形状轮廓的宽度增加一个像素

Ctrl+- 将画笔、直线或形状轮廓的宽度减少一个像素

Ctrl+I 将所选文本改为斜体

Ctrl+U 为所选文本添加下划线

Ctrl+E 打开“属性”对话框

Ctrl+W 打开“调整大小和扭曲”对话框

Ctrl+Page Up 放大

Ctrl+Page Down 缩小

F11 以全屏模式查看图片

Ctrl+R 显示或隐藏标尺

Ctrl+G 显示或隐藏网格线

F10 或 Alt 显示快捷键提示

Shift+F10 显示当前快捷菜单

F1 打开“画图”帮助

在写字板中的快捷键

Ctrl+N 新建一个文档

Ctrl+O 打开一个现有文档

Ctrl+S 将更改保存到文档

F12 将此文档另存为新文件

Ctrl+P 打印文档

Alt+F4 关闭“写字板”

Ctrl+Z 撤消更改

Ctrl+Y 恢复更改

Ctrl+A 选择整个文档

Ctrl+X 剪切选择内容

Ctrl+C 将选择内容复制到剪贴板

Ctrl+V 从剪贴板粘贴选择内容

Ctrl+B 将所选文本改为粗体

Ctrl+I 将所选文本改为斜体

Ctrl+U 为所选文本添加下划线

Ctrl+= 使选择的文本成为下标

Ctrl+Shift+= 使选择的文本成为上标

Ctrl+L 向左对齐文本

Ctrl+E 向中心对齐文本

Ctrl+R 向右对齐文本

Ctrl+J 对齐文本

Ctrl+1 设置单倍行距

Ctrl+2 设置双倍行距

Ctrl+5 将行距设置为 1.5

Ctrl+Shift+> 增加字体大小

Ctrl+Shift+< 减小字体大小

Ctrl+Shift+A 将字符更改为全部使用大写字母

Ctrl+Shift+L 更改项目符号样式

Ctrl+D 插入 Microsoft 画图图片

Ctrl+F 在文档中查找文本

F3 在“查找”对话框中查找文本的下一个实例

Ctrl+H 在文档中替换文本

Ctrl+向左键 将光标向左移动一个字

Ctrl+向右键 将光标向右移动一个字

Ctrl+向上键 将光标移动到上一行

Ctrl+向下键 将光标移动到下一行

Ctrl+Home 移动到文档的开头

Ctrl+End 移动到文档的结尾

Ctrl+Page Up 向上移动一个页面

Ctrl+Page Down 向下移动一个页面

Ctrl+Delete 删除下一个字

F10 显示快捷键提示

Shift+F10 显示当前快捷菜单

F1 打开“写字板”帮助

在计算器中的快捷键

Alt+1 切换到标准模式

Alt+2 切换到科学型模式

Alt+3 切换到程序员模式

Alt+4 切换到统计信息模式

Ctrl+E 打开日期计算

Ctrl+H 将计算历史记录打开或关闭

Ctrl+U 打开单位转换

Alt+C 计算或解决日期计算和工作表

F1 打开“计算器”帮助

Ctrl+Q 按下 M- 按钮

Ctrl+P 按下 M+ 按钮

Ctrl+M 按下 MS 按钮

Ctrl+R 按下 MR 按钮

Ctrl+L 按下 MC 按钮

% 按下 % 按钮

F9 按下 +/– 按钮

/ 按下 / 按钮

* 按下 * 按钮

+ 按下 + 按钮

– 按下 – 按钮

R 按下 1/× 按钮

@ 按下平方根按钮

0-9 按下数字按钮 (0-9)

= 按下 = 按钮

. 按下 .(小数点)按钮

Backspace 按下 Backspace 按钮

Esc 按下 C 按钮

Del 按下 CE 按钮

Ctrl+Shift+D 清除计算历史记录

F2 编辑计算历史记录

向上箭头键 在计算历史记录中向上导航

向下箭头键 在计算历史记录中向下导航

Esc 取消编辑计算历史记录

输入 编辑后重新计算计算历史记录

F3 在科学型模式下选择“角度”

F4 在科学型模式下选择“弧度”

F5 在科学型模式下选择“梯度”

I 在科学型模式下按 Inv 按钮

D 在科学型模式下按 Mod 按钮

Ctrl+S 在科学型模式下按 sinh 按钮

Ctrl+O 在科学型模式下按 cosh 按钮

Ctrl+T 在科学型模式下按 tanh 按钮

( 在科学型模式下按 ( 按钮

) 在科学型模式下按 ) 按钮

N 在科学型模式下按 ln 按钮

; 在科学型模式下按 Int 按钮

S 在科学型模式下按 sin 按钮

O 在科学型模式下按 cos 按钮

T 在科学型模式下按 tan 按钮

M 在科学型模式下按 dms 按钮

P 在科学型模式下按 pi 按钮

V 在科学型模式下按 F-E 按钮

X 在科学型模式下按 Exp 按钮

Q 在科学型模式下按 x^2 按钮

Y 在科学型模式下按 x^y 按钮

# 在科学型模式下按 x^3 按钮

L 在科学型模式下按 log 按钮

! 在科学型模式下按 n! 按钮

Ctrl+Y 在科学型模式下按 y√x 按钮

Ctrl+B 在科学型模式下按 3√x 按钮

Ctrl+G 在科学型模式下按 10x 按钮

F5 在程序员模式下选择 Hex

F6 在程序员模式下选择 Dec

F7 在程序员模式下选择 Oct

F8 在程序员模式下选择 Bin

F12 在程序员模式下选择 Qword

F2 在程序员模式下选择 Dword

F3 在程序员模式下选择 Word

F4 在程序员模式下选择 Byte

K 在程序员模式下按 RoR 按钮

J 在程序员模式下按 RoL 按钮

< 在程序员模式下按 Lsh 按钮

> 在程序员模式下按 Rsh 按钮

% 在程序员模式下按 Mod 按钮

( 在程序员模式下按 ( 按钮

) 在程序员模式下按 ) 按钮

| 在程序员模式下按 Or 按钮

^ 在程序员模式下按 Xor 按钮

~ 在程序员模式下按 Not 按钮

& 在程序员模式下按 And 按钮

A-F 在程序员模式下按 A-F 按钮

空格键 在程序员模式下切换位值

A 在统计信息模式下按 Average 按钮

Ctrl+A 在统计信息模式下按 Average Sq 按钮

S 在统计信息模式下按 Sum 按钮

Ctrl+S 在统计信息模式下按 Sum Sq 按钮

T 在统计信息模式下按 S.D. 按钮

Ctrl+T 在统计信息模式下按 Inv S.D. 按钮

D 在统计信息模式下按 CAD 按钮

在 Windows 日记中的快捷键

Ctrl+N 开始新的便笺

Ctrl+O 打开最近使用的便笺

Ctrl+S 将更改保存到便笺

Ctrl+Shift+V 将便笺移动到特定的文件夹

Ctrl+P 打印便笺

Alt+F4 关闭便笺及其日记本窗口

Ctrl+Z 撤消更改

Ctrl+Y 恢复更改

Ctrl+A 选择页面上的所有项目

Ctrl+X 剪切选择内容

Ctrl+C 将选择内容复制到剪贴板

Ctrl+V 从剪贴板粘贴选择内容

Esc 取消某个选择

删除 删除某个选择

Ctrl+F 开始基本查找

Ctrl+G 转到页面

F5 刷新查找结果

F5 刷新便笺列表

F6 在便笺列表和便笺之间切换

Ctrl+Shift+C 显示便笺列表中列标题的快捷菜单

F11 以全屏模式查看便笺

F1 打开“日记本”帮助

在帮助查看器中的快捷键

Alt+C 显示目录

Alt+N 显示“连接设置”菜单

F10 显示“选项”菜单

Alt+向左键 返回先前查看过的主题

Alt+向右键 向前移动到下一个(先前已查看过的)主题

Alt+A 显示客户支持页面

Alt+Home 显示帮助和支持主页

Home 移动到主题的开头

End 移动到主题的末尾

Ctrl+F 搜索当前主题

Ctrl+P 打印主题

F3 将光标移动到搜索框

轻松访问快捷键

按住右 Shift 八秒钟 启用和关闭筛选键

按左 Alt+左 Shift+PrtScn(或 PrtScn) 启用或关闭高对比度

按左 Alt+左 Shift+Num Lock 启用或关闭鼠标键

按 Shift 五次 启用或关闭粘滞键

按住 Num Lock 五秒钟 启用或关闭切换键

Windows 键 + U 打开轻松访问中心

[转载]理解模板引擎Razor 的原理 - James Li - 博客园

mikel阅读(1342)

[转载]理解模板引擎Razor 的原理 – James Li – 博客园.

Razor是ASP.NET MVC 3中新加入的技术,以作为ASPX引擎的一个新的替代项。简洁的语法与.NET Framework 结合,广泛应用于ASP.NET MVC 项目。Razor Pad是一个编辑Razor脚本的工具,脱离于ASP.NET 和Visual Studio。

image

微软已经有一套模板引擎T4,在设计ASP.NET MVC 3时又设计一套模板引擎Razor,对开发者而言是多一种选择,但同时也增加学习成本,至今我都没有时间和精力将Code Smith的模板转化为T4的模板。

 

Razor  从模板到.NET 代码

image

以cshtml为后缀名的为Razor模板,可以混合脚本语言和Html代码,以呈现为ASP.NET Page页面。

来看下面的Razor脚本片段,它以div的格式输出Posts对象的标题:

<div>
@foreach(var post in Posts) {
<div>@post.Title</div>
}
</div>

Razor引擎分析上面的脚本,产生如下所示的Xml文本片段

<Document>
  <Markup><div>\r\n\t</Markup>
     <Statement>
      <Transition>@</Transition>
       <Code>foreach(var post in posts) { </Code>
        <Markup>
        <Markup><div></Markup>
       <Expression>
         <Transition>@</Transition>
         <ImplicitExpression>post.Title</ImplicitExpression>
        </Expression>
        <Markup></div></Markup>
        </Markup>
       <Code> } </Code>
      <Markup></div></Markup>
    </Statement>
  <Markup>\r\n</div></Markup>
</Document>

以空格或是@作为区域(block)的分割符,将Razor解析成一个表达式树。@在Razor模板中的用法相当灵活,可以用于表达式的求值,或是定义代码块。

然后产生.NET代码,上面的脚本,会产生如下的.NET代码类型

namespace Razor {
public class __CompiledTemplate {
public __CompiledTemplate() {
}
public override void Execute() {
        WriteLiteral("<div>\r\n\t");
            @foreach(var post in posts) {
                 WriteLiteral("<div>");
                 Write(post.Title);
                 WriteLiteral("</div>");
                }
    WriteLiteral("\r\n</div>");
 }
}
}

最后,应用CodeDom API编译上面的代码,调用Execute方法输出结果。

 

Razor API

一开始接触这项技术时,以为是ASP.NET MVC的一个开源项目,接触之后才知道它是.NET Framework API中的类型,几年没有做Web开发,ASP.NET技术发展的确实很快。

System.Web.Razor.Parser.RazorParser

RazorParser用于解析脚本,但是它对Html,CS,VB脚本一无所知。MarkupParser用于解析Html标记,CSharpCodeParser用于解析包含C#代码的标记,相应的VBCodeParser则用于解析包含VB脚本的标记,

System.Web.Razor.Generator.RazorCodeGenerator

根据传入的Markup Parser,产生代码。与Parser相似,它也有二个与标记语言相关的类型,CSharpRazorCodeGenerator 和VBRazorCodeGenerator,用于处理标记包含的脚本。

System.Web.Razor.RazorEngineHost

System.Web.Razor.RazorTemplateEngine

引擎宿主,用于接受标记脚本,产生.NET 类型的代码。

把上面的内容综合起来,写一个例子程序,来看这些API的用法。

创建脚本宿主

var language = new CSharpRazorCodeLanguage();
var host = new RazorEngineHost(language) {
   DefaultBaseClass = "CustomTemplateBase",  
   DefaultClassName = "DemoTemplate",
   DefaultNamespace = "ProgrammingRazor",
};

添加Razor脚本引擎,读取标记(Markup)文件

RazorTemplateEngine engine = new RazorTemplateEngine(host);
GeneratorResults razorResult = engine.GenerateCode("C:\\Script.txt");

编译标记文件生成的类型,生成Code Dom 脚本

CompilerResults compilerResults =
new CSharpCodeProvider()
       .CompileAssemblyFromDom(
               new CompilerParameters(/*...*/),
               razorResult.GeneratedCode
);

执行生成的程序集类型,获取结果

var template = (CustomTemplateBase)Activator.CreateInstance(“ProgrammingRazor.DemoTemplate”);
template.Execute();

Razor Pad程序的功能,也是用的这个基本思路来展来的。

Razor Pad是一个开放源码的程序,你可以从网址http://razorpad.codeplex.com中获取它的源代码。

[转载]Kinect+OpenNI学习笔记之14(关于Kinect的深度信息) - tornadomeet - 博客园

mikel阅读(851)

[转载]Kinect+OpenNI学习笔记之14(关于Kinect的深度信息) – tornadomeet – 博客园.

前言

由于最近要研究kinect采集到的深度信息的一些统计特征,所以必须先对kinect深度信息做进一步的了解。这些了解包括kinect的深 度值精度,深度值的具体代表的距离是指哪个距离以及kinect深度和颜色扫描范围等。经过查找资料可以解决这些问题,并且后面通过实验也验证了这些问题 的答案。

开发环境:开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.3

 

实验基础

首先来看下Kinect性能的基本参数,如下图所示:

Kinect在使用时,微软官方推荐的距离为1220mm(4’)~3810mm(12.5’),网友heresy在他的博文Kinect + OpenNI 的深度值中 统计过,kinect在距离为1.0m时其精度大概是3mm,而当距离是3.0m时,其精度大概是3cm,因此当kinect在官方推荐的距离范围内使用 是,如果是1.2m时,其精度应该在3mm附近,如果是3.6m时其精度就大于3cm了,因此距离越远,其深度值精度越低。另外,通过OpenNI获取到 的深度信息(即z坐标)的单位是mm,这一点在程序编程中要注意,且一般的深度值用12bit(其实是可以用13bit表示的)表示,即最大值为 4095,也就是代表4.095m,所以平时我们采集到的深度数据如果需要扩展到灰度图,可以乘以一个因子255/4095(为了加快该结果,一般设置为 256/4096,即转换后的灰度值每变化1,代表kinect采集到的深度值变化了16mm,如果当人的距离为1米左右时,本来精度是3mm,现在经过 归一化后精度确更加下降了,这时候拿这个距离值来做算法不懂会不会有影响,当然了,拿来做灰度图像的显示肯定是OK的),最后如果其深度值为0表示该位置 处侦测不到像素点的深度。

Kinect的侦测范围入下图所示:

可以看出,kinect的水平侦测范围为57度(即以sensor为中心,左右各28.5度)。垂直范围为43度(同理,以sensor为中 心,上下各21.5度)。如果人体活动超过了kinect侦测范围,kinect还会自动追焦27度,即马达能够上下旋转27度(因为涉及到专利的问 题,OpenNI驱动没有这个功能,微软SDK可以),因此理论上上下扫描的范围应该为97度(27+27+43)。但是水平方向上虽然有马达,可以手动 掰动kinect,不过在驱动中并没有对应的水平角度旋转的API,即使是微软的SDK也一样。

Kinect的倾斜角度如下图所示:

下面来解释Kinect采集到的深度值的具体含义:

Openni的原始驱动类中的depth_metadata_其实也是一副图像,图像的坐标表示空间点的投影坐标,图像坐标里存的值是对应空间 点投影坐标的深度值。该深度值并不是指空间中对应像素点到深度sensor点之间的距离(即2点直接的距离),而是指空间中对应像素点到kinect传感 器所在平面的距离(即是一个垂直距离),因为前面已经提到,kinect是可以上下旋转的。现假设三种情况,第一:我们不上下旋转kinect,即保持 kinect传感器平面与水平地面垂直,这时像素点X深度值为a;第二:将kinect往上旋转一个角度(当然了,这个角度值小于27度),这时候同样一 个像素点X的深度值为b;第三:将kinect往下旋转一个角度Beta角度,这时候X的深度值为c;你会发现,a,b,c这3者不一定相等。

 

OpenCV知识点总结:

当Mat中数据的类型为CV_16UC1的时候,这里的16U并不是指unsigned int,而是指的是unsigned short int,因为在OpenCV框架中,int不是16位的,而是32位的。没想到我使用OpenCV一年了,今天才弄清楚这个。

 

实验结果:

该实验是测试一个垂直摆放的柜子,该柜子一个平面上的点本来与kinect之间的距离是相等的,现在测试kinect在不同上下旋转角度的情况 下,这个柜子上的点的深度值是否一样。首先将kinect往上旋转一个角度,即kinect平面与水平面之间有一个夹角。实验结果如下:

图中显示的数字为鼠标所在位置像素的真实深度值。

 

柜子中同一个平面上另一个像素点的深度值结果如下:

由此可以看出,同一个垂直柜子平面上的点像素值相差30cm以上(其实从图中深度图的颜色信息就可以看出,该柜子深度图像都是倾斜的,因为kinect本身就有转角)。

 

如果把kinect所在的平面摆正,即与水平面之间没有夹角,则柜子上某一点的深度值如下图所示:

 

同一垂直平面上另一个点的如下所示:

由此可以看出,其深度值变化不大。

上面的实验结果解释了在实验基础部分中所讲的kinect深度值的含义。

 

实验主要代码及注释:

copenni.h:

复制代码
#ifndef COpenniHand_H
#define COpenniHand_H

#include <XnCppWrapper.h>
#include <iostream>
#include <vector>
#include <map>

using namespace xn;
using namespace std;

class COpenniHand
{
public:
    COpenniHand();
    ~COpenniHand();

    /*OpenNI的内部初始化,属性设置*/
    bool Initial();

    /*启动OpenNI读取Kinect数据*/
    bool Start();

    /*更新OpenNI读取到的数据*/
    bool UpdateData();

    /*得到色彩图像的node*/
    ImageGenerator& getImageGenerator();

    /*得到深度图像的node*/
    DepthGenerator& getDepthGenerator();

    /*得到手势姿势的node*/
    GestureGenerator& getGestureGenerator();

    /*得到给定投影坐标的点,返回其对应真实坐标的深度值*/
    XnFloat GetProjectWorldPixpelDepth(XnPoint3D *project_world_pixpel);

    /*得到手部的node*/
    HandsGenerator& getHandGenerator();
    DepthMetaData depth_metadata_;   //返回深度图像数据
    ImageMetaData image_metadata_;   //返回彩色图像数据
    std::map<XnUserID, XnPoint3D> hand_points_;  //为了存储不同手的实时点而设置的
    std::map< XnUserID, vector<XnPoint3D> > hands_track_points_; //为了绘画后面不同手部的跟踪轨迹而设定的

private:
    /*该函数返回真代表出现了错误,返回假代表正确*/
    bool CheckError(const char* error);

    /*表示某个手势动作已经完成检测的回调函数*/
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                      const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition,
                                                      void *pCookie);

    /*表示检测到某个手势开始的回调函数*/
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                   const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie);

    /*手部开始建立的回调函数*/
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie);

    /*手部开始更新的回调函数*/
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie);

    /*手部销毁的回调函数*/
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime, void* pCookie);

    XnStatus status_;
    Context context_;
    XnMapOutputMode xmode_;
    ImageGenerator  image_generator_;
    DepthGenerator  depth_generator_;
    GestureGenerator gesture_generator_;
    HandsGenerator  hand_generator_;
};

#endif // COpenniHand_H
复制代码

 

  copenni.cpp:

复制代码
#include "copennihand.h"
#include <XnCppWrapper.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

COpenniHand::COpenniHand()
{
}

COpenniHand::~COpenniHand()
{
}

bool COpenniHand::Initial()
{
    status_ = context_.Init();
    if(CheckError("Context initial failed!")) {
        return false;
    }

    context_.SetGlobalMirror(true);//设置镜像
    xmode_.nXRes = 640;
    xmode_.nYRes = 480;
    xmode_.nFPS = 30;

    //产生颜色node
    status_ = image_generator_.Create(context_);
    if(CheckError("Create image generator  error!")) {
        return false;
    }

    //设置颜色图片输出模式
    status_ = image_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生深度node
    status_ = depth_generator_.Create(context_);
    if(CheckError("Create depth generator  error!")) {
        return false;
    }

    //设置深度图片输出模式
    status_ = depth_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生手势node
    status_ = gesture_generator_.Create(context_);
    if(CheckError("Create gesture generator error!")) {
        return false;
    }

    /*添加手势识别的种类*/
    gesture_generator_.AddGesture("Wave", NULL);
    gesture_generator_.AddGesture("click", NULL);
    gesture_generator_.AddGesture("RaiseHand", NULL);
    gesture_generator_.AddGesture("MovingHand", NULL);

    //产生手部的node
    status_ = hand_generator_.Create(context_);
    if(CheckError("Create hand generaotr error!")) {
        return false;
    }

    //视角校正
    status_ = depth_generator_.GetAlternativeViewPointCap().SetViewPoint(image_generator_);
    if(CheckError("Can't set the alternative view point on depth generator!")) {
        return false;
    }

    //设置与手势有关的回调函数
    XnCallbackHandle gesture_cb;
    gesture_generator_.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, this, gesture_cb);

    //设置于手部有关的回调函数
    XnCallbackHandle hands_cb;
    hand_generator_.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, hands_cb);

    return true;
}

bool COpenniHand::Start()
{
    status_ = context_.StartGeneratingAll();
    if(CheckError("Start generating error!")) {
        return false;
    }
    return true;
}

bool COpenniHand::UpdateData()
{
    status_ = context_.WaitNoneUpdateAll();
    if(CheckError("Update date error!")) {
        return false;
    }
    //获取数据
    image_generator_.GetMetaData(image_metadata_);
    depth_generator_.GetMetaData(depth_metadata_);

    return true;
}

ImageGenerator &COpenniHand::getImageGenerator()
{
    return image_generator_;
}

DepthGenerator &COpenniHand::getDepthGenerator()
{
    return depth_generator_;
}

GestureGenerator &COpenniHand::getGestureGenerator()
{
    return gesture_generator_;
}

HandsGenerator &COpenniHand::getHandGenerator()
{
    return hand_generator_;
}

bool COpenniHand::CheckError(const char *error)
{
    if(status_ != XN_STATUS_OK) {
        cerr << error << ": " << xnGetStatusString( status_ ) << endl;
        return true;
    }
    return false;
}

void COpenniHand::CBGestureRecognized(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    openni->hand_generator_.StartTracking(*pEndPosition);
}

void COpenniHand::CBGestureProgress(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie)
{
}

void COpenniHand::HandCreate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    pair<XnUserID, XnPoint3D> hand_point_pair(xUID, XnPoint3D());//在进行pair类型的定义时,可以将第2个设置为空
    hand_point_pair.second = project_pos;
    openni->hand_points_.insert(hand_point_pair);//将检测到的手部存入map类型的hand_points_中。

    pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
    hand_track_point.second.push_back(project_pos);
    openni->hands_track_points_.insert(hand_track_point);
}

void COpenniHand::HandUpdate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    openni->hand_points_.find(xUID)->second = project_pos;
    openni->hands_track_points_.find(xUID)->second.push_back(project_pos);
}

void COpenniHand::HandDestroy(HandsGenerator &rHands, XnUserID xUID, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    openni->hand_points_.erase(openni->hand_points_.find(xUID));
    openni->hands_track_points_.erase(openni->hands_track_points_.find(xUID ));
}
复制代码

 

  ckinectopencv.h:

复制代码
#ifndef CKINECTOPENCV_H
#define CKINECTOPENCV_H

#include <opencv2/core/core.hpp>
#include "copennihand.h"
#include "XnCppWrapper.h"

using namespace cv;
using namespace xn;

class CKinectOpenCV
{
public:
    CKinectOpenCV();
    ~CKinectOpenCV();
    void GetAllInformation();   //在返回有用信息前调用该函数,因为openni的数据在不断更新,信息的处理最好放在一个函数中
    Mat GetColorImage() ;
    Mat GetDepthImage() ;
    Mat GetDepthRealValueImage();
    std::map<XnUserID, XnPoint3D> GetHandPoints();

private:
    COpenniHand openni_hand_;
    std::map<XnUserID, XnPoint3D> hand_points_;  //为了存储不同手的实时点而设置的
    Mat color_image_;    //颜色图像
    Mat depth_image_;    //深度图像
    Mat depth_realvalue_image_;

};

#endif // CKINECTOPENCV_H
复制代码

 

  ckinectopencv.cpp:

复制代码
#include "ckinectopencv.h"
#include <opencv2/imgproc/imgproc.hpp>
#include <map>

using namespace cv;
using namespace std;

#define DEPTH_SCALE_FACTOR 255./4096.

CKinectOpenCV::CKinectOpenCV()
{   
    /*初始化openni对应的设备*/
     CV_Assert(openni_hand_.Initial());

    /*启动openni对应的设备*/
    CV_Assert(openni_hand_.Start());
}

CKinectOpenCV::~CKinectOpenCV()
{
}

void CKinectOpenCV::GetAllInformation()
{
    CV_Assert(openni_hand_.UpdateData());
    /*获取色彩图像*/
    Mat color_image_src(openni_hand_.image_metadata_.YRes(), openni_hand_.image_metadata_.XRes(),
                        CV_8UC3, (char *)openni_hand_.image_metadata_.Data());
    cvtColor(color_image_src, color_image_, CV_RGB2BGR);

    /*获取深度图像*/
    Mat depth_image_src(openni_hand_.depth_metadata_.YRes(), openni_hand_.depth_metadata_.XRes(),
                        CV_16UC1, (char *)openni_hand_.depth_metadata_.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
    depth_image_src.convertTo(depth_image_, CV_8U, DEPTH_SCALE_FACTOR);

    /*获取真实深度值图像,没有经过深度归一化的*/
    depth_realvalue_image_ = depth_image_src.clone();

    hand_points_ = openni_hand_.hand_points_;   //返回手部点的位置

    return;
}

Mat CKinectOpenCV::GetColorImage()
{
    return color_image_;
}

Mat CKinectOpenCV::GetDepthImage()
{
    return depth_image_;
}

Mat CKinectOpenCV::GetDepthRealValueImage()
{
    return depth_realvalue_image_;
}

std::map<XnUserID, XnPoint3D> CKinectOpenCV::GetHandPoints()
{
    return hand_points_;
}
复制代码

 

  main.cpp:

复制代码
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "ckinectopencv.h"
#include "XnCppWrapper.h"

using namespace std;
using namespace cv;

CKinectOpenCV kinect_opencv;
Mat color_image ;
Mat depth_image ;
Mat depth_realvalue_image;
unsigned int  pixpel_depth_value = 0;

Point mouse_pixpel(0, 0);

void on_mouse(int event,int x,int y,int,void*)
{
    if(event == CV_EVENT_MOUSEMOVE) {  //鼠标移动的过程中
        pixpel_depth_value = depth_realvalue_image.at<unsigned short int>(y, x);
        mouse_pixpel = Point(x, y);
    }
}

int main()
{

//    namedWindow("color_image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth_image", CV_WINDOW_AUTOSIZE);
    setMouseCallback("depth_image", on_mouse, 0);   //设置鼠标响应函数

    while(1)
    {
        kinect_opencv.GetAllInformation();
//        color_image = kinect_opencv.GetColorImage();
        depth_image = kinect_opencv.GetDepthImage();
        depth_realvalue_image = kinect_opencv.GetDepthRealValueImage();
        stringstream depth_value_string;
        depth_value_string << pixpel_depth_value/1000. << " m" ;

        putText(depth_image, depth_value_string.str(), mouse_pixpel, 3, 1, Scalar(50, 0, 0), 2, 8);

//        imshow("color_image", color_image);
        imshow("depth_image", depth_image);
        waitKey(30);
    }

    return 0;
}
复制代码

 

 

实验总结: 通过本次资料的查找和实验的验证,对kinect的深度值有了更一步的了解了。

 

参考文献:

Kinect + OpenNI 的深度值

[译]Kinect for Windows SDK开发入门(四):景深数据处理 上

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之13(Kinect驱动类,OpenCV显示类和手部预分割类的设计) - tornadomeet - 博客园

mikel阅读(802)

[转载]Kinect+OpenNI学习笔记之13(Kinect驱动类,OpenCV显示类和手部预分割类的设计) – tornadomeet – 博客园.

前言

为了减小以后项目的开发效率,本次实验将OpenNI底层驱动Kinect,OpenCV初步处理OpenNI获得的原始数据,以及手势识别中 的分割(因为本系统最后是开发手势识别的)这3个部分的功能单独做成类,以便以后移植和扩展。其实在前面已经有不少文章涉及到了这3部分的设计,比如说:Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计)Kinect+OpenNI学习笔记之11(OpenNI驱动kinect手势相关的类的设计)Kinect+OpenNI学习笔记之12(简单手势所表示的数字的识别) 。这次是综合前面几次的设计,优化了下这几个类。

开发环境:开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.3

 

实验基础

OPenNI/OPenCV知识点总结:

Kinect驱动类,OpenCV显示类和手部预分割类这3个类,单独来设计其实参考了前面的博文还是很简单的,但是由于这3个类之间有相互联 系,设计不好就会出现图像显示非常卡。这时候,需要注意下面几点问题(在本程序代码中,Kinect驱动类为COpenniHand,OpenCV显示类 为CKinectOpenCV, 手部预分割类为CKinectHandSegment):

因为在kinect驱动类中有完整的kinect驱动程序(这个驱动会占用一部分时间的),而OpenCV显示类调用了Kinect驱动类中的 内容,相当于完成了一次Kinect驱动完整过程,这时候,因为在手部预分割过程中,要获得手部的中心点,如果在该类中再次执行kinect的驱动来获得 该中心点,那么整个系统中一个流程的时间其kinect需要驱动两次,这会浪费很多系统资源,导致图像显示不流畅等。因此我们应该在OpenCV显示类中 就返回Kinect驱动类中能够返回的值,比如说手部中心点的位置

在CKinectOpenCV类中由于要返回手部的中心点位置,本打算在类内部公共部分设置一个获取手部中心点位置的函数的,但是发现如果这个 函数的返回值是map类型时,运行时老出错误(理论上应该是不会出错的),所以后面该为直接返回手部中心点的变量(map类型),但是在这个变量返回前要 保证它的值是实时更新的,所以应该在返回前加入kinect驱动程序中的Updata函数,我这里将其设计成了一个开关函数,即如果允许获取手部中心点, 就将开关函数中的参数设置为ture,具体参见代码部分。

C/C++知识点总结:

定义类的对象并使用该对象后,一般会先调用该类的初始化函数,该函数的作用一般是为类成员变量进行一些初始设置,但如果类中其它函数的调用前每 次都初始化某些变量时,这些变量的初始化不宜放在类的初始化函数中,而应该单独给个私有函数,在那些需要调用它的函数前面进行被调用,达到初始化某些变量 的目的。

类的设计的目的一是为了方便,而是为了提高效率,有时候不能够光为了方便而去设计,比如说在本次类设计中要获得分割好了的图像,或者原始图像, 或者深度图像等等,确实是可以直接使用一个函数每一幅图像,不过每次获得图像就要更新一个下kinect驱动中的数据,因此这样的效率就非常低了,在实际 设计中,我把那些kinect驱动设备的程序写在了一个函数中,但是这个函数又不能被获取图像的每个函数去调用,否则还是相当于驱动了多次,因此只能由类 所定义的对象来调用了。结果是每一个主函数循环中,我们在定义了类的对象后多调用一个函数,再去获得所需的图像,这样只是多了一句代码,却节省了不少时间 消耗。

 

实验结果

本次实验完成的功能依旧是获取kinect的深度图,颜色图,手势分割图,手势轮廓图等。下面是手势分割图和轮廓处理图的结果:

 

实验代码及注释:

copennihand.h:

复制代码
#ifndef COpenniHand_H
#define COpenniHand_H

#include <XnCppWrapper.h>
#include <iostream>
#include <vector>
#include <map>

using namespace xn;
using namespace std;

class COpenniHand
{
public:
    COpenniHand();
    ~COpenniHand();

    /*OpenNI的内部初始化,属性设置*/
    bool Initial();

    /*启动OpenNI读取Kinect数据*/
    bool Start();

    /*更新OpenNI读取到的数据*/
    bool UpdateData();

    /*得到色彩图像的node*/
    ImageGenerator& getImageGenerator();

    /*得到深度图像的node*/
    DepthGenerator& getDepthGenerator();

    /*得到手势姿势的node*/
    GestureGenerator& getGestureGenerator();

    /*得到手部的node*/
    HandsGenerator& getHandGenerator();
    DepthMetaData depth_metadata_;   //返回深度图像数据
    ImageMetaData image_metadata_;   //返回彩色图像数据
    std::map<XnUserID, XnPoint3D> hand_points_;  //为了存储不同手的实时点而设置的
    std::map< XnUserID, vector<XnPoint3D> > hands_track_points_; //为了绘画后面不同手部的跟踪轨迹而设定的

private:
    /*该函数返回真代表出现了错误,返回假代表正确*/
    bool CheckError(const char* error);

    /*表示某个手势动作已经完成检测的回调函数*/
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                      const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition,
                                                      void *pCookie);

    /*表示检测到某个手势开始的回调函数*/
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                   const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie);

    /*手部开始建立的回调函数*/
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie);

    /*手部开始更新的回调函数*/
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie);

    /*手部销毁的回调函数*/
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime, void* pCookie);

    XnStatus status_;
    Context context_;
    XnMapOutputMode xmode_;
    ImageGenerator  image_generator_;
    DepthGenerator  depth_generator_;
    GestureGenerator gesture_generator_;
    HandsGenerator  hand_generator_;
};

#endif // COpenniHand_H
复制代码

 

  copennihand.cpp:

复制代码
#include "copennihand.h"
#include <XnCppWrapper.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

COpenniHand::COpenniHand()
{
}

COpenniHand::~COpenniHand()
{
}

bool COpenniHand::Initial()
{
    status_ = context_.Init();
    if(CheckError("Context initial failed!")) {
        return false;
    }

    context_.SetGlobalMirror(true);//设置镜像
    xmode_.nXRes = 640;
    xmode_.nYRes = 480;
    xmode_.nFPS = 30;

    //产生颜色node
    status_ = image_generator_.Create(context_);
    if(CheckError("Create image generator  error!")) {
        return false;
    }

    //设置颜色图片输出模式
    status_ = image_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生深度node
    status_ = depth_generator_.Create(context_);
    if(CheckError("Create depth generator  error!")) {
        return false;
    }

    //设置深度图片输出模式
    status_ = depth_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生手势node
    status_ = gesture_generator_.Create(context_);
    if(CheckError("Create gesture generator error!")) {
        return false;
    }

    /*添加手势识别的种类*/
    gesture_generator_.AddGesture("Wave", NULL);
    gesture_generator_.AddGesture("click", NULL);
    gesture_generator_.AddGesture("RaiseHand", NULL);
    gesture_generator_.AddGesture("MovingHand", NULL);

    //产生手部的node
    status_ = hand_generator_.Create(context_);
    if(CheckError("Create hand generaotr error!")) {
        return false;
    }

    //视角校正
    status_ = depth_generator_.GetAlternativeViewPointCap().SetViewPoint(image_generator_);
    if(CheckError("Can't set the alternative view point on depth generator!")) {
        return false;
    }

    //设置与手势有关的回调函数
    XnCallbackHandle gesture_cb;
    gesture_generator_.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, this, gesture_cb);

    //设置于手部有关的回调函数
    XnCallbackHandle hands_cb;
    hand_generator_.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, hands_cb);

    return true;
}

bool COpenniHand::Start()
{
    status_ = context_.StartGeneratingAll();
    if(CheckError("Start generating error!")) {
        return false;
    }
    return true;
}

bool COpenniHand::UpdateData()
{
    status_ = context_.WaitNoneUpdateAll();
    if(CheckError("Update date error!")) {
        return false;
    }
    //获取数据
    image_generator_.GetMetaData(image_metadata_);
    depth_generator_.GetMetaData(depth_metadata_);

    return true;
}

ImageGenerator &COpenniHand::getImageGenerator()
{
    return image_generator_;
}

DepthGenerator &COpenniHand::getDepthGenerator()
{
    return depth_generator_;
}

GestureGenerator &COpenniHand::getGestureGenerator()
{
    return gesture_generator_;
}

HandsGenerator &COpenniHand::getHandGenerator()
{
    return hand_generator_;
}

bool COpenniHand::CheckError(const char *error)
{
    if(status_ != XN_STATUS_OK) {
        cerr << error << ": " << xnGetStatusString( status_ ) << endl;
        return true;
    }
    return false;
}

void COpenniHand::CBGestureRecognized(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    openni->hand_generator_.StartTracking(*pEndPosition);
}

void COpenniHand::CBGestureProgress(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie)
{
}

void COpenniHand::HandCreate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    pair<XnUserID, XnPoint3D> hand_point_pair(xUID, XnPoint3D());//在进行pair类型的定义时,可以将第2个设置为空
    hand_point_pair.second = project_pos;
    openni->hand_points_.insert(hand_point_pair);//将检测到的手部存入map类型的hand_points_中。

    pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
    hand_track_point.second.push_back(project_pos);
    openni->hands_track_points_.insert(hand_track_point);
}

void COpenniHand::HandUpdate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    openni->hand_points_.find(xUID)->second = project_pos;
    openni->hands_track_points_.find(xUID)->second.push_back(project_pos);
}

void COpenniHand::HandDestroy(HandsGenerator &rHands, XnUserID xUID, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    openni->hand_points_.erase(openni->hand_points_.find(xUID));
    openni->hands_track_points_.erase(openni->hands_track_points_.find(xUID ));
}
复制代码

 

  ckinectopencv.h:

复制代码
#ifndef CKINECTOPENCV_H
#define CKINECTOPENCV_H

#include <opencv2/core/core.hpp>
#include "copennihand.h"

using namespace cv;

class CKinectOpenCV
{
public:
    CKinectOpenCV();
    ~CKinectOpenCV();
    void GetAllInformation();   //在返回有用信息前调用该函数,因为openni的数据在不断更新,信息的处理最好放在一个函数中
    Mat GetColorImage() ;
    Mat GetDepthImage() ;
    std::map<XnUserID, XnPoint3D> GetHandPoints();

private:
    COpenniHand openni_hand_;
    std::map<XnUserID, XnPoint3D> hand_points_;  //为了存储不同手的实时点而设置的
    Mat color_image_;    //颜色图像
    Mat depth_image_;    //深度图像


};

#endif // CKINECTOPENCV_H
复制代码

 

  ckinectopencv.cpp:

复制代码
#include "ckinectopencv.h"
#include <opencv2/imgproc/imgproc.hpp>
#include <map>

using namespace cv;
using namespace std;

#define DEPTH_SCALE_FACTOR 255./4096.

CKinectOpenCV::CKinectOpenCV()
{   
    /*初始化openni对应的设备*/
     CV_Assert(openni_hand_.Initial());

    /*启动openni对应的设备*/
    CV_Assert(openni_hand_.Start());
}

CKinectOpenCV::~CKinectOpenCV()
{
}

void CKinectOpenCV::GetAllInformation()
{
    CV_Assert(openni_hand_.UpdateData());
    /*获取色彩图像*/
    Mat color_image_src(openni_hand_.image_metadata_.YRes(), openni_hand_.image_metadata_.XRes(),
                        CV_8UC3, (char *)openni_hand_.image_metadata_.Data());
    cvtColor(color_image_src, color_image_, CV_RGB2BGR);

    /*获取深度图像*/
    Mat depth_image_src(openni_hand_.depth_metadata_.YRes(), openni_hand_.depth_metadata_.XRes(),
                        CV_16UC1, (char *)openni_hand_.depth_metadata_.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
    depth_image_src.convertTo(depth_image_, CV_8U, DEPTH_SCALE_FACTOR);

    hand_points_ = openni_hand_.hand_points_;   //返回手部点的位置

    return;
}

Mat CKinectOpenCV::GetColorImage()
{
    return color_image_;
}

Mat CKinectOpenCV::GetDepthImage()
{
    return depth_image_;
}

std::map<XnUserID, XnPoint3D> CKinectOpenCV::GetHandPoints()
{
    return hand_points_;
}
复制代码

 

  ckinecthandsegment.h:

复制代码
#ifndef KINECTHAND_H
#define KINECTHAND_H

#include "ckinectopencv.h"

using namespace cv;

#define MAX_HANDS_COLOR 10
#define MAX_HANDS_NUMBER  10

class CKinectHandSegment
{
public:
    CKinectHandSegment();
    ~CKinectHandSegment();
    void Initial();
    void StartKinectHand(); //启动kinect手部设备驱动
    Mat GetColorImageWithHandsPoint();
    Mat GetHandSegmentImage();
    Mat GetHandHandlingImage();
    Mat GetColorImage();
    Mat GetDepthImage();

private:
    CKinectOpenCV kinect_opencv_;
    vector<Scalar> hand_center_color_array_;//采用默认的10种颜色
    std::map<XnUserID, XnPoint3D> hand_points_;
    vector<unsigned int> hand_depth_;
    vector<Rect> hands_roi_;
    bool hand_segment_flag_;
    Mat color_image_with_handspoint_;   //带有手部中心位置的色彩图
    Mat color_image_;   //色彩图
    Mat depth_image_;
    Mat hand_segment_image_;
    Mat hand_handling_image_;
    Mat hand_segment_mask_;
};

#endif // KINECTHAND_H
复制代码

 

  ckinecthandsegment.cpp:

复制代码
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "ckinecthandsegment.h"
#include "copennihand.h"
#include "ckinectopencv.h"

using namespace cv;
using namespace std;

#define DEPTH_SCALE_FACTOR 255./4096.
#define ROI_HAND_WIDTH 140
#define ROI_HAND_HEIGHT 140
#define MEDIAN_BLUR_K 5
#define XRES  640
#define YRES  480
#define DEPTH_SEGMENT_THRESH 5
#define HAND_LIKELY_AREA 2000

CKinectHandSegment::CKinectHandSegment()
{
}

CKinectHandSegment::~CKinectHandSegment()
{
}

void CKinectHandSegment::Initial()
{
    color_image_with_handspoint_ = kinect_opencv_.GetColorImage();
    depth_image_ = kinect_opencv_.GetDepthImage();
    {
        hand_center_color_array_.push_back(Scalar(255, 0, 0));
        hand_center_color_array_.push_back(Scalar(0, 255, 0));
        hand_center_color_array_.push_back(Scalar(0, 0, 255));
        hand_center_color_array_.push_back(Scalar(255, 0, 255));
        hand_center_color_array_.push_back(Scalar(255, 255, 0));
        hand_center_color_array_.push_back(Scalar(0, 255, 255));
        hand_center_color_array_.push_back(Scalar(128, 255, 0));
        hand_center_color_array_.push_back(Scalar(0, 128, 255));
        hand_center_color_array_.push_back(Scalar(255, 0, 128));
        hand_center_color_array_.push_back(Scalar(255, 128, 255));
    }
    vector<unsigned int> hand_depth_temp(MAX_HANDS_NUMBER, 0);
    hand_depth_ = hand_depth_temp;
    vector<Rect> hands_roi_temp(MAX_HANDS_NUMBER, Rect(XRES/2, YRES/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT));
    hands_roi_ = hands_roi_temp;
}

void CKinectHandSegment::StartKinectHand()
{
    kinect_opencv_.GetAllInformation();
}

Mat CKinectHandSegment::GetColorImage()
{
    return kinect_opencv_.GetColorImage();
}

Mat CKinectHandSegment::GetDepthImage()
{
    return kinect_opencv_.GetDepthImage();
}

/*该函数只是在Kinect获取的色彩图片上将手的中心点位置画出来而已,图片的其它地方不变*/
Mat CKinectHandSegment::GetColorImageWithHandsPoint()
{
    color_image_with_handspoint_ = kinect_opencv_.GetColorImage();
    hand_points_ = kinect_opencv_.GetHandPoints();
    for(auto itUser = hand_points_.cbegin(); itUser != hand_points_.cend(); ++itUser) {
        circle(color_image_with_handspoint_, Point(itUser->second.X, itUser->second.Y),
               5, hand_center_color_array_.at(itUser->first % hand_center_color_array_.size()), 3, 8);
    }

    return color_image_with_handspoint_;
}

Mat CKinectHandSegment::GetHandSegmentImage()
{
    hand_segment_flag_ = false;
    color_image_ = kinect_opencv_.GetColorImage();
    depth_image_ = kinect_opencv_.GetDepthImage();
    hand_points_ = kinect_opencv_.GetHandPoints();
    hand_segment_mask_ = Mat::zeros(color_image_.size(), CV_8UC1);  //  因为zeros是一个静态函数,所以不能直接用具体的对象去调用,而需要用类来调用

    for(auto itUser = hand_points_.cbegin(); itUser != hand_points_.cend(); ++itUser) {

        /*设置不同手部的深度*/
        hand_depth_.at(itUser->first % MAX_HANDS_COLOR) = (unsigned int)(itUser->second.Z* DEPTH_SCALE_FACTOR);//itUser->first会导致程序出现bug

        /*设置不同手部的不同感兴趣区域*/
        hands_roi_.at(itUser->first % MAX_HANDS_NUMBER) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2,
                                           ROI_HAND_WIDTH, ROI_HAND_HEIGHT);
        hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).x =  itUser->second.X - ROI_HAND_WIDTH/2;
        hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).y =  itUser->second.Y - ROI_HAND_HEIGHT/2;
        hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).width = ROI_HAND_WIDTH;
        hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).height = ROI_HAND_HEIGHT;
        if(hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).x <= 0)
            hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).x  = 0;
        if(hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).x > XRES)
            hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).x =  XRES;
        if(hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).y <= 0)
            hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).y = 0;
        if(hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).y > YRES)
            hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).y =  YRES;
    }

    //取出手的mask部分,不管原图像时多少通道的,mask矩阵声明为单通道就ok
    for(auto itUser = hand_points_.cbegin(); itUser != hand_points_.cend(); ++itUser) {
        for(int i = hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).x; i < min(hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).x+hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).width, XRES); i++)
            for(int j = hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).y; j < min(hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).y+hands_roi_.at(itUser->first % MAX_HANDS_NUMBER).height, YRES); j++) {
                hand_segment_mask_.at<unsigned char>(j, i) = ((hand_depth_.at(itUser->first % MAX_HANDS_NUMBER)-DEPTH_SEGMENT_THRESH) < depth_image_.at<unsigned char>(j, i))
                                                            & ((hand_depth_.at(itUser->first % MAX_HANDS_NUMBER)+DEPTH_SEGMENT_THRESH) > depth_image_.at<unsigned char>(j,i));
                hand_segment_mask_.at<unsigned char>(j, i) = 255*hand_segment_mask_.at<unsigned char>(j, i);
            }
        }

    medianBlur(hand_segment_mask_, hand_segment_mask_, MEDIAN_BLUR_K);
    hand_segment_image_.convertTo(hand_segment_image_, CV_8UC3, 0, 0 ); //  需要清零
    color_image_.copyTo(hand_segment_image_, hand_segment_mask_);
    hand_segment_flag_ = true;  //返回之前将分割标志置位为1,表示已经完成分割函数

    return hand_segment_image_;
}

Mat CKinectHandSegment::GetHandHandlingImage()
{
    /*对mask图像进行轮廓提取,并在手势识别图像中画出来*/
    std::vector< std::vector<Point> > contours;
    CV_Assert(hand_segment_flag_);  //  因为后面要用到分割函数的mask图,所以这里先要保证调用过分割函数
    findContours(hand_segment_mask_, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);//找出mask图像的轮廓
    hand_handling_image_ = Mat::zeros(color_image_.rows, color_image_.cols, CV_8UC3);

    for(int i = 0; i < contours.size(); i++) {  //只有在检测到轮廓时才会去求它的多边形,凸包集,凹陷集
        /*找出轮廓图像多边形拟合曲线*/
        Mat contour_mat = Mat(contours[i]);
        if(contourArea(contour_mat) > HAND_LIKELY_AREA) {   //比较有可能像手的区域
            std::vector<Point> approx_poly_curve;
            approxPolyDP(contour_mat, approx_poly_curve, 10, true);//找出轮廓的多边形拟合曲线
            std::vector< std::vector<Point> > approx_poly_curve_debug;
            approx_poly_curve_debug.push_back(approx_poly_curve);

             drawContours(hand_handling_image_, contours, i, Scalar(255, 0, 0), 1, 8); //画出轮廓
//            drawContours(hand_handling_image_, approx_poly_curve_debug, 0, Scalar(256, 128, 128), 1, 8); //画出多边形拟合曲线

            /*对求出的多边形拟合曲线求出其凸包集*/
            vector<int> hull;
            convexHull(Mat(approx_poly_curve), hull, true);
            for(int i = 0; i < hull.size(); i++) {
                circle(hand_handling_image_, approx_poly_curve[hull[i]], 2, Scalar(0, 255, 0), 2, 8);
            }

            /*对求出的多边形拟合曲线求出凹陷集*/
            std::vector<Vec4i> convexity_defects;
            if(Mat(approx_poly_curve).checkVector(2, CV_32S) > 3)
                convexityDefects(approx_poly_curve, Mat(hull), convexity_defects);
            for(int i = 0; i < convexity_defects.size(); i++) {
                circle(hand_handling_image_, approx_poly_curve[convexity_defects[i][2]] , 2, Scalar(0, 0, 255), 2, 8);

            }
        }
    }

    /**画出手势的中心点**/
    for(auto itUser = hand_points_.cbegin(); itUser != hand_points_.cend(); ++itUser) {
        circle(hand_handling_image_, Point(itUser->second.X, itUser->second.Y), 3, Scalar(0, 255, 255), 3, 8);
    }

    return hand_handling_image_;
}
复制代码

 

  main.cpp:

复制代码
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "ckinectopencv.h"
#include "ckinecthandsegment.h"

using namespace std;
using namespace cv;

int main()
{
    CKinectHandSegment kinect_hand_segment;
    Mat color_image ;
    Mat depth_image ;
    Mat hand_segment;
    Mat hand_handling_image;

    kinect_hand_segment.Initial();
    while(1)
    {
        kinect_hand_segment.StartKinectHand();
        color_image = kinect_hand_segment.GetColorImageWithHandsPoint();
        hand_segment = kinect_hand_segment.GetHandSegmentImage();
        hand_handling_image = kinect_hand_segment.GetHandHandlingImage();
        depth_image = kinect_hand_segment.GetDepthImage();

        imshow("color_image", color_image);
        imshow("depth_image", depth_image);
        imshow("hand_segment", hand_segment);
        imshow("hand_handling", hand_handling_image);
        waitKey(30);
    }

    return 0;
}
复制代码

 

 

实验总结:把这些基本功能类设计好了后,就可以更方面测试我后面的手势识别算法了,加油!

 

 

参考资料:

Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计)

Kinect+OpenNI学习笔记之11(OpenNI驱动kinect手势相关的类的设计)

Kinect+OpenNI学习笔记之12(简单手势所表示的数字的识别) 

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之12(简单手势所表示的数字的识别) - tornadomeet - 博客园

mikel阅读(750)

[转载]Kinect+OpenNI学习笔记之12(简单手势所表示的数字的识别) – tornadomeet – 博客园.

前言

这篇文章是本人玩kinect时做的一个小实验,即不采用机器学习等类似AI的方法来做简单的手势数字识别,当然了,该识别的前提是基于本人前面已提取出手部的博文Robert Walter手部提取代码的分析的基础上进行的。由于是纯数学形状上来判别手势,所以只是做了个简单的0~5的数字识别系统,其手势的分割部分效果还不错(因为其核心代码是由OpenNI提供的),手势数字识别时容易受干扰,效果一般般,毕竟这只是个简单的实验。

 

实验基础

首先来看下本系统的流程图,如下所示:

 

其中轮廓的提取,多边形拟合曲线的求法,凸包集和凹陷集的求法都是采用opencv中自带的函数。手势数字的识别是利用凸包点以及凹陷点和手部 中心点的几何关系,简单的做了下逻辑判别了(可以肯定的是这种方法很烂),具体的做法是先在手部定位出2个中心点坐标,这2个中心点坐标之间的距离阈值由 程序设定,其中一个中心点就是利用OpenNI跟踪得到的手部位置。有了这2个中心点的坐标,在程序中就可以分别计算出在这2个中心点坐标上的凸凹点的个 数。当然了,这样做的前提是用人在做手势表示数字的同时应该是将手指的方向朝上(因为没有像机器学习那样通过样本来训练,所以使用时条件要苛刻很多)。利 用上面求出的4种点的个数(另外程序中还设置了2个辅助计算点的个数,具体见代码部分)和简单的逻辑判断就可以识别出数字0~5了。其它的数字可以依照具 体的逻辑去设计(还可以设计出多位数字的识别),只是数字越多设计起来越复杂,因为要考虑到它们之间的干扰性,且这种不通用的设计方法也没有太多的实际意 义。

 

  OpenCV知识点总结

  void convexityDefects(InputArray contour, InputArray convexhull, OutputArray convexityDefects)

  这个在函数在前面的博文Robert Walter手部提取代码的分析中已经介绍过,当时是这么解释的:

  该函数的作用是对输入的轮廓contour,凸包集合来检测其轮廓的凸型缺陷,一个凸型缺陷结构体包括4个元素,缺陷起点坐标,缺陷终点坐标,缺陷中离凸包线距离最远的点的坐标,以及此时最远的距离。参数3即其输出的凸型缺陷结构体向量。

  其凸型缺陷的示意图如下所示:

  

  不过这里需要重新对这3个参数做个详细的说明:

  第1个参数虽然写的是contour,字面意思是轮廓,但是本人实验过很多次,发现如果该参数为目标通过轮廓检测得 到的原始轮廓的话,则程序运行到onvexityDefects()函数时会报内存错误。因此本程序中采用的不是物体原始的轮廓,而是经过多项式曲线拟合 后的轮廓,即多项式曲线,这样程序就会顺利地运行得很好。另外由于在手势识别过程中可能某一帧检测出来的轮廓非常小(由于某种原因),以致于少到只有1个 点,这时候如果程序运行到onvexityDefects()函数时就会报如下的错误:

  查看opencv源码中对应错误提示的位置,其源码如下:

  

  表示在1969行的位置出错,也就是CV_Assert( ptnum > 3 );出错,说明出错时此处的ptnum <=3;看上面一行代码ptnum = points.checkVector(2, CV_32S);所以我们需要了解checkVector()函数的功能,进入opencv中关于checkVector的源码,如下:

复制代码
int Mat::checkVector(int _elemChannels, int _depth, bool _requireContinuous) const

{

    return (depth() == _depth || _depth <= 0) &&

        (isContinuous() || !_requireContinuous) &&

        ((dims == 2 && (((rows == 1 || cols == 1) && channels() == _elemChannels) || (cols == _elemChannels))) ||

        (dims == 3 && channels() == 1 && size.p[2] == _elemChannels && (size.p[0] == 1 || size.p[1] == 1) &&

         (isContinuous() || step.p[1] == step.p[2]*size.p[2])))

    ? (int)(total()*channels()/_elemChannels) : -1;

}
复制代码

该函数源码大概意思就是说对应的Mat矩阵如果其深度,连续性,通道数,行列式满足一定条件的话就返回Mat元素的个数和其通道数的乘积,否则 返回-1;而本文是要求其返回值大于3,有得知此处输入多边形曲线(即参数1)的通道数为2,所以还需要求其元素的个数大于1.5,即大于2才满足 ptnum > 3。简单的说就是用convexityDefects()函数来对多边形曲线进行凹陷检测时,必须要求参数1曲线本身至少有2个点(也不知道这样分析对不 对)。因此本人在本次程序convexityDefects()函数前加入了 if(Mat(approx_poly_curve).checkVector(2,  CV_32S) > 3)来判断,只有满足该if条件,才会进行后面的凹陷检测。这样程序就不会再出现类似的bug了。

  第2个参数一般是由opencv中的函数convexHull()获得的,一般情况下该参数里面存的是凸包集合中的 点在多项式曲线点中的位置索引,且该参数以vector的形式存在,因此参数convexhull中其元素的类型为unsigned int。在本次凹陷点检测函数convexityDefects()里面根据文档,要求该参数为Mat型。因此在使用convexityDefects()的参数2时,一般将vector直接转换Mat型。

  参数3是一个含有4个元素的结构体的集合,如果在c++的版本中,该参数可以直接用 vector<Vec4i>来代替,Vec4i中的4个元素分别表示凹陷曲线段的起始坐标索引,终点坐标索引,离凸包集曲线最远点的坐标索引 以及此时的最远距离值,这4个值都是整数。在c版本的opencv中一般不是保存的索引,而是坐标值,如下所示:

复制代码
struct CvConvexityDefect

{

   CvPoint* start; // point of the contour where the defect begins

   CvPoint* end; // point of the contour where the defect ends

   CvPoint* depth_point; // the farthest from the convex hull point within the defect

   float depth; // distance between the farthest point and the convex hull

};
复制代码

 

  C/c++知识点总结:

  std::abs()函数中的参数不能为整型,否则编译的时候会报错参数不匹配,因此一般情况下可以传入long型,这样就不会报错了。

  实验结果:

  这里显示的是手势分割后的效果以及其对应的数字识别结果。

  数字“0”的识别结果:

  数字“1”的识别结果:

  数字“2”的识别结果:

  数字“3”的识别结果:

  数字“4”的识别结果:

  数字“5”的识别结果:

  实验主要部分代码及注释:

  本次实验程序实现过程和上面的系统流程图类似,大概过程如下:

1.  求出手部的掩膜

2.  求出掩膜的轮廓

3.  求出轮廓的多变形拟合曲线

4.  求出多边形拟合曲线的凸包集,找出凸点

5.  求出多变形拟合曲线的凹陷集,找出凹点

6.  利用上面的凸凹点和手部中心点的几何关系来做简单的数字手势识别

  copenni类采用前面博文设计的,这里给出主函数代码部分。

  main.cpp:

复制代码
#include <iostream>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copenni.cpp"

#include <iostream>

#define DEPTH_SCALE_FACTOR 255./4096.
#define ROI_HAND_WIDTH 140
#define ROI_HAND_HEIGHT 140
#define MEDIAN_BLUR_K 5
#define XRES  640
#define YRES  480
#define DEPTH_SEGMENT_THRESH 5
#define MAX_HANDS_COLOR 10
#define MAX_HANDS_NUMBER  10
#define HAND_LIKELY_AREA 2000
#define DELTA_POINT_DISTENCE 25     //手部中心点1和中心点2距离的阈值
#define SEGMENT_POINT1_DISTANCE 27  //凸点与手部中心点1远近距离的阈值
#define SEGMENT_POINT2_DISTANCE 30  //凸点与手部中心点2远近距离的阈值

using namespace cv;
using namespace xn;
using namespace std;

int main (int argc, char **argv)
{
    unsigned int convex_number_above_point1 = 0;
    unsigned int concave_number_above_point1 = 0;
    unsigned int convex_number_above_point2 = 0;
    unsigned int concave_number_above_point2 = 0;
    unsigned int convex_assist_above_point1 = 0;
    unsigned int convex_assist_above_point2 = 0;
    unsigned int point_y1 = 0;
    unsigned int point_y2 = 0;
    int number_result = -1;
    bool recognition_flag = false;  //开始手部数字识别的标志

    vector<Scalar> color_array;//采用默认的10种颜色
    {
        color_array.push_back(Scalar(255, 0, 0));
        color_array.push_back(Scalar(0, 255, 0));
        color_array.push_back(Scalar(0, 0, 255));
        color_array.push_back(Scalar(255, 0, 255));
        color_array.push_back(Scalar(255, 255, 0));
        color_array.push_back(Scalar(0, 255, 255));
        color_array.push_back(Scalar(128, 255, 0));
        color_array.push_back(Scalar(0, 128, 255));
        color_array.push_back(Scalar(255, 0, 128));
        color_array.push_back(Scalar(255, 128, 255));
    }
    vector<unsigned int> hand_depth(MAX_HANDS_NUMBER, 0);
    vector<Rect> hands_roi(MAX_HANDS_NUMBER, Rect(XRES/2, YRES/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT));

    namedWindow("color image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);    //显示分割出来的手的区域
    namedWindow("handrecognition", CV_WINDOW_AUTOSIZE); //显示0~5数字识别的图像

    COpenNI openni;
    if(!openni.Initial())
        return 1;

    if(!openni.Start())
        return 1;
    while(1) {
        if(!openni.UpdateData()) {
            return 1;
        }
        /*获取并显示色彩图像*/
        Mat color_image_src(openni.image_metadata_.YRes(), openni.image_metadata_.XRes(),
                            CV_8UC3, (char *)openni.image_metadata_.Data());
        Mat color_image;
        cvtColor(color_image_src, color_image, CV_RGB2BGR);
        Mat hand_segment_mask(color_image.size(), CV_8UC1, Scalar::all(0));

        for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) {

            point_y1 = itUser->second.Y;
            point_y2 = itUser->second.Y + DELTA_POINT_DISTENCE;
            circle(color_image, Point(itUser->second.X, itUser->second.Y),
                   5, color_array.at(itUser->first % color_array.size()), 3, 8);

            /*设置不同手部的深度*/
            hand_depth.at(itUser->first % MAX_HANDS_COLOR) = (unsigned int)(itUser->second.Z* DEPTH_SCALE_FACTOR);//itUser->first会导致程序出现bug

            /*设置不同手部的不同感兴趣区域*/
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2,
                                               ROI_HAND_WIDTH, ROI_HAND_HEIGHT);
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x =  itUser->second.X - ROI_HAND_WIDTH/2;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y =  itUser->second.Y - ROI_HAND_HEIGHT/2;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width = ROI_HAND_WIDTH;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height = ROI_HAND_HEIGHT;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x <= 0)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x  = 0;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x > XRES)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x =  XRES;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y <= 0)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = 0;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y > YRES)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y =  YRES;
        }
        imshow("color image", color_image);

        /*获取并显示深度图像*/
        Mat depth_image_src(openni.depth_metadata_.YRes(), openni.depth_metadata_.XRes(),
                            CV_16UC1, (char *)openni.depth_metadata_.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
        Mat depth_image;
        depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);
        imshow("depth image", depth_image);

        //取出手的mask部分
        //不管原图像时多少通道的,mask矩阵声明为单通道就ok
        for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) {
            for(int i = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x; i < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width, XRES); i++)
                for(int j = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y; j < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height, YRES); j++) {
                    hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                                & ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));
                }
         }
        medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);
        Mat hand_segment(color_image.size(), CV_8UC3);
        color_image.copyTo(hand_segment, hand_segment_mask);

        /*对mask图像进行轮廓提取,并在手势识别图像中画出来*/
        std::vector< std::vector<Point> > contours;
        findContours(hand_segment_mask, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);//找出mask图像的轮廓
        Mat hand_recognition_image = Mat::zeros(color_image.rows, color_image.cols, CV_8UC3);

        for(int i = 0; i < contours.size(); i++) {  //只有在检测到轮廓时才会去求它的多边形,凸包集,凹陷集
            recognition_flag = true;
            /*找出轮廓图像多边形拟合曲线*/
            Mat contour_mat = Mat(contours[i]);
            if(contourArea(contour_mat) > HAND_LIKELY_AREA) {   //比较有可能像手的区域
                std::vector<Point> approx_poly_curve;
                approxPolyDP(contour_mat, approx_poly_curve, 10, true);//找出轮廓的多边形拟合曲线
                std::vector< std::vector<Point> > approx_poly_curve_debug;
                approx_poly_curve_debug.push_back(approx_poly_curve);

                 drawContours(hand_recognition_image, contours, i, Scalar(255, 0, 0), 1, 8); //画出轮廓
    //            drawContours(hand_recognition_image, approx_poly_curve_debug, 0, Scalar(256, 128, 128), 1, 8); //画出多边形拟合曲线

                /*对求出的多边形拟合曲线求出其凸包集*/
                vector<int> hull;
                convexHull(Mat(approx_poly_curve), hull, true);
                for(int i = 0; i < hull.size(); i++) {
                    circle(hand_recognition_image, approx_poly_curve[hull[i]], 2, Scalar(0, 255, 0), 2, 8);

                    /*统计在中心点1以上凸点的个数*/
                    if(approx_poly_curve[hull[i]].y <= point_y1) {
                        /*统计凸点与中心点1的y轴距离*/
                        long dis_point1 = abs(long(point_y1 - approx_poly_curve[hull[i]].y));
                        int dis1 = point_y1 - approx_poly_curve[hull[i]].y;
                        if(dis_point1 > SEGMENT_POINT1_DISTANCE && dis1 >= 0)  {
                            convex_assist_above_point1++;
                        }
                        convex_number_above_point1++;
                    }

                    /*统计在中心点2以上凸点的个数*/
                    if(approx_poly_curve[hull[i]].y <= point_y2)    {
                        /*统计凸点与中心点1的y轴距离*/
                        long dis_point2 = abs(long(point_y2 - approx_poly_curve[hull[i]].y));
                        int dis2 = point_y2 - approx_poly_curve[hull[i]].y;
                        if(dis_point2 > SEGMENT_POINT2_DISTANCE && dis2 >= 0)  {
                            convex_assist_above_point2++;
                        }
                        convex_number_above_point2++;
                    }
                }

    //            /*对求出的多边形拟合曲线求出凹陷集*/
                std::vector<Vec4i> convexity_defects;
                if(Mat(approx_poly_curve).checkVector(2, CV_32S) > 3)
                    convexityDefects(approx_poly_curve, Mat(hull), convexity_defects);
                for(int i = 0; i < convexity_defects.size(); i++) {
                    circle(hand_recognition_image, approx_poly_curve[convexity_defects[i][2]] , 2, Scalar(0, 0, 255), 2, 8);

                    /*统计在中心点1以上凹陷点的个数*/
                    if(approx_poly_curve[convexity_defects[i][2]].y <= point_y1)
                        concave_number_above_point1++;

                    /*统计在中心点2以上凹陷点的个数*/
                    if(approx_poly_curve[convexity_defects[i][2]].y <= point_y2)
                        concave_number_above_point2++;
                }
            }
        }

        /**画出手势的中心点**/
        for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) {
            circle(hand_recognition_image, Point(itUser->second.X, itUser->second.Y), 3, Scalar(0, 255, 255), 3, 8);
            circle(hand_recognition_image, Point(itUser->second.X, itUser->second.Y + 25), 3, Scalar(255, 0, 255), 3, 8);
        }

        /*手势数字0~5的识别*/
        //"0"的识别
        if((convex_assist_above_point1 ==0 && convex_number_above_point2 >= 2 && convex_number_above_point2 <= 3 &&
                concave_number_above_point2 <= 1 && concave_number_above_point1 <= 1) || (concave_number_above_point1 ==0
                || concave_number_above_point2 == 0) && recognition_flag == true)
            number_result = 0;
        //"1"的识别
        if(convex_assist_above_point1 ==1 && convex_number_above_point1 >=1  && convex_number_above_point1 <=2 &&
                convex_number_above_point2 >=2 && convex_assist_above_point2 == 1)
            number_result = 1;
        //"2"的识别
        if(convex_number_above_point1 == 2 && concave_number_above_point1 == 1 && convex_assist_above_point2 == 2
                /*convex_assist_above_point1 <=1*/ && concave_number_above_point2 == 1)
            number_result = 2;
        //"3"的识别
        if(convex_number_above_point1 == 3 && concave_number_above_point1 <= 3 &&
                concave_number_above_point1 >=1 && convex_number_above_point2 >= 3 && convex_number_above_point2 <= 4 &&
                convex_assist_above_point2 == 3)
            number_result = 3;
        //"4"的识别
        if(convex_number_above_point1 == 4 && concave_number_above_point1 <=3 && concave_number_above_point1 >=2 &&
                convex_number_above_point2 == 4)
            number_result = 4;
        //"5"的识别
        if(convex_number_above_point1 >=4 && convex_number_above_point2 == 5 && concave_number_above_point2 >= 3 &&
                convex_number_above_point2 >= 4)
            number_result = 5;
        if(number_result !=0 && number_result != 1  && number_result != 2 && number_result != 3 && number_result != 4 && number_result != 5)
            number_result == -1;

        /*在手势识别图上显示匹配的数字*/
        std::stringstream number_str;
        number_str << number_result;
        putText(hand_recognition_image, "Match: ", Point(0, 60), 4, 1, Scalar(0, 255, 0), 2, 0 );
        if(number_result == -1)
            putText(hand_recognition_image, " ", Point(120, 60), 4, 2, Scalar(255, 0 ,0), 2, 0);
        else
            putText(hand_recognition_image, number_str.str(), Point(150, 60), 4, 2, Scalar(255, 0 ,0), 2, 0);

        imshow("handrecognition", hand_recognition_image);
        imshow("hand_segment", hand_segment);

        /*一个循环中对有些变量进行初始化操作*/
        convex_number_above_point1 = 0;
        convex_number_above_point2 = 0;
        concave_number_above_point1 = 0;
        concave_number_above_point2 = 0;
        convex_assist_above_point1 = 0;
        convex_assist_above_point2 = 0;
        number_result = -1;
        recognition_flag = false;
        number_str.clear();

        waitKey(20);
    }

}
复制代码

 

copenni.h:

复制代码
#ifndef COPENNI_H
#define COPENNI_H

#include <XnCppWrapper.h>
#include <XnCyclicStackT.h>
#include <XnHashT.h>
#include <XnListT.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

class COpenNI
{
public:
    COpenNI();
    ~COpenNI();

    /*OpenNI的内部初始化,属性设置*/
    bool Initial();

    /*启动OpenNI读取Kinect数据*/
    bool Start();

    /*更新OpenNI读取到的数据*/
    bool UpdateData();

    /*得到色彩图像的node*/
    ImageGenerator& getImageGenerator();

    /*得到深度图像的node*/
    DepthGenerator& getDepthGenerator();

    /*得到人体的node*/
    UserGenerator& getUserGenerator();

    /*得到手势姿势的node*/
    GestureGenerator& getGestureGenerator();

    /*得到手部的node*/
    HandsGenerator& getHandGenerator();

    DepthMetaData depth_metadata_;   //返回深度图像数据
    ImageMetaData image_metadata_;   //返回彩色图像数据
    std::map<XnUserID, XnPoint3D> hand_points_;  //为了存储不同手的实时点而设置的
    std::map< XnUserID, vector<XnPoint3D> > hands_track_points_; //为了绘画后面不同手部的跟踪轨迹而设定的

private:
    /*该函数返回真代表出现了错误,返回假代表正确*/
    bool CheckError(const char* error);

    /*表示有人体进入的回调函数*/
    static void XN_CALLBACK_TYPE CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie);

    /*表示骨骼校正完成的回调函数*/
    static void XN_CALLBACK_TYPE CBCalibrationComplete(SkeletonCapability &skeleton,
                                                       XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie);

    /*表示某个手势动作已经完成检测的回调函数*/
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                      const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition,
                                                      void *pCookie);

    /*表示检测到某个手势开始的回调函数*/
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                   const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie);

    /*手部开始建立的回调函数*/
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie);

    /*手部开始更新的回调函数*/
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie);

    /*手部销毁的回调函数*/
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime, void* pCookie);

    XnStatus status_;
    Context context_;
    XnMapOutputMode xmode_;
    UserGenerator user_generator_;
    ImageGenerator  image_generator_;
    DepthGenerator  depth_generator_;
    GestureGenerator gesture_generator_;
    HandsGenerator  hand_generator_;
};

#endif // COPENNI_H
复制代码

 

  copenni.cpp:

复制代码
#include "copenni.h"
#include <XnCppWrapper.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

COpenNI::COpenNI()
{
}

COpenNI::~COpenNI()
{
}

bool COpenNI::Initial()
{
    status_ = context_.Init();
    if(CheckError("Context initial failed!")) {
        return false;
    }

    context_.SetGlobalMirror(true);//设置镜像
    xmode_.nXRes = 640;
    xmode_.nYRes = 480;
    xmode_.nFPS = 30;

    //产生颜色node
    status_ = image_generator_.Create(context_);
    if(CheckError("Create image generator  error!")) {
        return false;
    }

    //设置颜色图片输出模式
    status_ = image_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生深度node
    status_ = depth_generator_.Create(context_);
    if(CheckError("Create depth generator  error!")) {
        return false;
    }

    //设置深度图片输出模式
    status_ = depth_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生手势node
    status_ = gesture_generator_.Create(context_);
    if(CheckError("Create gesture generator error!")) {
        return false;
    }

    /*添加手势识别的种类*/
    gesture_generator_.AddGesture("Wave", NULL);
    gesture_generator_.AddGesture("click", NULL);
    gesture_generator_.AddGesture("RaiseHand", NULL);
    gesture_generator_.AddGesture("MovingHand", NULL);

    //产生手部的node
    status_ = hand_generator_.Create(context_);
    if(CheckError("Create hand generaotr error!")) {
        return false;
    }

    //产生人体node
    status_ = user_generator_.Create(context_);
    if(CheckError("Create gesturen generator error!")) {
        return false;
    }

    //视角校正
    status_ = depth_generator_.GetAlternativeViewPointCap().SetViewPoint(image_generator_);
    if(CheckError("Can't set the alternative view point on depth generator!")) {
        return false;
    }

    //设置与手势有关的回调函数
    XnCallbackHandle gesture_cb;
    gesture_generator_.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, this, gesture_cb);

    //设置于手部有关的回调函数
    XnCallbackHandle hands_cb;
    hand_generator_.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, hands_cb);

    //设置有人进入视野的回调函数
    XnCallbackHandle new_user_handle;
    user_generator_.RegisterUserCallbacks(CBNewUser, NULL, NULL, new_user_handle);
    user_generator_.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//设定使用所有关节(共15个)

    //设置骨骼校正完成的回调函数
    XnCallbackHandle calibration_complete;
    user_generator_.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete, this, calibration_complete);
    return true;
}

bool COpenNI::Start()
{
    status_ = context_.StartGeneratingAll();
    if(CheckError("Start generating error!")) {
        return false;
    }
    return true;
}

bool COpenNI::UpdateData()
{
    status_ = context_.WaitNoneUpdateAll();
    if(CheckError("Update date error!")) {
        return false;
    }
    //获取数据
    image_generator_.GetMetaData(image_metadata_);
    depth_generator_.GetMetaData(depth_metadata_);

    return true;
}

ImageGenerator &COpenNI::getImageGenerator()
{
    return image_generator_;
}

DepthGenerator &COpenNI::getDepthGenerator()
{
    return depth_generator_;
}

UserGenerator &COpenNI::getUserGenerator()
{
    return user_generator_;
}

GestureGenerator &COpenNI::getGestureGenerator()
{
    return gesture_generator_;
}

HandsGenerator &COpenNI::getHandGenerator()
{
    return hand_generator_;
}

bool COpenNI::CheckError(const char *error)
{
    if(status_ != XN_STATUS_OK) {
        cerr << error << ": " << xnGetStatusString( status_ ) << endl;
        return true;
    }
    return false;
}

void COpenNI::CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie)
{
    //得到skeleton的capability,并调用RequestCalibration函数设置对新检测到的人进行骨骼校正
    generator.GetSkeletonCap().RequestCalibration(user, true);
}

void COpenNI::CBCalibrationComplete(SkeletonCapability &skeleton, XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie)
{
    if(calibration_error == XN_CALIBRATION_STATUS_OK) {
        skeleton.StartTracking(user);//骨骼校正完成后就开始进行人体跟踪了
    }
    else {
        UserGenerator *p_user = (UserGenerator*)p_cookie;
        skeleton.RequestCalibration(user, true);//骨骼校正失败时重新设置对人体骨骼继续进行校正
    }
}

void COpenNI::CBGestureRecognized(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie)
{
    COpenNI *openni = (COpenNI*)pCookie;
    openni->hand_generator_.StartTracking(*pEndPosition);
}

void COpenNI::CBGestureProgress(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie)
{
}

void COpenNI::HandCreate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenNI *openni = (COpenNI*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    pair<XnUserID, XnPoint3D> hand_point_pair(xUID, XnPoint3D());//在进行pair类型的定义时,可以将第2个设置为空
    hand_point_pair.second = project_pos;
    openni->hand_points_.insert(hand_point_pair);//将检测到的手部存入map类型的hand_points_中。

    pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
    hand_track_point.second.push_back(project_pos);
    openni->hands_track_points_.insert(hand_track_point);
}

void COpenNI::HandUpdate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenNI *openni = (COpenNI*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    openni->hand_points_.find(xUID)->second = project_pos;
    openni->hands_track_points_.find(xUID)->second.push_back(project_pos);
}

void COpenNI::HandDestroy(HandsGenerator &rHands, XnUserID xUID, XnFloat fTime, void *pCookie)
{
    COpenNI *openni = (COpenNI*)pCookie;
    openni->hand_points_.erase(openni->hand_points_.find(xUID));
    openni->hands_track_points_.erase(openni->hands_track_points_.find(xUID ));
}
复制代码

 

  实验总结:

  由本次实验的操作过程可以看出,识别效果抗干扰能力比较差差。因此后续的工作是建立一个手势识别的数据库,寻找一个好的手部特征向量,和一个好的分类器。这有可能将是本人研究生毕业论文的研究方向,加油!

  参考文献:

     Robert Walter手部提取代码的分析

     不需要骨骼跟踪的人体多个手部分割

     OpenNI驱动kinect手势相关的类的设计 

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之11(OpenNI驱动kinect手势相关的类的设计) - tornadomeet - 博客园

mikel阅读(934)

[转载]Kinect+OpenNI学习笔记之11(OpenNI驱动kinect手势相关的类的设计) – tornadomeet – 博客园.

前言

本文所设计的类主要是和人体的手部打交道的,与人体的检测,姿势校正,骨架跟踪没有关系,所以本次类的设计中是在前面的 OpenNI+Kinect系列博文基础上去掉那些与手势无关的驱动,较小代码量负担。类中保留下来有手势识别,手部跟踪,以及手部跟踪的轨迹和多个手部 的位置坐标等信息。本类的设计也开始慢慢遵循一些C/C++编程规范,这里采用的是google的编程规范。

本文测试设计出的类的功能是与博文不需要骨骼跟踪的人体多个手部分割 一样,进行人体多个手部跟踪和分割。

开发环境:开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.2

 

Google编程规范节选

类的设计的规范性,首先是遵循一定的编程风格,这里本人采用的是google C++编程风格。Google编程规范具体内容可以参考网页:Google C++ Style Guide这次类的设计是在前面的博文使用OpenNI自带的类进行简单手势识别 和博文不需要骨骼跟踪的人体多个手部分割 的基础上从下面几个地方做了编程风格规范:

1.  将类的设计分开为头文件和源文件,前面类的设计是在一个cpp文件中。分开设计的目的是使代码看起来结构更加清晰。

2. 在类中使用特定的声明次序,即public在前,随后依次为protected, private.且在其内部是成员函数在成员变量之前。比如在public的内部,一般的声明次序为:typedef和enums,常量,构造函数,析构 函数,成员函数(包括静态成员函数),数据成员(包括静态数据成员)。

3. 类中的函数体要尽量短小,紧凑,功能单一。

4. 类的成员函数命名以大写字母开头,每个单词首字母大写,没有下划线。内联函数命名除外。

5. 类的成员变量名一徇小写,单词间以下划线相还,并以下划线结尾。

  6. Typedef类型命名时,其类型名字每个单词以大写字母开头,不包含下划线。

    openNI知识点总结

  从OpenNI源码的下面2句代码中可以看出,XnUserID是unsigned int类型。

typedef    unsigned int              XnUInt32;

typedef XnUInt32 XnUserID;

openni有些回调函数中会有传入参数类型为XnUserID的数据,比如在本文讲类的设计中,XnUserID类型就代表检测到的不同手部 的id号,因为该id理论上是用来表明不同的手部的,因此当检测到新的手部时,系统中XnUserID类型对应变量将会加1。总的来看,XnUserID 类型对应的变量值一直在递增,刚刚上面有讲到XnUserID是unsigned int类型的,所以其表示的范围能力可以是很大,即使由于视频中出现有很多的误检导致其值增加很多,这还是足够用了的。误检主要是某些情况下,人的一只手 由于运动速度过快或者其背景的干扰,在手势检测过程中有可能同时被当做是几十只手,这时候手部的id值就会一下子增加很多。所以在程序的后续的跟踪过程 中,id值(通过输出到后台来观察)有可能很大。这时如果要遍历id的话,会影响程序的速度和内存开销。但是这也没有办法,因为我们的工作是基于 OpenNI的手部跟踪的基础上的,只能依靠手部跟踪的性能了。

  虽然说手部的id值会很大,但是我们在一个视野中出现的手的个数并不是特别多,顶多几十只手。所以在程序中如果要给 不同的手部不同的颜色跟踪,或者对不同的手部都采用不同的掩膜区域,此时设置为颜色的个数和掩膜区域的个数就不需要最大的id值那么大了,因为虽然id值 高,但是有些手部退出了视野,其内存中对应存的id那一项被删掉了,实际的手的数目不会特别大。因此在程序中,我们可以先不考虑id的最大值,而设置一个 手部最大的个数,比如说20。然后用id值对最大个数20取掩膜来进行遍历。如果程序中不用它来求默认个数的掩膜的话,随着id值的增加,程序运行时有可 能会连续出现下面3个错误:

  

  为了解决这个错误都弄了大半天了。其原因是程序中id的值一直在增加,且人为修改它的值起不到任何效果,它是由OpenNI内部决定的。

  C/C++知识点总结

  define和typedef使用时恰恰相反,简单一点来理解:define是将它后面的第1个来代替第2个使用,而typedef是将它后面的第2个来代替第1个使用。

  类的源码

  copennihand.h:

复制代码
#ifndef COpenniHand_H
#define COpenniHand_H

#include <XnCppWrapper.h>
#include <iostream>
#include <vector>
#include <map>

using namespace xn;
using namespace std;

class COpenniHand
{
public:
    COpenniHand();
    ~COpenniHand();

    /*OpenNI的内部初始化,属性设置*/
    bool Initial();

    /*启动OpenNI读取Kinect数据*/
    bool Start();

    /*更新OpenNI读取到的数据*/
    bool UpdateData();

    /*得到色彩图像的node*/
    ImageGenerator& getImageGenerator();

    /*得到深度图像的node*/
    DepthGenerator& getDepthGenerator();

    /*得到手势姿势的node*/
    GestureGenerator& getGestureGenerator();

    /*得到手部的node*/
    HandsGenerator& getHandGenerator();
    DepthMetaData depth_metadata_;   //返回深度图像数据
    ImageMetaData image_metadata_;   //返回彩色图像数据
    std::map<XnUserID, XnPoint3D> hand_points_;  //为了存储不同手的实时点而设置的
    std::map< XnUserID, vector<XnPoint3D> > hands_track_points_; //为了绘画后面不同手部的跟踪轨迹而设定的

private:
    /*该函数返回真代表出现了错误,返回假代表正确*/
    bool CheckError(const char* error);

    /*表示某个手势动作已经完成检测的回调函数*/
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                      const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition,
                                                      void *pCookie);

    /*表示检测到某个手势开始的回调函数*/
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture,
                                                   const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie);

    /*手部开始建立的回调函数*/
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie);

    /*手部开始更新的回调函数*/
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie);

    /*手部销毁的回调函数*/
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime, void* pCookie);

    XnStatus status_;
    Context context_;
    XnMapOutputMode xmode_;
    ImageGenerator  image_generator_;
    DepthGenerator  depth_generator_;
    GestureGenerator gesture_generator_;
    HandsGenerator  hand_generator_;
};

#endif // COpenniHand_H
复制代码

 

  copennihand.cpp:

复制代码
#include "copennihand.h"
#include <XnCppWrapper.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

COpenniHand::COpenniHand()
{
}

COpenniHand::~COpenniHand()
{
}

bool COpenniHand::Initial()
{
    status_ = context_.Init();
    if(CheckError("Context initial failed!")) {
        return false;
    }

    context_.SetGlobalMirror(true);//设置镜像
    xmode_.nXRes = 640;
    xmode_.nYRes = 480;
    xmode_.nFPS = 30;

    //产生颜色node
    status_ = image_generator_.Create(context_);
    if(CheckError("Create image generator  error!")) {
        return false;
    }

    //设置颜色图片输出模式
    status_ = image_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生深度node
    status_ = depth_generator_.Create(context_);
    if(CheckError("Create depth generator  error!")) {
        return false;
    }

    //设置深度图片输出模式
    status_ = depth_generator_.SetMapOutputMode(xmode_);
    if(CheckError("SetMapOutputMdoe error!")) {
        return false;
    }

    //产生手势node
    status_ = gesture_generator_.Create(context_);
    if(CheckError("Create gesture generator error!")) {
        return false;
    }

    /*添加手势识别的种类*/
    gesture_generator_.AddGesture("Wave", NULL);
    gesture_generator_.AddGesture("click", NULL);
    gesture_generator_.AddGesture("RaiseHand", NULL);
    gesture_generator_.AddGesture("MovingHand", NULL);

    //产生手部的node
    status_ = hand_generator_.Create(context_);
    if(CheckError("Create hand generaotr error!")) {
        return false;
    }

    //视角校正
    status_ = depth_generator_.GetAlternativeViewPointCap().SetViewPoint(image_generator_);
    if(CheckError("Can't set the alternative view point on depth generator!")) {
        return false;
    }

    //设置与手势有关的回调函数
    XnCallbackHandle gesture_cb;
    gesture_generator_.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, this, gesture_cb);

    //设置于手部有关的回调函数
    XnCallbackHandle hands_cb;
    hand_generator_.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, hands_cb);

    return true;
}

bool COpenniHand::Start()
{
    status_ = context_.StartGeneratingAll();
    if(CheckError("Start generating error!")) {
        return false;
    }
    return true;
}

bool COpenniHand::UpdateData()
{
    status_ = context_.WaitNoneUpdateAll();
    if(CheckError("Update date error!")) {
        return false;
    }
    //获取数据
    image_generator_.GetMetaData(image_metadata_);
    depth_generator_.GetMetaData(depth_metadata_);

    return true;
}

ImageGenerator &COpenniHand::getImageGenerator()
{
    return image_generator_;
}

DepthGenerator &COpenniHand::getDepthGenerator()
{
    return depth_generator_;
}

GestureGenerator &COpenniHand::getGestureGenerator()
{
    return gesture_generator_;
}

HandsGenerator &COpenniHand::getHandGenerator()
{
    return hand_generator_;
}

bool COpenniHand::CheckError(const char *error)
{
    if(status_ != XN_STATUS_OK) {
        cerr << error << ": " << xnGetStatusString( status_ ) << endl;
        return true;
    }
    return false;
}

void COpenniHand::CBGestureRecognized(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    openni->hand_generator_.StartTracking(*pEndPosition);
}

void COpenniHand::CBGestureProgress(GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie)
{
}

void COpenniHand::HandCreate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    pair<XnUserID, XnPoint3D> hand_point_pair(xUID, XnPoint3D());//在进行pair类型的定义时,可以将第2个设置为空
    hand_point_pair.second = project_pos;
    openni->hand_points_.insert(hand_point_pair);//将检测到的手部存入map类型的hand_points_中。

    pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
    hand_track_point.second.push_back(project_pos);
    openni->hands_track_points_.insert(hand_track_point);
}

void COpenniHand::HandUpdate(HandsGenerator &rHands, XnUserID xUID, const XnPoint3D *pPosition, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    XnPoint3D project_pos;
    openni->depth_generator_.ConvertRealWorldToProjective(1, pPosition, &project_pos);
    openni->hand_points_.find(xUID)->second = project_pos;
    openni->hands_track_points_.find(xUID)->second.push_back(project_pos);
}

void COpenniHand::HandDestroy(HandsGenerator &rHands, XnUserID xUID, XnFloat fTime, void *pCookie)
{
    COpenniHand *openni = (COpenniHand*)pCookie;
    openni->hand_points_.erase(openni->hand_points_.find(xUID));
    openni->hands_track_points_.erase(openni->hands_track_points_.find(xUID ));
}
复制代码

 

  main.cpp:

复制代码
#include <iostream>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copennihand.h"

#include <iostream>

#define DEPTH_SCALE_FACTOR 255./4096.
#define ROI_HAND_WIDTH 140
#define ROI_HAND_HEIGHT 140
#define MEDIAN_BLUR_K 5
#define XRES  640
#define YRES  480
#define DEPTH_SEGMENT_THRESH 5
#define MAX_HANDS_COLOR 10
#define MAX_HANDS_NUMBER  10

using namespace cv;
using namespace xn;
using namespace std;

int main (int argc, char **argv)
{
    vector<Scalar> color_array;//采用默认的10种颜色
    {
        color_array.push_back(Scalar(255, 0, 0));
        color_array.push_back(Scalar(0, 255, 0));
        color_array.push_back(Scalar(0, 0, 255));
        color_array.push_back(Scalar(255, 0, 255));
        color_array.push_back(Scalar(255, 255, 0));
        color_array.push_back(Scalar(0, 255, 255));
        color_array.push_back(Scalar(128, 255, 0));
        color_array.push_back(Scalar(0, 128, 255));
        color_array.push_back(Scalar(255, 0, 128));
        color_array.push_back(Scalar(255, 128, 255));
    }
    vector<unsigned int> hand_depth(MAX_HANDS_NUMBER, 0);
    vector<Rect> hands_roi(MAX_HANDS_NUMBER, Rect(XRES/2, YRES/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT));

    namedWindow("color image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);//显示分割出来的手的区域

    COpenniHand openni;
    if(!openni.Initial())
        return 1;

    if(!openni.Start())
        return 1;
    while(1) {
        if(!openni.UpdateData()) {
            return 1;
        }
        /*获取并显示色彩图像*/
        Mat color_image_src(openni.image_metadata_.YRes(), openni.image_metadata_.XRes(),
                            CV_8UC3, (char *)openni.image_metadata_.Data());
        Mat color_image;
        cvtColor(color_image_src, color_image, CV_RGB2BGR);

        for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser) {

            circle(color_image, Point(itUser->second.X, itUser->second.Y),
                   5, color_array.at(itUser->first % color_array.size()), 3, 8);

            /*设置不同手部的深度*/
            hand_depth.at(itUser->first % MAX_HANDS_COLOR) = (unsigned int)(itUser->second.Z* DEPTH_SCALE_FACTOR);//itUser->first会导致程序出现bug

            /*设置不同手部的不同感兴趣区域*/
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2,
                                               ROI_HAND_WIDTH, ROI_HAND_HEIGHT);
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x =  itUser->second.X - ROI_HAND_WIDTH/2;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y =  itUser->second.Y - ROI_HAND_HEIGHT/2;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width = ROI_HAND_WIDTH;
            hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height = ROI_HAND_HEIGHT;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x <= 0)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x  = 0;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x > XRES)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x =  XRES;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y <= 0)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y = 0;
            if(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y > YRES)
                hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y =  YRES;
        }
        imshow("color image", color_image);

        /*获取并显示深度图像*/
        Mat depth_image_src(openni.depth_metadata_.YRes(), openni.depth_metadata_.XRes(),
                            CV_16UC1, (char *)openni.depth_metadata_.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
        Mat depth_image;
        depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);
        imshow("depth image", depth_image);

        //取出手的mask部分
        //不管原图像时多少通道的,mask矩阵声明为单通道就ok
        Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0));
        for(auto itUser = openni.hand_points_.cbegin(); itUser != openni.hand_points_.cend(); ++itUser)
            for(int i = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x; i < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).x+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).width, XRES); i++)
                for(int j = hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y; j < std::min(hands_roi.at(itUser->first % MAX_HANDS_NUMBER).y+hands_roi.at(itUser->first % MAX_HANDS_NUMBER).height, YRES); j++) {
                    hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                                & ((hand_depth.at(itUser->first % MAX_HANDS_NUMBER)+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));
                }
        medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);
        Mat hand_segment(color_image.size(), CV_8UC3);
        color_image.copyTo(hand_segment, hand_segment_mask);

        imshow("hand_segment", hand_segment);
        waitKey(30);
    }

}
复制代码

 

  参考文献:

      Google C++ Style Guide

 

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之10(不需要骨骼跟踪的人体多个手部分割) - tornadomeet - 博客园

mikel阅读(716)

[转载]Kinect+OpenNI学习笔记之10(不需要骨骼跟踪的人体多个手部分割) – tornadomeet – 博客园.

 前言

  这篇文章主要是介绍多个手部的分割,是在前面的博文:不需要骨骼跟踪的人体手部分割 的基础上稍加改进的。因为识别有的一个应用场合就是手势语言识别,而手势一般都需要人的2只手相配合完成,因此很有必要对人体的多个手部来进行分割。

 实验说明

  其实本文中使用的还是OpenNI自带的一些算法实现的,因为OpenNI中自己本身就对每个手部有一个 UserID的标志,所以我们每当检测到一只手时就可以把手的位置连同他的ID一起存下来,后面进行手势分割时按照检测到的不同手势分别进行分割即可。其 程序流程图如下所示:

  

  下面是本次实验特别需要注意的一些细节。

  OpenNI知识点总结:

一般情况下OpenNI的回调函数中都会有一个参数pCookie,该参数就可以解决前面的博文Kinect+OpenNI学习笔记之7(OpenNI自带的类实现手部跟踪)

中提到的一个当时没有完美解决的问题:即回调函数与类的静态函数,类的静态变量这3者之间使用相互矛盾的问题。那个时候因为在回调函数中需要使用静 态成员变量,所以类中普通的非静态成员变量是不能够使用的,否则程序会编译时会报错误。但是如果我们把这些普通变量在类中定义成了静态变量后,这些静态变 量就属于类本身了,并不属于类的对象。因此该变量在类的其它成员函数中是不能够被使用的。这样就产生了矛盾,当时的解决方法是将这些变量不放在类中,而放 在类外称为整个工程的全局变量。虽然理论上可以解决问题,不过一个跟类有关的变量竟然不能够放在类的内部,听起来就像是个大笑话!这样的封装明显不合理。

  幸运的是,现在因为回调函数传进来时有了pCookie这个变量,这样我们在回调函数中可以间接使用类的非静态成员 变量了,使用这些变量既不需要定义为static类型,且也可以在类的成员函数中来进行初始化。具体方法是将某个节点(比如说手部,人体,姿势等节点)的 注册函数RegisterGestureCallbacks()中第3个参数设置为this指针,而不是null指针。同时在具体的回调函数中,首先把 pCookie指针强制转换成COpenNI这个类的指针,然后用转换过来的指针调用需要用到的类的成员变量。

  C/C++知识点总结:

pair和map的区别:map是一个容器,容器中的第一个元素表示的是键值key,其它元素分别表示以后用该容器存储数据时的数据类型。因此 map中的每一条记录的key值是不能重复的。当map定义的时候只有2个集合的时候,里面的每一条记录可以用pair来存储。因此可以简单的理解一个 pair对应的是一条具体的记录,而一个map是一个存放pair的容器,并且map声明了容器的属性。

当在进行pair数据类型的定义时,如果其元素中的一个已经确定,另一个还不知道,则在定义的同时可以直接传入确定的那个元素,另一个用它的数据类型后面接一个空括号即可。

在使用vector时,必须是已存在的元素才能用下标操作符进行索引。可以使用at和[]获取指定位置的数据或者给指定位置的数据赋值。

  Qt知识点总结:

在QtCreator的使用中,有时候会出现两个尖括号在一起的情况,这时候没有语法错误,但是QtCreator这个编辑环境会在你的代码下出现个红色的波纹,让人看起来非常不舒服。例如:

解决方法非常简单,即把两个尖括号中间不要紧挨着,用一个空格号隔开一下即可,这时候红色的波纹警告线就消失了。

 

 

 实验结果

本工程可以对多个手部进行分割,分割效果取决于OpenNI中的手部跟踪效果。其效果截图如下:

 

  实验代码(附录有工程code下载链接)

copenni.cpp:

复制代码
#ifndef COPENNI_CLASS
#define COPENNI_CLASS

#include <XnCppWrapper.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

class COpenNI
{
public:
    ~COpenNI() {
        context.Release();//释放空间
    }
    bool Initial() {
        //初始化
        status = context.Init();
        if(CheckError("Context initial failed!")) {
            return false;
        }
        context.SetGlobalMirror(true);//设置镜像
        xmode.nXRes = 640;
        xmode.nYRes = 480;
        xmode.nFPS = 30;
        //产生颜色node
        status = image_generator.Create(context);
        if(CheckError("Create image generator  error!")) {
            return false;
        }
        //设置颜色图片输出模式
        status = image_generator.SetMapOutputMode(xmode);
        if(CheckError("SetMapOutputMdoe error!")) {
            return false;
        }
        //产生深度node
        status = depth_generator.Create(context);
        if(CheckError("Create depth generator  error!")) {
            return false;
        }
        //设置深度图片输出模式
        status = depth_generator.SetMapOutputMode(xmode);
        if(CheckError("SetMapOutputMdoe error!")) {
            return false;
        }
        //产生手势node
        status = gesture_generator.Create(context);
        if(CheckError("Create gesture generator error!")) {
            return false;
        }
        /*添加手势识别的种类*/
        gesture_generator.AddGesture("Wave", NULL);
        gesture_generator.AddGesture("click", NULL);
        gesture_generator.AddGesture("RaiseHand", NULL);
        gesture_generator.AddGesture("MovingHand", NULL);
        //产生手部的node
        status = hands_generator.Create(context);
        if(CheckError("Create hand generaotr error!")) {
            return false;
        }
        //产生人体node
        status = user_generator.Create(context);
        if(CheckError("Create gesturen generator error!")) {
            return false;
        }
        //视角校正
        status = depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator);
        if(CheckError("Can't set the alternative view point on depth generator!")) {
            return false;
        }
        //设置与手势有关的回调函数
        XnCallbackHandle gesture_cb;
        gesture_generator.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, this, gesture_cb);
        //设置于手部有关的回调函数
        XnCallbackHandle hands_cb;
        hands_generator.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, this, hands_cb);
        //设置有人进入视野的回调函数
        XnCallbackHandle new_user_handle;
        user_generator.RegisterUserCallbacks(CBNewUser, NULL, NULL, new_user_handle);
        user_generator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//设定使用所有关节(共15个)
        //设置骨骼校正完成的回调函数
        XnCallbackHandle calibration_complete;
        user_generator.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete, NULL, calibration_complete);
        return true;
    }

    bool Start() {
        status = context.StartGeneratingAll();
        if(CheckError("Start generating error!")) {
            return false;
        }
        return true;
    }

    bool UpdateData() {
        status = context.WaitNoneUpdateAll();
        if(CheckError("Update date error!")) {
            return false;
        }
        //获取数据
        image_generator.GetMetaData(image_metadata);
        depth_generator.GetMetaData(depth_metadata);

        return true;
    }
    //得到色彩图像的node
    ImageGenerator& getImageGenerator() {
        return image_generator;
    }
    //得到深度图像的node
    DepthGenerator& getDepthGenerator() {
        return depth_generator;
    }
    //得到人体的node
    UserGenerator& getUserGenerator() {
        return user_generator;
    }
    //得到手势姿势node
    GestureGenerator& getGestureGenerator() {
        return gesture_generator;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;
    DepthGenerator  depth_generator;
    HandsGenerator  hands_generator;
    std::map<XnUserID, XnPoint3D> hand_points;//为了存储不同手的实时点而设置的
    map< XnUserID, vector<XnPoint3D> > hands_track_points;//为了绘画后面不同手部的跟踪轨迹而设定的

private:
    //该函数返回真代表出现了错误,返回假代表正确
    bool CheckError(const char* error) {
        if(status != XN_STATUS_OK ) {
            cerr << error << ": " << xnGetStatusString( status ) << endl;
            return true;
        }
        return false;
    }
    //手势某个动作已经完成检测的回调函数
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition,
                                                      const XnPoint3D *pEndPosition, void *pCookie) {
        COpenNI *openni = (COpenNI*)pCookie;
        openni->hands_generator.StartTracking(*pIDPosition);
    }
    //手势开始检测的回调函数
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition,
                                                   XnFloat fProgress, void *pCookie) {
        COpenNI *openni = (COpenNI*)pCookie;
        openni->hands_generator.StartTracking(*pPosition);
    }
    //手部开始建立的回调函数
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie) {
        COpenNI *openni = (COpenNI*)pCookie;
        XnPoint3D project_pos;
        openni->depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
        pair<XnUserID, XnPoint3D> hand_point_pair(xUID, XnPoint3D());//在进行pair类型的定义时,可以将第2个设置为空
        hand_point_pair.second = project_pos;
        openni->hand_points.insert(hand_point_pair);//将检测到的手部存入map类型的hand_points中。

        pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
        hand_track_point.second.push_back(project_pos);
        openni->hands_track_points.insert(hand_track_point);
    }
    //手部开始更新的回调函数
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie) {
        COpenNI *openni = (COpenNI*)pCookie;
        XnPoint3D project_pos;
        openni->depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
        openni->hand_points.find(xUID)->second = project_pos;
        openni->hands_track_points.find(xUID)->second.push_back(project_pos);
    }
    //销毁手部的回调函数
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime,
                                              void* pCookie) {
        COpenNI *openni = (COpenNI*)pCookie;
        openni->hand_points.erase(openni->hand_points.find(xUID));
        openni->hands_track_points.erase(openni->hands_track_points.find(xUID));
    }
    //有人进入视野时的回调函数
    static void XN_CALLBACK_TYPE CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie) {
        //得到skeleton的capability,并调用RequestCalibration函数设置对新检测到的人进行骨骼校正
        generator.GetSkeletonCap().RequestCalibration(user, true);
    }
    //完成骨骼校正的回调函数
    static void XN_CALLBACK_TYPE CBCalibrationComplete(SkeletonCapability &skeleton,
                                                       XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie) {
        if(calibration_error == XN_CALIBRATION_STATUS_OK) {
            skeleton.StartTracking(user);//骨骼校正完成后就开始进行人体跟踪了
        }
        else {
            UserGenerator *p_user = (UserGenerator*)p_cookie;
            skeleton.RequestCalibration(user, true);//骨骼校正失败时重新设置对人体骨骼继续进行校正
        }
    }

private:
    XnStatus status;
    Context context;
    ImageGenerator  image_generator;
    UserGenerator user_generator;
    GestureGenerator gesture_generator;
    XnMapOutputMode xmode;

};

#endif
复制代码

 

main.cpp:

复制代码
#include <iostream>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copenni.cpp"

#include <iostream>

#define DEPTH_SCALE_FACTOR 255./4096.
#define ROI_HAND_WIDTH 140
#define ROI_HAND_HEIGHT 140
#define MEDIAN_BLUR_K 5

int XRES = 640;
int YRES = 480;
#define DEPTH_SEGMENT_THRESH 5

using namespace cv;
using namespace xn;
using namespace std;

int main (int argc, char **argv)
{
    COpenNI openni;
    vector<Scalar> color_array;//采用默认的10种颜色
    {
        color_array.push_back(Scalar(255, 0, 0));
        color_array.push_back(Scalar(0, 255, 0));
        color_array.push_back(Scalar(0, 0, 255));
        color_array.push_back(Scalar(255, 0, 255));
        color_array.push_back(Scalar(255, 255, 0));
        color_array.push_back(Scalar(0, 255, 255));
        color_array.push_back(Scalar(128, 255, 0));
        color_array.push_back(Scalar(0, 128, 255));
        color_array.push_back(Scalar(255, 0, 128));
        color_array.push_back(Scalar(255, 128, 255));
    }
    vector<int> hand_depth(10, 0);
    vector<Rect> hands_roi(10, Rect(320, 240, ROI_HAND_WIDTH, ROI_HAND_HEIGHT));

    if(!openni.Initial())
        return 1;

    namedWindow("color image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);//显示分割出来的手的区域

    if(!openni.Start())
        return 1;
    while(1) {
        if(!openni.UpdateData()) {
            return 1;
        }
        /*获取并显示色彩图像*/
        Mat color_image_src(openni.image_metadata.YRes(), openni.image_metadata.XRes(),
                            CV_8UC3, (char *)openni.image_metadata.Data());
        Mat color_image;
        cvtColor(color_image_src, color_image, CV_RGB2BGR);
        for(auto itUser = openni.hand_points.cbegin(); itUser != openni.hand_points.cend(); ++itUser) {
            circle(color_image, Point(itUser->second.X, itUser->second.Y),
                   5, color_array.at(itUser->first % color_array.size()), 3, 8);
            /*设置不同手部的深度*/
            hand_depth.at(itUser->first) = itUser->second.Z* DEPTH_SCALE_FACTOR;
            /*设置不同手部的不同感兴趣区域*/
            hands_roi.at(itUser->first) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2,
                                               ROI_HAND_WIDTH, ROI_HAND_HEIGHT);
            hands_roi.at(itUser->first).x =  itUser->second.X - ROI_HAND_WIDTH/2;
            hands_roi.at(itUser->first).y =  itUser->second.Y - ROI_HAND_HEIGHT/2;
            hands_roi.at(itUser->first).width = ROI_HAND_WIDTH;
            hands_roi.at(itUser->first).height = ROI_HAND_HEIGHT;
            if(hands_roi.at(itUser->first).x <= 0)
                hands_roi.at(itUser->first).x  = 0;
            if(hands_roi.at(itUser->first).x > XRES)
                hands_roi.at(itUser->first).x =  XRES;
            if(hands_roi.at(itUser->first).y <= 0)
                hands_roi.at(itUser->first).y = 0;
            if(hands_roi.at(itUser->first).y > YRES)
                hands_roi.at(itUser->first).y =  YRES;
        }

        imshow("color image", color_image);

        /*获取并显示深度图像*/
        Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(),
                            CV_16UC1, (char *)openni.depth_metadata.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
        Mat depth_image;
        depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);
        imshow("depth image", depth_image);

        //取出手的mask部分
        //不管原图像时多少通道的,mask矩阵声明为单通道就ok
        Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0));
        for(auto itUser = openni.hand_points.cbegin(); itUser != openni.hand_points.cend(); ++itUser)
            for(int i = hands_roi.at(itUser->first).x; i < std::min(hands_roi.at(itUser->first).x+hands_roi.at(itUser->first).width, XRES); i++)
                for(int j = hands_roi.at(itUser->first).y; j < std::min(hands_roi.at(itUser->first).y+hands_roi.at(itUser->first).height, YRES); j++) {
                    hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth.at(itUser->first)-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                                & ((hand_depth.at(itUser->first)+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));
                }
        medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);
        Mat hand_segment(color_image.size(), CV_8UC3);
        color_image.copyTo(hand_segment, hand_segment_mask);

        imshow("hand_segment", hand_segment);
        waitKey(30);
    }

}
复制代码

 

 

  实验总结:

本次实验基本上都是基于OpenNI的一些算法的,其分割也是简单的用深度信息进行,没有什么特别的算法,且分割过程中没有引入色彩信息。后续的工作是从将色彩信息和深度信息结合起来做分割,即达到手部分割邻域自适应选择。

 

 

  附录:实验工程code

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之9(不需要骨骼跟踪的人体手部分割) - tornadomeet - 博客园

mikel阅读(793)

[转载]Kinect+OpenNI学习笔记之9(不需要骨骼跟踪的人体手部分割) – tornadomeet – 博客园.

前言

手势识别非常重要的一个特点是要体验要好,即需要以用户为核心。而手势的定位一般在手势识别过程的前面,在上一篇博文Kinect+OpenNI学习笔记之8(Robert Walter手部提取代码的分析) 中已经介绍过怎样获取手势区域,且取得了不错的效果,但是那个手势部位的提取有一个大的缺点,即需要人站立起来,当站立起来后才能够分隔出手。而手势在人 之间的交流时,并不一定要处于站立状态,所以这不是一个好的HCI。因此本文介绍的手势部位的提取并不需要人处于站立状态,同样取得了不错的效果。

 

实验说明

其实,本实验实现的过程非常简单。首先通过手部的跟踪来获取手所在的坐标,手部跟踪可以参考本人前面的博文:Kinect+OpenNI学习笔记之7(OpenNI自带的类实现手部跟踪)。当定位到手所在的坐标后,因为该坐标是3D的,因此在该坐标领域的3维空间领域内提取出手的部位即可,整个过程的大概流程图如下:

 

OpenCV知识点总结:

调用Mat::copyTo()函数时,如果需要有mask操作,则不管源图像是多少通道的,其mask矩阵都要定义为单通道,另外可以对一个mask矩阵画一个填充的矩形来达到使mask矩阵中对应ROI的位置的值为设定值,这样就不需要去一一扫描赋值了。

在使用OpenCV的Mat矩阵且需要对该矩阵进行扫描时,一定要注意其取值顺序,比如说列和行的顺序,如果弄反了,则经常会报内存错误。

 

 

实验结果

本实验并不要求人的手一定要放在人体的前面,且也不需要人一定是处在比较简单的背景环境中,本实验结果允许人处在复杂的背景环境下,且手可以到处随便移动。当然了,环境差时有时候效果就不太好。

下面是3张实验结果的截图,手势分隔图1:

 

手势分隔图2:

 

手势分隔图3:

 

实验主要部分代码即注释(附录有工程code下载链接):

main.cpp:

复制代码
#include <iostream>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/core/core.hpp>
#include "copenni.cpp"

#include <iostream>

#define DEPTH_SCALE_FACTOR 255./4096.
#define ROI_HAND_WIDTH 140
#define ROI_HAND_HEIGHT 140
#define MEDIAN_BLUR_K 5

int XRES = 640;
int YRES = 480;
#define DEPTH_SEGMENT_THRESH 5

using namespace cv;
using namespace xn;
using namespace std;

int main (int argc, char **argv)
{
    COpenNI openni;
    int hand_depth;
    Rect roi;
    roi.x = XRES/2;
    roi.y = YRES/2;
    roi.width = ROI_HAND_WIDTH;
    roi.height = ROI_HAND_HEIGHT;
    if(!openni.Initial())
        return 1;

    namedWindow("color image", CV_WINDOW_AUTOSIZE);
    namedWindow("depth image", CV_WINDOW_AUTOSIZE);
    namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);//显示分割出来的手的区域

    if(!openni.Start())
        return 1;
    while(1) {
        if(!openni.UpdateData()) {
            return 1;
        }
        /*获取并显示色彩图像*/
        Mat color_image_src(openni.image_metadata.YRes(), openni.image_metadata.XRes(),
                            CV_8UC3, (char *)openni.image_metadata.Data());
        Mat color_image;
        cvtColor(color_image_src, color_image, CV_RGB2BGR);
        circle(color_image, Point(hand_point.X, hand_point.Y), 5, Scalar(255, 0, 0), 3, 8);
        imshow("color image", color_image);

        /*获取并显示深度图像*/
        Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(),
                            CV_16UC1, (char *)openni.depth_metadata.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
        Mat depth_image;
        depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR);
        imshow("depth image", depth_image);

        /*下面的代码是提取手的轮廓部分*/
        hand_depth = hand_point.Z * DEPTH_SCALE_FACTOR;
        roi.x = hand_point.X - ROI_HAND_WIDTH/2;
        roi.y = hand_point.Y - ROI_HAND_HEIGHT/2;
        if(roi.x <= 0)
            roi.x = 0;
        if(roi.x >= XRES)
            roi.x = XRES;
        if(roi.y <=0 )
            roi.y = 0;
        if(roi.y >= YRES)
            roi.y = YRES;

        //取出手的mask部分
        //不管原图像时多少通道的,mask矩阵声明为单通道就ok
        Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0));
        for(int i = roi.x; i < std::min(roi.x+roi.width, XRES); i++)
            for(int j = roi.y; j < std::min(roi.y+roi.height, YRES); j++) {
                hand_segment_mask.at<unsigned char>(j, i) = ((hand_depth-DEPTH_SEGMENT_THRESH) < depth_image.at<unsigned char>(j, i))
                                                            & ((hand_depth+DEPTH_SEGMENT_THRESH) > depth_image.at<unsigned char>(j,i));
            }
        medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K);
        Mat hand_segment(color_image.size(), CV_8UC3);
        color_image.copyTo(hand_segment, hand_segment_mask);

        imshow("hand_segment", hand_segment);
        waitKey(20);
    }

}
复制代码

 

copenni,cpp:

复制代码
#ifndef COPENNI_CLASS
#define COPENNI_CLASS

#include <XnCppWrapper.h>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

static DepthGenerator  depth_generator;
static HandsGenerator  hands_generator;
static XnPoint3D hand_point;
static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;

class COpenNI
{
public:
    ~COpenNI() {
        context.Release();//释放空间
    }
    bool Initial() {
        //初始化
        status = context.Init();
        if(CheckError("Context initial failed!")) {
            return false;
        }
        context.SetGlobalMirror(true);//设置镜像
        xmode.nXRes = 640;
        xmode.nYRes = 480;
        xmode.nFPS = 30;
        //产生颜色node
        status = image_generator.Create(context);
        if(CheckError("Create image generator  error!")) {
            return false;
        }
        //设置颜色图片输出模式
        status = image_generator.SetMapOutputMode(xmode);
        if(CheckError("SetMapOutputMdoe error!")) {
            return false;
        }
        //产生深度node
        status = depth_generator.Create(context);
        if(CheckError("Create depth generator  error!")) {
            return false;
        }
        //设置深度图片输出模式
        status = depth_generator.SetMapOutputMode(xmode);
        if(CheckError("SetMapOutputMdoe error!")) {
            return false;
        }
        //产生手势node
        status = gesture_generator.Create(context);
        if(CheckError("Create gesture generator error!")) {
            return false;
        }
        /*添加手势识别的种类*/
        gesture_generator.AddGesture("Wave", NULL);
        gesture_generator.AddGesture("click", NULL);
        gesture_generator.AddGesture("RaiseHand", NULL);
        gesture_generator.AddGesture("MovingHand", NULL);
        //产生手部的node
        status = hands_generator.Create(context);
        if(CheckError("Create hand generaotr error!")) {
            return false;
        }
        //产生人体node
        status = user_generator.Create(context);
        if(CheckError("Create gesturen generator error!")) {
            return false;
        }
        //视角校正
        status = depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator);
        if(CheckError("Can't set the alternative view point on depth generator!")) {
            return false;
        }
        //设置与手势有关的回调函数
        XnCallbackHandle gesture_cb;
        gesture_generator.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, NULL, gesture_cb);
        //设置于手部有关的回调函数
        XnCallbackHandle hands_cb;
        hands_generator.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, NULL, hands_cb);
        //设置有人进入视野的回调函数
        XnCallbackHandle new_user_handle;
        user_generator.RegisterUserCallbacks(CBNewUser, NULL, NULL, new_user_handle);
        user_generator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//设定使用所有关节(共15个)
        //设置骨骼校正完成的回调函数
        XnCallbackHandle calibration_complete;
        user_generator.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete, NULL, calibration_complete);
        return true;
    }

    bool Start() {
        status = context.StartGeneratingAll();
        if(CheckError("Start generating error!")) {
            return false;
        }
        return true;
    }

    bool UpdateData() {
        status = context.WaitNoneUpdateAll();
        if(CheckError("Update date error!")) {
            return false;
        }
        //获取数据
        image_generator.GetMetaData(image_metadata);
        depth_generator.GetMetaData(depth_metadata);

        return true;
    }
    //得到色彩图像的node
    ImageGenerator& getImageGenerator() {
        return image_generator;
    }
    //得到深度图像的node
    DepthGenerator& getDepthGenerator() {
        return depth_generator;
    }
    //得到人体的node
    UserGenerator& getUserGenerator() {
        return user_generator;
    }
    //得到手势姿势node
    GestureGenerator& getGestureGenerator() {
        return gesture_generator;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;
//    static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;

private:
    //该函数返回真代表出现了错误,返回假代表正确
    bool CheckError(const char* error) {
        if(status != XN_STATUS_OK ) {
            //QMessageBox::critical(NULL, error, xnGetStatusString(status));
            cerr << error << ": " << xnGetStatusString( status ) << endl;
            return true;
        }
        return false;
    }
    //手势某个动作已经完成检测的回调函数
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition,
                                                      const XnPoint3D *pEndPosition, void *pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
    //    openni->hands_generator.StartTracking(*pIDPosition);
        hands_generator.StartTracking(*pIDPosition);
    }
    //手势开始检测的回调函数
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition,
                                                   XnFloat fProgress, void *pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
   //     openni->hands_generator.StartTracking(*pPosition);
        hands_generator.StartTracking(*pPosition);
    }
    //手部开始建立的回调函数
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
        XnPoint3D project_pos;
        depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
 //       openni->hand_point = project_pos;   //返回手部所在点的位置
        hand_point = project_pos;
        pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
        hand_track_point.second.push_back(project_pos);
        hands_track_points.insert(hand_track_point);
    }
    //手部开始更新的回调函数
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie) {
   //     COpenNI *openni = (COpenNI*)pCookie;
        XnPoint3D project_pos;
        depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
     //   openni->hand_point = project_pos;   //返回手部所在点的位置
        hand_point = project_pos;
        hands_track_points.find(xUID)->second.push_back(project_pos);
    }
    //销毁手部的回调函数
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime,
                                              void* pCookie) {
  //      COpenNI *openni = (COpenNI*)pCookie;
        //openni->hand_point.clear();   //返回手部所在点的位置
        hands_track_points.erase(hands_track_points.find(xUID));
    }
    //有人进入视野时的回调函数
    static void XN_CALLBACK_TYPE CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie) {
        //得到skeleton的capability,并调用RequestCalibration函数设置对新检测到的人进行骨骼校正
        generator.GetSkeletonCap().RequestCalibration(user, true);
    }
    //完成骨骼校正的回调函数
    static void XN_CALLBACK_TYPE CBCalibrationComplete(SkeletonCapability &skeleton,
                                                       XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie) {
        if(calibration_error == XN_CALIBRATION_STATUS_OK) {
            skeleton.StartTracking(user);//骨骼校正完成后就开始进行人体跟踪了
        }
        else {
            UserGenerator *p_user = (UserGenerator*)p_cookie;
            skeleton.RequestCalibration(user, true);//骨骼校正失败时重新设置对人体骨骼继续进行校正
        }
    }

private:
    XnStatus    status;
    Context     context;
    ImageGenerator  image_generator;
//    DepthGenerator depth_generator;
    UserGenerator user_generator;
    GestureGenerator gesture_generator;
//    HandsGenerator  hands_generator;
//    map<XnUserID, vector<XnPoint3D>> hands_track_points;
    XnMapOutputMode xmode;

public:
  // static XnPoint3D hand_point;
};

#endif
复制代码

 

 

实验总结:

本次实验简单的利用OpenNI的手部跟踪功能提实时分隔出了人体手所在的部位。但是该分隔效果并不是特别好,以后可以改进手利用色彩信息来分隔出手的区域,或者计算出自适应手部位的区域。另外,本程序只是暂时分隔出一个手,以后可以扩展到分隔出多个手的部位.

 

参考资料:

Kinect+OpenNI学习笔记之8(Robert Walter手部提取代码的分析)

http://dl.dropbox.com/u/5505209/FingertipTuio3d.zip

 

 

附录:实验工程code下载

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之8(Robert Walter手部提取代码的分析) - tornadomeet - 博客园

mikel阅读(745)

[转载]Kinect+OpenNI学习笔记之8(Robert Walter手部提取代码的分析) – tornadomeet – 博客园.

  前言

  一般情况下,手势识别的第一步就是先手势定位,即手势所在部位的提取。本文是基于kinect来提取手势识别的,即 先通过kinect找出人体的轮廓,然后定位轮廓中与手部有关的点,在该点的周围提取出满足一定要求的区域,对该区域进行滤波后得到的区域就是手部了。然 后利用凸包和凹陷的数学几何方法,画出手指和手指凹陷处的点,以及手的轮廓线,并在图像中显示出来。文章所有代码都是网友Robert Walter提供的,它的代码下载网站为:http://dl.dropbox.com/u/5505209/FingertipTuio3d.zip

  本人因为要做这方面的研究,所有本文只是读了他的代码,并稍加分析了下。

  开发环境:OpenNI+OpenCV

 实验说明

  手势定位和提取功能的实现主要是依靠OpenNI和OpenCV的实现,定位部分依靠OpenNI的人体骨架跟踪功能,手部的提取依靠OpenCV中一些与轮廓有关的函数。整个实验的流程图如下:

  

  手部提取时用到的几个OpenCV函数解释如下:

  void convexHull(InputArray points, OutputArray hull, bool clockwise=false, bool returnPoints=true )

  该函数的作用是找到输入点集points的凸包集合,参数1为输入的点集;参数2为是与输出凸包集合有关的,它是一 个向量,如果向量元素的类型为整型,则表示其为凸包集合的点在原始输入集合点的下标索引,如果向量的数据类型为Point型,则表示其为凸包的点集;参数 3表示输出凸包集合的方向,为true时表示顺时针方向输出;参数4表示是否输出凸包的集合中的点坐标,这个只有在参数2的类型为Mat型的时候有效,如 果参数2为vector类型,则根据vector中元素类型来选择输出凸包到底是点的坐标还是原始输入点的索引,也就是说此时的参数4没有作用。

  void convexityDefects(InputArray contour, InputArray convexhull, OutputArray convexityDefects)

  该函数的作用是对输入的轮廓contour,凸包集合来检测其轮廓的凸型缺陷,一个凸型缺陷结构体包括4个元素,缺陷起点坐标,缺陷终点坐标,缺陷中离凸包线距离最远的点的坐标,以及此时最远的距离。参数3即其输出的凸型缺陷结构体向量。

  其凸型缺陷的示意图如下所示:

  

  void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point()

  该函数是找到输入图像image的轮廓,存储在contours中。输入的图像类型必须要求是8位单通道的二值图 像,只要是非0元素都被看成是1,只要是0元素就被看做是0;参数hierarchy存储的是每个轮廓的拓扑信息,参数method表示轮廓提取的模式; 参数4表示轮廓提取的近似方法,即怎么保存轮廓信息;参数5表示轮廓可移动的位移。

  void *memcpy(void *dest, const void *src, size_t n);

  该函数的作用是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中,主要不要把源地址和目的地址的顺序搞反了。

  实验结果

  在进行手部的提取时,因为要先提取出人体全身的骨骼,再定位手的坐标,所以人必须先处于站立状态,一旦人体骨骼提取成功后,可以改为坐立姿态,其手部提取并显示的一张图如下所示:

  

  网友Robert Walter把它的演示视频放在这里: http://www.youtube.com/watch?v=lCuItHQEgEQ&feature=player_embedded

  实验主要部分代码和注释(参考资料中有工程代码下载地址):

main.cpp:

复制代码
#include "SkeletonSensor.h"

// openCV
#include <opencv/highgui.h>
#include <opencv/cv.h>
using namespace cv;

#include <iostream>
using namespace std;

// globals
SkeletonSensor* sensor;

const unsigned int XRES = 640;
const unsigned int YRES = 480;

const float DEPTH_SCALE_FACTOR = 255./4096.;

// defines the value about which thresholding occurs
const unsigned int BIN_THRESH_OFFSET = 5;

// defines the value about witch the region of interest is extracted
const unsigned int ROI_OFFSET = 70;

// median blur factor
const unsigned int MEDIAN_BLUR_K = 5;

// grasping threshold
const double GRASPING_THRESH = 0.9;

// colors
const Scalar COLOR_BLUE        = Scalar(240,40,0);
const Scalar COLOR_DARK_GREEN  = Scalar(0, 128, 0);
const Scalar COLOR_LIGHT_GREEN = Scalar(0,255,0);
const Scalar COLOR_YELLOW      = Scalar(0,128,200);
const Scalar COLOR_RED         = Scalar(0,0,255);

// returns true if the hand is near the sensor area
bool handApproachingDisplayPerimeter(float x, float y)
{
    return (x > (XRES - ROI_OFFSET)) || (x < (ROI_OFFSET)) ||
           (y > (YRES - ROI_OFFSET)) || (y < (ROI_OFFSET));
}

// conversion from cvConvexityDefect
struct ConvexityDefect
{
    Point start;
    Point end;
    Point depth_point;
    float depth;
};

// Thanks to Jose Manuel Cabrera for part of this C++ wrapper function
//Convexity為凸的意思,Defect為缺陷的意思,hull為殼的意思
//貌似這個函數在opencv中已經被實現了
void findConvexityDefects(vector<Point>& contour, vector<int>& hull, vector<ConvexityDefect>& convexDefects)
{
    if(hull.size() > 0 && contour.size() > 0)
    {
        CvSeq* contourPoints;
        CvSeq* defects;
        CvMemStorage* storage;
        CvMemStorage* strDefects;
        CvMemStorage* contourStr;
        CvConvexityDefect *defectArray = 0;

        strDefects = cvCreateMemStorage();
        defects = cvCreateSeq( CV_SEQ_KIND_GENERIC|CV_32SC2, sizeof(CvSeq),sizeof(CvPoint), strDefects );

        //We transform our vector<Point> into a CvSeq* object of CvPoint.
        contourStr = cvCreateMemStorage();
        contourPoints = cvCreateSeq(CV_SEQ_KIND_GENERIC|CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), contourStr);
        for(int i = 0; i < (int)contour.size(); i++) {
            CvPoint cp = {contour[i].x,  contour[i].y};
            cvSeqPush(contourPoints, &cp);
        }

        //Now, we do the same thing with the hull index
        int count = (int) hull.size();
        //int hullK[count];
        int* hullK = (int*) malloc(count*sizeof(int));
        for(int i = 0; i < count; i++) { hullK[i] = hull.at(i); }
        CvMat hullMat = cvMat(1, count, CV_32SC1, hullK);

        // calculate convexity defects
        storage = cvCreateMemStorage(0);
        defects = cvConvexityDefects(contourPoints, &hullMat, storage);
        defectArray = (CvConvexityDefect*)malloc(sizeof(CvConvexityDefect)*defects->total);
        cvCvtSeqToArray(defects, defectArray, CV_WHOLE_SEQ);
        //printf("DefectArray %i %i\n",defectArray->end->x, defectArray->end->y);

        //We store defects points in the convexDefects parameter.
        for(int i = 0; i<defects->total; i++){
            ConvexityDefect def;
            def.start       = Point(defectArray[i].start->x, defectArray[i].start->y);
            def.end         = Point(defectArray[i].end->x, defectArray[i].end->y);
            def.depth_point = Point(defectArray[i].depth_point->x, defectArray[i].depth_point->y);
            def.depth       = defectArray[i].depth;
            convexDefects.push_back(def);
        }

    // release memory
    cvReleaseMemStorage(&contourStr);
    cvReleaseMemStorage(&strDefects);
    cvReleaseMemStorage(&storage);

    }
}

int main(int argc, char** argv)
{

    // initialize the kinect
    sensor = new SkeletonSensor();
    sensor->initialize();
    sensor->setPointModeToProjective();

    Mat depthRaw(YRES, XRES, CV_16UC1);
    Mat depthShow(YRES, XRES, CV_8UC1);
    Mat handDebug;

    // this vector holds the displayed images of the hands
    vector<Mat> debugFrames;

    // rectangle used to extract hand regions from depth map
    Rect roi;
    roi.width  = ROI_OFFSET*2;
    roi.height = ROI_OFFSET*2;

    namedWindow("depthFrame", CV_WINDOW_AUTOSIZE);
    namedWindow("leftHandFrame", CV_WINDOW_AUTOSIZE);
    namedWindow("rightHandFrame", CV_WINDOW_AUTOSIZE);

    int key = 0;
    while(key != 27 && key != 'q')
    {

        sensor->waitForDeviceUpdateOnUser();

        // update 16 bit depth matrix
        //參數3後面乘以2是因為kinect獲得的深度數據是1個像素2字節的
        memcpy(depthRaw.data, sensor->getDepthData(), XRES*YRES*2);
        //轉換成8位深度圖
        depthRaw.convertTo(depthShow, CV_8U, DEPTH_SCALE_FACTOR);

        for(int handI = 0; handI < 2; handI++)
        {

            int handDepth;
            if(sensor->getNumTrackedUsers() > 0)
            {
                //Skeleton是包含15個人體骨骼節點的結構體
                Skeleton skel =  sensor->getSkeleton(sensor->getUID(0));
                //struct SkeletonPoint
                //{
                //   float x, y, z, confidence;
                //};
                SkeletonPoint hand;

                if( handI == 0)
                    hand = skel.leftHand;//hand中保存左手點的座標
                else
                    hand = skel.rightHand;//hand中保存有手點的座標
                if(hand.confidence == 1.0)//手部的置信度为1
                {
                    handDepth = hand.z * (DEPTH_SCALE_FACTOR);//轉換為8bit后的深度值
                    //handApproachingDisplayPerimeter返回為真是說明,手的位置已經越界
                    if(!handApproachingDisplayPerimeter(hand.x, hand.y))
                    {
                        roi.x = hand.x - ROI_OFFSET;    //截取出感興趣的區域roi,其區域大小由經驗值來設定
                        roi.y = hand.y - ROI_OFFSET;
                    }
                }
            }
            else
                handDepth = -1;

            // extract hand from image
            Mat handCpy(depthShow, roi);    //handCpy只是與depthShow共用數據內存而已
            Mat handMat = handCpy.clone();    //真正的擁有自己的內存區域

            // binary threshold
            if(handDepth != -1)
                //BIN_THRESH_OFFSET == 5;
                //手部的閾值化,檢測到手部后,根據手部前後的深度信息來提取手的輪廓,此時,handMat就是0和1的矩陣了
                handMat = (handMat > (handDepth - BIN_THRESH_OFFSET)) & (handMat < (handDepth + BIN_THRESH_OFFSET));

            // last pre-filtering step, apply median blur
            medianBlur(handMat, handMat, MEDIAN_BLUR_K);

            // create debug image of thresholded hand and cvt to RGB so hints show in color
            handDebug = handMat.clone();
            debugFrames.push_back(handDebug);
            //CV_GRAY2RGB表示3个通道的值是一样的
            cvtColor(debugFrames[handI], debugFrames[handI], CV_GRAY2RGB);

            std::vector< std::vector<Point> > contours;
            //提取全部轮廓信息到contours,轮廓信息采用水平,垂直对角线的末断点存储。
            findContours(handMat, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            //下面就是画多边形和点
            if (contours.size()) {
                for (int i = 0; i < contours.size(); i++) {
                    vector<Point> contour = contours[i];
                    //将vector转换成Mat型,此时的Mat还是列向量,只不过是2个通道的列向量而已
                    Mat contourMat = Mat(contour);
                    //返回轮廓的面积
                    double cArea = contourArea(contourMat);

                    if(cArea > 2000) // likely the hand
                    {
                        //找到轮廓的中心点
                        Scalar center = mean(contourMat);
                        Point centerPoint = Point(center.val[0], center.val[1]);

                        // approximate the contour by a simple curve
                        vector<Point> approxCurve;
                        //求出轮廓的封闭的曲线,保存在approxCurve,轮廓和封闭曲线直接的最大距离为10
                        approxPolyDP(contourMat, approxCurve, 10, true);

                        vector< vector<Point> > debugContourV;
                        debugContourV.push_back(approxCurve);
                        //在参数1中画出轮廓参数2,参数2必须是轮廓的集合,所以参数2是
                        //vector< vector<Point> >类型
                        //深绿色代表近似多边形
                        drawContours(debugFrames[handI], debugContourV, 0, COLOR_DARK_GREEN, 3);

                        vector<int> hull;
                        //找出近似曲线的凸包集合,集合hull中存储的是轮廓中凸包点的下标
                        convexHull(Mat(approxCurve), hull, false, false);

                        // draw the hull points
                        for(int j = 0; j < hull.size(); j++)
                        {
                            int index = hull[j];
                            //凸顶点用黄色表示
                            circle(debugFrames[handI], approxCurve[index], 3, COLOR_YELLOW, 2);
                        }

                        // find convexity defects
                        vector<ConvexityDefect> convexDefects;
                        findConvexityDefects(approxCurve, hull, convexDefects);
                        printf("Number of defects: %d.\n", (int) convexDefects.size());

                        for(int j = 0; j < convexDefects.size(); j++)
                        {
                            //缺陷点用蓝色表示
                            circle(debugFrames[handI], convexDefects[j].depth_point, 3, COLOR_BLUE, 2);

                        }

                        // assemble point set of convex hull
                        //将凸包集以点的坐标形式保存下来
                        vector<Point> hullPoints;
                        for(int k = 0; k < hull.size(); k++)
                        {
                            int curveIndex = hull[k];
                            Point p = approxCurve[curveIndex];
                            hullPoints.push_back(p);
                        }

                        // area of hull and curve
                        double hullArea  = contourArea(Mat(hullPoints));
                        double curveArea = contourArea(Mat(approxCurve));
                        double handRatio = curveArea/hullArea;

                        // hand is grasping
                        //GRASPING_THRESH == 0.9
                        if(handRatio > GRASPING_THRESH)
                            //握拳表示绿色
                            circle(debugFrames[handI], centerPoint, 5, COLOR_LIGHT_GREEN, 5);
                        else
                            //一般情况下手张开其中心点是显示红色
                            circle(debugFrames[handI], centerPoint, 5, COLOR_RED, 5);
                    }
                } // contour conditional
            } // hands loop
        }

        imshow("depthFrame", depthShow);

        //debugFrames只保存2帧图像
        if(debugFrames.size() >= 2 )
        {
            //长和宽的尺寸都扩大3倍
            resize(debugFrames[0], debugFrames[0], Size(), 3, 3);
            resize(debugFrames[1], debugFrames[1], Size(), 3, 3);
            imshow("leftHandFrame",  debugFrames[0]);
            imshow("rightHandFrame",  debugFrames[1]);
            debugFrames.clear();
        }

        key = waitKey(10);

    }

    delete sensor;

    return 0;
}
复制代码

 

skeletonSensor.h:

复制代码
#ifndef SKELETON_SENSOR_H
#define SKELETON_SENSOR_H

#include <XnCppWrapper.h>
#include <vector>

// A 3D point with the confidence of the point's location. confidence_ > 0.5 is good
struct SkeletonPoint
{
    float x, y, z, confidence;
};

struct Skeleton
{
    SkeletonPoint head;
    SkeletonPoint neck;
    SkeletonPoint rightShoulder;
    SkeletonPoint leftShoulder;
    SkeletonPoint rightElbow;
    SkeletonPoint leftElbow;
    SkeletonPoint rightHand;
    SkeletonPoint leftHand;
    SkeletonPoint rightHip;
    SkeletonPoint leftHip;
    SkeletonPoint rightKnee;
    SkeletonPoint leftKnee;
    SkeletonPoint rightFoot;
    SkeletonPoint leftFoot;
    SkeletonPoint torso;

};

// SkeletonSensor: A wrapper for OpenNI Skeleton tracking devices
//
// Requires the OpenNI + NITE framework installation and the device driver
// Tracks users within the device FOV, and assists in collection of user joints data
class SkeletonSensor
{
    public:
        SkeletonSensor();
        ~SkeletonSensor();

        // set up the device resolution and data generators
        int initialize();

        // non-blocking wait for new data on the device
        void waitForDeviceUpdateOnUser();

        // update vector of tracked users
        void updateTrackedUsers();

        // return true if UID is among the tracked users
        bool isTracking(const unsigned int uid);

        // returns skeleton of specified user
        Skeleton getSkeleton(const unsigned int uid);

        // returns vector of skeletons for all users
        std::vector<Skeleton> getSkeletons();

        // get number of tracked users
        unsigned int getNumTrackedUsers();

        // map tracked user index to UID
        unsigned int getUID(const unsigned int index);

        // change point mode
        void setPointModeToProjective();
        void setPointModeToReal();

        // get depth and image data
        const XnDepthPixel* getDepthData();
        const XnDepthPixel* getWritableDepthData(){};
        const XnUInt8* getImageData();
        const XnLabel*     getLabels();

    private:
        xn::Context context_;
        xn::DepthGenerator depthG_;
        xn::UserGenerator userG_;
        xn::ImageGenerator imageG_;

        std::vector<unsigned int> trackedUsers_;

        // current list of hands
        //std::list<XnPoint3D> handCursors;

        bool pointModeProjective_;

        // on user detection and calibration, call specified functions
        int setCalibrationPoseCallbacks();

        // joint to point conversion, considers point mode
        void convertXnJointsToPoints(XnSkeletonJointPosition* const j, SkeletonPoint* const p, unsigned int numPoints);

        // callback functions for user and skeleton calibration events
        static void XN_CALLBACK_TYPE newUserCallback(xn::UserGenerator& generator, XnUserID nId, void* pCookie);
        static void XN_CALLBACK_TYPE lostUserCallback(xn::UserGenerator& generator, XnUserID nId, void* pCookie);
        static void XN_CALLBACK_TYPE calibrationStartCallback(xn::SkeletonCapability& capability, XnUserID nId, void* pCookie);
        static void XN_CALLBACK_TYPE calibrationCompleteCallback(xn::SkeletonCapability& capability, XnUserID nId, XnCalibrationStatus eStatus, void* pCookie);
        static void XN_CALLBACK_TYPE poseDetectedCallback(xn::PoseDetectionCapability& capability, const XnChar* strPose, XnUserID nId, void* pCookie);
};

#endif
复制代码

 

skeletonSensor.cpp:

复制代码
#include "SkeletonSensor.h"
#include "log.h"

inline int CHECK_RC(const unsigned int rc, const char* const description)
{
    if(rc != XN_STATUS_OK)
    {
        put_flog(LOG_ERROR, "%s failed: %s", description, xnGetStatusString(rc));
        return -1;
    }

    return 0;
}

SkeletonSensor::SkeletonSensor()
{
    pointModeProjective_ = false;
}

SkeletonSensor::~SkeletonSensor()
{
    context_.Shutdown();
}

int SkeletonSensor::initialize()
{
    context_.Init();

    XnStatus rc = XN_STATUS_OK;

    // create depth and user generators
    rc = depthG_.Create(context_);

    if(CHECK_RC(rc, "Create depth generator") == -1)
        return -1;

    rc = userG_.Create(context_);

    if(CHECK_RC(rc, "Create user generator") == -1)
        return -1;

    rc = imageG_.Create(context_);

     if(CHECK_RC(rc, "Create image generator") == -1)
        return -1;

    XnMapOutputMode mapMode;
    depthG_.GetMapOutputMode(mapMode);

    // for now, make output map VGA resolution at 30 FPS
    mapMode.nXRes = XN_VGA_X_RES;
    mapMode.nYRes = XN_VGA_Y_RES;
    mapMode.nFPS  = 30;

    depthG_.SetMapOutputMode(mapMode);
    imageG_.SetMapOutputMode(mapMode);

    // turn on device mirroring
    if(depthG_.IsCapabilitySupported("Mirror") == true)
    {
        rc = depthG_.GetMirrorCap().SetMirror(true);
        CHECK_RC(rc, "Setting Image Mirroring on depthG");
    }

    // turn on device mirroring
    if(imageG_.IsCapabilitySupported("Mirror") == true)
    {
        rc = imageG_.GetMirrorCap().SetMirror(true);
        CHECK_RC(rc, "Setting Image Mirroring on imageG");
    }

    // make sure the user points are reported from the POV of the depth generator
    userG_.GetAlternativeViewPointCap().SetViewPoint(depthG_);
    depthG_.GetAlternativeViewPointCap().SetViewPoint(imageG_);

    // set smoothing factor
    userG_.GetSkeletonCap().SetSmoothing(0.9);

    // start data streams
    context_.StartGeneratingAll();

    // setup callbacks
    setCalibrationPoseCallbacks();

    return 0;
}

void SkeletonSensor::waitForDeviceUpdateOnUser()
{
    context_.WaitOneUpdateAll(userG_);
    updateTrackedUsers();
}

void SkeletonSensor::updateTrackedUsers()
{
    XnUserID users[64];
    XnUInt16 nUsers = userG_.GetNumberOfUsers();

    trackedUsers_.clear();

    userG_.GetUsers(users, nUsers);

    for(int i = 0; i < nUsers; i++)
    {
        if(userG_.GetSkeletonCap().IsTracking(users[i]))
        {
            trackedUsers_.push_back(users[i]);
        }
    }
}

bool SkeletonSensor::isTracking(const unsigned int uid)
{
    return userG_.GetSkeletonCap().IsTracking(uid);
}

Skeleton SkeletonSensor::getSkeleton(const unsigned int uid)
{
    Skeleton result;

    // not tracking user
    if(!userG_.GetSkeletonCap().IsTracking(uid))
        return result;

    // Array of available joints
    const unsigned int nJoints = 15;
    XnSkeletonJoint joints[nJoints] = 
    {   XN_SKEL_HEAD,
        XN_SKEL_NECK,
        XN_SKEL_RIGHT_SHOULDER,
        XN_SKEL_LEFT_SHOULDER,
        XN_SKEL_RIGHT_ELBOW,
        XN_SKEL_LEFT_ELBOW,
        XN_SKEL_RIGHT_HAND,
        XN_SKEL_LEFT_HAND,
        XN_SKEL_RIGHT_HIP,
        XN_SKEL_LEFT_HIP,
        XN_SKEL_RIGHT_KNEE,
        XN_SKEL_LEFT_KNEE,
        XN_SKEL_RIGHT_FOOT,
        XN_SKEL_LEFT_FOOT,
        XN_SKEL_TORSO 
    };

    // holds the joint position components
    XnSkeletonJointPosition positions[nJoints];

    for (unsigned int i = 0; i < nJoints; i++)
    {
        userG_.GetSkeletonCap().GetSkeletonJointPosition(uid, joints[i], *(positions+i));
    }

    SkeletonPoint points[15];
    convertXnJointsToPoints(positions, points, nJoints);

    result.head              = points[0];
    result.neck              = points[1];
    result.rightShoulder     = points[2];
    result.leftShoulder      = points[3];
    result.rightElbow        = points[4];
    result.leftElbow         = points[5];
    result.rightHand         = points[6];
    result.leftHand          = points[7];
    result.rightHip          = points[8];
    result.leftHip           = points[9];
    result.rightKnee         = points[10];
    result.leftKnee          = points[11];
    result.rightFoot         = points[12];
    result.leftFoot          = points[13];
    result.torso             = points[14];

    return result;
}

std::vector<Skeleton> SkeletonSensor::getSkeletons()
{
    std::vector<Skeleton> skeletons;

    for(unsigned int i = 0; i < getNumTrackedUsers(); i++)
    {
        Skeleton s = getSkeleton(trackedUsers_[i]);
        skeletons.push_back(s);
    }

    return skeletons;
}

unsigned int SkeletonSensor::getNumTrackedUsers()
{
    return trackedUsers_.size();
}

unsigned int SkeletonSensor::getUID(const unsigned int index)
{
    return trackedUsers_[index];
}

void SkeletonSensor::setPointModeToProjective()
{
    pointModeProjective_ = true;
}

void SkeletonSensor::setPointModeToReal()
{
    pointModeProjective_ = false;
}

const XnDepthPixel* SkeletonSensor::getDepthData()
{
    return depthG_.GetDepthMap();
}

const XnUInt8* SkeletonSensor::getImageData()
{
    return imageG_.GetImageMap();
}

const XnLabel* SkeletonSensor::getLabels()
{
    xn::SceneMetaData sceneMD;

    userG_.GetUserPixels(0, sceneMD);

    return sceneMD.Data();
}

int SkeletonSensor::setCalibrationPoseCallbacks()
{
    XnCallbackHandle hUserCallbacks, hCalibrationStart, hCalibrationComplete;

    userG_.RegisterUserCallbacks(newUserCallback, lostUserCallback, this, hUserCallbacks);
    userG_.GetSkeletonCap().RegisterToCalibrationStart(calibrationStartCallback, this, hCalibrationStart);
    userG_.GetSkeletonCap().RegisterToCalibrationComplete(calibrationCompleteCallback, this, hCalibrationComplete);

    // turn on tracking of all joints
    userG_.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);

    return 0;
}

void SkeletonSensor::convertXnJointsToPoints(XnSkeletonJointPosition* const joints, SkeletonPoint* const points, unsigned int numPoints)
{
    XnPoint3D xpt;

    for(unsigned int i = 0; i < numPoints; i++)
    {
        xpt = joints[i].position;

        if(pointModeProjective_)
            depthG_.ConvertRealWorldToProjective(1, &xpt, &xpt);

        points[i].confidence = joints[i].fConfidence;
        points[i].x = xpt.X;
        points[i].y = xpt.Y;
        points[i].z = xpt.Z;
    }
}

void XN_CALLBACK_TYPE SkeletonSensor::newUserCallback(xn::UserGenerator& generator, XnUserID nId, void* pCookie)
{
    put_flog(LOG_DEBUG, "New user %d, auto-calibrating", nId);

    SkeletonSensor* sensor = (SkeletonSensor*) pCookie;
    sensor->userG_.GetSkeletonCap().RequestCalibration(nId, true);
}

void XN_CALLBACK_TYPE SkeletonSensor::lostUserCallback(xn::UserGenerator& generator, XnUserID nId, void* pCookie)
{
    put_flog(LOG_DEBUG, "Lost user %d", nId);
}

void XN_CALLBACK_TYPE SkeletonSensor::calibrationStartCallback(xn::SkeletonCapability& capability, XnUserID nId, void* pCookie)
{
    put_flog(LOG_DEBUG, "Calibration started for user %d", nId);
}

void XN_CALLBACK_TYPE SkeletonSensor::calibrationCompleteCallback(xn::SkeletonCapability& capability, XnUserID nId, XnCalibrationStatus eStatus, void* pCookie)
{
    SkeletonSensor* sensor = (SkeletonSensor*) pCookie;

    if(eStatus == XN_CALIBRATION_STATUS_OK)
    {
        put_flog(LOG_DEBUG, "Calibration completed: start tracking user %d", nId);

        sensor->userG_.GetSkeletonCap().StartTracking(nId);
    }
    else
    {
        put_flog(LOG_DEBUG, "Calibration failed for user %d", nId);

        sensor->userG_.GetSkeletonCap().RequestCalibration(nId, true);
    }
}

void XN_CALLBACK_TYPE SkeletonSensor::poseDetectedCallback(xn::PoseDetectionCapability& capability, const XnChar* strPose, XnUserID nId, void* pCookie)
{
    put_flog(LOG_DEBUG, "Pose detected for user %d", nId);

    SkeletonSensor* sensor = (SkeletonSensor*) pCookie;

    sensor->userG_.GetPoseDetectionCap().StopPoseDetection(nId);
    sensor->userG_.GetSkeletonCap().RequestCalibration(nId, true);
}
复制代码

 

   实验总结:通过本次实验,可以学会初步结合OpenCV和OpenNI来简单的提取手部所在的区域。

  参考资料:

     heresy博文:http://kheresy.wordpress.com/2012/08/23/hand-processing-with-openni/

     Robert Walter工程代码下载:http://dl.dropbox.com/u/5505209/FingertipTuio3d.zip

   附录:

  听网友说本文提供的工程代码下载地址失效了,故现在提供本人以前测试的工程:http://download.csdn.net/detail/wuweigreat/5101183

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。

[转载]Kinect+OpenNI学习笔记之7(OpenNI自带的类实现手部跟踪) - tornadomeet - 博客园

mikel阅读(747)

[转载]Kinect+OpenNI学习笔记之7(OpenNI自带的类实现手部跟踪) – tornadomeet – 博客园.

前言

本文主要介绍使用OpenNI中的HandsGenerator来完成对人体手部的跟踪,在前面的文章Kinect+OpenNI学习笔记之5(使用OpenNI自带的类进行简单手势识别)中 已经介绍过使用GestureGenerator这个类来完成对几个简单手势的识别,这次介绍的手部跟踪是在上面简单手势识别的结果上开始跟踪的,这是 OpenNI的优点,微软的SDK据说是不能单独对手部进行跟踪,因为使用MS的SDK需要检测站立人体的骨骼,然后找出节点再进行跟踪,不懂最新版本的 是否支持这个功能。而此节讲的OpenNI完成手部的跟踪就不要求人必须处于站立姿势。

开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2

 

实验说明

本次实验是分为2个类来设计的。COpenNI和CKinectReader这2个类。COpenNI类负责完成Kinect的OpenNI驱 动,而CKinectReader类负责将kinect读取的信息在Qt中显示出来,且使用定时器定时刷新,此过程中可以在图像中画内容。

进行手势的整体流程大概如下:

有关手势识别和跟踪的回调函数的设置在COpenNI这个类中进行,但是因为回调函数是static类型,所以对应函数里面的变量也必须是 static类型,但是我们的变量初始化又放在了类中进行,而static类型的变量不能在类中进行初始化,因此最好将回调函数用到的几个static类 型的变量直接放在了类外,这样虽然达到了效果,不过貌似不是一个完整的类的设计。暂时没找到好的解决方法。

从官方文档来看,OpenNI中进行手部跟踪,即采用节点hand generator来跟踪需要搭配手势检测的节点gesture generator,其代码实现流程如下:

先使用gesture generator来侦测特定的手势

当检测到特定的手势后开始进行handsgenerator的starttracking()函数来进行跟踪手部。

当hands generator开始跟踪手部位置时,HandCreate()函数被调用。

以后每当有变化的时候,都会执行HandUpdate()函数。

如果手势超出了可侦测的范围,则其回自动调用HandDestroy()函数。

 

C/c++知识点总结:

如果一个数据类型声明为auto了,那么说明该数据类型为local局部变量,一般auot关键字可以省略。

map表示的是一个键值对,其中第一个参数为键值对的类型id,这个具有唯一性,第二个是该数据类型的对应值。map的cbegin()方法表示的是返回一个常量迭代器。

array数据类型其实就是一个数组类型,定义它的时为array<int, n>表示,其长度为n,数组中的元素数据类型为int型。

static函数有点类似回调函数,一般是用来记录类对象被引用的次数或者这个函数的地址需要被外部代码调用。静态函数有2个好处,一是只能被其自己的文件使用,不能被其它的文件使用。二是其它文件可以定义相同名字的函数,不会发生冲突。

如果是在类中使用静态函数,则它是为类服务的而不是为了某一个类的具体对象服务。普通的成员函数都隐含了一个this指针,因为普通成员函数总 是与具体的某个类的具体对象的。但静态成员函数由于不是与任何对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的某个非静态 数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。

  由于在本程序中,需要用到回调函数,而回调函数在类中一般需要声明成静态函数,所以在回调函数中调用类的成员变量时 这些变量不能够是非静态的成员变量,编程时一定要注意。比如说在回调函数中有代码 hands_generator.StartTracking(*pIDPosition);其中hands_generaotr是普通私有变量,这时编 译代码时会出现如下错误提示:

  

  另外类中的静态成员变量是属于类的,不是属于对象的,因此在定义对象的时候不能够对其进行初始化,也就是说不能够用构造函数来初始化它,如果在类外来初始化它,应该加上在变量前加上类名,而不是变量名。

  Qt知识点总结:

  如果需要用QPainter来绘图的话,则需要将绘图部分的代码放在begin()和end()方法中,外,用QPainter 来创建一个新的绘图类时,其内部已经隐含了具有begin()方法。

  实验结果:

  这是实验中截图的一张结果,该实验可以同时跟踪多个手部,每一个使用不同的颜色来显示其轨迹,当识别到手部后,可以使用手指在空中写字。

  

  实验主要部分代码及注释(附录有实验工程code下载地址):

  copenni.cpp:

复制代码
#ifndef COPENNI_CLASS
#define COPENNI_CLASS

#include <XnCppWrapper.h>
#include <QtGui/QtGui>
#include <iostream>
#include <map>

using namespace xn;
using namespace std;

static DepthGenerator  depth_generator;
static HandsGenerator  hands_generator;
static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;

class COpenNI
{
public:
    ~COpenNI() {
        context.Release();//释放空间
    }
    bool Initial() {
        //初始化
        status = context.Init();
        if(CheckError("Context initial failed!")) {
            return false;
        }
        context.SetGlobalMirror(true);//设置镜像
        xmode.nXRes = 640;
        xmode.nYRes = 480;
        xmode.nFPS = 30;
        //产生颜色node
        status = image_generator.Create(context);
        if(CheckError("Create image generator  error!")) {
            return false;
        }
        //设置颜色图片输出模式
        status = image_generator.SetMapOutputMode(xmode);
        if(CheckError("SetMapOutputMdoe error!")) {
            return false;
        }
        //产生深度node
        status = depth_generator.Create(context);
        if(CheckError("Create depth generator  error!")) {
            return false;
        }
        //设置深度图片输出模式
        status = depth_generator.SetMapOutputMode(xmode);
        if(CheckError("SetMapOutputMdoe error!")) {
            return false;
        }
        //产生手势node
        status = gesture_generator.Create(context);
        if(CheckError("Create gesture generator error!")) {
            return false;
        }
        /*添加手势识别的种类*/
        gesture_generator.AddGesture("Wave", NULL);
        gesture_generator.AddGesture("click", NULL);
        gesture_generator.AddGesture("RaiseHand", NULL);
        gesture_generator.AddGesture("MovingHand", NULL);
        //产生手部的node
        status = hands_generator.Create(context);
        if(CheckError("Create hand generaotr error!")) {
            return false;
        }
        //产生人体node
        status = user_generator.Create(context);
        if(CheckError("Create gesturen generator error!")) {
            return false;
        }
        //视角校正
        status = depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator);
        if(CheckError("Can't set the alternative view point on depth generator!")) {
            return false;
        }
        //设置与手势有关的回调函数
        XnCallbackHandle gesture_cb;
        gesture_generator.RegisterGestureCallbacks(CBGestureRecognized, CBGestureProgress, NULL, gesture_cb);
        //设置于手部有关的回调函数
        XnCallbackHandle hands_cb;
        hands_generator.RegisterHandCallbacks(HandCreate, HandUpdate, HandDestroy, NULL, hands_cb);
        //设置有人进入视野的回调函数
        XnCallbackHandle new_user_handle;
        user_generator.RegisterUserCallbacks(CBNewUser, NULL, NULL, new_user_handle);
        user_generator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//设定使用所有关节(共15个)
        //设置骨骼校正完成的回调函数
        XnCallbackHandle calibration_complete;
        user_generator.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete, NULL, calibration_complete);
        return true;
    }

    bool Start() {
        status = context.StartGeneratingAll();
        if(CheckError("Start generating error!")) {
            return false;
        }
        return true;
    }

    bool UpdateData() {
        status = context.WaitNoneUpdateAll();
        if(CheckError("Update date error!")) {
            return false;
        }
        //获取数据
        image_generator.GetMetaData(image_metadata);
        depth_generator.GetMetaData(depth_metadata);

        return true;
    }
    //得到色彩图像的node
    ImageGenerator& getImageGenerator() {
        return image_generator;
    }
    //得到深度图像的node
    DepthGenerator& getDepthGenerator() {
        return depth_generator;
    }
    //得到人体的node
    UserGenerator& getUserGenerator() {
        return user_generator;
    }
    //得到手势姿势node
    GestureGenerator& getGestureGenerator() {
        return gesture_generator;
    }

public:
    DepthMetaData depth_metadata;
    ImageMetaData image_metadata;
//    static std::map<XnUserID, vector<XnPoint3D>> hands_track_points;

private:
    //该函数返回真代表出现了错误,返回假代表正确
    bool CheckError(const char* error) {
        if(status != XN_STATUS_OK ) {
            QMessageBox::critical(NULL, error, xnGetStatusString(status));
            cerr << error << ": " << xnGetStatusString( status ) << endl;
            return true;
        }
        return false;
    }
    //手势某个动作已经完成检测的回调函数
    static void XN_CALLBACK_TYPE  CBGestureRecognized(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition,
                                                      const XnPoint3D *pEndPosition, void *pCookie) {
        hands_generator.StartTracking(*pIDPosition);
    }
    //手势开始检测的回调函数
    static void XN_CALLBACK_TYPE CBGestureProgress(xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition,
                                                   XnFloat fProgress, void *pCookie) {
        hands_generator.StartTracking(*pPosition);
    }
    //手部开始建立的回调函数
    static void XN_CALLBACK_TYPE HandCreate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition,
                                            XnFloat fTime, void* pCookie) {
        XnPoint3D project_pos;
        depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
        pair<XnUserID, vector<XnPoint3D>> hand_track_point(xUID, vector<XnPoint3D>());
        hand_track_point.second.push_back(project_pos);
        hands_track_points.insert(hand_track_point);
    }
    //手部开始更新的回调函数
    static void XN_CALLBACK_TYPE HandUpdate(HandsGenerator& rHands, XnUserID xUID, const XnPoint3D* pPosition, XnFloat fTime,
                                            void* pCookie) {
        XnPoint3D project_pos;
        depth_generator.ConvertRealWorldToProjective(1, pPosition, &project_pos);
        hands_track_points.find(xUID)->second.push_back(project_pos);
    }
    //销毁手部的回调函数
    static void XN_CALLBACK_TYPE HandDestroy(HandsGenerator& rHands, XnUserID xUID, XnFloat fTime,
                                              void* pCookie) {
        hands_track_points.erase(hands_track_points.find(xUID));
    }
    //有人进入视野时的回调函数
    static void XN_CALLBACK_TYPE CBNewUser(UserGenerator &generator, XnUserID user, void *p_cookie) {
        //得到skeleton的capability,并调用RequestCalibration函数设置对新检测到的人进行骨骼校正
        generator.GetSkeletonCap().RequestCalibration(user, true);
    }
    //完成骨骼校正的回调函数
    static void XN_CALLBACK_TYPE CBCalibrationComplete(SkeletonCapability &skeleton,
                                                       XnUserID user, XnCalibrationStatus calibration_error, void *p_cookie) {
        if(calibration_error == XN_CALIBRATION_STATUS_OK) {
            skeleton.StartTracking(user);//骨骼校正完成后就开始进行人体跟踪了
        }
        else {
            UserGenerator *p_user = (UserGenerator*)p_cookie;
            skeleton.RequestCalibration(user, true);//骨骼校正失败时重新设置对人体骨骼继续进行校正
        }
    }

private:
    XnStatus    status;
    Context     context;
    ImageGenerator  image_generator;
    UserGenerator user_generator;
    GestureGenerator gesture_generator;
    XnMapOutputMode xmode;
};

#endif
复制代码

 

  ckinectreader.cpp:

复制代码
#include <QtGui>
#include <QDebug>
#include <XnCppWrapper.h>
#include "copenni.cpp"  //要包含cpp文件,不能直接包含类
#include <iostream>
#include <QPen>
#include <array>

using namespace std;

class CKinectReader: public QObject
{
public:
    //构造函数,用构造函数中的变量给类的私有成员赋值
    CKinectReader(COpenNI &openni, QGraphicsScene &scene) : openni(openni), scene(scene) {
        test = 0.0;
        {   //设置画笔的颜色
            pen_array[0].setWidth(3);
            pen_array[1].setColor(QColor::fromRgb( 255, 0, 0, 128 ));

            pen_array[1].setWidth( 3 );
            pen_array[1].setColor( QColor::fromRgb( 0, 255, 0, 128 ) );

            pen_array[2].setWidth( 3 );
            pen_array[2].setColor( QColor::fromRgb( 0, 0, 255, 128 ) );

            pen_array[3].setWidth( 3 );
            pen_array[3].setColor( QColor::fromRgb( 255, 0, 255, 128 ) );

            pen_array[4].setWidth( 3 );
            pen_array[4].setColor( QColor::fromRgb( 255, 255, 0, 128 ) );

            pen_array[5].setWidth( 3 );
            pen_array[5].setColor( QColor::fromRgb( 0, 255, 255, 128 ) );
        }
    }
    ~CKinectReader() {
        scene.removeItem(image_item);
        scene.removeItem(depth_item);
        delete [] p_depth_argb;
    }
    bool Start(int interval = 33) {
        openni.Start();//因为在调用CKinectReader这个类的之前会初始化好的,所以这里直接调用Start了
        image_item = scene.addPixmap(QPixmap());
        image_item->setZValue(1);
        depth_item = scene.addPixmap(QPixmap());
        depth_item->setZValue(2);
        openni.UpdateData();
        p_depth_argb = new uchar[4*openni.depth_metadata.XRes()*openni.depth_metadata.YRes()];
        startTimer(interval);//这里是继承QObject类,因此可以调用该函数
        return true;
    }
    float test ;
private:
    COpenNI &openni;    //定义引用同时没有初始化,因为在构造函数的时候用冒号来初始化
    QGraphicsScene &scene;
    QGraphicsPixmapItem *image_item;
    QGraphicsPixmapItem *depth_item;
    uchar *p_depth_argb;
    array<QPen, 6> pen_array;

private:
    void timerEvent(QTimerEvent *) {

        openni.UpdateData();
        //这里使用const,是因为右边的函数返回的值就是const类型的
        const XnDepthPixel *p_depth_pixpel = openni.depth_metadata.Data();
        unsigned int size = openni.depth_metadata.XRes()*openni.depth_metadata.YRes();
        QImage draw_depth_image;
        //找深度最大值点
        XnDepthPixel max_depth = *p_depth_pixpel;
        for(unsigned int i = 1; i < size; ++i)
            if(p_depth_pixpel[i] > max_depth )
                max_depth = p_depth_pixpel[i];
        test = max_depth;

        //将深度图像格式归一化到0~255
        int idx = 0;
        for(unsigned int i = 1; i < size; ++i) {
            //一定要使用1.0f相乘,转换成float类型,否则该工程的结果会有错误,因为这个要么是0,要么是1,0的概率要大很多
            float fscale = 1.0f*(*p_depth_pixpel)/max_depth;
            if((*p_depth_pixpel) != 0) {
                p_depth_argb[idx++] = 255*(1-fscale);    //蓝色分量
                p_depth_argb[idx++] = 0; //绿色分量
                p_depth_argb[idx++] = 255*fscale;   //红色分量,越远越红
                p_depth_argb[idx++] = 255*(1-fscale); //距离越近,越不透明
            }
            else {
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 0;
                p_depth_argb[idx++] = 255;
            }
            ++p_depth_pixpel;//此处的++p_depth_pixpel和p_depth_pixpel++是一样的
        }
        draw_depth_image = QImage(p_depth_argb, openni.depth_metadata.XRes(), openni.depth_metadata.YRes()
                                 , QImage::Format_ARGB32);
        //该句隐含的调用了begin()方法
        QPainter painter(&draw_depth_image);
        {
            for(auto itUser = hands_track_points.cbegin(); itUser != hands_track_points.cend(); ++itUser) {
                painter.setPen(pen_array[itUser->first % pen_array.size()]);//设置画笔的颜色,按照一定的顺序循环不同的颜色
                const vector<XnPoint3D> &points = itUser->second;
                for(vector<XnPoint3D>::size_type i = 1; i < points.size(); ++i)
                    painter.drawLine(points[i-1].X, points[i-1].Y, points[i].X, points[i].Y);//画出轨迹线
            }
        }
        painter.end();//绘图结束
        //往item中设置图像色彩数据
        image_item->setPixmap(QPixmap::fromImage(
                              QImage(openni.image_metadata.Data(), openni.image_metadata.XRes(), openni.image_metadata.YRes(),
                              QImage::Format_RGB888)));
//        //往item中设置深度数据
//        depth_item->setPixmap(QPixmap::fromImage(
//                              QImage(p_depth_argb, openni.depth_metadata.XRes(), openni.depth_metadata.YRes()
//                              , QImage::Format_ARGB32)));

        depth_item->setPixmap(QPixmap::fromImage(draw_depth_image));
    }
};
复制代码

 

  main.cpp:

复制代码
#include <QtGui/QtGui>
#include "copenni.cpp"
#include "ckinectreader.cpp"

int main( int argc, char **argv )
{
    COpenNI openni;
    if(!openni.Initial())//初始化返回1表示初始化成功
        return 1;
    QApplication app(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view;
    view.setScene(&scene);
    view.resize(650, 540);
    view.show();

    CKinectReader kinect_reader(openni, scene);
    kinect_reader.Start();//启动,读取数据

    return app.exec();
}
复制代码

 

  实验总结:通过本次实验可以学会使用openni中手势跟踪的实现。

  参考资料:

     OpenNI 的手部追踪、外加简单的滑鼠模拟

  附录:实验工程code下载

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。