[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom：＃最多替换为4个自定义赞助商URL，例如['link1'，'link2']\n"
  },
  {
    "path": "README.md",
    "content": "# OpenCV contrib扩展模块中文教程\n\n&nbsp;\n<p align=\"center\"><strong>本教程由小白学视觉团队 译</strong></p>\n&nbsp;\n<p align=\"center\"><img src=\"https://img-blog.csdnimg.cn/20200301190623909.png\" height=\"200\"></img></p>\n<p align=\"center\"><strong>关注微信公众号“小白学视觉”获取更多计算机视觉学习资料</strong></p>\n\n小白学视觉是2018年5月由哈尔滨工业大学博士生创办的技术类公众号，主要面向对计算机视觉、图像处理感兴趣的爱好者，分享计算机视觉学习资源、学习过程总结、前沿技术等内容，搭建计算机视觉、图像处理学习和交流的平台。\n\n小白学视觉分享内容包括：  **OpenCV入门 &emsp; SLAM学习 &emsp; 优质书籍介绍 &emsp; 优质课程分享 &emsp; 编程技巧 &emsp; 论文解读 &emsp; 前沿方向介绍 &emsp; 招聘内推 &emsp; 招聘信息** \n\n&nbsp;\n\n- [引言](#引言)\n- 第一章  Contrib Modules介绍与安装\n    - 1.1 [Contrib Modules安装](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%201/Windows%E7%B3%BB%E7%BB%9F%E4%B8%AD%E5%AE%89%E8%A3%85%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9D%97.md)\n    - 1.2 [Contrib Modules模块内容介绍](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%201/Contrib%20Modules%E6%A8%A1%E5%9D%97%E5%86%85%E5%AE%B9%E4%BB%8B%E7%BB%8D.md)\n- 第二章 ArUco标记检测(aruco模块)\n    - 2.1 [ArUco标记检测](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%202/ArUco%E6%A0%87%E8%AE%B0%E6%A3%80%E6%B5%8B.md)\n    - 2.2 [ArUco标记板的检测](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%202/ArUco%E6%A0%87%E8%AE%B0%E6%9D%BF%E7%9A%84%E6%A3%80%E6%B5%8B.md)\n    - 2.3 [ChArUco角的检测](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%202/ChArUco%E8%A7%92%E7%9A%84%E6%A3%80%E6%B5%8B.md)\n    - 2.4 [菱形标记检测](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%202/%E8%8F%B1%E5%BD%A2%E6%A0%87%E8%AE%B0%E6%A3%80%E6%B5%8B.md)\n    - 2.5 [使用ArUco和ChArUco进行相机标定](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%202/%E4%BD%BF%E7%94%A8ArUco%E5%92%8CChArUco%E8%BF%9B%E8%A1%8C%E7%9B%B8%E6%9C%BA%E6%A0%87%E5%AE%9A.md)\n    - 2.6 [Aruco模块常见问题](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%202/Aruco%E6%A8%A1%E5%9D%97%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.md)\n- 第三章 背景分割(bgsegm模块)\n    - 3.1 [背景分割](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%203/%E8%83%8C%E6%99%AF%E5%88%86%E5%89%B2.md)\n- 第四章 生物视觉 (bioinspired模块)\n    - 4.1 [视网膜视觉和真实世界的视觉](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%204/%E8%A7%86%E7%BD%91%E8%86%9C%E8%A7%86%E8%A7%89%E5%92%8C%E7%9C%9F%E5%AE%9E%E4%B8%96%E7%95%8C%E7%9A%84%E8%A7%86%E8%A7%89.md)\n    - 4.2 [处理引起视错觉的图像](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%204/%E5%A4%84%E7%90%86%E5%BC%95%E8%B5%B7%E8%A7%86%E9%94%99%E8%A7%89%E7%9A%84%E5%9B%BE%E5%83%8F.md)\n- 第五章 相机标定(ccalib模块)\n    - 5.1 [广角相机标定](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%205/%E5%B9%BF%E8%A7%92%E7%9B%B8%E6%9C%BA%E6%A0%87%E5%AE%9A.md)\n    - 5.2 [多相机标定](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%205/%E5%A4%9A%E7%9B%B8%E6%9C%BA%E6%A0%87%E5%AE%9A.md)\n- 第六章 3D物体分类和位姿估计(cnn_3dobj模块)\n    - 6.1 [使用Icosphere训练数据](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%206/%E4%BD%BF%E7%94%A8Icosphere%E8%AE%AD%E7%BB%83%E6%95%B0%E6%8D%AE.md)\n    - 6.2 [分类](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%206/%E5%88%86%E7%B1%BB.md)\n    - 6.3 [分析训练模型](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%206/%E5%88%86%E6%9E%90%E8%AE%AD%E7%BB%83%E6%A8%A1%E5%9E%8B.md)\n- 第七章  可视化调试(cvv模块)\n    - 7.1 [计算机视觉应用的交互式可视化调试](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%207/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89%E5%BA%94%E7%94%A8%E7%9A%84%E4%BA%A4%E4%BA%92%E5%BC%8F%E5%8F%AF%E8%A7%86%E5%8C%96%E8%B0%83%E8%AF%95.md)\n- 第八章 CNNs目标检测(dnn_objdetect模块)\n    - 8.1 [使用CNNs进行目标检测](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%208/%E4%BD%BF%E7%94%A8CNNs%E8%BF%9B%E8%A1%8C%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B.md)\n- 第九章 图像超分(dnn_superresm模块)\n    - 9.1 [放大图像：单输出](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%209/%E6%94%BE%E5%A4%A7%E5%9B%BE%E5%83%8F%EF%BC%9A%E5%8D%95%E8%BE%93%E5%87%BA.md)\n    - 9.2 [放大图像：多输出](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%209/%E6%94%BE%E5%A4%A7%E5%9B%BE%E5%83%8F%EF%BC%9A%E5%8D%95%E8%BE%93%E5%87%BA.md)\n    - 9.3 [放大视频](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%209/%E6%94%BE%E5%A4%A7%E8%A7%86%E9%A2%91.md)\n    - 9.4 [超分辨率基准测试](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%209/%E8%B6%85%E5%88%86%E8%BE%A8%E7%8E%87%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95.md)\n- 第十章 人脸识别(face模块)\n    - 10.1 [OpenCV中的人脸识别](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/OpenCV%E4%B8%AD%E7%9A%84%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB.md)\n    - 10.2 [图像中的人脸标志检测](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/%E5%9B%BE%E5%83%8F%E4%B8%AD%E7%9A%84%E4%BA%BA%E8%84%B8%E6%A0%87%E5%BF%97%E6%A3%80%E6%B5%8B.md)\n    - 10.3 [训练面部标志探测器](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/%E8%AE%AD%E7%BB%83%E9%9D%A2%E9%83%A8%E6%A0%87%E5%BF%97%E6%8E%A2%E6%B5%8B%E5%99%A8.md)\n    - 10.4 [视频中的人脸标志检测](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/%E8%A7%86%E9%A2%91%E4%B8%AD%E7%9A%84%E4%BA%BA%E8%84%B8%E6%A0%87%E5%BF%97%E6%A3%80%E6%B5%8B.md)\n    - 10.5 [使用人脸标志检测进行人脸交换](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/%E4%BD%BF%E7%94%A8%E4%BA%BA%E8%84%B8%E6%A0%87%E5%BF%97%E6%A3%80%E6%B5%8B%E8%BF%9B%E8%A1%8C%E4%BA%BA%E8%84%B8%E4%BA%A4%E6%8D%A2.md)\n    - 10.6 [向人脸标志API添加新算法](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/%E5%90%91%E4%BA%BA%E8%84%B8%E6%A0%87%E5%BF%97API%E6%B7%BB%E5%8A%A0%E6%96%B0%E7%AE%97%E6%B3%95.md)\n    - 10.7 [使用人脸标志 API](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/%E4%BD%BF%E7%94%A8%E4%BA%BA%E8%84%B8%E6%A0%87%E5%BF%97%20API.md)\n    - 10.8 [使用人脸标志AMM](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2010/%E4%BD%BF%E7%94%A8%E4%BA%BA%E8%84%B8%E6%A0%87%E5%BF%97AMM.md)\n- 第十一章 模糊图像处理(fuzzy模块)\n    - 11.1 [模糊变换理论](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2011/%E6%A8%A1%E7%B3%8A%E5%8F%98%E6%8D%A2%E7%90%86%E8%AE%BA.md)\n    - 11.2 [通过模糊变换进行图像修复](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2011/%E9%80%9A%E8%BF%87%E6%A8%A1%E7%B3%8A%E5%8F%98%E6%8D%A2%E8%BF%9B%E8%A1%8C%E5%9B%BE%E5%83%8F%E4%BF%AE%E5%A4%8D.md)\n    - 11.3 [使用模糊变换滤波](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2011/%E4%BD%BF%E7%94%A8%E6%A8%A1%E7%B3%8A%E5%8F%98%E6%8D%A2%E6%BB%A4%E6%B3%A2.md)\n- 第十二章 分层数据格式的输入与输出(hdf)\n    - 12.1 [建立群组](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2012/%E5%BB%BA%E7%AB%8B%E7%BE%A4%E7%BB%84.md)\n    - 12.2 [创建、写入和读取数据集](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2012/%E5%88%9B%E5%BB%BA%E3%80%81%E5%86%99%E5%85%A5%E5%92%8C%E8%AF%BB%E5%8F%96%E6%95%B0%E6%8D%AE%E9%9B%86.md)\n    - 12.3 [读写属性](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2012/%E8%AF%BB%E5%86%99%E5%B1%9E%E6%80%A7.md)\n- 第十三章 线特征(line_descriptor模块)\n    - 13.1 [线特征教程](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2013/%E7%BA%BF%E7%89%B9%E5%BE%81%E6%95%99%E7%A8%8B.md)\n- 第十四章 相位展开(phase_unwrapping)\n    - 14.1 [二维相位图展开](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2014/%E4%BA%8C%E7%BB%B4%E7%9B%B8%E4%BD%8D%E5%9B%BE%E5%B1%95%E5%BC%80.md)\n- 第十五章 SFM运动恢复结构 (sfm模块)\n    - 15.1 [SFM 模块安装](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2015/SFM%20%E6%A8%A1%E5%9D%97%E5%AE%89%E8%A3%85.md)\n    - 15.2 [相机运动估计](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2015/%E7%9B%B8%E6%9C%BA%E8%BF%90%E5%8A%A8%E4%BC%B0%E8%AE%A1.md)\n    - 15.3 [场景重建](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2015/%E5%9C%BA%E6%99%AF%E9%87%8D%E5%BB%BA.md)\n    - 15.4 [导入重建模型](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2015/%E5%AF%BC%E5%85%A5%E9%87%8D%E5%BB%BA%E6%A8%A1%E5%9E%8B.md)\n- 第十六章 立体准稠密匹配(stereo模块)\n    - 16.1 [准稠密立体视觉](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2016/%E5%87%86%E7%A8%A0%E5%AF%86%E7%AB%8B%E4%BD%93%E8%A7%86%E8%A7%89.md)\n    - 16.2 [生成模板参数文件](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2016/%E7%94%9F%E6%88%90%E6%A8%A1%E6%9D%BF%E5%8F%82%E6%95%B0%E6%96%87%E4%BB%B6.md)\n- 第十七章 结构光教程(structured_light模块)\n    - 17.1 [捕捉格雷码](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2017/%E6%8D%95%E6%8D%89%E6%A0%BC%E9%9B%B7%E7%A0%81.md)\n    - 17.2 [解码格雷码教程](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2017/%E8%A7%A3%E7%A0%81%E6%A0%BC%E9%9B%B7%E7%A0%81%E6%95%99%E7%A8%8B.md)\n    - 17.3 [捕捉正弦图案](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2017/%E6%8D%95%E6%8D%89%E6%AD%A3%E5%BC%A6%E5%9B%BE%E6%A1%88.md)\n- 第十八章 目标跟踪(tracking模块)\n    - 18.1 [使用MultiTracker跟踪](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2018/%E4%BD%BF%E7%94%A8MultiTracker%E8%B7%9F%E8%B8%AA.md)\n    - 18.2 [OpenCV跟踪器介绍](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2018/OpenCV%E8%B7%9F%E8%B8%AA%E5%99%A8%E4%BB%8B%E7%BB%8D.md)\n    - 18.3 [自定义CN跟踪器](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2018/%E8%87%AA%E5%AE%9A%E4%B9%89CN%E8%B7%9F%E8%B8%AA%E5%99%A8.md)\n- 第十九章 立体可视化(viz模块)\n    - 19.1 [运行viz](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2019/%E8%BF%90%E8%A1%8Cviz.md)\n    - 19.2 [创建小部件](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2019/%E5%88%9B%E5%BB%BA%E5%B0%8F%E9%83%A8%E4%BB%B6.md)\n    - 19.3 [设置物体位姿](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2019/%E8%AE%BE%E7%BD%AE%E7%89%A9%E4%BD%93%E4%BD%8D%E5%A7%BF.md)\n    - 19.4 [位姿变换](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2019/%E4%BD%8D%E5%A7%BF%E5%8F%98%E6%8D%A2.md)\n    - 19.5 [创建3D直方图](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2019/%E5%88%9B%E5%BB%BA3D%E7%9B%B4%E6%96%B9%E5%9B%BE.md)\n- 第二十章 扩展图像处理(ximgproc模块)\n    - 20.1 [视差图滤波](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2020/%E8%A7%86%E5%B7%AE%E5%9B%BE%E6%BB%A4%E6%B3%A2.md)\n    - 20.2 [用于快速边缘检测的结构化森](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2020/%E7%94%A8%E4%BA%8E%E5%BF%AB%E9%80%9F%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%9A%84%E7%BB%93%E6%9E%84%E5%8C%96%E6%A3%AE.md)\n    - 20.3 [训练结构化森林](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2020/%E8%AE%AD%E7%BB%83%E7%BB%93%E6%9E%84%E5%8C%96%E6%A3%AE%E6%9E%97.md)\n- 第二十一章 对照片进行处理(xphoto模块)\n    - 21.1 [图像修复](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2021/%E5%9B%BE%E5%83%8F%E4%BF%AE%E5%A4%8D.md)\n    - 21.2 [油画效果](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2021/%E6%B2%B9%E7%94%BB%E6%95%88%E6%9E%9C.md)\n    - 21.3 [训练基于学习的白平衡算法](https://github.com/fengzhenHIT/OpenCV-contrib-module-Chinese-Tutorials/blob/master/chapter%2021/%E8%AE%AD%E7%BB%83%E5%9F%BA%E4%BA%8E%E5%AD%A6%E4%B9%A0%E7%9A%84%E7%99%BD%E5%B9%B3%E8%A1%A1%E7%AE%97%E6%B3%95.md)\n\n# 引言\n<p align=\"center\">为什么翻译此书？</p>\n<p align=\"center\">小白</p>\n<p align=\"center\">2020年2月8日</p>\n\n### 1 什么是OpenCV-Contrib Modules\nContrib Modules是OpenCV的扩展模块，包含了很多用于实现特定算法的子模块。由于计算机视觉和图像处理算法研究的发展，有很多优秀的算法无法很快集成到基础库中（就是我们平时最常使用的OpenCV库），这样将其单独编写独立的模块不仅有利于新算法的推广和维护，而且也可以避免随着版本的更新基础库中的内容越来越冗余，避免所占内存越来越大，方便使用。此外，由于由部分算法被专利保护，使用扩展模块的形式也避免了很多不必要的纠纷，例如大名鼎鼎的SIFT算法就有专利保护。\n\n### 2 为什么是这本书\n近些年来，人工智能快速发展，众多效果好、速度快的计算机视觉算法被提出。然而OpenCV基础库中的增加的算法却有限，造成了一种OpenCV只适合基础的算法学习的错觉。实际上，OpenCV每个版本的更新都带来众多最新的算法，只是很多内容被放置在了Contrib Modules扩展模块中。\n目前市面上很多关于OpenCV学习的书籍都是在介绍如何使用OpenCV的基础库，却很少有提及扩展模块。虽然书籍的面向群体不同，但是当读者完成OpenCV入门之后，却鲜有书籍能够帮助读者进一步提高，因此读者十分需要一本介绍OpenCV扩展模块的进阶书籍。本书结合官网内容，对OpenCV扩展模块的使用进行整理，更够帮助有一定图像处理基础的读者在OpenCV的使用上更进一步。\n\n### 3 本书如何使用\n本书是OpenCV学习的进阶版，建议结合《OpenCV 4计算机视觉编程实战》进行配套学习。\n\n### 4 本书的目标读者\n本书面向对计算机视觉和图像处理感兴趣并且使用OpenCV进行学术研究的读者，建议读者对OpenCV具有一定的了解，也可以建议结合《OpenCV 4计算机视觉编程实战》进行配套学习。本书教程为C++语言，因此本书主要面向使用C++语言编程的读者。\n\n### 5 本书的翻译人员\n- 庞家明，测控专业在读本科生。主要负责2.5、2.6节和第5章到第9章的内容。\n- 王润泽，圣马家沟男子职业技术学院研究生在读，主要负责2.2、2.3节和第11章到第14章内容。\n- 唐佳满，北下关军事基地信号处理研一在读，主要负责2.4节和第17章、第20章和第21章内容。\n- 吴鹏飞 珠江环岛工业大学未知环境侦查研究生在读，主要负责第10章内容\n- 税科，C++图像处理工程师（天津开发区中环系统电子工程股份有限公司博士后工作站），主要负责第18章和第19章内容。\n- 瀚海古月，马家沟大沙河驻地机器人研一在读，主要负责第3章、第15章和第16章内容。\n- 赵宏峰 霍尼韦尔航空航天部门软件工程师，主要负责2.1节和第4章内容。\n- 小白，小白学视觉公众号博主，主要负责第一章和全书的排版校对。\n\n### 特别声明\n由于翻译人员的水平和精力有限，在翻译过程中难免会有错误发生，请读者予以理解。如果阅读本文档的过程中发现问题，可以与我们联系，我们会第一时间对内容进行更正。\n\n&nbsp;\n&nbsp;\n<p align=\"center\"><strong>特殊说明</strong></p>\n&nbsp;\n\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\n\n&nbsp;\n\n完整版电子书pdf已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\n"
  },
  {
    "path": "chapter 1/Contrib Modules模块内容介绍.md",
    "content": "目前Contrib Modules中有五十个子模块，涵盖了从传统机器视觉到深度神经网络，从相机标定到立体视觉，从背景分割到图像识别等众多领域。本节主要目的是为了介绍子模块的功能，让读者了解被隐藏在OpenCV-Contrib中的众多强大功能，也让读者能够直观的了解到模块中都有哪些部分是直接需要的，做到在学习和使用时能够有的放矢。\n\n全部的模块名称和模块用处在表1-1中给出。\n\n\n<p align=\"center\">\n  <strong>表1-1 contrib中各模块名称和含义</strong>\n</p>\n\n<center>\n  \n模块名称|含义\n---|---\naruco|ArUco标记检测\nbgsegm|改进的背景前景分割方法\nbioinspired.|受生物启发的视觉模型和衍生工具\nccalib|用于3D重建的自定义校准图案\ncnn_3dobj|3D对象识别和姿态估计API\ncudaarithm.|矩阵运算（CUDA）\ncudabgsegm|背景分割\ncudacodec|视频编码/解码\ncudafeatures2d|特征检测与描述\ncudafilters|图像过滤\ncudaimgproc|图像处理\ncudalegacy|版权支持\ncudaobjdetect|物体检测\ncudaoptflow|光流\ncudastereo|立体视觉\ncudawarping|图像映射\ncudev|设备层\ncvv|用于计算机视觉程序的交互式视觉调试的GUI\ndatasets|处理不同数据集的框架\ndnn_objdetect|DNN用于物体检测\ndpm|基于零件的可变形模型\nface|人脸分析\nfreetype|用freetype / harfbuzz绘制UTF-8字符串\nfuzzy|基于模糊数学的图像处理\nhdf|分层数据格式I / O例程\nhfs|分层特征选择，实现有效的图像分割\nimg_hash|该模块带来了不同图像哈希算法的实现。\nline_descriptor|从图像提取的行的二进制描述符\noptflow\t|光流算法\novis|OGRE 3D可视化工具\nphase_unwrapping|相位展开API\nplot|Mat数据的绘图功能\nquality|图像质量分析（IQA）API\nreg|图像配准\nrgbd|RGB深度处理\nsaliency|显着性API\nsfm|运动结构\nshape|形状距离和匹配\nstereo|立体对应算法\nstructured_light|结构化光源API\nsuperres|超分辨率\nsurface_matching|表面匹配\ntext|场景文字检测与识别\ntracking|追踪API\nvideostab|视频稳定\nviz|3D可视化器\nxfeatures2d|额外的2D功能框架\nximgproc|扩展图像处理\nxobjdetect|扩展物体检测\nxphoto|其他照片处理算法\n\n</center>\n\n下面是部分子模块的详细介绍：\n\n-\taruco：全名是“ArUco and ChArUco Markers”，AR增强现实模块。ArUco and ChArUco是两个增强现实中常用的标记。ArUco是被嵌入在棋盘的白色区域内的标记。\n-\tbgsegm：全名是“Background segmentation”，背景分割算法。模块中主要包括统计背景图像估计和按像素的贝叶斯分割等。 \n-\tbioinspired：全名是“Biological Vision -- Biologically inspired vision model”，生物视觉-生物启发的视觉模型。模块中主要包括最小化噪声、亮度差异、瞬态事件分割、高动态范围色调映射方法等。\n-\tccalib：全名是“Custom Calibration”，自定义标定。模块中主要包括三维重建、广角相机标定、随机模式标定和多相机标定等。\n-\tcnn_3dobj：全名是“cnn_ 3D object recognition and pose estimation”，深度对象识别和姿态检测。模块中主要包含基于Caffe深度神经网络库构建、训练和测试视觉对象识别和姿势的CNN模型。\n-\tcvv：全名是“Computer Vision Debugger”计算机视觉调试器。模块提供GUI调试界面，便于交互式的调试计算机视觉的程序。\n-\tdatasets：全名是“Datasets Reader”，数据集读取器。模块用于读取现有计算机视觉数据库和使用该读取器训练、测试和运行该数据集提供的示例程序。\n-\ttext：全名是“Visual Text Matching”，文字检测与识别。模块主要包括检测文字、分割词汇、识别文本等。\n"
  },
  {
    "path": "chapter 1/Windows系统中安装扩展模块.md",
    "content": "本小节将介绍如何安装contrib扩展模块，考虑到读者可能使用windows系统或者Ubuntu系统，本书中将分别介绍在这两个系统中安装contrib扩展模块的过程。\n\n\n安装contrib扩展模块时不仅需要扩展模块安装包，同时需要基础库安装包。contrib扩展模块安装包以opencv_contrib-x.x.x的形式统一命名，在安装时基础库安装包的版本需要和contrib扩展模块的安装包版本相对应。可以在GitHub上获取与自己OpenCV版本相匹配的opencv_contrib安装包。\n\n## 1.1.1Windows系统中安装扩展模块\n在Windows系统中安装opencv_contrib扩展模块其实就是重新将OpenCV编译一遍，因此需要用到CMake编译器，在CMake官网下载.msi安装包，通过双击直接完成安装任务。本书使用的是CMake 3.7.0版本，这里需要注意，由于OpenCV版本更新速度慢于CMake，因此不推荐使用过高版本的CMake编译器，只要满足OpenCV 4.0要求的最低版本即可，因此为了减少读者编译过程中的错误，建议与读者使用同版本的CMake。打开安装好的CMake软件，可以看到如图1-1所示的页面。我们需要选择OpenCV源码所在地址与编译文件的输出地址。源码放在了“…\\opencv\\sources”文件夹中，为了与OpenCV原有文件区分，在 “…\\opencv”中创建一个名为newbuild文件夹用于存放编译输出文件。\n\n<p align=\"center\">\n  <img src=\"https://img-blog.csdnimg.cn/20200226130256257.png\" align=\"center\" height=\"300\"></img>\n</p>\n\n \n之后通过点击【Configure】按钮，选择将源码编译成与Visual Studio版本相对应的项目工程文件，作者安装的是Visual Studio 2015，同时想使用64位的OpenCV，因此选择“Visual Studio 14 2015 Win64”选项，同时选择本地编辑器“Use default native compilers”。选择配置的操作界面如图1-2所示。\n\n<p align=\"center\">\n<img src=\"https://img-blog.csdnimg.cn/2020022613035076.png\" align=\"center\" height=\"250\">\n</p>\n\n之后再次点击【Configure】开始构建，当出现“Configuring done”后说明构建成功，在CMake界面会出现很多变量，如图1-3所示。首先找到“BUILD_opencv_world”和“OPENCV_ENABLE_NONFREE”这两个变量，在变量后面的方框内打上“√”。第一个变量的含义是生成一个大的.lib文件，在被配置链接器时只有一个“opencv_world400d.lib”文件。第二个变量的含义是为了在编译成功后可以使用具有专利保护的算法，如果该变量不被选中，就不能使用例如SIFT算法在内的具有专利保护的算法，之后找到“OPENCV_EXTRA_MODULES_PATH”变量，该变量的含义是告诉编译器扩展包的源码在哪里，选择我们刚才下载的opencv_contrib安装包里的modules文件夹。如果这个变量为空，在编译过程中也不会报错，只是安装了OpenCV的基础版。\n\n<p align=\"center\">\n<img src=\"https://img-blog.csdnimg.cn/20200226130522407.png\" align=\"center\" height=\"300\">\n</p>\n\n在编译完成的界面内，我们可以看到扩展模块中将要编译的子模块，部分模块如图1-4中所示，比如aruco模块、bgsegm模块等。我们可以在右侧的方框中来选择是否编译该模块，默认情况下contrib中的子模块都是进行编译的，不过为了稳妥期间，读者在编译时可以查看并确认一下这些数据。\n\n<p align=\"center\">\n<img src=\"https://img-blog.csdnimg.cn/20200226130629146.png\" align=\"center\" height=\"250\">\n</p>\n\n再次点击【Configure】，直到所有的红色变量变成白色，之后点击【Generate】开始编译。编译成功后会在newbuild文件夹中生成许多文件，找到OpenCV.sln文件，用Visual Studio 2015打开该文件并重新生成解决方案，这个过程会比较漫长。 经过漫长时间的等待，在资源管理器中找到CMakeTargets中的INSTALL文件，右键选择“仅用于项目”中的“仅生成INSTALL”，会在newbuild文件夹中生成一个名为install的文件夹，我们用来环境配置的所有文件都存放在这个文件夹中。按照配置OpenCV基础库环境的方式配置环境即可。\n\n**提示\n编译后newbuild 文件夹非常的大，会有几个G的文件，但是除了install文件夹最重要，绝大多数文件都是垃圾文件，如果觉得硬盘存储量有限，可以选择性的删除一些文件。**\n\n**注意\n*我们认为读者熟练的掌握OpenCV基础库的环境配置方案，如果读者对基础库环境配置过程不是很熟悉，可以参考我们推荐的配套书籍《OpenCV 4计算机视觉编程实战》。***\n\n***警告\n编译过程较为繁琐，并且由于网络、系统版本等问题容易遇到比较冷门的错误，若遇到相关棘手问题，可以在“小白学视觉”微信公众号交流学习。\n如果读者只是想使用扩展模块，我们在“小白学视觉”微信公众号上准备了多个版本已经编译完成的含有contrib扩展模块的库文件，下载后直接配置环境即可。***\n\n## 1.1.2Ubuntu系统中安装扩展模块\n在Ubuntu系统中安装扩展模块比较容易，只需要在cmake编译时使用代码清单1-1中的命令，在“OPENCV_EXTRA_MODULES_PATH=”后面添加上扩展安装包的路径即可，其余步骤与安装OpenCV基础模块没有区别。\n\n```cpp\n代码清单1-1 编译OpenCV命令\ncmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local OPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules ..\n```\n\n***注意\n我们认为读者熟练的掌握OpenCV在Ubuntu系统中的编译和配置工作。这部分相关内容网络资源较多，这里不再过多叙述。***\n"
  },
  {
    "path": "chapter 10/OpenCV中的人脸识别.md",
    "content": "## 10.1.1人脸识别介绍\r\n人脸识别对于人类而言是一项轻松的任务。实验表明，即使是1至3天大的婴儿也能够区分已知面孔。那么，对于计算机来说有多难呢？事实证明，到目前为止，计算机对人类的认识还知之甚少。内部特征（眼睛，鼻子，嘴巴）或外部特征（头部形状，发际线）是否可成功用于的面部识别？我们如何分析图像，大脑如何编码呢？David Hubel和Torsten Wiesel证明，我们的大脑具有专门的神经细胞，可以响应场景的特定局部特征，例如线条，边缘，角度或运动。由于我们不认为世界是零散的碎片，因此我们的视觉皮层必须以某种方式将不同的信息源组合成有用的模式。自动人脸识别就是从图像中提取那些有意义的特征，将它们变成有用的表示并对其进行某种分类。\r\n\r\n基于面部的几何特征的面部识别可能是最直观的面部识别方法。Picture processing system by computer complex and recognition of human faces中描述了最早的自动面部识别系统之一：标记点（眼睛，耳朵，鼻子等器官的的位置）用于构建特征向量（点之间的距离，它们之间的角度等 ）。识别是通过计算探针和参考图像的特征向量之间的欧式距离来实现的。 这种方法由于其性质使得抵抗照明变化的能力很强，但是有一个巨大的缺点就是即使使用最先进的算法，标记点的精确配准也很复杂。Face recognition through geometrical features中进行了一些有关几何人脸识别的最新工作。通过使用22维特征向量对大型数据集进行的实验表明，仅几何特征可能无法携带足够的信息来进行人脸识别。\r\n\r\nEigenfaces for recognition中描述的Eigenfaces采用了一种整体方法来进行脸部识别：脸部图像是来自高维图像空间的一个点，并且找到了低维表示，使得分类变得容易。使用主成分分析可以找到较低维的子空间，该子空间可以识别具有最大方差的轴。从重建的角度来看，这种转换是最佳的，但它没有考虑任何类标签。想象一种情况，其中的变化是由外部来源产生的，方差最大的轴根本不必包含任何判别信息，因此分类变得不可能。因此，在Eigenfaces vs. fisherfaces: Recognition using class specific linear projection中将具有线性判别分析的特定类投影应用于面部识别。 基本思想是最小化一个类中的方差，同时最大化两个类之间的方差。\r\n\r\n最近出现了各种用于局部特征提取的方法。 为了避免输入数据的高维性，仅描述了图像的局部区域，提取的特征（希望）对部分遮挡，照明和较小的样本量更加健壮。 用于局部特征提取的算法是Gabor小波，离散余弦变换和局部二值模式。 应用空间特征提取时，保留空间信息的最佳方法是什么仍然是一个悬而未决的问题，因为空间信息可能是有用的信息。\r\n\r\n## 10.1.2人脸数据集\r\n人脸识别需要一些人脸图像，我们可以创建自己的数据集，也可以从可用的人脸数据库中获取图像， http://face-rec.org/databases/提供最新的数据库。 接下来介绍三个有趣的数据库：\r\n\r\n- AT&T Facedatabase AT＆T人脸数据库（有时也称为ORL人脸数据库）包含40个不同主题中每个主题的十幅不同图像。 对于某些对象，图像是在不同的时间拍摄的，光线，面部表情（睁开/闭合的眼睛，微笑/不微笑）和面部细节（眼镜/不戴眼镜）均不同。 所有图像都是在深色均匀背景下拍摄的，对象处于直立的，正面的位置（允许某些侧向移动）。\r\n\r\n- Yale Facedatabase A, 也称为Yalefaces。AT＆T Facedatabase适用于初始测试，但是它是一个相当简单的数据库。Eigenfaces方法已经具有97％的识别率，因此使用其他算法不会看到任何重大改进。Yale Facedatabase A（也称为Yalefaces）是更适合初始实验的数据集，因为识别问题比较困难。该数据库由15个人（男性14位，女性1位）组成，每个人具有11张320×243像素的灰度图像。光线条件（中心光线，左光线，右光线），面部表情（快乐，正常，悲伤，困倦，惊讶，眨眼）和眼镜（眼镜，无眼镜）都有变化。但是原始图像未裁剪和对齐。\r\n\r\n- Extended Yale Facedatabase B 扩展的Yale Facedatabase B在其裁剪版本中包含3814个不同人物的2414张图像。该数据库的重点放在提取对照明稳定的特征上，图像的情感/遮挡/ ...几乎没有变化。我个人认为，对于我在本文中执行的实验而言，该数据集太大。 您最好使用AT＆T Facedatabase进行初始测试。Yale Facedatabase B的第一个版本曾用于查看在剧烈光照变化下Eigenfaces和Fisherfaces方法的性能。\r\n\r\n## 10.1.3准备数据\r\n一旦获取了一些数据，就需要在程序中读取它们。在演示应用程序中，我决定从一个非常简单的CSV文件读取图像。因为这是最简单的与平台无关的方法。基本上，所有CSV文件都需要包文件名和标签（作为整数），组成代码清单10-1中的形式。\r\n\r\n```cpp\r\n代码清单10-1\r\n1./path/to/image.ext;0\r\n```\r\n\r\n/path/to/image.ext是图像的路径，如果在Windows中，则可能是这样的：C：/faces/person0/image0.jpg。 然后是分隔符“; ”最后，我们给图像分配标签0。将标签视为该图像所属的主题（人），因此相同的主题（人）应具有相同的标签。\r\n\r\n从AT＆T Facedatabase下载AT＆T Facedatabase，并从at.txt下载相应的CSV文件，该文件中的内容如代码清单10-2中所示\r\n\r\n```cpp\r\n代码清单10-2\r\n./at/s1/1.pgm;0\r\n./at/s1/2.pgm;0\r\n...\r\n./at/s2/1.pgm;1\r\n./at/s2/2.pgm;1\r\n...\r\n./at/s40/1.pgm;39\r\n./at/s40/2.pgm;39\r\n```\r\n\r\n想象一下，我已经将文件提取到D:/ data / at，并将CSV文件下载到D:/data/at.txt。然后，我们只需要用D:/ data /搜索并替换./。我们可以在自己选择的编辑器中执行此操作，每个足够高级的编辑器都可以执行此操作。拥有包含有效文件名和标签的CSV文件后，我们可以通过将路径作为参数传递到CSV文件来运行任何示例程序，方式如代码清单10-3中所示。\r\n\r\n\r\n```cpp\r\n代码清单10-3\r\nfacerec_demo.exe D:/data/at.txt\r\n```\r\n\t\r\n## 10.1.4Eigenfaces介绍\r\n我们得到的图像表示的最大问题是它的大尺寸。二维p×q灰度图像跨越m = pq维矢量空间，因此100×100像素的图像已经位于10000维图像空间中。 但是并不是所有的维度对我们都有用，我们只能决定数据是否存在差异，因此我们要寻找的是构成大多数信息的组件。 主成分分析（PCA）由Karl Pearson（1901）和Harold Hotelling（1933）独立提出，将一组可能相关的变量转换为较小的一组不相关变量。具体想法是，高维数据集通常由相关变量来描述，因此，只有少数几个有意义的维度才能说明大多数信息。PCA方法找到数据中方差最大的方向，称为主成分。\r\n\r\n接下来介绍Eigenfaces方法算法\r\n\r\n令X={x<sub>1</sub>,x<sub>2</sub>,…,x<sub>n</sub> }是带有观测值x<sub>i</sub>∈R<sup>d</sup>的随机向量。计算平均值μ\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex=\\mu&space;=&space;\\frac{1}{n}\\sum\\limits_{i&space;=&space;1}^n&space;{{x_i}}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?\\mu&space;=&space;\\frac{1}{n}\\sum\\limits_{i&space;=&space;1}^n&space;{{x_i}}\" title=\"\\mu = \\frac{1}{n}\\sum\\limits_{i = 1}^n {{x_i}}\" /></a></p>\r\n计算协方差矩阵S\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex=S&space;=&space;\\frac{1}{n}\\sum\\limits_{i&space;=&space;1}^n&space;{\\left(&space;{{x_i}&space;-&space;\\mu&space;}&space;\\right){{\\left(&space;{{x_i}&space;-&space;\\mu&space;}&space;\\right)}^T}}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?S&space;=&space;\\frac{1}{n}\\sum\\limits_{i&space;=&space;1}^n&space;{\\left(&space;{{x_i}&space;-&space;\\mu&space;}&space;\\right){{\\left(&space;{{x_i}&space;-&space;\\mu&space;}&space;\\right)}^T}}\" title=\"S = \\frac{1}{n}\\sum\\limits_{i = 1}^n {\\left( {{x_i} - \\mu } \\right){{\\left( {{x_i} - \\mu } \\right)}^T}}\" /></a></p>\r\n\r\n计算S的特征值λ<sub>i</sub>和特征向量v<sub>i</sub>\r\n<center>Sv<sub>i</sub>=λ<sub>i </sub>v<sub>i</sub>，i=1,2,…,n</center>\r\n\r\n对特征向量按其特征值降序进行排序。k个主要成分是与k个最大特征值相对应的特征向量。所观察到的向量x的k个主要成分如下：\r\n\r\n<center>y=W<sup>T</sup> (x-μ)</center>\r\n\r\n其中W={v<sub>1</sub>,v<sub>2</sub>,…,v<sub>k</sub> }。\r\n\r\n从PCA基础上进行的重构，方式由下式给出：\r\n<center>x=Wy+μ</center>\r\n\r\n其中W={v<sub>1</sub>,v<sub>2</sub>,…,v<sub>k </sub>}。\r\n\r\n然后，Eigenfaces方法通过以下方式执行人脸识别：\r\n\r\n- 将所有训练样本投影到PCA子空间中。\r\n- 将查询图像投影到PCA子空间中。\r\n- 在计划的训练图像和计划的查询图像之间找到最近的邻居\r\n\r\n这里仍然有一个问题需要解决。想象一下，我们得到了400张尺寸为100×100像素的图像。主成分分析求解协方差矩阵S = XXT，在本教程中为size（X）= 10000×400。我们最终将得到10000×10000矩阵，大约为0.8GB。 解决此问题是不可行的，因此我们需要应用技巧。从线性代数课程中，您知道M> N的M×N矩阵只能具有N-1个非零特征值。 因此，有可能采用大小为N×N的特征值分解S = X<sup>T</sup>X：\r\n<center>X<sup>T</sup> Xv<sub>i</sub>=λ<sub>i </sub>vi</center>\r\n\r\n并使用数据矩阵的左乘法获得S = XX<sup>T</sup>的原始特征向量：\r\n<center>XX<sup>T</sup> (Xv<sub>i </sub>)=λ<sub>i </sub>(Xv<sub>i</sub>)</center>\r\n\r\n所得的特征向量是正交的，为了获得正交特征向量，需要将它们标准化为单位长度。具体方式读者可以Pattern classification中了解。\r\n\r\n## 10.1.5Eigenface示例\r\n\r\n```cpp\r\n代码清单10-4\r\n/*\r\n * Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>.\r\n * Released to public domain under terms of the BSD Simplified license.\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are met:\r\n *   * Redistributions of source code must retain the above copyright\r\n *     notice, this list of conditions and the following disclaimer.\r\n *   * Redistributions in binary form must reproduce the above copyright\r\n *     notice, this list of conditions and the following disclaimer in the\r\n *     documentation and/or other materials provided with the distribution.\r\n *   * Neither the name of the organization nor the names of its contributors\r\n *     may be used to endorse or promote products derived from this software\r\n *     without specific prior written permission.\r\n *\r\n *   See <http://www.opensource.org/licenses/bsd-license>\r\n */\r\n#include \"opencv2/core.hpp\"\r\n#include \"opencv2/face.hpp\"\r\n#include \"opencv2/highgui.hpp\"\r\n#include \"opencv2/imgproc.hpp\"\r\n#include <iostream>\r\n#include <fstream>\r\n#include <sstream>\r\nusing namespace cv;\r\nusing namespace cv::face;\r\nusing namespace std;\r\nstatic Mat norm_0_255(InputArray _src) {\r\n    Mat src = _src.getMat();\r\n    // Create and return normalized image:\r\n    Mat dst;\r\n    switch(src.channels()) {\r\n    case 1:\r\n        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);\r\n        break;\r\n    case 3:\r\n        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);\r\n        break;\r\n    default:\r\n        src.copyTo(dst);\r\n        break;\r\n    }\r\n    return dst;\r\n}\r\nstatic void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {\r\n    std::ifstream file(filename.c_str(), ifstream::in);\r\n    if (!file) {\r\n        string error_message = \"No valid input file was given, please check the given filename.\";\r\n        CV_Error(Error::StsBadArg, error_message);\r\n    }\r\n    string line, path, classlabel;\r\n    while (getline(file, line)) {\r\n        stringstream liness(line);\r\n        getline(liness, path, separator);\r\n        getline(liness, classlabel);\r\n        if(!path.empty() && !classlabel.empty()) {\r\n            images.push_back(imread(path, 0));\r\n            labels.push_back(atoi(classlabel.c_str()));\r\n        }\r\n    }\r\n}\r\nint main(int argc, const char *argv[]) {\r\n    // Check for valid command line arguments, print usage\r\n    // if no arguments were given.\r\n    if (argc < 2) {\r\n        cout << \"usage: \" << argv[0] << \" <csv.ext> <output_folder> \" << endl;\r\n        exit(1);\r\n    }\r\n    string output_folder = \".\";\r\n    if (argc == 3) {\r\n        output_folder = string(argv[2]);\r\n    }\r\n    // Get the path to your CSV.\r\n    string fn_csv = string(argv[1]);\r\n    // These vectors hold the images and corresponding labels.\r\n    vector<Mat> images;\r\n    vector<int> labels;\r\n    // Read in the data. This can fail if no valid\r\n    // input filename is given.\r\n    try {\r\n        read_csv(fn_csv, images, labels);\r\n    } catch (const cv::Exception& e) {\r\n        cerr << \"Error opening file \\\"\" << fn_csv << \"\\\". Reason: \" << e.msg << endl;\r\n        // nothing more we can do\r\n        exit(1);\r\n    }\r\n    // Quit if there are not enough images for this demo.\r\n    if(images.size() <= 1) {\r\n        string error_message = \"This demo needs at least 2 images to work. Please add more images to your data set!\";\r\n        CV_Error(Error::StsError, error_message);\r\n    }\r\n    // Get the height from the first image. We'll need this\r\n    // later in code to reshape the images to their original\r\n    // size:\r\n    int height = images[0].rows;\r\n    // The following lines simply get the last images from\r\n    // your dataset and remove it from the vector. This is\r\n    // done, so that the training data (which we learn the\r\n    // cv::BasicFaceRecognizer on) and the test data we test\r\n    // the model with, do not overlap.\r\n    Mat testSample = images[images.size() - 1];\r\n    int testLabel = labels[labels.size() - 1];\r\n    images.pop_back();\r\n    labels.pop_back();\r\n    // The following lines create an Eigenfaces model for\r\n    // face recognition and train it with the images and\r\n    // labels read from the given CSV file.\r\n    // This here is a full PCA, if you just want to keep\r\n    // 10 principal components (read Eigenfaces), then call\r\n    // the factory method like this:\r\n    //\r\n    //      EigenFaceRecognizer::create(10);\r\n    //\r\n    // If you want to create a FaceRecognizer with a\r\n    // confidence threshold (e.g. 123.0), call it with:\r\n    //\r\n    //      EigenFaceRecognizer::create(10, 123.0);\r\n    //\r\n    // If you want to use _all_ Eigenfaces and have a threshold,\r\n    // then call the method like this:\r\n    //\r\n    //      EigenFaceRecognizer::create(0, 123.0);\r\n    //\r\n    Ptr<EigenFaceRecognizer> model = EigenFaceRecognizer::create();\r\n    model->train(images, labels);\r\n    // The following line predicts the label of a given\r\n    // test image:\r\n    int predictedLabel = model->predict(testSample);\r\n    //\r\n    // To get the confidence of a prediction call the model with:\r\n    //\r\n    //      int predictedLabel = -1;\r\n    //      double confidence = 0.0;\r\n    //      model->predict(testSample, predictedLabel, confidence);\r\n    //\r\n    string result_message = format(\"Predicted class = %d / Actual class = %d.\", predictedLabel, testLabel);\r\n    cout << result_message << endl;\r\n    // Here is how to get the eigenvalues of this Eigenfaces model:\r\n    Mat eigenvalues = model->getEigenValues();\r\n    // And we can do the same to display the Eigenvectors (read Eigenfaces):\r\n    Mat W = model->getEigenVectors();\r\n    // Get the sample mean from the training data\r\n    Mat mean = model->getMean();\r\n    // Display or save:\r\n    if(argc == 2) {\r\n        imshow(\"mean\", norm_0_255(mean.reshape(1, images[0].rows)));\r\n    } else {\r\n        imwrite(format(\"%s/mean.png\", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows)));\r\n    }\r\n    // Display or save the Eigenfaces:\r\n    for (int i = 0; i < min(10, W.cols); i++) {\r\n        string msg = format(\"Eigenvalue #%d = %.5f\", i, eigenvalues.at<double>(i));\r\n        cout << msg << endl;\r\n        // get eigenvector #i\r\n        Mat ev = W.col(i).clone();\r\n        // Reshape to original size & normalize to [0...255] for imshow.\r\n        Mat grayscale = norm_0_255(ev.reshape(1, height));\r\n        // Show the image & apply a Jet colormap for better sensing.\r\n        Mat cgrayscale;\r\n        applyColorMap(grayscale, cgrayscale, COLORMAP_JET);\r\n        // Display or save:\r\n        if(argc == 2) {\r\n            imshow(format(\"eigenface_%d\", i), cgrayscale);\r\n        } else {\r\n            imwrite(format(\"%s/eigenface_%d.png\", output_folder.c_str(), i), norm_0_255(cgrayscale));\r\n        }\r\n    }\r\n    // Display or save the image reconstruction at some predefined steps:\r\n    for(int num_components = min(W.cols, 10); num_components < min(W.cols, 300); num_components+=15) {\r\n        // slice the eigenvectors from the model\r\n        Mat evs = Mat(W, Range::all(), Range(0, num_components));\r\n        Mat projection = LDA::subspaceProject(evs, mean, images[0].reshape(1,1));\r\n        Mat reconstruction = LDA::subspaceReconstruct(evs, mean, projection);\r\n        // Normalize the result:\r\n        reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));\r\n        // Display or save:\r\n        if(argc == 2) {\r\n            imshow(format(\"eigenface_reconstruction_%d\", num_components), reconstruction);\r\n        } else {\r\n            imwrite(format(\"%s/eigenface_reconstruction_%d.png\", output_folder.c_str(), num_components), reconstruction);\r\n        }\r\n    }\r\n    // Display if we are not writing to an output folder:\r\n    if(argc == 2) {\r\n        waitKey(0);\r\n    }\r\n    return 0;\r\n}\r\n```\r\n\r\n我们使用了喷射色图，因此我们可以看到灰度值如何在特定的Eigenface内分布。可以看到，特征不仅编码面部特征，而且还编码图像中的光照（请参见特征4中的左灯，特征5中的右灯）。具体结果在图10-1给出。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226135302981.png\" height=\"300\">\r\n</p>\r\n \r\n我们可以看到，可以从其较低维度的近似值重建面孔。因此，让我们看看一个好的重建需要多少本征面。我们将用10,30，…，310个本征面做一个子图，具体操作如代码清单10-5中所示。\r\n\r\n```cpp\r\n代码清单10-5\r\n// Display or save the image reconstruction at some predefined steps:\r\nfor(int num_components = 10; num_components < 300; num_components+=15) {\r\n    // slice the eigenvectors from the model\r\n    Mat evs = Mat(W, Range::all(), Range(0, num_components));\r\n    Mat projection = LDA::subspaceProject(evs, mean, images[0].reshape(1,1));\r\n    Mat reconstruction = LDA::subspaceReconstruct(evs, mean, projection);\r\n    // Normalize the result:\r\n    reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));\r\n    // Display or save:\r\n    if(argc == 2) {\r\n        imshow(format(\"eigenface_reconstruction_%d\", num_components), reconstruction);\r\n    } else {\r\n        imwrite(format(\"%s/eigenface_reconstruction_%d.png\", output_folder.c_str(), num_components), reconstruction);\r\n    }\r\n}\r\n```\r\n\r\n10个特征向量显然不足以进行良好的图像重建，而50个特征向量可能已经足以编码重要的面部特征。如果使用300个特征向量描述AT＆T Facedatabase，我们将获得良好的重构。 根据经验，要成功识别人脸，应该选择多少个特征脸，但这在很大程度上取决于输入数据。 如图10-5是特征向量增加对同一个图像重建的效果的影响。\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226135512221.png\" height=\"300\">\r\n</p>\r\n \r\n\r\n## 10.1.6Fisherfaces介绍\r\n主成分分析（PCA）是Eigenfaces方法的核心，它找到特征的线性组合，该特征使数据的总方差最大化。 尽管这显然是表示数据的一种有效方法，但是它不考虑任何类，因此在丢弃组件时可能会丢失很多区分性信息。想象一种情况，我们的数据差异是由外部来源生成的，那就容易了。 PCA标识的组件根本不必包含任何歧视性信息，因此将投影的样本混在一起，并且无法进行分类（有关示例： http://www.bytefish.de/wiki/pca_lda_with_gnu_octave）。\r\n\r\n线性判别分析是特定类的降维，它是由伟大的统计学家Sir R. A. Fisher提出的。他在1936年的论文《分类学问题中多次测量的使用》中成功地将其用于花朵分类。为了找到在类别之间最佳分离的特征组合，线性判别分析使类别之间对类别内部散布的比率最大化，而不是使整体散布最大化。 这个想法很简单：相同的类应该紧密地聚在一起，而不同的类在低维表示中则要尽可能地远离彼此。 Belhumeur，Hespanha和Kriegman也认识到这一点，因此他们在[14]中将判别分析应用于人脸识别。\r\n\r\n接下来介绍**Fisherfaces**方法的算法\r\n\r\n令X为具有从c类中抽取的样本的随机向量：\r\n<center>X={X<sub>1</sub>,X<sub>2</sub>,…,X<sub>C </sub>}</center>\r\n\r\n<center>X<sub>I</sub>={x<sub>1</sub>,x<sub>2</sub>,…,x<sub>n</sub> }</center>\r\n\r\n散射矩阵S<sub>B</sub>和S_ {W}的计算公式为：\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex={S_B}&space;=&space;\\sum\\limits_{i&space;=&space;1}^c&space;{{N_i}\\left(&space;{{\\mu&space;_i}&space;-&space;u}&space;\\right){{\\left(&space;{{\\mu&space;_i}&space;-&space;u}&space;\\right)}^T}}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?{S_B}&space;=&space;\\sum\\limits_{i&space;=&space;1}^c&space;{{N_i}\\left(&space;{{\\mu&space;_i}&space;-&space;u}&space;\\right){{\\left(&space;{{\\mu&space;_i}&space;-&space;u}&space;\\right)}^T}}\" title=\"{S_B} = \\sum\\limits_{i = 1}^c {{N_i}\\left( {{\\mu _i} - u} \\right){{\\left( {{\\mu _i} - u} \\right)}^T}}\" /></a></p>\r\n\r\n\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex={S_W}&space;=&space;\\sum\\limits_{i&space;=&space;1}^c&space;{\\sum\\limits_{{x_j}&space;\\in&space;{X_i}}&space;{\\left(&space;{{x_j}&space;-&space;{u_i}}&space;\\right){{\\left(&space;{{x_j}&space;-&space;{u_i}}&space;\\right)}^T}}&space;}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?{S_W}&space;=&space;\\sum\\limits_{i&space;=&space;1}^c&space;{\\sum\\limits_{{x_j}&space;\\in&space;{X_i}}&space;{\\left(&space;{{x_j}&space;-&space;{u_i}}&space;\\right){{\\left(&space;{{x_j}&space;-&space;{u_i}}&space;\\right)}^T}}&space;}\" title=\"{S_W} = \\sum\\limits_{i = 1}^c {\\sum\\limits_{{x_j} \\in {X_i}} {\\left( {{x_j} - {u_i}} \\right){{\\left( {{x_j} - {u_i}} \\right)}^T}} }\" /></a></p>\r\n\r\n其中，μ是总平均值：\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex=\\mu&space;=&space;\\frac{1}{N}\\sum\\limits_{i&space;=&space;1}^N&space;{{x_i}}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?\\mu&space;=&space;\\frac{1}{N}\\sum\\limits_{i&space;=&space;1}^N&space;{{x_i}}\" title=\"\\mu = \\frac{1}{N}\\sum\\limits_{i = 1}^N {{x_i}}\" /></a></p>\r\n\r\nμ<sub>i</sub>是类i∈{1，…，c}的平均值：\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex={\\mu&space;_i}&space;=&space;\\frac{1}{{\\left|&space;{{X_i}}&space;\\right|}}\\sum\\limits_{{x_j}&space;\\in&space;{X_i}}^N&space;{{x_j}}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?{\\mu&space;_i}&space;=&space;\\frac{1}{{\\left|&space;{{X_i}}&space;\\right|}}\\sum\\limits_{{x_j}&space;\\in&space;{X_i}}^N&space;{{x_j}}\" title=\"{\\mu _i} = \\frac{1}{{\\left| {{X_i}} \\right|}}\\sum\\limits_{{x_j} \\in {X_i}}^N {{x_j}}\" /></a></p>\r\n\r\nFisher的经典算法现在寻找投影W，该投影W使类可分离性标准最大化：\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex={W_{opt}}&space;=&space;\\arg&space;{\\max&space;_W}\\frac{{\\left|&space;{{W^T}{S_B}W}&space;\\right|}}{{\\left|&space;{{W^T}{S_W}W}&space;\\right|}}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?{W_{opt}}&space;=&space;\\arg&space;{\\max&space;_W}\\frac{{\\left|&space;{{W^T}{S_B}W}&space;\\right|}}{{\\left|&space;{{W^T}{S_W}W}&space;\\right|}}\" title=\"{W_{opt}} = \\arg {\\max _W}\\frac{{\\left| {{W^T}{S_B}W} \\right|}}{{\\left| {{W^T}{S_W}W} \\right|}}\" /></a></p>\r\n\r\n之后，通过解决一般特征值问题，给出了该优化问题的解决方案：\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex={S_B}{\\upsilon&space;_i}&space;=&space;{\\lambda&space;_i}{S_\\omega&space;}{\\upsilon&space;_i}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?{S_B}{\\upsilon&space;_i}&space;=&space;{\\lambda&space;_i}{S_\\omega&space;}{\\upsilon&space;_i}\" title=\"{S_B}{\\upsilon _i} = {\\lambda _i}{S_\\omega }{\\upsilon _i}\" /></a></p>\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex=S_W^{&space;-&space;1}{S_B}{\\upsilon&space;_i}&space;=&space;{\\lambda&space;_i}{\\upsilon&space;_i}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?S_W^{&space;-&space;1}{S_B}{\\upsilon&space;_i}&space;=&space;{\\lambda&space;_i}{\\upsilon&space;_i}\" title=\"S_W^{ - 1}{S_B}{\\upsilon _i} = {\\lambda _i}{\\upsilon _i}\" /></a></p>\r\n\r\n还有一个问题要解决：SW的等级最多为（N-c），具有N个样本和c个类。 在模式识别问题中，样本数N几乎总是比输入数据的维数（像素数）小，因此散射矩阵SW变得奇异（请参阅[173]）。这是通过对数据执行主成分分析并将样本投影到（NC）维空间来解决的。 然后对简化的数据执行线性判别分析，因为SW不再是奇异的。\r\n\r\n然后可以将优化问题重写为：\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex={W_{pac}}&space;=&space;\\arg&space;{\\max&space;_W}\\left|&space;{{W^T}{S_T}W}&space;\\right|\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?{W_{pac}}&space;=&space;\\arg&space;{\\max&space;_W}\\left|&space;{{W^T}{S_T}W}&space;\\right|\" title=\"{W_{pac}} = \\arg {\\max _W}\\left| {{W^T}{S_T}W} \\right|\" /></a></p>\r\n\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex={W_{fld}}&space;=&space;\\arg&space;{\\max&space;_W}\\frac{{\\left|&space;{{W^T}W_{pca}^T{S_B}{W_{pca}}W}&space;\\right|}}{{\\left|&space;{{W^T}W_{pca}^T{S_W}{W_{pca}}W}&space;\\right|}}\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?{W_{fld}}&space;=&space;\\arg&space;{\\max&space;_W}\\frac{{\\left|&space;{{W^T}W_{pca}^T{S_B}{W_{pca}}W}&space;\\right|}}{{\\left|&space;{{W^T}W_{pca}^T{S_W}{W_{pca}}W}&space;\\right|}}\" title=\"{W_{fld}} = \\arg {\\max _W}\\frac{{\\left| {{W^T}W_{pca}^T{S_B}{W_{pca}}W} \\right|}}{{\\left| {{W^T}W_{pca}^T{S_W}{W_{pca}}W} \\right|}}\" /></a></p>\r\n\r\n\r\n之后，将样本投影到（c-1）维空间的变换矩阵W由下式给出\r\n\r\n<p align=\"center\"><a href=\"https://www.codecogs.com/eqnedit.php?latex=W&space;=&space;W_{fld}^TW_{pca}^T\" target=\"_blank\"><img src=\"https://latex.codecogs.com/gif.latex?W&space;=&space;W_{fld}^TW_{pca}^T\" title=\"W = W_{fld}^TW_{pca}^T\" /></p>\r\n\r\n\r\n## 10.1.7\tFisherfaces示例\r\n\r\n```cpp\r\n代码清单10-6\r\n/*\r\n * Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>.\r\n * Released to public domain under terms of the BSD Simplified license.\r\n *\r\n * Redistribution and use in source and binary forms, with or without\r\n * modification, are permitted provided that the following conditions are met:\r\n *   * Redistributions of source code must retain the above copyright\r\n *     notice, this list of conditions and the following disclaimer.\r\n *   * Redistributions in binary form must reproduce the above copyright\r\n *     notice, this list of conditions and the following disclaimer in the\r\n *     documentation and/or other materials provided with the distribution.\r\n *   * Neither the name of the organization nor the names of its contributors\r\n *     may be used to endorse or promote products derived from this software\r\n *     without specific prior written permission.\r\n *\r\n *   See <http://www.opensource.org/licenses/bsd-license>\r\n */\r\n#include \"opencv2/core.hpp\"\r\n#include \"opencv2/face.hpp\"\r\n#include \"opencv2/highgui.hpp\"\r\n#include \"opencv2/imgproc.hpp\"\r\n#include <iostream>\r\n#include <fstream>\r\n#include <sstream>\r\nusing namespace cv;\r\nusing namespace cv::face;\r\nusing namespace std;\r\nstatic Mat norm_0_255(InputArray _src) {\r\n    Mat src = _src.getMat();\r\n    // Create and return normalized image:\r\n    Mat dst;\r\n    switch(src.channels()) {\r\n    case 1:\r\n        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);\r\n        break;\r\n    case 3:\r\n        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);\r\n        break;\r\n    default:\r\n        src.copyTo(dst);\r\n        break;\r\n    }\r\n    return dst;\r\n}\r\nstatic void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {\r\n    std::ifstream file(filename.c_str(), ifstream::in);\r\n    if (!file) {\r\n        string error_message = \"No valid input file was given, please check the given filename.\";\r\n        CV_Error(Error::StsBadArg, error_message);\r\n    }\r\n    string line, path, classlabel;\r\n    while (getline(file, line)) {\r\n        stringstream liness(line);\r\n        getline(liness, path, separator);\r\n        getline(liness, classlabel);\r\n        if(!path.empty() && !classlabel.empty()) {\r\n            images.push_back(imread(path, 0));\r\n            labels.push_back(atoi(classlabel.c_str()));\r\n        }\r\n    }\r\n}\r\nint main(int argc, const char *argv[]) {\r\n    // Check for valid command line arguments, print usage\r\n    // if no arguments were given.\r\n    if (argc < 2) {\r\n        cout << \"usage: \" << argv[0] << \" <csv.ext> <output_folder> \" << endl;\r\n        exit(1);\r\n    }\r\n    string output_folder = \".\";\r\n    if (argc == 3) {\r\n        output_folder = string(argv[2]);\r\n    }\r\n    // Get the path to your CSV.\r\n    string fn_csv = string(argv[1]);\r\n    // These vectors hold the images and corresponding labels.\r\n    vector<Mat> images;\r\n    vector<int> labels;\r\n    // Read in the data. This can fail if no valid\r\n    // input filename is given.\r\n    try {\r\n        read_csv(fn_csv, images, labels);\r\n    } catch (const cv::Exception& e) {\r\n        cerr << \"Error opening file \\\"\" << fn_csv << \"\\\". Reason: \" << e.msg << endl;\r\n        // nothing more we can do\r\n        exit(1);\r\n    }\r\n    // Quit if there are not enough images for this demo.\r\n    if(images.size() <= 1) {\r\n        string error_message = \"This demo needs at least 2 images to work. Please add more images to your data set!\";\r\n        CV_Error(Error::StsError, error_message);\r\n    }\r\n    // Get the height from the first image. We'll need this\r\n    // later in code to reshape the images to their original\r\n    // size:\r\n    int height = images[0].rows;\r\n    // The following lines simply get the last images from\r\n    // your dataset and remove it from the vector. This is\r\n    // done, so that the training data (which we learn the\r\n    // cv::BasicFaceRecognizer on) and the test data we test\r\n    // the model with, do not overlap.\r\n    Mat testSample = images[images.size() - 1];\r\n    int testLabel = labels[labels.size() - 1];\r\n    images.pop_back();\r\n    labels.pop_back();\r\n    // The following lines create an Fisherfaces model for\r\n    // face recognition and train it with the images and\r\n    // labels read from the given CSV file.\r\n    // If you just want to keep 10 Fisherfaces, then call\r\n    // the factory method like this:\r\n    //\r\n    //      FisherFaceRecognizer::create(10);\r\n    //\r\n    // However it is not useful to discard Fisherfaces! Please\r\n    // always try to use _all_ available Fisherfaces for\r\n    // classification.\r\n    //\r\n    // If you want to create a FaceRecognizer with a\r\n    // confidence threshold (e.g. 123.0) and use _all_\r\n    // Fisherfaces, then call it with:\r\n    //\r\n    //      FisherFaceRecognizer::create(0, 123.0);\r\n    //\r\n    Ptr<FisherFaceRecognizer> model = FisherFaceRecognizer::create();\r\n    model->train(images, labels);\r\n    // The following line predicts the label of a given\r\n    // test image:\r\n    int predictedLabel = model->predict(testSample);\r\n    //\r\n    // To get the confidence of a prediction call the model with:\r\n    //\r\n    //      int predictedLabel = -1;\r\n    //      double confidence = 0.0;\r\n    //      model->predict(testSample, predictedLabel, confidence);\r\n    //\r\n    string result_message = format(\"Predicted class = %d / Actual class = %d.\", predictedLabel, testLabel);\r\n    cout << result_message << endl;\r\n    // Here is how to get the eigenvalues of this Eigenfaces model:\r\n    Mat eigenvalues = model->getEigenValues();\r\n    // And we can do the same to display the Eigenvectors (read Eigenfaces):\r\n    Mat W = model->getEigenVectors();\r\n    // Get the sample mean from the training data\r\n    Mat mean = model->getMean();\r\n    // Display or save:\r\n    if(argc == 2) {\r\n        imshow(\"mean\", norm_0_255(mean.reshape(1, images[0].rows)));\r\n    } else {\r\n        imwrite(format(\"%s/mean.png\", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows)));\r\n    }\r\n    // Display or save the first, at most 16 Fisherfaces:\r\n    for (int i = 0; i < min(16, W.cols); i++) {\r\n        string msg = format(\"Eigenvalue #%d = %.5f\", i, eigenvalues.at<double>(i));\r\n        cout << msg << endl;\r\n        // get eigenvector #i\r\n        Mat ev = W.col(i).clone();\r\n        // Reshape to original size & normalize to [0...255] for imshow.\r\n        Mat grayscale = norm_0_255(ev.reshape(1, height));\r\n        // Show the image & apply a Bone colormap for better sensing.\r\n        Mat cgrayscale;\r\n        applyColorMap(grayscale, cgrayscale, COLORMAP_BONE);\r\n        // Display or save:\r\n        if(argc == 2) {\r\n            imshow(format(\"fisherface_%d\", i), cgrayscale);\r\n        } else {\r\n            imwrite(format(\"%s/fisherface_%d.png\", output_folder.c_str(), i), norm_0_255(cgrayscale));\r\n        }\r\n    }\r\n    // Display or save the image reconstruction at some predefined steps:\r\n    for(int num_component = 0; num_component < min(16, W.cols); num_component++) {\r\n        // Slice the Fisherface from the model:\r\n        Mat ev = W.col(num_component);\r\n        Mat projection = LDA::subspaceProject(ev, mean, images[0].reshape(1,1));\r\n        Mat reconstruction = LDA::subspaceReconstruct(ev, mean, projection);\r\n        // Normalize the result:\r\n        reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));\r\n        // Display or save:\r\n        if(argc == 2) {\r\n            imshow(format(\"fisherface_reconstruction_%d\", num_component), reconstruction);\r\n        } else {\r\n            imwrite(format(\"%s/fisherface_reconstruction_%d.png\", output_folder.c_str(), num_component), reconstruction);\r\n        }\r\n    }\r\n    // Display if we are not writing to an output folder:\r\n    if(argc == 2) {\r\n        waitKey(0);\r\n    }\r\n    return 0;\r\n}\r\n```\r\n\r\n对于代码清单10-6中的程序，我将使用Yale Facedatabase A，因为结果显示效果更好。每个Fisherface的长度与原始图像的长度相同，因此可以将其显示为图像。该演示显示（或保存）第一张，最多16张Fisherfaces，程序运行结果在图10-3中给出。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226144508114.png\" height=\"300\">\r\n</p>\r\n\r\nFisherfaces方法学习特定于类的变换矩阵，因此它们不像Eigenfaces方法那样明显捕获照明。判别分析改为找到可区分人的面部特征。值得一提的是，Fisherfaces的性能也很大程度上取决于输入数据。 实用地说：如果您仅学习具有良好照明效果的图片的Fisherfaces，并尝试识别照明条件较差的场景中的人脸，则该方法很可能会找到错误的成分（只是因为这些功能在照明不良的图像中可能并不占主导地位）。这有点合乎逻辑，因为该方法没有机会学习照明。\r\n\r\nFisherfaces可以像Eigenfaces一样重建投影图像。但是，由于我们仅识别出可区分主题的功能，因此不能指望对原始图像进行很好的重建。对于Fisherfaces方法，我们将示例图像投影到每个Fisherfaces上，将获得一个不错的可视化效果。具体操作在代码清单10-7中给出，可视化效果在图10-4给出，对于人眼来说，差异可能是微妙的，但是任然能够看到一些差异：\r\n\r\n```cpp\r\n代码清单10-7\r\n// Display or save the image reconstruction at some predefined steps:\r\nfor(int num_component = 0; num_component < min(16, W.cols); num_component++) {\r\n    // Slice the Fisherface from the model:\r\n    Mat ev = W.col(num_component);\r\n    Mat projection = LDA::subspaceProject(ev, mean, images[0].reshape(1,1));\r\n    Mat reconstruction = LDA::subspaceReconstruct(ev, mean, projection);\r\n    // Normalize the result:\r\n    reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));\r\n    // Display or save:\r\n    if(argc == 2) {\r\n        imshow(format(\"fisherface_reconstruction_%d\", num_component), reconstruction);\r\n    } else {\r\n        imwrite(format(\"%s/fisherface_reconstruction_%d.png\", output_folder.c_str(), num_component), reconstruction);\r\n    }\r\n}\r\n```\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226144628258.png\" height=\"300\">\r\n</p>\r\n\r\n"
  },
  {
    "path": "chapter 10/w",
    "content": "\n"
  },
  {
    "path": "chapter 10/使用人脸标志 API.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 10/使用人脸标志AMM.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 10/使用人脸标志检测进行人脸交换.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 10/向人脸标志API添加新算法.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 10/图像中的人脸标志检测.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 10/视频中的人脸标志检测.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 10/训练面部标志探测器.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 11/3",
    "content": "\n"
  },
  {
    "path": "chapter 11/使用模糊变换滤波.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 11/模糊变换理论.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 11/通过模糊变换进行图像修复.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 12/2",
    "content": "\n"
  },
  {
    "path": "chapter 12/创建、写入和读取数据集.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 12/建立群组.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 12/读写属性.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 13/4",
    "content": "\n"
  },
  {
    "path": "chapter 13/线特征教程.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 14/4",
    "content": "\n"
  },
  {
    "path": "chapter 14/二维相位图展开.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 15/SFM 模块安装.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 15/e",
    "content": "\n"
  },
  {
    "path": "chapter 15/场景重建.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 15/导入重建模型.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 15/相机运动估计.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 16/5",
    "content": "\n"
  },
  {
    "path": "chapter 16/准稠密立体视觉.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 16/生成模板参数文件.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 17/3",
    "content": "\n"
  },
  {
    "path": "chapter 17/捕捉格雷码.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 17/捕捉正弦图案.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 17/解码格雷码教程.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 18/4",
    "content": "\n"
  },
  {
    "path": "chapter 18/OpenCV跟踪器介绍.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 18/使用MultiTracker跟踪.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 18/自定义CN跟踪器.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 19/3",
    "content": "\n"
  },
  {
    "path": "chapter 19/位姿变换.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 19/创建3D直方图.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 19/创建小部件.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 19/设置物体位姿.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 19/运行viz.md",
    "content": "<p align=\"center\">\r\n  <img src=\"https://img-blog.csdnimg.cn/202003011934311.jpg\" height=\"300\"></img>\r\n</p>\r\n\r\n&nbsp;\r\n&nbsp;\r\n<p align=\"center\"><strong>特殊说明</strong></p>\r\n&nbsp;\r\n\r\n由于小白还在读书，最近项目进度有些紧，因此近期能够用来整理Github的时间比较少，因此现将已经整理完成的部分公开，供大家交流学习。\r\n\r\n&nbsp;\r\n\r\n**完整版电子书pdf**已经整理完成，喜欢阅读电子书，和迫切需要完整版朋友可以在**小白学视觉**微信公众号后台回复**扩展模块中文教程**获取\r\n\r\n&nbsp;\r\n[也可以通过CSDN下载抢鲜版](https://download.csdn.net/download/qq_42722197/12178880)\r\n"
  },
  {
    "path": "chapter 2/ArUco标记板的检测.md",
    "content": "ArUco标记板是一组如图2-13所示的标志。在为相机提供单个姿势上，他们就像单个标记一样工作。常用的方式是所有标记都在同一平面上的标志板，因为我们可以直接打印出来。但是，标记板不限于这种布置，还可以用2维或3维布局表示。\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223141221760.jpg\" height=\"300\">\r\n</p>\r\n\r\n板和一组独立标记之间的区别在于，板中标记之间的相对位置是先验的。这样可以将所有标记的角点用于估计摄像机相对于整个标记板的姿态。当我们使用一组独立的标记时，由于我们不知道标记在环境中的相对位置，因此可以分别估计每个标记的姿态。\r\n\r\n使用标记板的最主要好处有：\r\n-\t姿态估计更加通用。执行姿态估计仅需要一部分标记。因此，即便存在一些遮挡或仅是局部视图，也可以估算出姿态。\r\n-\t由于采用了大量的点对应关系标记角点，因此获得的姿态通常更加准确。\r\n\r\nAruco模块允许使用标记板，使用的是cv::aruco::Board类，它用代码清单2-15中的方式定义了板的布局。\r\n```cpp\r\n代码清单2-15 图像修复\r\nclass Board {\r\npublic:\r\n    std::vector<std::vector<cv::Point3f> > objPoints;\r\n    cv::Ptr<cv::aruco::Dictionary> dictionary;\r\n    std::vector<int> ids;\r\n};\r\n```\r\n\r\nBoard类对象具有三个参数：\r\n-\tObjPoints结构是在三维板参考系统中角点位置的列表，即其布局。对于每个标记，其四个角点均以标准顺序储存，即以顺时针顺序并从左上角开始存储。\r\n-\t字典参数指示板中标记属于哪个标识字典。\r\n-\t最后，ids结构指示objPoints中相对于指定字典的每一个标记的标记符。\r\n\r\n## 2.2.1\t标志板检测\r\nAruco模块提供了一个特定的函数estimatePoseBoard()用于执行标记板的姿态估计，该函数的使用方式在代码清单2-16中给出。\r\n```cpp \r\n代码清单2-16 板姿态估计\r\ncv::Mat inputImage;\r\n// 从某处读取相机参数\r\ncv::Mat cameraMatrix, distCoeffs;\r\nreadCameraParameters(cameraMatrix, distCoeffs);\r\n// 假设我们有一个创建板对象的函数\r\ncv::Ptr<cv::aruco::Board> board = cv::aruco::Board::create();\r\n...\r\nstd::vector<int> markerIds;\r\nstd::vector<std::vector<cv::Point2f>> markerCorners;\r\ncv::aruco::detectMarkers(inputImage, board.dictionary, markerCorners, markerIds);\r\n// 如果至少检测到一个标识\r\nif(markerIds.size() > 0) {\r\n    cv::Vec3d rvec, tvec;\r\n    int valid = cv::aruco::estimatePoseBoard(markerCorners, markerIds, board, cameraMatrix, distCoeffs, rvec, tvec);\r\n}\r\n\r\n```\r\nestimatePoseBoard()函数的每个参数含义为：\r\n\r\n-\tmakerCorners和markerlds：从detectMarkers()函数检测到的标记的结构。\r\n-\tboard：定义板布局及其ID。\r\n-\tcameraMatrix和distCoeffs：姿态估计所需的相机标定参数。\r\n-\t该函数返回值用于估计板姿态的标记总数。需要注意，由于仅考虑了其ID在Board::ids结构中列出的标记，因此不应使用markerCorners和markerlds中提供的所有标记。\r\n\r\ndrawAxis()函数可用于绘制获得的姿态，绘制结果如图2-14所示。即使标记版存在部分遮挡情况，依然可以估计姿态并绘制坐标轴，具体如图2-15所示。\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223142039640.jpg\" height=\"300\">\r\n</p>\r\n\r\n## 2.2.2\t标记板生成\r\n创建标记板对象需要指定环境中每个标记的角位置。但是，在很多情况下，标记板只是在同一平面和网格布局中的一组标记。aruco模块提供了可以轻松创建并打印这些类型标记的相关函数。GridBoard类是一个继承自Board类的类，它代表一个Board及其所有标记都位于同一平面和网格布局中，如图2-13所示。\r\n\r\n具体来说，标记板中的坐标系位于板平面中，坐标原点位于标记板左下角，同时Z轴指向外，具体形式如图2-14中所示（X轴：红色；Y轴：绿色；Z轴：蓝色）。\r\n\r\n可以通过调整GirdBoard类型的参数来调整标记板样式，具体参数为：\r\n-\tX轴方向上的标识数；\r\n-\tY轴方向上的标识数；\r\n-\t标识的边长；\r\n-\t标识的间隔长度；\r\n-\t标识的字典；\r\n-\t所有标识（X×Y标识）的ID。\r\n\r\n在GirdBoard类中提供了cv::aruco::GridBoard::create()静态函数用于上述参数的设置，该函数具体使用方法在代码清单2-17中给出。\r\n```cpp\r\n代码清单2-17 创建GirdBoard对象\r\ncv::aruco::GridBoard board = cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);\r\n```\r\n-\t第一和第二参数分别是在X轴和Y轴方向上的标记数；\r\n-\t第三和第四参数分别是标记边长和标记间隔。考虑到该板的估计姿态将以相同的单位进行测量，因此可以以任何单位提供它们（通常使用米）。\r\n-\t最后，提供了标记的字典。\r\n\r\n代码清单2-17中的代码创建的标记板由5×7=35个标识组成。默认情况下，每个标识的ID均以从0开始的升序分配，因此他们的编号为0、1、2，…，34。可以通过从Board.ids访问ID向量来轻松地对其进行自定义。\r\n\r\n在创建标记板之后，接下来将打印并使用它。cv::aruco::GridBoard::draw()中提供了生成GridBoard图像的功能，具体方法在代码清单2-18中给出。\r\n```cpp\r\n代码清单2-18 生成GridBoard图像\r\ncv::Ptr<cv::aruco::GridBoard> board = cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);\r\ncv::Mat boardImage;\r\nboard->draw( cv::Size(600, 500), boardImage, 10, 1 );\r\n```\r\n-\t第一个参数是输出图像的大小（以像素为单位）。在这种情况下为600x500像素。如果这与（标识）板尺寸不成比例，它将在图像上居中。\r\n-\tboardImage：带有板的输出图像。\r\n-\t第三个参数是（可选）以像素为单位的边距，因此所有标记都没有触及图像边界。在这种情况下，边距为10。\r\n-\t最后，标记边框的大小，类似于drawMarker()函数。预设值为1。\r\n\r\n使用代码清单2-18中的代码将输入如图2-16所示的标记板。为了方便用户使用，aruco模块中提供了创建标记板的示例程序create_board.cpp，可以直接调用创建标记板。该程序调用方式在代码清单2-19中给出。\r\n\r\n```cpp\r\n代码清单2-19：调用示例程序\r\n\"_output path_/aboard.png\" -w=5 -h=7 -l=100 -s=10 -d=10\r\n```\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223162544554.jpg\" height=\"300\">\r\n</p>\r\n \r\n```cpp\r\n接下来在代码清单2-20中给出检测标记板并估计位姿完整的示例程序。\r\n代码清单1-24 板检测样例\r\ncv::VideoCapture inputVideo;\r\ninputVideo.open(0);\r\ncv::Mat cameraMatrix, distCoeffs;\r\n// camera parameters are read from somewhere\r\nreadCameraParameters(cameraMatrix, distCoeffs);\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::Ptr<cv::aruco::GridBoard> board = cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);\r\nwhile (inputVideo.grab()) {\r\n    cv::Mat image, imageCopy;\r\n    inputVideo.retrieve(image);\r\n    image.copyTo(imageCopy);\r\n    std::vector<int> ids;\r\n    std::vector<std::vector<cv::Point2f> > corners;\r\n    cv::aruco::detectMarkers(image, dictionary, corners, ids);\r\n    // if at least one marker detected\r\n    if (ids.size() > 0) {\r\n        cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);\r\n        cv::Vec3d rvec, tvec;\r\n        int valid = estimatePoseBoard(corners, ids, board, cameraMatrix, distCoeffs, rvec, tvec);\r\n        // if at least one board marker detected\r\n        if(valid > 0)\r\n            cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvec, tvec, 0.1);\r\n    }\r\n    cv::imshow(\"out\", imageCopy);\r\n    char key = (char) cv::waitKey(waitTime);\r\n    if (key == 27)\r\n        break;\r\n}\r\n```\r\n\r\n为了方便使用，OpenCV在aruco模块中提供了detect_board.cpp文件，可以调用实现标记板的检测与位姿估计。使用该文件的参数输入在代码清单2-21中给出。\r\n```cpp\r\n代码清单2-21\r\n-c=\"_path_\"/calib.txt\" \"_path_/aboard.png\" -w=5 -h=7 -l=100 -s=10 -d=10\r\n```\r\n标记板检测视频的截图在图2-17给出。\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223163022569.jpg\" height=\"300\">\r\n</p>\r\n\r\n**提示**\r\n***完整视频可以在小白学视觉微信公众号后台回复“ArUco标记板检测”获取。***\r\n\r\n## 2.2.3\t增强标识检测\r\n如果我们已经知道标记板的布局信息，则可以利用已有信息对存在遮挡的标记板进行增强，记找到标记版中没有被识别的标记。opencv提供了fineDetectedMarkers()函数来实现这个步骤，但是需要注意的是，我们应该在执行detectMarkers()函数之后再执行这个函数。\r\n\r\n此功能的主要参数是检测到标记的原始图像、Board对象、检测到的标记的角点、检测到的标识的id和被拒标记的角点。我们可以从detectMarkers()函数获得被拒标记的角点，也被称为候选标记。这些候选对象是在原始图像中找到但未通过识别步骤的正方形（可能它们的内部编码存在太多错误），因此尚未被识别为标记。\r\n\r\n由于图像中的高频噪声、低分辨率或影响二进制代码提取的其他相关问题，这些候选对象有时候无法正确识别为实际标记。函数fineDetectedMarkers()可查找这些候选标记与标记板被遮挡标记之间的对应关系。该搜索函数基于以下两个信息：\r\n-\t候选对象与缺失标记的投影之间的距离。为了获得这些投影，有必要检测检测至少一个在板中的标记。如果提供了相机参数，则可使用相机参数获得投影。如果没有，则从局部单应性变换得出，但是这种情况仅允许使用平面板（即所有标记的角点的Z轴坐标都应相同）。\r\nfineDetectedMarkers()中的minRepDistance参数确定侯选角和投影标识角之间的最小欧几里得距离（默认值为10）。\r\n-\t二进制编码。如果候选对象超过最小距离条件，则再次分析其内部，以确定其是否确为投影标识。然而，在这种情况下，条件不是特别强并且允许的错误位数可能会更多。fineDetectedMarkers()函数中的参数errorCorrectionRate（默认值3.0）表示二进制编码。如果设置为负值，则根本不分析内部位而仅估计角点之间的距离。\r\n\r\nrefineDetectedMarkers()函数的使用方式再代码清单2-22中给出。\r\n```cpp\r\n代码清单2-22 标识检测完善示例\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::Ptr<cv::aruco::GridBoard> board = cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);\r\nstd::vector<int> markerIds;\r\nstd::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;\r\ncv::aruco::detectMarkers(inputImage, dictionary, markerCorners, markerIds, cv::aruco::DetectorParameters(), rejectedCandidates);\r\ncv::aruco::refineDetectedMarkersinputImage, board, markerCorners, markerIds, rejectedCandidates);\r\n// 调用此函数后，如果检测到任何新标识，它将从被拒的候选对象中删除，\r\n// 并将其包含在markerCorners和MarkerIds的末尾\r\n```\r\n还必须注意，在某些情况下，如果首先检测到的标记数过少（例如仅1个或2个标记），则丢失标记的投影质量可能很差，从而产生错误的对应关系。\r\n"
  },
  {
    "path": "chapter 2/ArUco标记检测.md",
    "content": "\r\n姿态估计在许多计算机视觉应用程序中非常重要，如机器人导航，增强现实等。该过程基于发现真实环境中的点与2D图像投影之间的对应关系。 由于投影是从3D到2D，缺少了一个维度，使得位姿估计变成一个比较困难的问题，因此通常需要使用一些人为制作的标记或这基准标记来使增加信息量，以此使位姿估计更容易一些。\r\n\r\n最受欢迎的方法之一是使用二进制方形基准标记。 这些标记的主要优点是单个标记可以提供足够的对应关系（四个角）来获得相机的姿态。 而且，内部二进制编码原理使得它们识别稳定，并且具有一定的容错性，从而允许应用错误检测和纠正技术。\r\n\r\naruco模块基于ArUco库，该库是由RafaelMuñoz和Sergio Garrido开发的用于检测方形基准标记的最受欢迎的库之一。\r\n\r\n可以通过“#include <opencv2/aruco.hpp>”代码在程序中加入aruco模块中的相关函数。\r\n## 2.1.1 标记和字典\t\r\nArUco标记是由宽黑色边框和确定其标识符（id）的内部二进制矩阵组成的正方形标记。黑色边框有助于其在图像中的快速检测，内部二进制编码用于识别标记和提供错误检测和纠正。标记尺寸的大小决定内部矩阵的大小，例如尺寸为4x4的标记由16位组成二进制组成。在图2-1中给出了一些ArUco标记的示例。\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223104948460.jpg\" height=\"350\">\r\n</p>\r\n需要注意的是，在环境中ArUco标记可能会发生旋转或者倒着的情况，但是在检测时借助二进制编码能够确定其旋转的情况，以便明确标识每个角。因此ArUco标记不会出现中心对称和轴对称的图案。\r\n\r\n标记字典是在具体应用时对标记的识别依据，它是对每个二进制编码及对应含义的存储。\r\n字典具有两个主要特性，分别是字典大小和标记尺寸：\r\n\r\n-\t字典大小是组成字典中存储的标记数目。\r\n-\t标记尺寸是这些标记的位数。\r\n\r\n\r\naruco模块中具有一些预定义的词典，这些词典具有不同的字典大小和标记尺寸。\r\n\r\n有些标记是将信息直接存储在二进制中，在读取时将二进制转换成十进制从而得到真实的数字信息。但是ArUco标记不是这样做的，因此如果标记的尺寸较大，，那么二进制数据将有较多的位数，管理较多的二进制数据在实时系统中是不实际的。但是，通过标记id来寻找字典中的信息将极大的缩减二进制位数。例如，字典中的前5个标记的id为：0、1、2、3和4，而这5个标记可以存储任意的数据。\r\n## 2.1.2 创建标记\r\n在检测到标记之前，需要先打印标记，然后将其放置在环境中。可以使用drawMarker()函数生成标记图像。例如可以通过代码清单2-1中的代码生成标记图像。\r\n```cpp\r\n代码清单2-1：生成标记图像\r\ncv::Mat markerImage;\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::aruco::drawMarker(dictionary, 23, 200, markerImage, 1)\r\n\r\n```\r\n在生成标记时，首先通过选择aruco模块中的预定义词典来创建Dictionary对象。代码清单2-1中创建的是一个具有250个标记和标记尺寸6×6位（DICT_6X6_250）字典。\r\n\r\n绘制ArUCo标记的drawMarker()函数具有五个参数，每个参数的含义具体为：\r\n-\t第一个参数是之前创建的Dictionary对象。\r\n-\t第二个参数是标记id，代码清单2-1中设置的是字典DICT_6X6_250的标记23。需要注意的是，每个字典由不同数量的标记组成。在DICT_6X6_250字典中，有效id从0到249。任何超出有效范围的特定id都会产生异常。\r\n-\t第三个参数200是输出标记图像的大小。在这种情况下，输出图像的大小将为200x200像素。请注意，此参数应足够大以存储特定字典的位数。例如，对于6×6位的标记，我们无法生成5×5像素的图像（并且这不考虑标记边界）。此外，为避免变形，此参数应与位数+边界大小成比例，或者至少比标记大小大得多，以使变形不明显。\r\n-\t第四个参数是输出图像。\r\n-\t最后一个参数是一个可选参数，用于指定标记黑色边框的宽度。指定的大小与位数成正比。例如，值2表示边框的宽度等于两个内部位的大小。预设值为1。\r\n\r\n代码清单2-1生成的图像在图2-2给出。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200223105957333.jpg\" height=\"300\">\r\n</p>\r\n\r\naruco模块样例中提供了create_marker.cpp文件用于生成指定的ArRco标记，调用该函数时输入参数需要与代码清单2-2中的格式相同。\r\n```cpp\r\n代码清单2-2：create_marker.cpp的参数\r\n\"/Users/Sarthak/Dropbox/OpenCV_GSoC/marker.png\" -d=10 -id=1\r\n```\r\n## 2.1.3 标记检测\r\n对ArUco标记进行检测时，需要返回检测结果的一系列信息，包括：\r\n-\t图像中四个角的位置（按原始顺序）。\r\n-\t标记的ID。\r\n\r\n标记检测过程主要分为两步：\r\n1.\t筛选候选标记。在此步中，将对图像进行分析，找到可以用作标记的正方形。首先从自适应阈值分割标记开始，然后从阈值图像中提取轮廓，之后去除那些非凸形或不近似于正方形的轮廓。此外还可以设置一些额外筛选条件，例如删除太小或太大的轮廓，删除彼此太近的轮廓等。\r\n2.\t在确定候选标记后，需要通过分析其内部编码来确定它们是否为ArUco标记。这步中需要提取每个标记的标记位。首先应用透视变换获得规范形式的标记，然后使用Otsu算法对规范图像进行阈值处理进行白色位和黑色位分离。根据标记大小和边框大小将图像划分为不同的单元，并对每个单元上的黑色或白色像素进行计数，以确定其是白色位还是黑色位。最后，分析这些像素位以确定标记是否属于特定词典，并在必要时使用纠错技术。\r\n\r\n例如需要检测的图片如图2-3所示，检测出的标记如图2-4所示。候选标记在第二步被去除的情况如图2-5所示。\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200223111435710.jpg\" height=\"350\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200223111650824.png\" height=\"350\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022311195836.jpg\" height=\"350\">\r\n</p>\r\n\r\n在aruco模块中，detectMarkers()函数实现标志的检测。此函数是该模块中是最重要的函数，其他函数都是基于detectMarkers()函数的返回值进行再处理。代码清单2-3给出了检测标记的示例程序。\r\n```cpp\r\n代码清单2-3：检测标记\r\ncv::Mat inputImage;\r\n...\r\nstd::vector<int> markerIds;\r\nstd::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;\r\ncv::Ptr<cv::aruco::DetectorParameters> parameters;\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::aruco::detectMarkers(inputImage, dictionary, markerCorners, markerIds, parameters, rejectedCandidates);\r\n```\t\t\t\r\ndetectMarkers()函数具有六个参数，每个参数的含义为：\r\n1.\t第一个参数是需要检测标记的图像。\r\n11.\t第二个参数是字典对象，本教程中使用的是DICT_6X6_250预定义词典。\r\n12.\t第三个参数是检测到的标记的角点列表。对于每个标记，其四个角均按其原始顺序返回（从左上角开始顺时针旋转）。因此，第一个角是左上角，然后是右上角，右下角和左下角。\r\n13.\t第四个参数是检测到的每个标记的id。需要注意的是第三个参数和第四个参数具有相同的大小。\r\n14.\t第五个参数是类型DetectionParameters的对象。该对象包括在检测过程中可以自定义的所有参数。下一部分将详细说明此参数。\r\n15.\t第六个参数是去除的候选标记列表，即找到的但未提供有效编码的正方形。每个候选标记也由其四个角定义，其格式与第三个参数相同，该参数可以省略。\r\n\r\n检测并识别标记之后之后，可以利用aruco模块提供的drawDetectedMarkers()函数在输入图像中绘制检测到的标记，该函数的使用方法在代码清单2-4中给出。\r\n```cpp\r\n代码清单2-4：绘制标记\r\ncv::Mat outputImage\r\ncv::aruco::drawDetectedMarkers(image, markerCorners, markerIds);\r\n```\r\ndrawDetectedMarkers()函数具有三个参数，每个参数的含义如下：\r\n18. 第一个参数是将绘制标记的输入/输出图像（通常是与检测到标记的图像相同）\r\n19.\t第二个参数是检测到的标记的角点列表\r\n20.\t第三个参数是检测到的每个标记的id\r\n\r\n标记绘制结果在图2-6给出。\r\n**注意**\r\n*此功能仅用于可视化，在实际项目中可以省略。*\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/202002231129324.jpg\" height=\"350\">\r\n</p>\r\n\r\n使用上面两个功能，我们可以创建一个基本的标记检测循环程序，对相机拍摄的场景直接检测标记，该例程在代码清单2-5中给出。\r\n\r\n```cpp\r\n代码清单2-5：相机检测标记\r\ncv::VideoCapture inputVideo;\r\ninputVideo.open(0);\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\nwhile (inputVideo.grab()) {\r\n    cv::Mat image, imageCopy;\r\n    inputVideo.retrieve(image);\r\n    image.copyTo(imageCopy);\r\n    std::vector<int> ids;\r\n    std::vector<std::vector<cv::Point2f> > corners;\r\n    cv::aruco::detectMarkers(image, dictionary, corners, ids);\r\n    // if at least one marker detected\r\n    if (ids.size() > 0)\r\n        cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);\r\n    cv::imshow(\"out\", imageCopy);\r\n    char key = (char) cv::waitKey(waitTime);\r\n    if (key == 27)\r\n        break;\r\n}\r\n```\r\n 完整的示例程序在aruco模块文件夹内的detect_markers.cpp中。可以通过代码清单2-6中的命令执行该项目。\r\n\r\n```cpp\r\n代码清单2-6：detect_markers.cpp文件需要的参数\r\n-c=\"_path_/calib.txt\" -d=10\r\n```\r\n## 2.1.4 姿态估计\r\n检测到标记后，我们需要从标记中获取相机姿态。要执行相机姿态估计，我们需要了解相机的标定参数。这是相机内参矩阵和畸变系数。使用OpenCV基础库中标定函数即可，这里不详细介绍如何对相机进行标定。我们默认读者已经完成了相机的标定。\r\n\r\n使用ArUco标记估计姿态时，可以分别估计每个标记的姿态。如果要从一组标记中估计一个姿态，则要使用aruco Boards（请参阅ArUco Boards教程，这里不做过多介绍）。\r\n\r\n相机相对于标记的姿态是从标记坐标系到相机坐标系的3d转换。它由旋转量和平移矢量确定。aruco模块提供了cv::aruco::estimatePoseSingleMarkers()函数用于估计所有检测到的标记的姿态，具体使用方法在代码清单2-7中给出。\r\n\r\n```cpp\r\n代码清单2-7：估计姿态\r\ncv::Mat cameraMatrix, distCoeffs;\r\n2.\t...\r\nstd::vector<cv::Vec3d> rvecs, tvecs;\r\ncv::aruco::estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs, rvecs, tvecs);\r\n```\r\ncv::aruco::estimatePoseSingleMarkers()函数有六个参数，每个参数的含义如下：\r\n1. 第一个参数是detectMarkers()函数返回的标记角点的向量。\r\n7.\t第二个参数是标记的大小，以米或其他长度单位为单位。\r\n8.\t第三个参数和第四个参数是相机的标定参数。\r\n9.\t最后两个参数分别是角中每个标记的旋转和平移向量。\r\n\r\n此外aruco模块还提供了绘制坐标轴的cv::aruco::drawAxis()函数，可以检查姿态估计结果，该函数的使用方法在代码清单2-8中给出。\r\n\r\n```cpp\r\n代码清单2-8：绘制坐标轴\r\ncv::aruco::drawAxis(image, cameraMatrix, distCoeffs, rvec, tvec, 0.1);\r\n```\r\ncv::aruco::drawAxis()函数具有六个参数，每个参数的含义如下\r\n1.\t第一个参数是绘制坐标轴轴的输入/输出图像（通常是与检测到标记的图像相同）。\r\n13.\t第二个和第三个是相机的标定参数。\r\n14.\t第四个和第五个是要绘制坐标轴物体的姿态参数。\r\n15.\t第六个参数是轴的长度，单位与第五个参数（通常为米）相同\r\n\r\n此函数假定标记坐标系位于Z轴指向的标记中心。 坐标轴颜色对应为X：红色，Y：绿色，Z：蓝色，具体如图2-7所示。\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223114221549.jpg\" height=\"350\">\r\n</p>\r\n\r\n**提示**\r\n***在小白学视觉微信公众号后台回复“坐标轴检测”，即可获得该检测视频完整版*。** \r\n\r\n可以通过代码清单2-9实现基于单个标记利用相机进行实时姿态估计，程序运行结果的视频截图在图2-8给出。\r\n\r\n```cpp\r\n代码清单2-9：实时姿态估计\r\ncv::VideoCapture inputVideo;\r\ninputVideo.open(0);\r\ncv::Mat cameraMatrix, distCoeffs;\r\n// camera parameters are read from somewhere\r\nreadCameraParameters(cameraMatrix, distCoeffs);\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\nwhile (inputVideo.grab()) {\r\n\t   cv::Mat image, imageCopy;\r\n    inputVideo.retrieve(image);\r\n    image.copyTo(imageCopy);\r\n    std::vector<int> ids;\r\n    std::vector<std::vector<cv::Point2f>> corners;\r\n    cv::aruco::detectMarkers(image, dictionary, corners, ids);\r\n    // if at least one marker detected\r\n    if (ids.size() > 0) {\r\n        cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);\r\n        std::vector<cv::Vec3d> rvecs, tvecs;\r\n        cv::aruco::estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs, rvecs, tvecs);\r\n        // draw axis for each marker\r\n        for(int i=0; i<ids.size(); i++)\r\n            cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvecs[i], tvecs[i], 0.1);\r\n    }\r\n    cv::imshow(\"out\", imageCopy);\r\n    char key = (char) cv::waitKey(waitTime);\r\n    if (key == 27)\r\n        break;\r\n}\r\n ```\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200223114941709.jpg\" height=\"350\">\r\n</p>\r\n\r\n\r\n完整的代码程序在aruco模块文件夹内的detect_markers.cpp中。可以通过代码清单2-10中的命令执行该项目。\r\n```cpp\r\n代码清单2-10：调用实时姿态的参数\r\n-c=\"_path_/calib.txt\" -d=10\r\n```\r\n## 2.1.5 选择字典\r\naruco模块了提供Dictionary类来表示标记字典。除了标记的尺寸和字典中标记的数量之外，标记间的距离也是字典的重要参数。标记间的距离是指字典中所有标记之间的最小距离，它决定了词典的错误检测能力和纠错能力。\r\n\r\n一般来说，字典大小越小，标记尺寸越大，标记间的距离越远，反之亦然。但是，如果标记尺寸变大，那么需要冲图像中提取的数据量也更大，这使得对较大尺寸标记的检测更加复杂，因此通常采用尽量缩小字典大小的方式来增加标记间的距离。例如，如果我们只需要10个标记，那么此时使用由10个标记组成的字典要比使用由1000个标记组成的字典更好。因为由10个标记组成的字典会有更高的标记间的距离，从而对错误有更强的鲁棒性。为了让用户可以选择合适的字典来增加程序的鲁棒性，aruco模块提供了多种字典供拥护选择，接下来将详细介绍每种字典构建的方法。\r\n### 1. 预定义的字典\r\n\r\n使用预定义字典是选择字典最简单的方法。aruco模块内提供了一组预定义的字典，其中包含多种标记尺寸和标记数量，可以通过代码清单2-11中的代码选择字典的大小和标记的尺寸。\r\n```cpp\r\n代码清单2-11：选择预定义字典\r\ncv::Ptr<cv::aruco::Dictionary>dictionary=cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\n```\r\n代码清单2-11中的代码表示创建一个字典大小为250，标记尺寸为6×6的字典。使用预定于的字典，标记尺寸可以在4×4到7×7之间自由选择。同样，字典大小可以在50、100、250和1000中自由选择。在实际需求中，我们需要选择合适的字典。例如在同时满足需求的前提下，DICT_6X6_250的字典要好于DICT_6X6_1000，因为字典越小，标记间的距离就越大。\r\n### 2. \t自动选择字典\r\n\r\n有时我们并不知道应该选择多大的字典，因此可以将选择字典的任务交给程序，由程序自动选择一个合适的字典，来保证字符间的距离最大。可以用代码清单2-12中的命令实现自动选择字典。\r\n```cpp\r\n代码清单2-12：自动选择字典\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::generateCustomDictionary(36, 5);\r\n```\r\n代码清单2-12中的命令将会生成一个由36个尺寸为5×5的标记组成的自定义字典。根据参数的不同，这个过程可能会花费几秒钟的时间(对于较大的字典和较高的标记尺寸，这个过程会慢一些)。\r\n\r\n### 3. \t自定义字典:\r\n\r\n有时我们可以根据自己的需要自定义字典，这种方式的好处是我们可以使用任意的编码方式。在自定义字典之前，我们需要重新定义Dictionary类，定义方式在代码清单2-13中给出。\r\n\r\n```cpp\r\n代码清单2-13：重定义Dictionary类\r\nclass Dictionary {\r\n    public:\r\n    Mat bytesList;\r\n    int markerSize;\r\n    int maxCorrectionBits;    \r\n    ...\r\n}\r\n```\r\n代码清2-13中每个参数的含义如下：字典参数为：\r\n-\tbytesList是包含关于标记代码的所有信息的数组。\r\n-\tmarkerSize是每个标记尺寸的大小(例如，尺寸5×5标记为5)。\r\n-\tmaxCorrectionBits是标记检测过程中可以纠正的错误比特数的最大值。如果这个值过高，就会导致大量的误报。\r\n\r\n**注意**\r\n***除非有特殊的需求，否则建议使用模块中自带的预定义字典。***\r\n\r\nbytesList中的每一行表示一个字典标记。但是，这个标记不是以二进制形式存储的，而是以一种特殊的格式存储的，以简化它们的检测。Dictionary类中提供了Dictionary::getByteListFromBits()函数将标记转换为这种形式，具体如代码清单2-14中所示。\r\n```cpp\r\n代码清单2-14：自定义字典示例\r\ncv::aruco::Dictionary dictionary;\r\n// markers of 6x6 bits\r\ndictionary.markerSize = 6;\r\n// maximum number of bit corrections\r\ndictionary.maxCorrectionBits = 3;\r\n// lets create a dictionary of 100 markers\r\nfor(int i=0; i<100; i++)\r\n{\r\n    // assume generateMarkerBits() generate a new marker in binary \r\n    //format, so that markerBits is a 6x6 matrix of CV_8UC1 type,\r\n    // only containing 0s and 1s\r\n    cv::Mat markerBits = generateMarkerBits();\r\n    cv::Mat markerCompressed = cv::aruco::Dictionary::getByteListFromBits(markerBits);\r\n    // add the marker as a new row\r\n    dictionary.bytesList.push_back(markerCompressed);\r\n}\r\n```\r\n\r\n## 2.1.6参数检测器\r\n前文我们见到detectmarker()函数中的DetectorParameters对象。该对象包含标记检测过程中可以自定义的所有选项。在本小节中，我们将详细介绍这些参数。\r\n\r\n### 1. 阈值\r\n\r\n标记检测过程的第一步是对输入图像进行自适应阈值化。例如，本教程所使用图像的阈值图像如所示。\r\n阈值参数可以通过如下参数进行定义：\r\n-\tint  adaptiveThreshWinSizeMin, \r\n-\tint  adaptiveThreshWinSizeMax, \r\n-\tint  adaptiveThreshWinSizeStep\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223120037423.jpg\" height=\"350\">\r\n</p>\r\nadaptivewellwinsizemin和adaptivewellwinsizemax参数表示为自适应阈值选择阈值窗口大小(以像素为单位)的区间，adaptiveThreshWinSizeStep表示自适应窗口每次改变的大小。例如，默认值adaptivewellwinsizemin=3、adaptivewellwinsizemax=23以及adaptiveThreshWinSizeStep=10。默认值表示阈值窗口依次为3×3、13×13和23×23。\r\n\r\n此外，标记的尺寸也也对图像阈值处理产生影响，关于阈值尺寸的相关参数为：\r\n\r\n- double  minMarkerPerimeterRate\r\n-\tdouble  maxMarkerPerimeterRate\r\n\r\n这些参数决定了标记点的最小和最大尺寸，具体来说就是标记点的最大和最小周长。这两个参数的单位不是像素值，而是相对于输入图像的最大尺寸的比例。例如，大小为640×480，相对标记最小周长为0.05的图像，其最小标记周长为640×0.05 = 32像素，因为640是图像的最大尺寸。\r\n\r\n如果minMarkerPerimeterRate太小，则会大大降低检测性能，因为在未来阶段需要考虑更多的轮廓。对于maxMarkerPerimeterRate参数，这种影响不是很明显，因为通常小轮廓比大轮廓要多得多。如果minMarkerPerimeterRate值为0和maxMarkerPerimeterRate值为4(或更大)则等效于考虑图像中的所有轮廓，但是出于性能原因不建议这样做。\r\n\r\n有时标记在图像中会显示成多边形，可以用多边形率来表示形变的程度：\r\n\r\n-\tdouble  polygonalApproxAccuracyRat\r\n\r\n这个值决定了多边形近似可以产生的最大误差。此参数是相对于候选对象周长长度的比例。例如，如果候选对象的周长为100像素，且polygonalApproxAccuracyRate的值为0.04，则最大误差为100x0.04=5.4像素。在大多数情况下，使用该参数的默认值即可正常工作，但是对于高失真的图像，可能需要更高的数值。该参数的默认值为0.05。\r\n加下来详细介绍多个标记之间的距离参数：\r\n\r\n-\tdouble  minCornerDistanceRate\r\n\r\n同一标记上任意对角之间的最小距离。它是相对于界标周长表示的。像素的最小距离是周长* minCornerDistanceRate。该参数的默认值为0.05。\r\n\r\n-\tdouble  minMarkerDistanceRate\r\n\r\n两个不同的标记之间的最小距离。它是相对于两个标记的最小标记周长来表示的。如果两个候选标记太接近，较小的那个就会被忽略。该参数的默认值为0.05。\r\n\r\n-\tint  minDistanceToBorder\r\n\r\n任何标记角到图像边界的最小距离(以像素为单位)。该参数的默认值为3。\r\n\r\n2.\t分析标志图像的位信息\r\n\r\n在候选标志检测完成后需要对每个候选标志进行数位分析，以确定它们是否是ArUco标记。“位”就是组成标志的最小单元，每个位表示一位二进制，例如一个6×6的标志位数就是36位。\r\n\r\n在分析标志中二进制代码之前，需要提取二进制位。为了能够精准的提取二进制信息，首先需要消除了视角形变，之后使用Otsu算法对去除形变的图像进行阈值处理，以分离黑白像素。图2-10就是去除形变和二值化后的结果。\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223120719951.jpg\" height=\"350\">\r\n</p>\r\n\r\n之后将图像划分到一个网格中，网格的单元格数目与标记中的位数目相同。在每个单元格上，计算黑白像素的数量来决定最终单元格的的颜色，从而确定该位表示的是0还是1。图2-11是划分单元格结果。\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/202002231208441.jpg\" height=\"350\">\r\n</p>\r\n\r\n在这个过程中有几个重要的参数可以选择，具体内容如下：\r\n\r\n-\tint  markerBorderBits\r\n\r\n此参数指示标记边框的宽度。它与每个位的大小有关。例如，数值2表示边界的宽度为每个位宽度的2倍。此参数需要与我们正在使用标记的边框大小一致。我们可以在标记绘制函数drawMarker()中设置边框的大小，并及时记录它。该参数的默认值为1.\r\n\r\n-\tdouble  minOtsuStdDev\r\n\r\n这个值决定了执行Otsu阈值算法的像素值的最小标准偏差。如果偏差很小，可能所有的单元格都是黑色(或白色)，应用Otsu没有意义。如果是这种情况，所有的位都被设置为0(或1)，这取决于平均值与128的关系。该参数的默认值为5.0。\r\n\r\n-\tint  perspectiveRemovePixelPerCell\r\n\r\n该参数为在去除透视失真(包括边框)后得到的图像中每个单元格像素数目，即图2-11中红色方框的大小。例如，我们处理的是5×5的标记尺寸和1边界的标记图像，那么每个的单元格像素宽度为5 + 2*1 = 7(边界必须计数两次)。像素总数为7×7。如果tiveremovepixelpercell的值为10，则每个单元格内有70×70个像素。该参数值越高，提取过程越快，但性能越差。该参数的默认值为4。\r\n\r\n-\tdouble  perspectiveRemoveIgnoredMarginPerCell\r\n\r\n在提取每个单元的位元时，将计算黑白像素的数目。通常，不建议考虑所有的单元格像素。相反，最好忽略单元格边缘的一些像素。这样做的原因是，在消除了透视失真之后，每个像素的颜色通常并不是完全分离的，白色单元可以入侵黑色单元的一些像素(反之亦然)。因此，为了避免计算错误的像素，最好忽略一些像素，如图2-12所示只考虑绿色方块内的像素。在右边的图像中可以看到，生成的像素包含来自邻居单元的更低的噪声。参数指示了红色和绿色方块之间的差异。\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223121121631.jpg\" height=\"300\">\r\n</p>\r\n\r\n此参数表示绿色方框外忽略部分相对于单元格的总大小。例如，如果单元格大小为40像素，并且该参数的值为0.1，则单元格中忽略40*0.1=4像素的空白。这意味着在每个单元上要分析的像素总数实际上是32x32，而不是40x40。该参数默认值为0.13。\r\n\r\n### 3.\t标记识别\r\n\r\n提取标记中二进制信息后，接下来检查提取的二进制代码是否属于标记字典，如果需要，可以执行错误纠正。接下来依次介绍在这个过程中涉及的参数：\r\n\r\n-\tdouble  maxErroneousBitsInBorderRate\r\n\r\n标记边界的单元格应该是黑色的。此参数指定边界中允许的错误率，即边界中白色单元格的最大数量。它是相对于标记中总的单元格数目来表示的。该参数的默认值为0.35。\r\n\r\n-\tdouble  errorCorrectionRate\r\n\r\n每个标记字典都有一个理论上可以更正的最大位数，这个值可以通过errorCorrectionRate参数进行修改。例如，如果允许可以更正的位数(与字典种类相关)是6，并且errorCorrectionRate的值是0.5，那么可以更正的实际最大比特数是6*0.5=3位。这个值有助于降低错误纠正能力，以避免误报，默认值为0.6。\r\n\r\n### 4.\t角点位置细化\r\n\r\n检测并识别标记后，最后一步是对角点位置执行亚像素细化。这一步是可选的，只有在标记角点位置必须为精确值的情况下才有意义，例如姿态估计、运动测量等问题中。角点位置细化通常是一个耗时的步骤，默认情况下是不使用的。接下来依次介绍在这个过程中涉及的参数：\r\n\r\n-\tint  cornerRefinementMethod\r\n\r\n此参数确定是否执行角亚像素处理。如果不需要精确的角点坐标，可以进行角点位置细化。该参数默认值为CORNER_REFINE_NONE，表示不进行角点位置细化。\r\n\r\n-\tint  cornerRefinementWinSize\r\n\r\n此参数确定亚像素细化过程的窗口大小。数值较大会产生窗口区域包含临近的图像角点的效果，使得标记角在处理过程中移动到错误位置。并且，数值较大会影响计算时间。该参数的默认值为5。\r\n\r\n-\tint  cornerRefinementMaxIterations      double  cornerRefinementMinAccuracy\r\n\r\n这两个参数决定了亚像素细化过程的停止条件。cornerRefinementMaxIterations表示迭代的最大次数，cornerRefinementMinAccuracy表示停止迭代之前的最小错误值，两者满足任意一个条件即停止细化迭代。如果迭代次数太多，计算时间将会极大延长，但是如果过低，则会产生较差的亚像素细化。这两个参数的默认值分别为：30和0.1。\r\n\r\n"
  },
  {
    "path": "chapter 2/Aruco模块常见问题.md",
    "content": "# 2.6\tAruco模块常见问题\r\n我们在使用aruco模块是可能经常会遇到一些问题，我们整理了部分常见的内容，在下方详细列出：\r\n-\t如果只想标记一些对象，应该使用什么?\r\n\r\n如果只需要单个ArUco标记，那可以在要识别的每个对象中放置一个或多个具有不同ID的标记。\r\n\r\n-\t标记检测用的哪种算法？\r\n\r\naruco模块基于原始的aruco库。检测过程算法的完整介绍可以阅读Automatic generation and detection of highly reliable fiducial markers under occlusion文论了解。\r\n\r\n-\t如果标记没有被正确探测到，应该怎么办?\r\n\r\n许多因素都可能影响对标记的正确检测。我们可能需要调整DetectorParameters对象中的一些参数。首先检查detectmarker()函数是否将标记作为被拒绝的候选标记返回。根据这一点，可以尝试修改不同的参数。如果使用ArUco board，也可以尝试refineDetectedMarker()函数。\r\n\r\n-\tArUco board有什么优点？有什么缺点？\r\n\r\n使用ArUco board，可以从一组标记而不是单个标记中获得摄像机的姿态。这样就可以解决标记版局部视图被遮挡的问题，因为仅需一个标记就可获得摄像机的姿态。大多数情况下，使用更多的标记来估计姿态将比使用单一标记的结果更准确。\r\n主要缺点是不如单个标记那样通用。\r\n\r\n-\t与ArUco board相比，ChArUco board有什么优点和缺点?\r\n\r\nChArUco board将棋盘和ArUco board结合起来。因此ChArUco board提供的角点比ArUco board (或单标记)提供的角点更准确。\r\n主要缺点是ChArUco board不如ArUco board用途广泛。ChArUco board是具有特定标记布局的平面板，而ArUco board甚至可以在3d中具有任何布局。此外，ChArUco板上的标记通常更小，更难以检测。\r\n\r\n-\t如果不需要相机姿态估计，应该使用ChArUco board吗?\r\n\r\n不，ChArUco board的主要目标是为摄像机姿态估计和摄像机标定提供高精度的角点。\r\n\r\n-\t应该将ArUco board上的所有标记放在同一个平面上吗?\r\n\r\n不，ArUco board上的标记可以放置在其3d坐标系中的任何位置。\r\n\r\n-\t应该将ChArUco board上的所有标记放在同一个平面上吗?\r\n\r\n是的，ChArUco棋盘上的所有标记都需要在同一个平面上，它们的布局由棋盘形状决定。\r\n\r\n-\t想进行相机标定，可以使用这个模块吗?\r\n\r\n可以，aruco模块提供了使用aruco board和ChArUco board标定摄像机的功能。\r\n\r\n- 应该使用ChArUco board还是ArUco board进行标定？\r\n\r\n强烈推荐使用ChArUco板进行标定，因为它精度更高。\r\n\r\n-\t可以使用这个模块来检测基于二进制基准标记的其他标记吗?\r\n\r\n可能可以，您需要将原始库的字典转换成符合aruco模块的格式。\r\n\r\n-\t是否需要将字典信息存储在一个文件中，以便在不同的应用中读取它?\r\n\r\n如果正在使用某个预定义的字典，则没有必要这样做。否则，建议将其保存到文件中。\r\n\r\n-\t是否需要将板子信息存储在一个文件中，以便在不同的执行中使用它?\r\n\r\n如果使用的是GridBoard或ChArUco board，则只需要存储提供给GridBoard::create()或ChArUco::create()函数的板的测量值。如果手动修改板的标记id，或者使用不同类型的板，就应该将它保存到文件中。\r\n\r\n-\taruco模块是否提供将字典或板信息保存到文件的功能?\r\n\r\n现在还没有。Dictionary和Board类的数据成员都是公共的，可以很容易地存储。\r\n"
  },
  {
    "path": "chapter 2/ChArUco角的检测.md",
    "content": "ArUco标识和标记板由于其快速检测和多功能性而非常有用。然而，ArUco标记存在即便是在应用亚像素细化之后，其角点的位置精度也不太高的问题。相反，如果每个角点都被两个黑色正方形包围，那么棋盘图案的角点可以更精确地细化。但是，找到棋盘图案并不像找到ArUco板那样通用，它要求它必须完全可见，并且不允许有任何遮挡。ChArUco标记板试图将这两种方法的优点结合起来，具体形式如图2-18所示。\r\n\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/2020022316374383.jpg\" height=\"250\">\r\n</p>\r\n\r\nArUco标记用于插补棋盘的角点的位置，因此它允许遮挡或使用局部视图，故其具有标记板的通用性。而且由于内插的角点属于棋盘，因此就压像素精度而言它们非常精确。当在需要高精度场合时，例如在相机校准中，ChArUco标记板比标准ArUco标记板更好。\r\n\r\n## 2.3.1\t创建ChArUco标志板\r\naruco模块提供了cv::aruco::CharucoBoard类代表Charuco标记板，并继承自Board类。此类被定义在<opencv2/aruco/charuco.hpp>头文件中，在使用时需要将该头文件包含进去。该类中需要如下信息：\r\n-\tX轴方向上的棋盘方块数\r\n-\tY轴方向上的棋盘方块数\r\n-\t方块的边长\r\n-\t标识的边长\r\n-\t标识的字典\r\n-\t标识的ID\r\n\r\naruco模块提供了创建CharucoBoard类的静态函数cv::aruco::CharucoBoard::create()，该函数的使用方式在代码清单2-23中给出\r\n```cpp\r\n代码清单2-23 创建Charuco Board\r\ncv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);\r\n```\r\n-\t第一个和第二个参数分别是X轴、Y轴方向上的正方形数。\r\n-\t第三个和第四个参数分别是正方形和标记的边长。考虑到估计姿态时将以相同的单位进行测量（通常使用米），因此可以使用任何单位。\r\n-\t最后一个参数提供了标记的字典。\r\n\r\n在默认情况下，每个标识的ID都如GirdBoard::create()一样从0开始按升序分配。也如同Board父类一样，可以通过board.ids访问ID向量，从而轻松地对其进行自定义。一旦有了CharucoBoard对象，我们就能创建图像并进行打印。我们可以通过CharucoBoard::draw()函数实现生成图像，该函数的使用方式在代码清单2-24中给出。\r\n```cpp\r\n代码清单2-24 创建图像\r\ncv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);\r\ncv::Mat boardImage;\r\nboard->draw( cv::Size(600, 500), boardImage, 10, 1 );\r\n```\r\n- 第一个参数是输出图像的大小（以像素为单位）。在示例中为600×500像素。\r\n-\tboardImage：带有标记板的输出图像；\r\n-\t第三个参数（可选）是以像素为单位的边距，在示例中，边距为10；\r\n-\t最后是标识边框的大小，类似于函数drawMarker()。预设值为1。\r\n\r\n调用代码清单2-24中的程序，输出图像如图2-19所示。\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223164241928.jpg\" height=\"300\">\r\n</p>\r\n \r\n    OpenCV中aruco模块提供了create_board_charuco.cpp文件，文件中含有生成ChArUco标记板的完整程序，使用该程序需要出入代码清单2-25中的参数。\r\n```cpp\r\n代码清单2-25\r\n\"_ output path_/chboard.png\" -w=5 -h=7 -sl=200 -ml=120 -d=10\r\n```\r\n## 2.3.2\tChArUco标记板检测\r\n当我们检测一个ChArUco标记板时，实际检测到的是板上的每个棋盘角点。ChArUco板上的每个角点都分配有唯一的标识符（id）。这些id从0到板上角点的总数。所以，ChArUco板的检测由以下几部分组成：\r\n\r\n-\tstd::vector<cv::Point2f> charucoCorners:检测到的角的图像位置列表；\r\n-\tstd::vector<int> charucoIds:在charucoCorners中的每个检测到的角的ID。\r\n\r\nChArUco角点的检测基于先前检测到的标识。因此，首先检测标识，然后在标识中插入ChArUco角点。aruco模块中提供了cv::aruco::interpolateCornersCharuco()函数用于检测ChArUco角点，该函数的的使用方式在代码清单2-26中给出。\r\n```cpp\r\n代码清单2-26 检测ChArUco角点\r\ncv::Mat inputImage;\r\ncv::Mat cameraMatrix, distCoeffs;\r\n// 从某处读取相机参数\r\nreadCameraParameters(cameraMatrix, distCoeffs);\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);\r\n...\r\nstd::vector<int> markerIds;\r\nstd::vector<std::vector<cv::Point2f>> markerCorners;\r\ncv::aruco::detectMarkers(inputImage, board.dictionary, markerCorners, markerIds);\r\n// 如果至少检测到一个标识\r\nif(markerIds.size() > 0) {\r\n    std::vector<cv::Point2f> charucoCorners;\r\n    std::vector<int> charucoIds;\r\n    cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, inputImage, board, charucoCorners, charucoIds, cameraMatrix, distCoeffs);\r\n}\r\n```\r\ninterpolateCornersCharuco()函数的每个参数的含义为：\r\n\r\n-\tmarkerCorners和markerIds：从函数detectMarkers()检测到的标识；\r\n-\tinputImage：检测到标识的原始图像。若要在ChArUco角中进行亚像素化，则必须使用该图像；\r\n-\tboard：CharucoBoard对象；\r\n-\tcharucoCorners和charucoIds：输出的插入的Charuco角；\r\n-\tcameraMatrix和distCoeffs：可选的相机校准参数；\r\n-\t该函数返回插入的Charuco角的数目。\r\n\r\n在代码清单-26中，我们调用interpolateCorneresCharuco()函数时提供了相机标定参数。但这些参数页可以不使用，不使用相机标定参数的示例程序在代码清单2-27中给出。\r\n```cpp\r\n代码清单1-32 （未使用相机校准参数）检测ChArUco角\r\ncv::Mat inputImage;\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);\r\n...\r\nstd::vector<int> markerIds;\r\nstd::vector<std::vector<cv::Point2f>> markerCorners;\r\ncv::Ptr<cv::aruco::DetectorParameters> params;\r\nparams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_NONE;\r\ncv::aruco::detectMarkers(inputImage, board.dictionary, markerCorners, markerIds, params);\r\n// 如果至少有一个标识被检测到\r\nif(markerIds.size() > 0) {\r\n    std::vector<cv::Point2f> charucoCorners;\r\n    std::vector<int> charucoIds;\r\n    cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, inputImage, board, charucoCorners, charucoIds);\r\n}\r\n```\r\n如果提供了校准参数，则首先通过ArUco标记粗略估计姿态，然后将ArUco角点重新投影回图像来对ChArUco角点进行插值。如果未提供校准参数，则可以通过计算ChArUco平面和ChArUco图像投影之间对应的单应性变换来对ChArUco角点进行插值。使用单应性变换的主要问题是插值对图像失真更敏感。实际上，在使用每个ChArUco角点的最接近的标记以减少失真效果时，才会执行单应性变换。\r\n\r\n当检测ChArUco板的标识时，尤其是在使用单应性变换时，建议禁用标记的边角细化。因为棋盘方块的形状相似，压像素处理会在拐角位置产生较大的偏差，并且这些偏差会传递到ChArUco角点的插值中，从而得到较差的结果。\r\n\r\n此外，仅返回那些找到两个周围标记的角。如果未检测到两个周围标记中的任何一个，则通常意味着存在某些遮挡或该区域的图像质量不佳。无论哪用情况，最好不要考虑这种角点，因为我们要确保插入的ChArUco角点非常准确。在对ChArUco角点进行插值后，再执行压像素细化。一旦我们插入ChArUco角后，我们可能希望绘制它们以查看检测是否正确。OpenCV提供了drawDetectedCornersCharuco()函数用于显示检测结果，该函数的使用方式在代码清单2-28中给出。检测结果如图2-20和图2-21所示。\r\n```cpp\r\n代码清单2-28绘制\r\ncv::aruco::drawDetectedCornersCharuco(image, charucoCorners, charucoIds, color);\r\n```\r\n\r\n-\timage是将要绘制角点的图像（通常与检测到角点图像相同），同时也是绘制后的输出图像；\r\n-\tcharucoCorners和charucoIds是从函数interpolateCornersCharuco()中检测到的Charuco角点；\r\n-\t最后一个参数（可选）是我们要用来绘制角的颜色，类型为cv::Scalar。\r\n \r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223165150456.jpg\" height=\"300\">\r\n</p>\r\n\r\n不使用相机标定参数情况下对ChArUco标记板检测的示例程序在代码清单2-29中给出。\r\n```cpp\r\n代码清单2-29 ChArUco检测的完整示例\r\ncv::VideoCapture inputVideo;\r\ninputVideo.open(0);\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);\r\ncv::Ptr<cv::aruco::DetectorParameters> params;\r\nparams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_NONE;\r\nwhile (inputVideo.grab()) {\r\n    cv::Mat image, imageCopy;\r\n    inputVideo.retrieve(image);\r\n    image.copyTo(imageCopy);\r\n    std::vector<int> ids;\r\n    std::vector<std::vector<cv::Point2f>> corners;\r\n    cv::aruco::detectMarkers(image, dictionary, corners, ids, params);\r\n    // if at least one marker detected\r\n    if (ids.size() > 0) {\r\n        cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);\r\n        std::vector<cv::Point2f> charucoCorners;\r\n        std::vector<int> charucoIds;\r\n        cv::aruco::interpolateCornersCharuco(corners, ids, image, board, charucoCorners, charucoIds);\r\n        // if at least one charuco corner detected\r\n        if(charucoIds.size() > 0)\r\n            cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));\r\n    }\r\n    cv::imshow(\"out\", imageCopy);\r\n   char key = (char) cv::waitKey(waitTime);\r\n    if (key == 27)\r\n        break;\r\n}\r\n```\r\naruco模块中提供了检测ChArUco标记板的完整的示例程序，完整程序存放在detect_board_charuco.cpp文件中，我们可以通过代码清单2-30中的代码直接调用该文件。检测ChArUco标记板的结果在图2-22给出。\r\n\r\n**提示\r\n*完整视频可以在小白学视觉微信公众号后台回复“ChArUco标记板检测”获取。***\r\n```cpp\r\n代码清单2-30 样本输入\r\n-c=\"_path_/calib.txt\" -dp=\"_path_/detector_params.yml\" -w=5 -h=7 -sl=0.04 -ml=0.02 -d=10\r\n```\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223165438347.jpg\" height=\"300\">\r\n</p>\r\n\r\n\r\n## 2.3.3\tChArUco姿态估计\r\nChArUco标记板的最终目的是非常精确地找到角点以进行高精度校准或姿态估计。aruco模块提供了estimatePoseCharucoBoard()函数用于ChArUco姿态估计。ChArUco标记板的坐标系与GridBoard一样，坐标系放置在板平面中，Z轴指向外侧，并居中于板的左下角。estimatePoseCharucoBoard()函数的使用方法在代码清单2-31中给出。\r\n```cpp\r\n代码清单2-31姿态估计函数\r\ncv::aruco::estimatePoseCharucoBoard(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);\r\n```\r\n-\tcharucoCoerners和charucoIds参数是从函数interpolateCornersCharuco()中检测到的charuco角点；\r\n-\t第三个参数是CharucoBoard对象；\r\n-\tcameraMatrix和distCoeffs是姿态估计所需的相机标定参数；\r\n-\trvec和tvec参数是Charuco板的输出姿态；\r\n-\t如果正确估计了姿态，则函数返回true，否则返回false。失败的主要原因是没有足够的角点进行姿态估计或者它们不在同一条线。\r\n\r\n可以使用drawAxis()绘制轴轴以检查姿态是否正确估计。绘制结果如所示（X轴：红色；Y轴：绿色；Z轴：蓝色）。\r\n<p align=\"center\">\r\n <img src=\"https://img-blog.csdnimg.cn/20200223170110524.jpg\" height=\"300\">\r\n</center>\r\n\r\n代码清单2-33中给出了姿态估计完整示例程序。该程序被存放在aruco模块中的detect_board_charuco.cpp文件中，可以通过代码清单2-32中的参数对该文件进行调用。\r\n```cpp\r\n代码清单2-32 样本输入\r\n\"_path_/calib.txt\" -dp=\"_path_/detector_params.yml\" -w=5 -h=7 -sl=0.04 -ml=0.02 -d=10\r\n```\r\n```cpp\r\n代码清单2-33：带有姿态估计的ChArUco检测\r\ncv::VideoCapture inputVideo;\r\ninputVideo.open(0);\r\ncv::Mat cameraMatrix, distCoeffs;\r\n// camera parameters are read from somewhere\r\nreadCameraParameters(cameraMatrix, distCoeffs);\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::Ptr<cv::aruco::CharucoBoard> board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);\r\nwhile (inputVideo.grab()) {\r\n    cv::Mat image, imageCopy;\r\n    inputVideo.retrieve(image);\r\n    image.copyTo(imageCopy);\r\n    std::vector<int> ids;\r\n    std::vector<std::vector<cv::Point2f>> corners;\r\n    cv::aruco::detectMarkers(image, dictionary, corners, ids);\r\n    // if at least one marker detected\r\n    if (ids.size() > 0) {\r\n        std::vector<cv::Point2f> charucoCorners;\r\n        std::vector<int> charucoIds;\r\n        cv::aruco::interpolateCornersCharuco(corners, ids, image, board, charucoCorners, charucoIds, cameraMatrix, distCoeffs);\r\n        // if at least one charuco corner detected\r\n        if(charucoIds.size() > 0) {\r\n            cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));\r\n            cv::Vec3d rvec, tvec;\r\n            bool valid = cv::aruco::estimatePoseCharucoBoard(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);\r\n            // if charuco pose is valid\r\n            if(valid)\r\n                cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvec, tvec, 0.1);\r\n        }\r\n    }\r\n    cv::imshow(\"out\", imageCopy);\r\n    char key = (char) cv::waitKey(waitTime);\r\n    if (key == 27)\r\n        break;\r\n}\r\n```\r\n"
  },
  {
    "path": "chapter 2/使用ArUco和ChArUco进行相机标定.md",
    "content": "ArUco模块也可以用来标定相机。相机标定包括获取相机的内参系数和畸变系数。除非相机光学系统发生变化，否则这一参数保持不变。因此，相机标定只需要做一次。但是，如果相机长时间未使用，建议再次使用时重新标定一次。\r\n\r\n相机机标定通常使用标准库中的calibrateCamera()函数。该函数要求空间点与图像中投影点之间存在一定的对应关系。一般来说，这些对应是从棋盘图案的角点获得的。有关更多详细信息，可以阅读基础库中的calibrateCamera()函数文档或OpenCV标定教程。在《OpenCV 4计算机视觉编程实战》书中也有详细的标定算法和流程介绍。\r\n\r\n使用ArUco模块，可以基于ArUco标记角点或ChArUco角点进行标定。与使用传统棋盘图案相比，使用ArUco进行标定的功能要更广泛得，因为即使部分标定板被遮挡了，一样可以实现相机的标定。\r\n\r\n可以同时使用ArUco标记角点或ChArUco角点进行标定。但是，强烈推荐使用ChArUco的方法，因为它提供的角点更精确。但是如果由于限制而无法使用ChArUco Board的情况下，也可以使用ArUco标定板板进行标定。\r\n\r\n## 2.5.1\t使用 ChArUco Boards 进行标定\r\n要使用ChArUco进行标定，必须获得不同的角度的棋盘图像，这与传统标定方法对棋盘格的检测方法相同。使用ChArUco的好处是即使标定板部分被遮挡，只要任然有角点可以被检测，就可以实现标定。标定过程中获取的含有ChArUco标定板的图像如图2-28所示。\r\n <center>\r\n<img src=\"https://img-blog.csdnimg.cn/20200224113108960.png\">\r\n</center>\r\n\r\n使用ChArUco标定板进行标定的函数是calibrateCameraCharuco()，其使用方法在代码清单2-40给出。\r\n\r\n```cpp\r\n代码清单2-40 创建Mat类\r\ncv::Ptr<aruco::CharucoBoard> board = ... // create charuco board\r\ncv::Size imgSize = ... // camera image size\r\nstd::vector<std::vector<cv::Point2f>> allCharucoCorners;\r\nstd::vector<std::vector<int>> allCharucoIds;\r\n// Detect charuco board from several viewpoints and fill allCharucoCorners and allCharucoIds\r\n...\r\n...\r\n// After capturing in several viewpoints, start calibration\r\ncv::Mat cameraMatrix, distCoeffs;\r\nstd::vector<cv::Mat> rvecs, tvecs;\r\nint calibrationFlags = ... // Set calibration flags (same than in calibrateCamera() function)\r\ndouble repError = cv::aruco::calibrateCameraCharuco(allCharucoCorners, allCharucoIds, board, imgSize, cameraMatrix, distCoeffs, rvecs, tvecs, calibrationFlags);\r\n```\r\n在每个视角上捕获的ChArUco角点和ChArUco标识符存储在vector容器allCharucoCorners和allcharucoid中，每个视角一个元素。\r\n\r\ncalibrateCameraCharuco()函数将相机参数存储在数组cameraMatrix和distCoeffs中。该函数的返回值是标定的重投影误差。rvecs和tvecs中存储每幅图对应的相机姿态(相对于ChArUco板)。最后，calibrationFlags参数确定一些用于标定的选项。它的格式与基础库中calibrateCamera()函数中的flags参数格式相同。\r\n\r\n完整的工作示例包含在模块示例文件夹中的calibrate_camera_charuco.cpp中，读者可以在扩展模块的文件中找到它。该函数调用时需要输入的参数在代码清单2-41中给出。\r\n\r\n```cpp\r\n代码清单2-41 ：调用参数\r\n_output path_\" -dp=\"_path_/detector_params.yml\" -w=5 -h=7 -sl=0.04 -ml=0.02 -d=10\r\n```\r\n\r\n## 2.5.2\t使用 ArUco Boards 进行标定\r\n如前所述，因为ChArUco角点比更准确ChAruco角点更精确，因此建议使用ChAruco标定板进行相机标定。进行摄像机校准，。但是在某些特殊情况下不得不使用ArUco标定板进行标定。使用ArUco标定板时，需要使用calibrateCameraAruco()函数。同样该函数也需要输入不同角度拍摄到的ArUco标定板图像。标定过程中获取的含有ArUco标定板的图像如图2-29所示\r\n\r\n <center>\r\n<img src=\"https://img-blog.csdnimg.cn/20200224113411996.png\">\r\n</center>\r\n\r\ncalibrateCameraAruco()函数的使用方法在代码清单2-42中给出。\r\n```cpp\r\n代码清单2-42 创建Mat类\r\ncv::Ptr<aruco::Board> board = ... // create aruco board\r\ncv::Size imgSize = ... // camera image size\r\nstd::vector<std::vector<cv::Point2f>> allCornersConcatenated;\r\nstd::vector<int> allIdsConcatenated;\r\nstd::vector<int> markerCounterPerFrame;\r\n// Detect aruco board from several viewpoints and fill allCornersConcatenated, allIdsConcatenated and markerCounterPerFrame\r\n...\r\n...\r\n// After capturing in several viewpoints, start calibration\r\ncv::Mat cameraMatrix, distCoeffs;\r\nstd::vector<cv::Mat> rvecs, tvecs;\r\nint calibrationFlags = ... // Set calibration flags (same than in calibrateCamera() function)\r\ndouble repError = cv::aruco::calibrateCameraAruco(allCornersConcatenated, allIdsConcatenated, markerCounterPerFrame, board, imgSize, cameraMatrix, distCoeffs, rvecs, tvecs, calibrationFlags);\r\n```\r\n\r\n与calibrateCameraCharuco（）函数相反，在每幅图中检测到的标记将被串联存储在数组allCornersConcatenated和allCornersConcatenated（该函数的前两个参数）中。第三个参数（数组markerCounterPerFrame）指示在每幅图中检测到的标记数。\r\n\r\n完整的工作示例包含在模块示例文件夹中的calibrate_camera.cpp中，读者可以在扩展模块的文件中找到它。该函数调用时需要输入的参数在代码清单2-43中给出。\r\n\r\n```cpp\r\n代码清单2-43 ：调用参数\r\n\"_path_/calib.txt\" -w=5 -h=7 -l=100 -s=10 -d=10\r\n```\r\n\r\n"
  },
  {
    "path": "chapter 2/菱形标记检测.md",
    "content": "ChArUco菱形标记(或简称菱形标记)是由3×3的方块和白色方块内的4个ArUco标记组成的棋盘，如图2-24中所示。它在外观上和ChArUco board很相似，但它们在概念上是不同的。  \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224110740699.png\" height=\"300\">\r\n</p>\r\n\r\n无论是ChArUco board还是Diamond marker，其检测都是基于前文检测到的ArUco marker。在ChArUco中，使用的标记是通过直接查找它们的标识符来选择的。这意味着如果在一个图像上发现一个标记(包括在图板中)，它将自动被认为是属于图板的。此外，如果图像中存在多个标识版，就会产生歧义，因为系统无法知道应该使用哪个标识板。\r\n\r\n然而，菱形标记检测并不是基于标识符的。相反，它们的检测是基于标记的相对位置。因此，标识符可以在同一菱形标记中重复出现，也可以在不同的菱形中重复出现，并且可以同时检测它们而不产生歧义。然而，由于根据相对位置寻找标记的复杂性，菱形标记的大小被限制在3×3个正方形和4个标记。\r\n\r\n与单个ArUco标记一样，每个菱形标记由4个角和一个标识符组成。四个角对应于标记中的4个棋盘角，标识符实际上是一个由4个数字组成的数组，这些数字是菱形内4个ArUco标记的标识符。\r\n\r\n菱形标记在图像中需要重复标记的情况下十分有用。例如，通过使用菱形标记来增加单个标记的标识数量。它们最多允许 个不同的id，其中N为使用的字典中标记的数目。例如,可以用四个标记id中的一个来表示标记的规模(即正方形的大小),这样可以找到不同大小的相同的菱形。此外，由于它的角是棋盘的角，可以用来进行精确的姿态估计。检测菱形标记的函数在<opencv2/aruco/charuco.hpp>中，OpenCV提供了检测菱形标志的示例程序，该程序在aruco模块的samples文件夹中的diamond_detector.cpp文件中。\r\n\r\n## 2.4.1\t创建ChArUco菱形\r\n菱形标记图像可以通过使用drawCharucoDiamond()函数很方便地创建，具体代码在代码清单2-34中给出。\r\n```cpp\r\n代码清单2-34\r\ncv::Mat diamondImage;\r\ncv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);\r\ncv::aruco::drawCharucoDiamond(dictionary, cv::Vec4i(45,68,28,74), 200, 120, markerImage);\r\n```\r\n代码清单2-34中的程序将创建一个大小为200像素，标记大小为120像素的菱形标记图像。标记id在第二个参数中以Vec4i对象给出。菱形布局中标记id的顺序与标准ChArUco板相同，即顶部、左侧、右侧和底部。产生的图像如图2-25所示。\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224111303855.png\" height=\"300\">\r\n</p>\r\n模块sample文件夹中的create_diamond.cpp中包含了一个完整的生成菱形标记的的示例程序，我们也可以直接使用该文件来生成菱形标记。这个文件的使用方式在代码清单2-35中给出\r\n\r\n```cpp\r\n代码清单2-35\r\n\"_path_/mydiamond.png\" -sl=200 -ml=120 -d=10 -ids=45,68,28,74\r\n```\r\n## 2.4.2\tChArUco菱形检测\r\n在大多数情况下，菱形标记的检测需要先检测ArUco标记。检测标记后，使用detectCharucoDiamond()函数检测菱形，具体代码在代码清单2-36中给出。\r\n\r\n```cpp\r\n代码清单2-36\r\ncv::Mat inputImage;\r\nfloat squareLength = 0.40;\r\nfloat markerLength = 0.25;\r\n...\r\nstd::vector<int> markerIds;\r\nstd::vector<std::vector< cv::Point2f>> markerCorners;\r\n// detect ArUco markers\r\ncv::aruco::detectMarkers(inputImage, dictionary, markerCorners, markerIds);\r\nstd::vector<cv::Vec4i> diamondIds;\r\nstd::vector<std::vector<cv::Point2f>> diamondCorners;\r\n// detect diamon diamonds\r\ncv::aruco::detectCharucoDiamond(inputImage, markerCorners, markerIds, squareLength / markerLength, diamondCorners, diamondIds);\r\n```\r\ndetectCharucoDiamond()函数的参数有原始图像和检测到的标记角和标记对应的id。输入图像必须对ChArUco角点执行亚像素细化。此外，函数的参数还包括正方形大小和标记大小的比例，这是从标记相对位置检测菱形和插入ChArUco所需要的。第五个参数diamondCorners是一个包含每个检测到的菱形的所有四个角点的数组，是一个输出参数。它的格式类似于detectmarker()函数所检测到的角点，对于每个菱形，角点的表示顺序与ArUco标记相同，即从左上角开始顺时针排列。第六个参数diamondIds是返回的所有菱形角的id。每个id实际上是一个由4个整数组成的数组，可以用Vec4i表示。使用drawDetectedDiamonds()函数可以对检测到的菱形进行可视化处理，该函数只接收图像和菱形的边角和id，菱形标记可视化的结果如图2-26所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224111709324.png\" height=\"300\">\r\n</p>\r\n\r\nOpenCV提供了完整的菱形标记检测程序，该程序在模块示例文件夹内的detect_diamonds.cpp中。该程序的使用当时如代码清单2-37中所示。\r\n\r\n```cpp\r\n代码清单2-37\r\n-c=\"_path_/calib.txt\" -dp=\"_path_/detector_params.yml\" -sl=0.04 -ml=0.02 -d=10\r\n```\r\n## 2.4.3\tChArUco菱形姿态估计\r\n由于ChArUco菱形标记是由它的四个角表示的，所以它的姿态可以用与单个ArUco标记相同的方式进行估计，即使用estimatePoseSingleMarkers()函数。代码清单2-38中给出了示例程序。\r\n```cpp\r\n代码清单2-38\r\nstd::vector<cv::Vec4i> diamondIds;\r\nstd::vector<std::vector<cv::Point2f>> diamondCorners;\r\n// detect diamon diamonds\r\ncv::aruco::detectCharucoDiamond(inputImage, markerCorners, markerIds, squareLength / markerLength, diamondCorners, diamondIds);\r\n// estimate poses\r\nstd::vector<cv::Vec3d> rvecs, tvecs;\r\ncv::aruco::estimatePoseSingleMarkers(diamondCorners, squareLength, camMatrix, distCoeffs, rvecs, tvecs);\r\n// draw axis\r\nfor(unsigned int i=0; i<rvecs.size(); i++)\r\n    cv::aruco::drawAxis(inputImage, camMatrix, distCoeffs, rvecs[i], tvecs[i], axisLength);\r\n```\r\n该函数将获取每个菱形标记的旋转和平移向量，并将它们存储在rvecs和tvecs中。注意，菱形角点是棋盘上的正方形角点，因此必须提供正方形长度来估计姿态，而不是标记长度。同时还需要相机标定参数。最后，可以使用drawAxis(函数)绘制坐标轴来检查预估的姿态是否正确，效果如图2-27所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224112203454.png\" height=\"300\">\r\n</p>\r\n菱形位姿的坐标系统将位于标记的中心，Z轴指向外，就像简单的ArUco标记位姿估计一样。OpenCV提供了完整的程序代码，该代码在模块sample文件夹内的detect_diamonds.cpp文件中。该程序可以通过代码清单2-39中的命令进行调用。\r\n\r\n```cpp\r\n代码清单2-39\r\n-c=\"_output path_/calib.txt\" -dp=\"_path_/detector_params.yml\" -sl=0.04 -ml=0.02 -d=10\r\n```\r\n\r\n"
  },
  {
    "path": "chapter 20/用于快速边缘检测的结构化森.md",
    "content": "# 20.2 用于快速边缘检测的结构化森林(Structured forests)\r\n\r\n在本节教程中,我们将学会使用结构化森林来进行图像边缘检测。该算法的具体原理读者可以查阅下面两篇文章进行详细学习:\r\n\r\n- Structured forests for fast edge detection\r\n- Sketch tokens: A learned mid-level representation for contour and object detection\r\n\r\n## 20.2.1 例图\r\n\r\n本历程中所有使用的图片和处理结果都在下方给出。读者也可以在小白学视觉公众号后台回复“**快速边缘检测**”获取所有图片。\r\n\r\n<img src=\"./img/20-5.png\">\r\n\r\n<img src=\"./img/20-6.png\">\r\n\r\n<img src=\"./img/20-7.png\">\r\n\r\n<img src=\"./img/20-8.png\">\r\n\r\n> 注意：\r\n>\r\n> Canny边 缘 检 测 等 二 值 化 技 术 适 用 于 两 种 算 法 (Sobel 和StructuredEdgeDetection::detectEdges)生成的边缘。\r\n\r\n## 20.2.2 C++代码\r\n\r\n```\r\n/**************************************************************************************\r\n The structered edge demo requires you to provide a model.\r\n This model can be found at the opencv_extra repository on Github on the following link:\r\n https://github.com/opencv/opencv_extra/blob/master/testdata/cv/ximgproc/model.yml.gz\r\n ***************************************************************************************/\r\n \r\n #include <opencv2/ximgproc.hpp>\r\n #include \"opencv2/highgui.hpp\"\r\n #include \"opencv2/core/utility.hpp\"\r\n #include <iostream>\r\n \r\n using namespace cv;\r\n using namespace cv::ximgproc;\r\n \r\n const char* keys =\r\n {\r\n     \"{i || input image name}\"\r\n     \"{m || model name}\"\r\n     \"{o || output image name}\"\r\n };\r\n \r\n int main( int argc, const char** argv )\r\n {\r\n     bool printHelp = ( argc == 1 );\r\n     printHelp = printHelp || ( argc == 2 && String(argv[1]) == \"--help\" );\r\n     printHelp = printHelp || ( argc == 2 && String(argv[1]) == \"-h\" );\r\n \r\n     if ( printHelp )\r\n     {\r\n         std::cout << \"\\nThis sample demonstrates structured forests for fast edge detection\\n\"\r\n                \"Call:\\n\"\r\n                \"    structured_edge_detection -i=in_image_name -m=model_name [-o=out_image_name]\\n\\n\";\r\n         return 0;\r\n     }\r\n \r\n     CommandLineParser parser(argc, argv, keys);\r\n     if ( !parser.check() )\r\n     {\r\n         parser.printErrors();\r\n         return -1;\r\n     }\r\n \r\n     String modelFilename = parser.get<String>(\"m\");\r\n     String inFilename = parser.get<String>(\"i\");\r\n     String outFilename = parser.get<String>(\"o\");\r\n \r\n     Mat image = imread(inFilename, 1);\r\n     if ( image.empty() )\r\n         CV_Error(Error::StsError, String(\"Cannot read image file: \") + inFilename);\r\n \r\n     if ( modelFilename.size() == 0)\r\n         CV_Error(Error::StsError, String(\"Empty model name\"));\r\n \r\n     image.convertTo(image, DataType<float>::type, 1/255.0);\r\n \r\n     Mat edges(image.size(), image.type());\r\n \r\n     Ptr<StructuredEdgeDetection> pDollar =\r\n         createStructuredEdgeDetection(modelFilename);\r\n     pDollar->detectEdges(image, edges);\r\n \r\n     // computes orientation from edge map\r\n     Mat orientation_map;\r\n     pDollar->computeOrientation(edges, orientation_map);\r\n \r\n     // suppress edges\r\n     Mat edge_nms;\r\n     pDollar->edgesNms(edges, orientation_map, edge_nms, 2, 0, 1, true);\r\n \r\n     if ( outFilename.size() == 0 )\r\n     {\r\n         namedWindow(\"edges\", 1);\r\n         imshow(\"edges\", edges);\r\n         namedWindow(\"edges nms\", 1);\r\n         imshow(\"edges nms\", edge_nms);\r\n         waitKey(0);\r\n     }\r\n     else\r\n         imwrite(outFilename, 255*edges);\r\n \r\n     return 0;\r\n }\r\n```\r\n\r\n## 20.2.3 代码解释\r\n\r\n1、加载源彩色图片\r\n\r\n代码清单 20-7:加载图片\r\n\r\n```\r\ncv::Mat image = cv::imread(inFilename, 1);\r\nif ( image.empty() )\r\n{\r\n    printf(\"Cannot read image file: %s\\n\", inFilename.c_str());\r\n    return -1;\r\n}\r\n```\r\n\r\n2、将源图转换到[0,1]范围内\r\n\r\n代码清单 20-8:改变图像数据类型\r\n\r\n```\r\nimage.convertTo(image, cv::DataType<float>::type, 1/255.0);\r\n```\r\n\r\n3、执行主算法\r\n\r\n代码清单 20-9:执行算法\r\n\r\n```\r\ncv::Mat edges(image.size(), image.type());\r\ncv::Ptr<StructuredEdgeDetection> pDollar =\r\n    cv::createStructuredEdgeDetection(modelFilename);\r\npDollar->detectEdges(image, edges);\r\n```\r\n\r\n4、展示结果\r\n\r\n代码清单 20-10:展示结果\r\n\r\n```\r\nif ( outFilename == \"\" )\r\n{\r\n    cv::namedWindow(\"edges\", 1);\r\n    cv::imshow(\"edges\", edges);\r\n    cv::waitKey(0);\r\n}\r\nelse\r\n    cv::imwrite(outFilename, 255*edges);\r\n```\r\n\r\n"
  },
  {
    "path": "chapter 20/视差图滤波.md",
    "content": "# 20.1 视差图滤波\r\n\r\n## 20.1.1 简介\r\n\r\n立体匹配算法，尤其是只使用 CPU 需要实时处理的高度优化算法，在面对具有挑战性的序列上往往会出现相当多的错误。这些误差通常集中在均匀的无纹理区域、半遮挡区域和靠近深度不连续区域。一个解决立体匹配误差的方法就是使用各种技术检测潜在的不准确的视差值并使其无效，从而使视差图半稀疏化。一些这种思想的算法已经在 StereoBM 和StereoSGBM 中实现。另一种方法是使用某种过滤手段将视差图的边缘与源图像的边缘对齐，并将视差值从高置信区域传播到低置信区域(如半遮挡区域)。最近在边缘感知过滤方面的进展使得在 CPU 实时处理的约束下能够执行这种后过滤。\r\n\r\n在本节教程中，我们将学习如何对视差图进行滤波来改进 StereoBM 和 StereoSGBM 算法的结果。本教程使用的两张图像分别在图 20-1 和图 20-2 中给出。\r\n\r\n<img src=\"./img/20-1.png\">\r\n\r\n<img src=\"./img/20-2.png\">\r\n\r\n## 20.1.2 C++代码\r\n\r\n我们将使用示例应用程序中的代码片段作为讲解使用,完整的代码可以从 OpenCV 官网[下载](https://github.com/opencv/opencv_contrib/blob/master/modules/ximgproc/samples/disparity_filtering.cpp)。\r\n\r\n> 提示\r\n>\r\n> 本教程的完整程序可以在小白学视觉微信公众号后台回复“视差图滤波”获取。\r\n\r\n  \r\n\r\n## 20.1.3 代码解释\r\n\r\n所提供的示例具有多个选项，这些选项在生成的视差图的速度和质量之间产生了不同的权衡。 如果用户提供了真实的视差图，则将同时测量速度和质量。 在本教程中，我们将详细介绍默认管道，该管道旨在在CPU实时处理的约束下提供最佳质量。\r\n\r\n**代码清单 20-1:加载左右视图**\r\n\r\n```\r\n    Mat left  = imread(left_im ,IMREAD_COLOR);\r\n    if ( left.empty() )\r\n    {\r\n        cout<<\"Cannot read image file: \"<<left_im;\r\n        return -1;\r\n    }\r\n    Mat right = imread(right_im,IMREAD_COLOR);\r\n    if ( right.empty() )\r\n    {\r\n        cout<<\"Cannot read image file: \"<<right_im;\r\n        return -1;\r\n    }\r\n```\r\n\r\n我们首先加载源立体图对。 在本教程中，我们将以MPI-Sintel数据集中具有很多无纹理区域的示例为例，该示例具有挑战性。\r\n\r\n## 20.1.4 准备匹配的视图\r\n\r\n**代码清单 20-2**\r\n\r\n```\r\n\t\t\tmax_disp/=2;\r\n            if(max_disp%16!=0)\r\n                max_disp += 16-(max_disp%16);\r\n            resize(left ,left_for_matcher ,Size(),0.5,0.5, INTER_LINEAR_EXACT);\r\n            resize(right,right_for_matcher,Size(),0.5,0.5, INTER_LINEAR_EXACT);\r\n```\r\n\r\n我们在允许质量轻微下降的前提下对视图进行缩小以加速匹配。如果要获取最好的质量\r\n图,则应该避免进行图像缩小。\r\n\r\n## 20.1.5 进行匹配和生成 filter 实例\r\n\r\n```\r\n\t\t\tPtr<StereoBM> left_matcher = StereoBM::create(max_disp,wsize);\r\n            wls_filter = createDisparityWLSFilter(left_matcher);\r\n            Ptr<StereoMatcher> right_matcher = createRightMatcher(left_matcher);\r\n            cvtColor(left_for_matcher,  left_for_matcher,  COLOR_BGR2GRAY);\r\n            cvtColor(right_for_matcher, right_for_matcher, COLOR_BGR2GRAY);\r\n            matching_time = (double)getTickCount();\r\n            left_matcher-> compute(left_for_matcher, right_for_matcher,left_disp);\r\n            right_matcher->compute(right_for_matcher,left_for_matcher, right_disp);\r\n            matching_time = ((double)getTickCount()-matching_time)/getTickFrequency();\r\n```\r\n\r\n为了更快地处理,我们使用 StereoBM 算法。如果对速度要求不高，那么 StereoSGBM算法则能够提供更好的质量。filter 实例是通过我们打算使用的 StereoMatcher 实例创建的。createRightMatcher()函数会返回另一个 matcher 实例。然后使用这两个 matcher 实例来计算左边和右边视图的视差图。\r\n\r\n## 20.1.6 进行滤波\r\n\r\n```\r\n\t\twls_filter->setLambda(lambda);\r\n        wls_filter->setSigmaColor(sigma);\r\n        filtering_time = (double)getTickCount();\r\n        wls_filter->filter(left_disp,left,filtered_disp,right_disp);\r\n        filtering_time = ((double)getTickCount() - filtering_time)/getTickFrequency();\r\n```\r\n\r\n由各个 matcher 实例得到的视差图以及源左视图传给 filter。这里需要注意,滤波过程需要使用原始的非缩小视图。视差图会以边缘感知的方式自动放大,以匹配原始的视图分辨率。结果存储在 filtered_disp 中。\r\n\r\n## 20.1.7 可视化视差图\r\n\r\n```\r\n\t\tMat raw_disp_vis;\r\n        getDisparityVis(left_disp,raw_disp_vis,vis_mult);\r\n        namedWindow(\"raw disparity\", WINDOW_AUTOSIZE);\r\n        imshow(\"raw disparity\", raw_disp_vis);\r\n        Mat filtered_disp_vis;\r\n        getDisparityVis(filtered_disp,filtered_disp_vis,vis_mult);\r\n        namedWindow(\"filtered disparity\", WINDOW_AUTOSIZE);\r\n        imshow(\"filtered disparity\", filtered_disp_vis);\r\n        if(!solved_disp.empty())\r\n        {\r\n            Mat solved_disp_vis;\r\n            getDisparityVis(solved_disp,solved_disp_vis,vis_mult);\r\n            namedWindow(\"solved disparity\", WINDOW_AUTOSIZE);\r\n            imshow(\"solved disparity\", solved_disp_vis);\r\n            Mat solved_filtered_disp_vis;\r\n            getDisparityVis(solved_filtered_disp,solved_filtered_disp_vis,vis_mult);\r\n            namedWindow(\"solved wls disparity\", WINDOW_AUTOSIZE);\r\n            imshow(\"solved wls disparity\", solved_filtered_disp_vis);\r\n        }\r\n        while(1)\r\n        {\r\n            char key = (char)waitKey();\r\n            if( key == 27 || key == 'q' || key == 'Q') // 'ESC'\r\n                break;\r\n        }\r\n```\r\n\r\n为了方便,我们使用函数 getdisityvis()来可视化视差图。该函数的第二个参数定义对比度(在可视化中,所有的视差值都由这个值缩放)。\r\n\r\n## 20.1.8 结果\r\n\r\n<img src=\"./img/20-3.png\">\r\n\r\n<img src=\"./img/20-4.png\">"
  },
  {
    "path": "chapter 20/训练结构化森林.md",
    "content": "# 20.3 训练结构化森林\r\n\r\n在本节教程中,我们展示如何使用 Matlab 文件来训练我们自己的结构化森林。\r\n\r\n## 20.3.1 训练流程\r\n\r\n1. 通过网络寻找资源并下载 Piotr's Toolbox,并将其放入单独的目录中,如 PToolbox。\r\n\r\n下载地址为:http://pdollar.github.io/toolbox/index.html\r\n\r\n2. 通过网络寻找资源并下载 BSDS500 数据集,并将其放入单独的目录中,并命名为BSR。下载地址为:https://www2.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/BSR/\r\n\r\n3.  将这两个文件及其子文件都放到 Matlab 目录中。\r\n\r\n4. 下载检测代码,下载地址为:http://research.microsoft.com/en-us/downloads/389109f6-b4e8-404c-84bf-239f7cbf4e3d/\r\n\r\n   并将其放到根目录中,现在我们应该有如下文件:\r\n\r\n   ```\r\n       BSR\r\n       PToolbox\r\n       models\r\n       private\r\n       Contents.m\r\n       edgesChns.m\r\n       edgesDemo.m\r\n       edgesDemoRgbd.m\r\n       edgesDetect.m\r\n       edgesEval.m\r\n       edgesEvalDir.m\r\n       edgesEvalImg.m\r\n       edgesEvalPlot.m\r\n       edgesSweeps.m\r\n       edgesTrain.m\r\n       license.txt\r\n       readme.txt\r\n   ```\r\n\r\n5. 将 models/forest/modelFinal.mat 重命名为 models/forest/modelFinal.mat.backup。\r\n\r\n6. 打开 edgesChns.m,注释 26-41 行,并在注释后面加入代码清单 20-11 中的代码:\r\n\r\n   代码清单 20-11\r\n\r\n   ```\r\n   shrink=opts.shrink;\r\n   chns = single(getFeatures( im2double(I) ));\r\n   ```\r\n\r\n7. 之后编译 getFeatures。代码清单 20-22 是实现代码。\r\n\r\n```\r\n#include <cv.h>\r\n#include <highgui.h>\r\n#include <mat.h>\r\n#include <mex.h>\r\n#include \"MxArray.hpp\" // https://github.com/kyamagu/mexopencv\r\nclass NewRFFeatureGetter : public cv::RFFeatureGetter\r\n{\r\npublic:\r\n    NewRFFeatureGetter() : name(\"NewRFFeatureGetter\"){}\r\n    virtual void getFeatures(const cv::Mat &src, NChannelsMat &features,\r\n                             const int gnrmRad, const int gsmthRad,\r\n                             const int shrink, const int outNum, const int gradNum) const\r\n    {\r\n        // here your feature extraction code, the default one is:\r\n        // resulting features Mat should be n-channels, floating point matrix\r\n    }\r\nprotected:\r\n    cv::String name;\r\n};\r\nMEXFUNCTION_LINKAGE void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])\r\n{\r\n    if (nlhs != 1) mexErrMsgTxt(\"nlhs != 1\");\r\n    if (nrhs != 1) mexErrMsgTxt(\"nrhs != 1\");\r\n    cv::Mat src = MxArray(prhs[0]).toMat();\r\n    src.convertTo(src, cv::DataType<float>::type);\r\n    std::string modelFile = MxArray(prhs[1]).toString();\r\n    NewRFFeatureGetter *pDollar = createNewRFFeatureGetter();\r\n    cv::Mat edges;\r\n    pDollar->getFeatures(src, edges, 4, 0, 2, 13, 4);\r\n    // you can use other numbers here\r\n    edges.convertTo(edges, cv::DataType<double>::type);\r\n    plhs[0] = MxArray(edges);\r\n}\r\n```\r\n\r\n8. 将编译得到的 mex 文件放到根目录,运行 edgesDemo。我们需要等待几个小时，之后新的模型会出现在 models/forest/文件夹下。\r\n9. 最后一步将经过训练的模型从 Matlab 二进制格式转换为 YAML，这样就可以使用我们的ocv::StructuredEdgeDetection 函数。为此，运行opencv_contrib/ximpgroc/tutorials/scripts / modelConvert(model,“model.yml”)即可。\r\n\r\n## 20.3.2 如何使用你的模型\r\n\r\n如果想使用自己的模型，只需使用上面定义的 NewRFFeatureGetter 类的扩展构造函数。\r\n\r\n代码清单 20-13\r\n\r\n```\r\ncv::StructuredEdgeDetection pDollar\r\n    = cv::createStructuredEdgeDetection( modelName, makePtr<NewRFFeatureGetter>() );\r\n```\r\n\r\n"
  },
  {
    "path": "chapter 21/图像修复.md",
    "content": "# 21.1 图像修复\r\n\r\n在本节篇教程中,我们将学习如何使用快速频率选择重构(FSR)来进行图像修复。\r\n\r\n## 21.1.1 图像修复基础\r\n\r\n图像修补是重建图像中受损或缺失部分的过程,通过用类似于相邻像素的像素值替换失真的像素实现的图像修复。目前图像修复有多种算法,本教程使用的是快速频率选择重构(FSR)法。FSR 利用图像小区域在傅里叶域中可以稀疏表示的特性来重构图像信号。对这个算法感兴趣的读者可以详细阅读下面两篇论文:\r\n\r\n- Signal and loss geometry aware frequency selective extrapolation for error concealment\r\n- Resampling images to a regular grid from a non-regular subset of pixel positions using\r\n  frequency selective reconstruction\r\n\r\nFSR 可以应用于以下应用领域:\r\n\r\n- 图像修复:采样掩模表示需要重建的失真输入图像的缺失像素。\r\n-  不规则采样:有关如何选择好的采样掩模。\r\n\r\n## 21.1.2 示例教程\r\n\r\n代码清单 21-1 中给出了如何使用 FSR 进行图像修复。掩模的非零像素表示有效的图像区域，零像素表示需要重建的区域。我们可以使用 Paint 或 GIMP 等工具手动创建任意掩码。例如从一个简单的白色图像开始,用黑色画一些变形。原始图像、含有失真区域的图像以及修复后的图像分别在图 21-1 和图 21-2 中给出。\r\n\r\n**代码清单 21-1:图像修复示例程序**\r\n\r\n```c++\r\n#include <opencv2/opencv.hpp>\r\n#include <opencv2/xphoto/inpainting.hpp>\r\n#include <iostream>\r\nusing namespace cv;\r\nint main(int argc, char** argv)\r\n{\r\n    // read image and error pattern\r\n    Mat original_, mask_;\r\n    original_ = imread(\"images/kodim22.png\");\r\n    mask_ = imread(\"images/pattern_random.png\", IMREAD_GRAYSCALE);\r\n    // make sure that mask and source image have the same size\r\n    Mat mask;\r\n    resize(mask_, mask, original_.size(), 0.0, 0.0, cv::INTER_NEAREST);\r\n    // distort image\r\n    Mat im_distorted(original_.size(), original_.type(), Scalar::all(0));\r\n    original_.copyTo(im_distorted, mask); // copy valid pixels only (i.e. non-zero pixels in mask)\r\n    // reconstruct the distorted image\r\n    // choose quality profile fast (xphoto::INPAINT_FSR_FAST) or best (xphoto::INPAINT_FSR_BEST)\r\n    Mat reconstructed;\r\n    xphoto::inpaint(im_distorted, mask, reconstructed, xphoto::INPAINT_FSR_FAST);\r\n    imshow(\"orignal image\", original_);\r\n    imshow(\"distorted image\", im_distorted);\r\n    imshow(\"reconstructed image\", reconstructed);\r\n    waitKey();\r\n    return 0;\r\n}\r\n```\r\n\r\n<p align=\"center\">\r\n\r\n<img src=\"./img/21-1.png\">\r\n\r\n</p>\r\n\r\n<p align=\"center\">\r\n\r\n<img src=\"./img/21-2.png\">\r\n\r\n</p>\r\n\r\n"
  },
  {
    "path": "chapter 21/油画效果.md",
    "content": "# 21.2 油画效果\r\n\r\n图像具有多个颜色空间,默认颜色空间为 COLOR_BGR2GRAY。对于图像中的每个像素,如果计算其相邻的大小为 2×size+1 区域的直方图(颜色空间的第一个平面),并将最常出现的值赋给该像素点,这样的结果看起来就很像油画了。xphoto 模块中的 oilPainting()函数便可以实现将普通图像转换成优化的效果的参数 4 来降低图像的动态性,从而提高油画的效果。该函数的使用方式在代码清单 21-2 中给出,转换成的优化效果在图 21-4 给出。\r\n\r\n**代码清单 21-2**\r\n\r\n```\r\nMat img;\r\nMat dst;\r\nimg = imread(\"opencv/samples/data/baboon.jpg\");\r\nxphoto::oilPainting(img, dst, 10, 1, COLOR_BGR2Lab);\r\nimshow(\"oil painting effect\", dst);\r\n```\r\n\r\n<p align=\"center\">\r\n\r\n<img src=\"./img/21-3.png\">\r\n\r\n</p>\r\n\r\n"
  },
  {
    "path": "chapter 21/训练基于学习的白平衡算法.md",
    "content": "# 21.3 训练基于学习的白平衡算法\r\n\r\n## 21.3.1 简介\r\n\r\n许多传统的白平衡算法是基于统计的,也就是说,它们依赖于这样一个事实，即某些前提假设应该在适当的白平衡图像中存在,比如众所周知的灰色世界假设。然而，基于学习的算法框架利用大规模有标签的图像数据集往往能够获得更好的结果。本小节教程将演示如何训练一个基于学习的白平衡算法并评价其质量。\r\n\r\n## 21.3.2 如何训练模型\r\n\r\n首先下载训练数据集。在本教程中，我们使用 [Gehler-Shi dataset](http://www.cs.sfu.ca/~colour/data/shi_gehler/) 数据集。该数据集解压后会得到一个具有 568 张训练图片的文件夹。同时我们需要名为 real_illum_568..mat 的文件，该文件中包含 groundtruth 真实值。教程中使用的文件需要分别下载，我们可以在 OpenCV官网中找到下载链接。\r\n\r\n> 提示\r\n>\r\n> 教程中使用的数据文件和脚本文件都可以在小白学视觉微信公众号后台回复“Gehler-Shi dataset”进行获取。\r\n\r\n接下来使用一个 [Python 脚本](https://github.com/opencv/opencv_contrib/tree/master/modules/xphoto/samples/learn_color_balance.py)来训练模型,使用代码清单 21-3 中的参数实现调用。\r\n\r\n**代码清单 21-3:调用脚本**\r\n\r\n```\r\npython learn_color_balance.py -i <path to the folder with training\r\nimages> -g <path to real_illum_568..mat> -r 0,378 --num_trees 30 --\r\nmax_tree_depth 6 --num_augmented 0\r\n```\r\n\r\n代码清单 21-3 调用的脚本会使用 378 张图片(2/3 数据集)训练模型。我们将模型的大小设置为每个特征有 30 个回归树对,并将树的深度限制为不超过 6 个。默认情况下，训练得到的模型会保存到color_balance_model.yml 中。\r\n\r\n在构造一个 LearningBasedWB 实例时,将训练好的模型路径传入,操作代码在代码清单 21-4 中给出。\r\n\r\n**代码清单 21-4:加载模型**\r\n\r\n```\r\nPtr<xphoto::LearningBasedWB> wb = xphoto::createLearningBasedWB(modelFilename);\r\n```\r\n\r\n## 21.3.3 如何评估模型\r\n\r\n我们将使用 [benchmarking 脚本](https://github.com/opencv/opencv_contrib/tree/master/modules/xphoto/samples/color_balance_benchmark.py)在剩余的 1/3 数据集上比较我们训练的模型与经典的算法。脚本调用参数在代码清单 21-5 中给出。\r\n\r\n**代码清单 21-5**\r\n\r\n```\r\npython color_balance_benchmark.py -a grayworld,learning_based:color_balance_model.yml -m <full path to folder containing the model> -i <path to the folder with training images> -g <path to real_illum_568..mat> -r 379,567 -d \"img\"\r\n```\r\n\r\n默认情况下评估结果存储在 white_balance_eval_result.html 中，生成的白平衡图像存储在 img 文件夹中,用于对算法进行定性比较。\r\n\r\n\r\n"
  },
  {
    "path": "chapter 3/3",
    "content": "\n"
  },
  {
    "path": "chapter 3/背景分割.md",
    "content": "在一些应用中，我们需要提取前景来进一步操作，例如目标跟踪、移动物体检测。在这些情况下背景分割是一种最为常见和需要的技术。\r\n\r\n在本节教程中，我们将学习OpenCV中的背景抠除的方法。\r\n\r\n## 3.1.1\t方法介绍\r\n背景抠除是在很多基于视觉应用预处理的主要步骤。例如，对于顾客访问柜台的情况，静态相机记录进入或离开房间的顾客人数，或者交通摄像头提取关于车辆的信息等等。在这些所有的案例中，我们首先需要单独提取人员或车辆。从技术上，我们需要从静态的背景提取动态的前景。\r\n\r\n如果我们存在只有图像的背景的一张图像，例如一张没有顾客的房间图像，或者没有车辆的道路图像等，那么此时从图像提取动态的前景是一件容易的事情。只需从背景中减去新的图像即可，就能够单独得到前景的物体。但在大多数的情况下，我们可能没有这样的图像，所以我们需要从已有的图像中提取背景。当车辆有阴影的时候，问题将会变得更加复杂。因为阴影也会移动，简单的抠除也会将其标记为前景，这将会复杂化问题。\r\n\r\n在下面的内容中，我们将介绍bgsegm模块中抠除背景的两种算法。\r\n\r\n## 3.1.2\tBackgroundSubstractorMOG\r\n这种方法是基于高斯混合的背景/前景分割算法。它是在由P.KadewTraKuPong在2001年的论文 An improved adaptive background mixture model for real-time tracking with shadow detection中所提出的。它使用一种方法通过混合K个高斯分布(K=3或5)来对每个背景像素进行建模。高斯混合分布的权重代表这些颜色在场景中停留的时间的比例。背景颜色是保持更长且更静态的颜色。\r\n\r\nOpenCV中提供cv.bgsegm.createBackgroundSubstractorMOG()函数寻找背景对象。它具有一些可选的参数，例如历史记录的长度，高斯混合的数量，阈值等等。所有这些参数均被设置为一些默认值。然后在视频循环中，使用backgroudsubtractor.apply()方法获取前景模板。在代码清单3-1中给出了利用该函数实现背景抠除的示例程序。\r\n```python\r\n代码清单3-1\r\nimport numpy as np\r\nimport cv2 as cv\r\ncap = cv.VideoCapture('vtest.avi')\r\nfgbg = cv.bgsegm.createBackgroundSubtractorMOG()\r\nwhile(1):\r\n \tret, frame = cap.read()\r\n \tfgmask = fgbg.apply(frame)\r\n \tcv.imshow('frame',fgmask)\r\n \tk = cv.waitKey(30) & 0xff\r\n \tif k == 27:\r\n \t\tbreak\r\ncap.release()\r\ncv.destroyAllWindows()\r\n```\r\n\r\n## 3.1.3\tBackgroundSubtractorGMG\r\n该算法结合了统计背景图像估计和按像素贝叶斯分割。它是在2012年由Andrew B.Godbehere,Akihiro Matsukawa 和Ken Goldberg在论文中Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive Audio Art Installation提出。\r\n\r\n该方法使用前几帧(默认为120帧)进行背景建模。它采用概率前景分割算法，该算法使用贝叶斯推断来识别可能的前景对象。这种估计是自适应的；为了适应变化的光照，较新的观测值比旧的观测值有更大的权重。如果像要结果较好，需要一些形态滤波操作，例如闭运算和开运算，以消除不想要的噪声。在最初的几帧中，我们将得到一个黑色的窗口。OpenCV中提供cv.bgsegm.createBackgroundSubtractorGMG()实现该算法，在代码清单3-2中给出了利用该函数实现背景抠除的示例程序。\r\n\r\n```python\r\n代码清单3-2\r\nimport numpy as np\r\nimport cv2 as cv\r\ncap = cv.VideoCapture('vtest.avi')\r\nkernel = cv.getStructuringElement(cv.MORPH_ELLIPSE,(3,3))\r\nfgbg = cv.bgsegm.createBackgroundSubtractorGMG()\r\nwhile(1):\r\n\tret, frame = cap.read()\r\n\tfgmask = fgbg.apply(frame)\r\n\tfgmask = cv.morphologyEx(fgmask, cv.MORPH_OPEN, kernel)\r\n\tcv.imshow('frame',fgmask)\r\n\tk = cv.waitKey(30) & 0xff\r\n\tif k == 27:\r\n  \tbreak\r\ncap.release()\r\ncv.destroyAllWindows()\r\n```\r\n\r\n## 3.1.4\t结果\r\n原始图像和两种方法去除背景后的结果分别在图3-1、图3-2和图3-3中给出。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224120425374.png\" height=\"300\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224120430639.png\" height=\"300\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224120438176.png\" height=\"300\">\r\n</p>\r\n"
  },
  {
    "path": "chapter 4/3",
    "content": "\n"
  },
  {
    "path": "chapter 4/处理引起视错觉的图像.md",
    "content": "## 4.2.1\t目标\r\n在本节教程中，我们将介绍如何重现我们的眼睛在特定光线条件下感知到的视觉错觉：阿德尔森棋盘。\r\n\r\n## 4.2.2\t阿德尔森棋盘\r\n当看到如图4-6所示的棋盘图，人类的眼睛感知到“B”方块比“A”方块更亮，尽管它们拥有完全相同的RGB颜色。当然，在现实世界中，棋盘上有一个“B”正方形，它比“A”的颜色浅，但在这幅图中，绿色圆柱投射在“B”正方形上的阴影最终使“A”和“B”正方形的亮度相同。这个棋盘就被称为阿德森棋盘。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022412270887.png\" height=\"300\">\r\n</p>\r\n \r\n我们的视觉系统确实对阴影进行了“补偿”，使我们感知到“B”方更亮，好像这阴影不会在那里。这是由于中央视网膜凹区域的局部适应过程造成的。我们可以把两个正方形的一部分切下来，然后在没有任何背景的情况下看着它们。我们还可以使用工具测量两个正方形的RGB值。在这张图中，我们裁剪了一小块A和B的方块，并把它们放在一起。很明显它们有相同的亮度，结果图在图4-7给出。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224122822926.png\" height=\"100\">\r\n</p>\r\n\r\n实际上的原理是，当我们看一个区域时，我们的眼睛局部适应亮度，过滤噪音，加强轮廓等，考虑到周围的区域，这使得产生错觉。因此，被较亮细胞包围的A细胞可以被认为较暗。相反，B细胞的邻域较暗，B细胞则被认为较亮。\r\n\r\n## 4.2.3\t重现错觉\r\nbioinspired模块模拟了视网膜的过程，它再现了我们眼睛的局部适应能力。这意味着我们可以将细小通道的输出真正包含的亮度值与我们用眼睛感知的亮度值相似。具体来说，在这种情况下，我们期望“B”方块的RGB值实际上比“A”值更小。\r\n\r\n为了正确地模仿我们的眼睛，我们需要opencv对正确的图像部分进行局部适应。这意味着我们必须确保opencv的“局部”概念与我们的图像尺寸匹配，否则局部适应将不能像预期的那样工作。由于这个原因，我们可能不得不根据图像分辨率调整hcellsSpatialConstant参数(该参数在技术上指定较低的空间切割频率，或较慢的亮度变化灵敏度)。针对本教程中的图像，可以使用视网膜模型默认参数。\r\n\r\nbioinspired模块自带了用于展示的example_bioinspired_retinaDemo示例将图像加载给视网膜模型。运行该示例的命令在代码清单4-10中给出。\r\n\r\n```cpp\r\n代码清单4-10\r\nexample_bioinspired_retinaDemo -image checkershadow_illusion4med.jpg\r\n```\r\n\r\n这是一个静态图像，但我们的视网膜刚刚开始移动到一个新的环境(眼睛睁开)，必须适应这个环境。在这种瞬态状态下，亮度信息起作用，我们或多或少地看到绝对亮度值。为了重现幻觉，绝对亮度是你不需要看的。\r\n\r\n一旦达到稳定状态，我们就会收到更多的背景亮度信息。眼睛以中心环绕的方式工作，并考虑周围的亮度来评估感兴趣区域的亮度水平。那就是我们的幻觉出现的时候!\r\n\r\n当处理一个单独的帧时，只需要稳态响应，我们需要做的是重复地给视网膜输入相同的帧(这是示例代码所做的)，就像处理一个静止的视频一样。或者，我们可以将视网膜时间参数设置为0以立即获得稳定状态(xml文件的photoreceptorsTemporalConstant和hcellsTemporalConstant参数)。然而，在这种情况下，我们应该意识到，我们正在做的实验再现真实视网膜的行为时，故意降低了准确性!\r\n\r\n代码清单4-11中给出了处理图像的一小段python代码，它将迭代20次。这是经过实验发现的较好的次数\r\n\r\n```python\r\n代码清单4-11：python代码\r\nimport cv2 as cv\r\ninputImage = cv.imread('checkershadow_illusion4med.jpg', 1)\r\nretina = cv.bioinspired.createRetina((inputImage.shape[1], inputImage.shape[0]))\r\n# the retina object is created with default parameters. If you want to read\r\n# the parameters from an external XML file, uncomment the next line\r\n#retina.setup('MyRetinaParameters.xml')\r\n# feed the retina with several frames, in order to reach 'steady' state\r\nfor i in range(20):\r\n    retina.run(inputImage)\r\n# get our processed image :)\r\nretinaOut_parvo = retina.getParvo()\r\n# show both the original image and the processed one\r\ncv.imshow('image', inputImage)\r\ncv.imshow('retina parvo out', retinaOut_parvo)\r\n# wait for a key to be pressed and exit\r\ncv.waitKey(0)\r\ncv.destroyAllWindows()\r\n# write the output image on a file\r\ncv.imwrite('checkershadow_parvo.png', retinaOut_parvo)\r\n```\r\n最终我们将得到如图4-8所示的结果。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022412331978.png\" height=\"300\">\r\n</p>\r\n\r\n## 4.2.4\t结果分析\r\n将图4-8中“A”块和“B”块裁剪出来放在一起，结果如图4-9所示。我们可以看到经过处理，此时“A”块的像素值要大于“B”块，这样的结果更加符合我们看到的结果。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224123523891.png\" height=\"100\">\r\n</p>\r\n\r\n \r\n"
  },
  {
    "path": "chapter 4/视网膜视觉和真实世界的视觉.md",
    "content": "## 4.1.1\t目标\r\n在本节教程中，我们将介绍人类视网膜模型，它显示了一些有趣的图像预处理和增强特性，本教程中主要内容包括：\r\n\r\n-\t视网膜的两个主要通道\r\n-\t视网膜模型的基本使用\r\n-\t微调参数\r\n\r\n## 4.1.2\t总体介绍\r\n模型的提出来源于Jeanny Herault在Gipsa的研究，其主要研究成果在论文Vision: Images, Signals and Neural Networks-Models of Neural Processing in Visual Perception中给出。它涉及到Listic(代码维护者和用户)实验室的图像处理应用程序。它不是一个完整的模型，但它的提出揭示了一些有趣的事情，可以应用到增强图像处理中。该模型允许使用以下人类视网膜特性：\r\n\r\n-\t光谱白化具有3个重要的效应：高频率信号抵消(噪声)、中频细节增强和低频亮度能量降低。\r\n-\t局部对数亮度压缩可以增强细节，即使在低光条件也可以增强细节。\r\n-\t细节信息和瞬态信息的去相关性。\r\n\r\n接下俩对前两点特性进行详细说明：\r\n\r\n在图4-1中，左侧图像是一个高动态范围的图像。为了使它能较为清晰的看见细节，将原始的输入图像线性的调整图像亮度范围，并转换为8bit/channel格式。因为太强烈的局部对比，这种较强的转换隐藏了许多细节。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224120835276.png\" height=\"300\">\r\n</p>\r\n\r\n在图4-2中，一起应用局部亮度适应、空间噪声去除和光谱白化，就像你的视网膜一样，在较低范围的8位数据通道上传输准确的信息。在这张图片上，噪音被明显的被去除，局部细节被强烈的亮度对比所掩盖。输出图像保持自然，增强视觉内容。\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224121000879.png\" height=\"300\">\r\n</p>\r\n \r\n## 4.1.3\t视网膜模型输出通道\r\n视网膜模型有两种形式的输出：\r\n-\t第一个种叫做单细胞通道。它主要活跃于视网膜中央窝区(具有彩色感光感受器的高分辨率中央视觉)，其目的是为静止在视网膜上的视觉细节提供准确的彩色视觉。另一方面，模糊在视网膜上运动的物体。\r\n\r\n-\t第二个是Magnocellular通道。它主要活跃于视网膜周围视觉，并发出与变化事件(运动、瞬变事件等)相关的信号。这些输出信号还有助于视觉系统将视网膜聚焦在“瞬态”/移动区域进行更详细的分析，从而改进视觉场景上下文联系和对象分类。\r\n\r\n注意：与真实的视网膜不同，我们用相同的分辨率将这两个通道应用于整个输入图像。这使得增强的视觉细节和运动信息可以应用在所有的图像种。但是，这两个通道是互斥的。例如，如果单细胞通道在一个区域提供了强大的能量，那么，由于存在瞬态事件，部分细胞通道肯定是模糊的。\r\n\r\n为了说明这个问题，我们使用一个在较为黑暗环境种的相机采集的视频作为原始图像，分析视网膜模型中两个通道各起到什么作用。这个视频在一个大学的会场录制，里面有一些学生一边和老师交谈一边在移动。\r\n\r\n在这个视频中，由于黑暗的环境，信噪比低，所以图像采集的质量较低，在视觉特征边缘上出现了虚假的色彩。具体形式如图4-3所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224121200816.png\" height=\"300\">\r\n</p>\r\n\r\n之后将视网膜模型应用于图4-3，单细胞输出可以得到如图4-4所示的图片。在使用的视网膜配置中，全局亮度被保留，局部对比度被增强。此外，信噪比也得到了改善:由于高频时空噪声得到了降低，增强的细节不会被任何增强的噪声所破坏。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224121316946.png\" height=\"300\">\r\n</p>\r\n\r\n继续输出Magnocellular通道的信息，输出结果如图4-5所示。当瞬态事件发生时，它的信号很强。当一个学生在图像的底部移动时，会产生高能量。其余的图像是静态的，但它是产生了一个强大的噪音。在这里，视网膜过滤掉大部分的噪音，从而产生低虚假运动区域的“警报”。这个通道可以用作瞬态/移动区域检测器：它可以为低成本的分割工具提供相关信息，从而突出显示事件发生的区域。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224121508878.png\" height=\"300\">\r\n</p>\r\n\r\n## 4.1.4\t代码教程\r\n本教程使用的源码可以在opencv_folder/samples/cpp/tutorial_code/bioinspired/ retina_tutorial.cpp找到。视网膜模型在:cv::bioinspired名称空间命名空间中，因此一定要编译bioinspired模块，该模块需要依赖opencv_core (cv::Mat和friends对象管理)、opencv_highgui (显示和图像/视频读取)这两个基础库。我们可以分别用include包含这三个库，如代码清单4-1中所示。\r\n\r\n```cpp\r\n代码清单4-1：包含头文件\r\n#include <opencv2/bioinspired.hpp>\r\n#include < opencv2/core.hpp >\r\n#include <opencv2/highgui.hpp>\r\n```\r\n程序中，首先声明一个cv::Mat矩阵，并加载输入图像。同时分配一个cv::VideoCapture对象准备加载视频流(如果必要)，这个过程在代码清单4-2中实现。\r\n\r\n```cpp\r\n代码清单4-2\r\nint main(int argc, char* argv[]) {\r\n    // declare the retina input buffer... that will be fed differently in \r\n    //regard of the input media\r\n  \tcv::Mat inputFrame;\r\n  \tcv::VideoCapture videoCapture; // in case a video media is used, its \r\n```\r\n\r\n在处理之前，首先检查输入的命令参数。这里我们可以根据用户输入选择加载的数据类型，如果用户选择command –image，就加载图像，如果用户选择command –video就加载视频。\r\n\r\n此外，如果用户在程序调用结束时添加log命令，那么由视网膜执行的空间对数图像采样将被布尔标志useLogSampling考虑在内。\r\n\r\n```cpp\r\n代码清单4-3\r\n// welcome message\r\nstd::cout<<\"*********************************************\"<<std::endl;\r\nstd::cout<<\"* Retina demonstration : demonstrates the use of is a wrapper class of the Gipsa/Listic Labs retina model.\"<<std::endl;\r\nstd::cout<<\"* This demo will try to load the file 'RetinaSpecificParameters.xml' (if exists).\\nTo create it, copy the autogenerated template 'RetinaDefaultParameters.xml'.\\nThen tweak it with your own retina parameters.\"<<std::endl;\r\n// basic input arguments checking\r\nif (argc<2)\r\n{\r\n  \thelp(\"bad number of parameter\");\r\n  \treturn -1;\r\n}\r\nbool useLogSampling = !strcmp(argv[argc-1], \"log\"); // check if user wants retina log sampling processing\r\nstd::string inputMediaType=argv[1];\r\n// checking input media type (still image, video file, live video acquisition)\r\nif (!strcmp(inputMediaType.c_str(), \"-image\") && argc >= 3)\r\n{\r\n    std::cout<<\"RetinaDemo: processing image \"<<argv[2]<<std::endl;\r\n    // image processing case\r\n  \tinputFrame = cv::imread(std::string(argv[2]), 1); // load image in RGB mode\r\n}\r\nif (!strcmp(inputMediaType.c_str(), \"-video\"))\r\n{\r\n    if (argc == 2 || (argc == 3 && useLogSampling)) // attempt to grab images from a video capture device\r\n    {\r\n        videoCapture.open(0);\r\n    }\r\n    else// attempt to grab images from a video filestream\r\n    {\r\n        std::cout<<\"RetinaDemo: processing video stream \"<<argv[2]<<std::endl;\r\n        videoCapture.open(argv[2]);\r\n    }\r\n   \t// grab a first frame to check if everything is ok\r\n    videoCapture>>inputFrame;\r\n}\r\nelse\r\n{\r\n    // bad command parameter\r\n  \thelp(\"bad command parameter\");\r\n  \treturn -1;\r\n}\r\n```\r\n\r\n检测图像是否被读取，如果没有，显示错误并停止程序。\r\n\r\n```cpp\r\n代码清单4-4\r\nif (inputFrame.empty())\r\n{\r\n    help(\"Input media could not be loaded, aborting\");\r\n    return -1;\r\n}\r\n```\r\n之后运行视网膜模型。在这里建议分配一个视网膜实例并管理最终的日志采样选项。视网膜构造器至少需要一个cv::Size对象来显示必须管理的输入数据大小。可以设置其他选项，如颜色及其相关的颜色多路复用策略(这里，Bayer多路复用使用enum cv::bioinspired::RETINA_COLOR_BAYER标志)。如果使用对数采样，可以调整图像简化系数(较小的输出图像)和对数采样强度。\r\n\r\n```cpp\r\n代码清单4-5\r\n// pointer to a retina object\r\ncv::Ptr<cv::bioinspired::Retina> myRetina;\r\n// if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision)\r\nif (useLogSampling)\r\n{\r\n    myRetina = cv::bioinspired::createRetina(inputFrame.size(), true, cv::bioinspired::RETINA_COLOR_BAYER, true, 2.0, 10.0);\r\n}\r\nelse// -> else allocate \"classical\" retina :\r\n    myRetina = cv::bioinspired::createRetina(inputFrame.size());\r\n```\r\n\r\n之后将参数写入一个包含视网膜默认参数的默认xml文件，用于创建模板。这里生成的模板xml文件称为RetinaDefaultParameters.xml。\r\n\r\n```cpp\r\n代码清单4-6\r\n// save default retina parameters file in order to let you see this and \r\n//maybe modify it and reload using method \"setup\"\r\nmyRetina->write(\"RetinaDefaultParameters.xml\");\r\n```\r\n\r\n之后，视网膜尝试加载另一个名为RetinaSpecificParameters.xml的xml文件。如果我们创建了它，并引入了自己的设置，它将被加载，或者我们可以使用默认的视网膜参数。\r\n\r\n```cpp\r\n代码清单4-7\r\n// load parameters if file exists\r\nmyRetina->setup(\"RetinaSpecificParameters.xml\");\r\n```\r\n\r\n创建一些输出缓冲区，准备接收两个视网膜通道的输出.\r\n\r\n```cpp\r\n代码清单4-8\r\n// declare retina output buffers\r\ncv::Mat retinaOutput_parvo;\r\ncv::Mat retinaOutput_magno;\r\n```\r\n\r\n然后，在一个循环中运行视网膜，如果需要，从视频序列加载新帧，并将视网膜输出返回到专用缓冲区。\r\n\r\n```cpp\r\n代码清单4-9\r\n// processing loop with no stop condition\r\nwhile(true)\r\n{\r\n    // if using video stream, then, grabbing a new frame, else, input remains the same\r\n    if (videoCapture.isOpened())\r\n  \tvideoCapture>>inputFrame;\r\n  \t// run retina filter on the loaded input frame\r\n  \tmyRetina->run(inputFrame);\r\n  \t// Retrieve and display retina output\r\n  \tmyRetina->getParvo(retinaOutput_parvo);\r\n   \tmyRetina->getMagno(retinaOutput_magno);\r\n  \tcv::imshow(\"retina input\", inputFrame);\r\n  \tcv::imshow(\"Retina Parvo\", retinaOutput_parvo);\r\n  \tcv::imshow(\"Retina Magno\", retinaOutput_magno);\r\n  \tcv::waitKey(10);\r\n}\r\n```\r\n"
  },
  {
    "path": "chapter 5/5",
    "content": "\n"
  },
  {
    "path": "chapter 5/多相机标定.md",
    "content": "## 5.2.1\t目标\r\n在本节教程中，我们将介绍如何使用多相机校准工具箱主要内容包括：\r\n\r\n1.\t“随机”图案介绍和单个相机标定\r\n2.\t多相机标定\r\n\r\n## 5.2.2\t“随机”图案介绍和标定\r\n“随机”图案是一幅随机生成的图像。因为它是随机的，所以有许多特征点。生成后可将其打印出来并用作标定板。图5-10和图5-11分别是随机图像和为它拍摄的图片。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224130609660.png\" height=\"300\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224130634268.png\" height=\"300\">\r\n</p>\r\n \r\n要生成随机图像，需要使用ccalib模块中的类cv::randpattern::RandomPatternGenerator。该类的使用方法在代码清单5-9中给出。\r\n\r\n```cpp\r\n代码清单5-9：生成随机图像\r\ncv::randpattern::RandomPatternGenerator generator(width, height);\r\ngenerator.generatePattern();\r\npattern = generator.getPattern();\r\n```\r\n这里的width和height是图像的宽和高。生成图案后，我们可以把它打印出来并拍几张照片。之后便可以用这些图像来标定相机。\r\n\r\n首先，需要检测拍摄的图像中的objectPoints和imagePoints。这里我们需要使用类cv::randpattern::RandomPatternCornerFinder来检测它们。该类的使用方法在代码清单5-10中给出。\r\n\r\n```cpp\r\n代码清单5-10：检测图像角点\r\ncv::randpattern::RandomPatternCornerFinder finder(patternWidth, patternHeight, nMiniMatches);\r\nfinder.loadPattern(pattern);\r\nfinder.computeObjectImagePoints(vecImg);\r\nvector<Mat> objectPoints = finder.getObjectPoints();\r\nvector<Mat> imagePoints = finder.getImagePoints();\r\n```\r\n\r\n这里可变的参数patternWidth和patternHeight是物理图像的宽度和高度，它使用用户自定义的单位。vecImg是存储标定图像的vector容器。\r\n\r\n然后，使用标定函数，如cv::calibrateCamera或cv::omnidir::calibrate来标定相机。\r\n\r\n## 5.2.3\t多相机标定\r\n接下来我们介绍多个相机标定，到目前为止这个工具箱必须使用前文介绍的随机图像。\r\n\r\n为了标定多个相机，我们需要拍摄一些包含随机图案的照片。为了标定外部参数，需要同时使用多个摄像机(至少两个)来拍摄同一个图案。为了分清拍摄的相机和对应的图像，图像文件应该像这样命名：“cameraIdx-timestamp.*”。具有相同时间标记的照片意味着它们是由多个相机拍摄的同一个图案。另外，cameraIdx应该从0开始。例如：“0-129.png”、“0-187.png”、“1-187.png”、“2-129.png”。\r\n\r\n然后，运行多个相机标定的程序，具体内容如代码清单5-11中所示。\r\n\r\n```cpp\r\n代码清单5-11：多相机标定\r\ncv::multicalib::MultiCameraCalibration multiCalib(cameraType, nCamera, inputFilename,patternWidth, patternHeight, showFeatureExtraction, nMiniMatches);\r\nmultiCalib.run();\r\nmultiCalib.writeParameters(outputFilename);\r\n```\r\n\r\n这里的cameraType表示相机类型，支持multicalib::MultiCameraCalibration::PINHOLE和multicalib::MultiCameraCalibration::OMNIDIRECTIONAL两种类型。有关广角相机的更多内容，可以参考cv::omnidir模块了解详情。nCamera是相机的数量。inputFilename是由opencv/sample中的imagelist_creator生成的文件名称。patternWidth和patternHeight是图案的物理宽度和高度。showFeatureExtraction是一个标志，指示是否显示特征提取过程。nMiniMatches是应该在每幅图中检测到的最小的点数，不满足该条件时这幅图将被舍弃。outputFilename是用于存储参数的xml文件名。\r\n\r\n"
  },
  {
    "path": "chapter 5/广角相机标定.md",
    "content": "本章将介绍contrib扩展模块中相机标定模块，将以示例的形式重点介绍全景相机和多相机的联合标定。其中对广角相机标定包括广角相机的标定、校正和立体重建等三部分；对多相机标定包括“随机”图案的介绍和多相机标定两部分。\r\n\r\n\r\n## 5.1.1\t目标\r\n在本节教程中，我们将介绍如何对广角相机进行标定，主要内容包括：\r\n\r\n1.\t标定单台相机\r\n2.\t标定一对立体相机\r\n3.\t校正图像、大幅消除失真\r\n4.\t从两幅具有大视场的立体图像中进行三维重建\r\n5.\t与opencv/calib3d/中的鱼眼模型进行比较\r\n\r\n## 5.1.2\t 单个相机标定\r\n标定相机的第一步是获取标定图案并拍摄一些照片。OpenCV支持多种图案，比如棋盘格和圆形网格。还可以使用一个名为random pattern的新图案，我们称为“随机图案”。关于随机图案的更多细节，读者自行查阅官方文档opencv_contrib/modules/ccalib进行了解。\r\n\r\n第二步是从标定图案中提取角点。针对棋盘格，可以使用OpenCV基础库中的函数cv::findChessboardCorners提取角点；针对圆形网格，可以使用函数cv::findCirclesGrid提取角点；针对随机图案，可以使用opencv_contrib/modules/ccalib/src/randomPattern.hpp中的randomPatternCornerFinder类提取角点。之后将图像中的角点坐标保存在imagePoints这样类型的变量中。imagePoints的类型可以是std::vector<std::vector<cv::Vec2f>>，第一个vector容器存储每一张图片的角点，第二个vector容器存储同一图案的所有图片中的角点。imagePoints的类型也可以是std::vector<cv::Mat>，其中cv::Mat的类型为CV_32FC2。\r\n\r\n此外，还需要世界坐标中相应的三维点坐标。我们可以任意设定世界坐标系，并通过图案的物理大小计算三维点的坐标。之后将这些点保存在objectPoints类型中，类似于imagePoints，objectPoints类型可以是std::vector<std::vector<Vec3f>>或std::vector<cv::Mat>类型，其中cv::Mat为CV_32FC3类型。\r\n\r\n最后一个需要输入的参数是图像的尺寸大小。\r\n\r\n**警告\r\n*objectPoints和imagePoints的大小必须相同，因为它们彼此对应。***\r\n\r\n在官方示例中，标定所需要的数据被存放在一个xml文件中，我们可以通过opencv_contrib/modules/ccalib/tutorial/data/omni_calib_data.xml找到它。文件中存储了一个objectPoints、imagePoints和imageSize这三个类数据。代码清单5-1是加载该文件的代码\r\n\r\n```cpp\r\n代码清单5-1：加载数据\r\ncv::FileStorage fs(\"omni_calib_data.xml\", cv::FileStorage::READ);\r\nstd::vector<cv::Mat> objectPoints, imagePoints;\r\ncv::Size imgSize;\r\nfs[\"objectPoints\"] >> objectPoints;\r\nfs[\"imagePoints\"] >> imagePoints;\r\nfs[\"imageSize\"] >> imgSize;\r\n```\r\n\r\n**提示\r\n*如果没有下载opencv_contrib安装包，可以在“小白学视觉”公众号后台回复“omni_calib_data”获取这个数据文件***\r\n\r\n然后定义一些变量来存储输出参数，并运行标定代码，具体代码如下：\r\n\r\n```cpp\r\n代码清单5-2：标定代码\r\ncv::Mat K, xi, D, idx;\r\nint flags = 0;\r\ncv::TermCriteria critia(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 200, 0.0001);\r\nstd::vector<cv::Mat> rvecs, tvecs;\r\ndouble rms = cv::omnidir::calibrate(objectPoints, imagePoints, imgSize, K, xi, D, rvecs, tvecs, flags, critia, idx);\r\n```\r\n\r\nK, xi, D是内部参数，rvecs和tvecs是存储图案姿态的外部参数。它们的深度都是CV_64F。xi是广角相机模型的一个单值变量。idx是一个CV_32S的Mat，存储实际用于校准的图像索引，这是因为一些图像在初始化步骤中失败，所以在最后的优化中没有使用它们。返回值rms是重投影误差的均方根值。\r\n\r\n校准支持一些功能，flags是一些特性的枚举变量，包括：\r\n\r\n-\tcv::omnidir::CALIB_FIX_SKEW\r\n-\tcv::omnidir::CALIB_FIX_K1\r\n-\tcv::omnidir::CALIB_FIX_K2\r\n-\tcv::omnidir::CALIB_FIX_P1\r\n-\tcv::omnidir::CALIB_FIX_P2\r\n-\tcv::omnidir::CALIB_FIX_XI\r\n-\tcv::omnidir::CALIB_FIX_GAMMA\r\n-\tcv::omnidir::CALIB_FIX_CENTER\r\n\r\n我们可以指定flags来在标定期间修正参数。使用“+”操作符可以设置多个参数。例如，CALIB_FIX_SKEW+CALIB_FIX_K1意味着修正kew和K1。\r\n\r\ncriteria是优化过程中的停止条件，例如，cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 200, 0.0001)，即迭代200次，或者当相对变化小于0.0001时停止。\r\n\r\n## 5.1.3\t立体标定\r\n立体标定是同时标定两个摄像机。输出参数包括两台相机的相机内部参数和相机的相对位姿。为了确定相对位姿，两个相机必须同时拍摄相同的图案，这样两个相机的objectPoints是相同的。\r\n\r\n首先根据前一小节的方法分别检测两个相机拍摄图像的角点，以获得imagePoints1和imagePoints2。然后计算共同的objectPoints。\r\n\r\n立体标定所需要的数据被存放在一个xml文件中，我们可以通过opencv_contrib/modules/ccalib/tutorial/data/omni_stereocalib_data.xml找到它。可以通过代码清单5-3中的代码加载数据。\r\n\r\n**提示\r\n*同样可以在“小白学视觉”公众号后台回复“omni_stereocalib_data.xml”获取这个数据文件***\r\n\r\n```cpp\r\n代码清单5-3：加载立体标定数据\r\ncv::FileStorage fs(\"omni_stereocalib_data.xml\", cv::FileStorage::READ);\r\nstd::vector<cv::Mat> objectPoints, imagePoints1, imagePoints2;\r\ncv::Size imgSize1, imgSize2;\r\nfs[\"objectPoints\"] >> objectPoints;\r\nfs[\"imagePoints1\"] >> imagePoints1;\r\nfs[\"imagePoints2\"] >> imagePoints2;\r\nfs[\"imageSize1\"] >> imgSize1;\r\nfs[\"imageSize2\"] >> imgSize2;\r\n```\r\n之后利用代码清单5-4中的代码进行立体标定。\r\n\r\n```cpp\r\n代码清单5-4：立体标定\r\ncv::Mat K1, K2, xi1, xi2, D1, D2;\r\nint flags = 0;\r\ncv::TermCriteria critia(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 200, 0.0001);\r\nstd::vector<cv::Mat> rvecsL, tvecsL;\r\ncv::Mat rvec, tvec;\r\ndouble rms = cv::omnidir::stereoCalibrate(objectPoints, imagePoints1, imagePoints2, imgSize1, imgSize2, K1, xi1, D1, K2, xi2, D2, rvec, tvec, rvecsL, tvecsL, flags, critia, idx);\r\n```\r\n\r\n程序中的rvec和tvec是第一个和第二个照相机之间的转换。rvecsL和tvecsL是空间中标定物体和第一个摄像头之间的转换。\r\n\r\n## 5.1.4\t图像矫正\r\n全景图像的失真很大，与人眼观测的结果有很大差异。但是如果摄像机的参数已知，可以对全景图像进行矫正。图5-1是一个360度水平视场全景图像的例子。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224124608869.png\" height=\"300\">\r\n</p>\r\n \r\n\r\n矫正后，会生成一张类似透视图的图像。接下来将利用ccalib模块中的cv::omnidir::undistortImage函数对全景图像进行矫正。\r\n\r\n```cpp\r\n代码清单5-5：全景图像校正函数\r\ncv::omnidir::undistortImage(distorted, undistorted, K, D, xi, int flags, Knew, new_size)\r\n```\r\n\r\n函数中参数distorted和undistorted分别为原始图像和矫正后的图象。K、D、xi为相机参数。Knew和new_size是矫正后图像的相机矩阵和图像大小。flags是矫正类型，它可以选择的类型如下：\r\n\r\n-\tRECTIFY_PERSPECTIVE: 对透视图进行透视矫正，会丢失部分视场。\r\n-\tRECTIFY_CYLINDRICAL: 矫正成圆柱形图像，保留所有视场。\r\n-\tRECTIFY_STEREOGRAPHIC: 矫正成可能会失去一点视场的立体图像。\r\n-\tRECTIFY_LONGLATI: 矫正成像世界地图的经纬度图。这种矫正可以用于立体重建，但可能不方便查看。\r\n\r\n图5-2、图5-3、图5-4和图5-5分别是这四种矫正后的结果\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224125217615.png\" height=\"300\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224125252628.png\" height=\"300\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022412531392.png\" height=\"300\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022412542145.png\" height=\"300\">\r\n</p>\r\n \r\n可以看出，透视矫正后的图像只保留了很少的视野。柱面矫正保留了所有的视野，只在画面底部中间位置不自然。在底部中间的位置，立体校正畸变小于柱面矫正，但其他位置的畸变较大，并且无法保留所有视场。对于失真非常大的图像，经纬度矫正效果不好，但是可以在一条线上形成对极约束，因此可以将立体匹配应用于全景图像。\r\n\r\n**注意\r\n*为了获得更好的效果，应该谨慎选择参数Knew，它与相机有关。通常来说，较小的焦距会导致较小的视野，反之亦然。下面是一些推荐的设置。***\r\n\r\n针对 RECTIFY_PERSPECTIVE （透视矫正）方法，Knew计算方法如代码清单5-6中所示。\r\n\r\n```cpp\r\n代码清单5-6：计算Knew\r\nKnew = Matx33f(new_size.width/4, 0, new_size.width/2, 0, new_size.height/4, new_size.height/2, 0, 0, 1);\r\n```\r\n针对 RECTIFY_CYLINDRICAL, RECTIFY_STEREOGRAPHIC, RECTIFY_LONGLATI（柱面校正、立体校正、经纬度校正）方法，Knew计算方法如代码清单5-7中所示。\r\n\r\n```cpp\r\n代码清单5-7：计算Knew\r\nKnew = Matx33f(new_size.width/3.1415, 0, 0, 0, new_size.height/3.1415, 0,0, 0, 1);\r\n```\r\n\r\n此外，可能需要更改(u0, v0)以获得更好的视图。\r\n\r\n## 5.1.5\t立体重建\r\n立体重建是从标定好的立体像机对中重建空间中的三维点。这是计算机视觉中的一个基本问题。但是，对于全景摄像机来说，由于畸变较大使得重建困难。常规方法是将图像校正为透视图像，并在透视图像中进行立体重建。但是，上一小节表明，对图像进行透视矫正会损失太多的视场，这就浪费了全景摄像机最大的优势——大视场。\r\n\r\n立体重建的第一步是对图像进行立体矫正，使极线成为水平线。我们使用经纬度矫正来保留所有视场。也可以使用透视矫正，但并不推荐这样做。第二步是通过立体匹配来获得视差图。最后，通过视差图生成空间中的三维点。\r\n\r\n广角相机立体重建的函数是omnidir::stereoReconstruct。接下来我们将用一个例子来说明如何使用它进行立体重建。\r\n\r\n首先，按照前面描述的步骤标定一对立体像机，并获取K1，D1，xi1，K2，D2，xi2，rvec，tvec等参数。然后分别从第一台和第二台相机读取两个图像，例如image1和image2，如图5-6中所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224125836319.png\">\r\n</p>\r\n\r\n然后，运行omnidir :: stereoReconstruct，示例代码在代码清单5-8中给出。\r\n\r\n```cpp\r\n代码清单5-8：三维重建\r\ncv::Size imgSize = img1.size();\r\nint numDisparities = 16*5;\r\nint SADWindowSize = 5;\r\ncv::Mat disMap;\r\nint flag = cv::omnidir::RECTIFY_LONGLATI;\r\nint pointType = omnidir::XYZRGB;\r\n// the range of theta is (0, pi) and the range of phi is (0, pi)\r\ncv::Matx33d KNew(imgSize.width / 3.1415, 0, 0, 0, imgSize.height / 3.1415, 0, 0, 0, 1);\r\nMat imageRec1, imageRec2, pointCloud;\r\ncv::omnidir::stereoReconstruct(img1, img2, K1, D1, xi1, K2, D2, xi2, R, T, flag, numDisparities, SADWindowSize, disMap, imageRec1, imageRec2, imgSize, KNew, pointCloud);\r\n```\r\n\r\n其中，变量flag表示校正类型，只能使用RECTIFY_LONGLATI(推荐)或者RECTIFY_PERSPECTIVE。numDisparities是最大视差值，SADWindowSize是cv :: StereoSGBM的窗口大小。pointType是一个用来定义点云类型的标志，omnidir :: XYZRGB类型表示每个点都是6维向量，前三个元素是xyz坐标，后三个元素是rgb颜色信息。另一种类型omnidir::XYZ表示每个点都是三维的，并且只有XYZ坐标。imageRec1和imagerec2分别是第一幅和第二幅图像校正后的图像。它们的极线具有相同的y坐标，这个特点使得立体匹配变得更容易，结果如图5-7所示。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224130019967.png\" height=\"300\">\r\n</p>\r\n\r\n从结果中可以看出它们对齐的很好。变量disMap存储了通过函数cv :: StereoSGBM从imageRec1和imageRec2计算出的视差图。图5-7中两张图片的视差图如图5-8所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224130123151.png\" height=\"300\">\r\n</p>\r\n \r\n有了视差图后，我们可以计算每个像素对应的3D位置。点云存储在变量pointCloud中，pointCloud是3通道或6通道cv :: Mat。计算的点云结果如图5-9所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200224130231143.png\" height=\"300\">\r\n</p>\r\n\r\n\r\n"
  },
  {
    "path": "chapter 6/e",
    "content": "\n"
  },
  {
    "path": "chapter 6/使用Icosphere训练数据.md",
    "content": "本章介绍的内容需要的OpenCV版本新于OpenCV 3.0.0。\r\n\r\n## 6.1.1\t目标\r\n在本节教程中，我们将学习如何从3D模型中以适当的姿态生成训练图像用来进行CNN训练，主要内容包括：\r\n\r\n-\t如何生成物体表面的3D点云模型\r\n-\t如何使用3D模型生成训练图像。\r\n\r\n## 6.1.2\tC++代码\r\n```cpp\r\n代码清单6-1\r\n/*\r\n * Software License Agreement (BSD License)\r\n *\r\n *  Copyright (c) 2009, Willow Garage, Inc.\r\n *  All rights reserved.\r\n *\r\n *  Redistribution and use in source and binary forms, with or without\r\n *  modification, are permitted provided that the following conditions\r\n *  are met:\r\n *\r\n *   * Redistributions of source code must retain the above copyright\r\n *     notice, this list of conditions and the following disclaimer.\r\n *   * Redistributions in binary form must reproduce the above\r\n *     copyright notice, this list of conditions and the following\r\n *     disclaimer in the documentation and/or other materials provided\r\n *     with the distribution.\r\n *   * Neither the name of Willow Garage, Inc. nor the names of its\r\n *     contributors may be used to endorse or promote products derived\r\n *     from this software without specific prior written permission.\r\n *\r\n *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n *  \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\r\n *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r\n *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\r\n *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\r\n *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r\n *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r\n *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r\n *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r\n *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r\n *  POSSIBILITY OF SUCH DAMAGE.\r\n *\r\n */\r\n#include <opencv2/cnn_3dobj.hpp>\r\n#include <opencv2/viz/vizcore.hpp>\r\n#include <iostream>\r\n#include <stdlib.h>\r\n#include <time.h>\r\nusing namespace cv;\r\nusing namespace std;\r\nusing namespace cv::cnn_3dobj;\r\nstatic void listDir(const char *path, std::vector<String>& files, bool r)\r\n{\r\n    DIR *pDir;\r\n    struct dirent *ent;\r\n    char childpath[512];\r\n    pDir = opendir(path);\r\n    memset(childpath, 0, sizeof(childpath));\r\n    while ((ent = readdir(pDir)) != NULL)\r\n    {\r\n        if (ent->d_type & DT_DIR)\r\n        {\r\n            if (strcmp(ent->d_name, \".\") == 0 || strcmp(ent->d_name, \"..\") == 0 || strcmp(ent->d_name, \".DS_Store\") == 0)\r\n            {\r\n                continue;\r\n            }\r\n            if (r)\r\n            {\r\n                sprintf(childpath, \"%s/%s\", path, ent->d_name);\r\n                listDir(childpath,files,false);\r\n            }\r\n        }\r\n        else\r\n        {\r\n            if (strcmp(ent->d_name, \".DS_Store\") != 0)\r\n                files.push_back(ent->d_name);\r\n        }\r\n    }\r\n    sort(files.begin(),files.end());\r\n};\r\nint main(int argc, char *argv[])\r\n{\r\n    const String keys = \"{help | | demo :$ ./sphereview_test -ite_depth=2 -plymodel=../data/3Dmodel/ape.ply -imagedir=../data/images_all/ -labeldir=../data/label_all.txt -num_class=6 -label_class=0, then press 'q' to run the demo for images generation when you see the gray background and a coordinate.}\"\r\n    \"{ite_depth | 3 | Iteration of sphere generation.}\"\r\n    \"{plymodel | ../data/3Dmodel/ape.ply | Path of the '.ply' file for image rendering. }\"\r\n    \"{imagedir | ../data/images_all/ | Path of the generated images for one particular .ply model. }\"\r\n    \"{labeldir | ../data/label_all.txt | Path of the generated images for one particular .ply model. }\"\r\n    \"{bakgrdir | | Path of the backgroud images sets. }\"\r\n    \"{cam_head_x | 0 | Head of the camera. }\"\r\n    \"{cam_head_y | 0 | Head of the camera. }\"\r\n    \"{cam_head_z | -1 | Head of the camera. }\"\r\n    \"{semisphere | 1 | Camera only has positions on half of the whole sphere. }\"\r\n    \"{z_range | 0.6 | Maximum camera position on z axis. }\"\r\n    \"{center_gen | 0 | Find center from all points. }\"\r\n    \"{image_size | 128 | Size of captured images. }\"\r\n    \"{label_class |  | Class label of current .ply model. }\"\r\n    \"{label_item |  | Item label of current .ply model. }\"\r\n    \"{rgb_use | 0 | Use RGB image or grayscale. }\"\r\n    \"{num_class | 6 | Total number of classes of models. }\"\r\n    \"{binary_out | 0 | Produce binaryfiles for images and label. }\"\r\n    \"{view_region | 0 | Take a special view of front or back angle}\";\r\n    /* Get parameters from comand line. */\r\n    cv::CommandLineParser parser(argc, argv, keys);\r\n    parser.about(\"Generating training data for CNN with triplet loss\");\r\n    if (parser.has(\"help\"))\r\n    {\r\n        parser.printMessage();\r\n        return 0;\r\n    }\r\n    int ite_depth = parser.get<int>(\"ite_depth\");\r\n    String plymodel = parser.get<String>(\"plymodel\");\r\n    String imagedir = parser.get<String>(\"imagedir\");\r\n    String labeldir = parser.get<String>(\"labeldir\");\r\n    String bakgrdir = parser.get<String>(\"bakgrdir\");\r\n    int label_class = parser.get<int>(\"label_class\");\r\n    int label_item = parser.get<int>(\"label_item\");\r\n    float cam_head_x = parser.get<float>(\"cam_head_x\");\r\n    float cam_head_y = parser.get<float>(\"cam_head_y\");\r\n    float cam_head_z = parser.get<float>(\"cam_head_z\");\r\n    int semisphere = parser.get<int>(\"semisphere\");\r\n    float z_range = parser.get<float>(\"z_range\");\r\n    int center_gen = parser.get<int>(\"center_gen\");\r\n    int image_size = parser.get<int>(\"image_size\");\r\n    int rgb_use = parser.get<int>(\"rgb_use\");\r\n    int num_class = parser.get<int>(\"num_class\");\r\n    int binary_out = parser.get<int>(\"binary_out\");\r\n    int view_region = parser.get<int>(\"view_region\");\r\n    double obj_dist, bg_dist, y_range;\r\n    if (view_region == 1 || view_region == 2)\r\n    {\r\n        /* Set for TV */\r\n        if (label_class == 12)\r\n            obj_dist = 340;\r\n        else\r\n            obj_dist = 250;\r\n        ite_depth = ite_depth + 1;\r\n        bg_dist = 700;\r\n        y_range = 0.85;\r\n    }\r\n    else if (view_region == 0)\r\n    {\r\n        obj_dist = 370;\r\n        bg_dist = 400;\r\n    }\r\n    if (label_class == 5 || label_class == 10 || label_class == 11 || label_class == 12)\r\n        ite_depth = ite_depth + 1;\r\n    cv::cnn_3dobj::icoSphere ViewSphere(10,ite_depth);\r\n    std::vector<cv::Point3d> campos;\r\n    std::vector<cv::Point3d> campos_temp = ViewSphere.CameraPos;\r\n    /* Regular objects on the ground using a semisphere view system */\r\n    if (semisphere == 1)\r\n    {\r\n        if (view_region == 1)\r\n        {\r\n            for (int pose = 0; pose < static_cast<int>(campos_temp.size()); pose++)\r\n            {\r\n                if (campos_temp.at(pose).z >= 0 && campos_temp.at(pose).z < z_range && campos_temp.at(pose).y < -y_range)\r\n                    campos.push_back(campos_temp.at(pose));\r\n            }\r\n        }\r\n        else if (view_region == 2)\r\n        {\r\n            for (int pose = 0; pose < static_cast<int>(campos_temp.size()); pose++)\r\n            {\r\n                if (campos_temp.at(pose).z >= 0 && campos_temp.at(pose).z < z_range && campos_temp.at(pose).y > y_range)\r\n                campos.push_back(campos_temp.at(pose));\r\n            }\r\n        }\r\n        else\r\n        {\r\n            /* Set for sofa */\r\n            if (label_class == 10)\r\n            {\r\n                for (int pose = 0; pose < static_cast<int>(campos_temp.size()); pose++)\r\n                {\r\n                    if (campos_temp.at(pose).z >= 0 && campos_temp.at(pose).z < z_range && campos_temp.at(pose).y < -0.4)\r\n                    campos.push_back(campos_temp.at(pose));\r\n                }\r\n            }\r\n            else\r\n            {\r\n                for (int pose = 0; pose < static_cast<int>(campos_temp.size()); pose++)\r\n                {\r\n                    if (campos_temp.at(pose).z >= 0 && campos_temp.at(pose).z < z_range)\r\n                        campos.push_back(campos_temp.at(pose));\r\n                }\r\n            }\r\n        }\r\n    }\r\n    /* Special object such as plane using a full space of view sphere */\r\n    else\r\n    {\r\n        if (view_region == 1)\r\n        {\r\n            for (int pose = 0; pose < static_cast<int>(campos_temp.size()); pose++)\r\n            {\r\n                if (campos_temp.at(pose).z < 0.2 && campos_temp.at(pose).z > -0.2 && campos_temp.at(pose).y < -y_range)\r\n                    campos.push_back(campos_temp.at(pose));\r\n            }\r\n        }\r\n        else if (view_region == 2)\r\n        {\r\n            for (int pose = 0; pose < static_cast<int>(campos_temp.size()); pose++)\r\n            {\r\n                if (campos_temp.at(pose).z < 0.2 && campos_temp.at(pose).z > -0.2 && campos_temp.at(pose).y > y_range)\r\n                campos.push_back(campos_temp.at(pose));\r\n            }\r\n        }\r\n        else\r\n        {\r\n            for (int pose = 0; pose < static_cast<int>(campos_temp.size()); pose++)\r\n            {\r\n                if (campos_temp.at(pose).z < 0.2 && campos_temp.at(pose).z > -0.6)\r\n                    campos.push_back(campos_temp.at(pose));\r\n            }\r\n        }\r\n    }\r\n    std::fstream imglabel;\r\n    imglabel.open(labeldir.c_str(), fstream::app|fstream::out);\r\n    bool camera_pov = true;\r\n    /* Create a window using viz. */\r\n    viz::Viz3d myWindow(\"Coordinate Frame\");\r\n    /* Set window size. */\r\n    myWindow.setWindowSize(Size(image_size,image_size));\r\n    /* Set background color. */\r\n    myWindow.setBackgroundColor(viz::Color::gray());\r\n    myWindow.spinOnce();\r\n    /* Create a Mesh widget, loading .ply models. */\r\n    viz::Mesh objmesh = viz::Mesh::load(plymodel);\r\n    /* Get the center of the generated mesh widget, cause some .ply files, this could be ignored if you are using PASCAL database*/\r\n    Point3d cam_focal_point;\r\n    if (center_gen)\r\n        cam_focal_point = ViewSphere.getCenter(objmesh.cloud);\r\n    else\r\n        cam_focal_point = Point3d(0,0,0);\r\n    const char* headerPath = \"../data/header_for_\";\r\n    const char* binaryPath = \"../data/binary_\";\r\n    if (binary_out)\r\n    {\r\n        ViewSphere.createHeader(static_cast<int>(campos.size()), image_size, image_size, headerPath);\r\n    }\r\n    float radius = ViewSphere.getRadius(objmesh.cloud, cam_focal_point);\r\n    objmesh.cloud = objmesh.cloud/radius*100;\r\n    cam_focal_point = cam_focal_point/radius*100;\r\n    Point3d cam_y_dir;\r\n    cam_y_dir.x = cam_head_x;\r\n    cam_y_dir.y = cam_head_y;\r\n    cam_y_dir.z = cam_head_z;\r\n    char temp[1024];\r\n    std::vector<String> name_bkg;\r\n    if (bakgrdir.size() != 0)\r\n    {\r\n        /* List the file names under a given path */\r\n        listDir(bakgrdir.c_str(), name_bkg, false);\r\n        for (unsigned int i = 0; i < name_bkg.size(); i++)\r\n        {\r\n            name_bkg.at(i) = bakgrdir + name_bkg.at(i);\r\n        }\r\n    }\r\n    /* Images will be saved as .png files. */\r\n    size_t cnt_img;\r\n    srand((int)time(0));\r\n    do\r\n    {\r\n        cnt_img = 0;\r\n        for(int pose = 0; pose < static_cast<int>(campos.size()); pose++){\r\n            /* Add light. */\r\n            // double alpha1 = rand()%(314/2)/100;\r\n            // double alpha2 = rand()%(314*2)/100;\r\n            // printf(\"%f %f %f/n\", ceil(10000*sqrt(1 - sin(alpha1)*sin(alpha1))*sin(alpha2)), 10000*sqrt(1 - sin(alpha1)*sin(alpha1))*cos(alpha2), sin(alpha1)*10000);\r\n            // myWindow.addLight(Vec3d(10000*sqrt(1 - sin(alpha1)*sin(alpha1))*sin(alpha2),10000*sqrt(1 - sin(alpha1)*sin(alpha1))*cos(alpha2),sin(alpha1)*10000), Vec3d(0,0,0), viz::Color::white(), viz::Color::white(), viz::Color::black(), viz::Color::white());\r\n            int label_x, label_y, label_z;\r\n            label_x = static_cast<int>(campos.at(pose).x*100);\r\n            label_y = static_cast<int>(campos.at(pose).y*100);\r\n            label_z = static_cast<int>(campos.at(pose).z*100);\r\n            sprintf (temp,\"%02i_%02i_%04i_%04i_%04i_%02i\", label_class, label_item, label_x, label_y, label_z, static_cast<int>(obj_dist/100));\r\n            String filename = temp;\r\n            filename += \".png\";\r\n            imglabel << filename << ' ' << label_class << endl;\r\n            filename = imagedir + filename;\r\n            /* Get the pose of the camera using makeCameraPoses. */\r\n            if (view_region != 0)\r\n            {\r\n                cam_focal_point.x = cam_focal_point.y - label_x/5;\r\n            }\r\n            Affine3f cam_pose = viz::makeCameraPose(campos.at(pose)*obj_dist+cam_focal_point, cam_focal_point, cam_y_dir*obj_dist+cam_focal_point);\r\n            /* Get the transformation matrix from camera coordinate system to global. */\r\n            Affine3f transform = viz::makeTransformToGlobal(Vec3f(1.0f,0.0f,0.0f), Vec3f(0.0f,1.0f,0.0f), Vec3f(0.0f,0.0f,1.0f), campos.at(pose));\r\n            viz::WMesh mesh_widget(objmesh);\r\n            /* Pose of the widget in camera frame. */\r\n            Affine3f cloud_pose = Affine3f().translate(Vec3f(1.0f,1.0f,1.0f));\r\n            /* Pose of the widget in global frame. */\r\n            Affine3f cloud_pose_global = transform * cloud_pose;\r\n            /* Visualize camera frame. */\r\n            if (!camera_pov)\r\n            {\r\n                viz::WCameraPosition cpw(1); // Coordinate axes\r\n                viz::WCameraPosition cpw_frustum(Vec2f(0.5, 0.5)); // Camera frustum\r\n                myWindow.showWidget(\"CPW\", cpw, cam_pose);\r\n                myWindow.showWidget(\"CPW_FRUSTUM\", cpw_frustum, cam_pose);\r\n            }\r\n            /* Visualize widget. */\r\n            if (bakgrdir.size() != 0)\r\n            {\r\n                cv::Mat img_bg = cv::imread(name_bkg.at(rand()%name_bkg.size()));\r\n                /* Back ground images has a distance of 2 times of radius of camera view distance */\r\n                cv::viz::WImage3D background_widget(img_bg, Size2d(image_size*4.2, image_size*4.2), Vec3d(-campos.at(pose)*bg_dist+cam_focal_point), Vec3d(campos.at(pose)*bg_dist-cam_focal_point), Vec3d(0,0,-1)*bg_dist+Vec3d(0,2*cam_focal_point.y,0));\r\n                myWindow.showWidget(\"bgwidget\", background_widget, cloud_pose_global);\r\n            }\r\n            // mesh_widget.setRenderingProperty(viz::LINE_WIDTH, 4.0);\r\n            myWindow.showWidget(\"targetwidget\", mesh_widget, cloud_pose_global);\r\n            /* Set the viewer pose to that of camera. */\r\n            if (camera_pov)\r\n                myWindow.setViewerPose(cam_pose);\r\n            /* Save screen shot as images. */\r\n            myWindow.saveScreenshot(filename);\r\n            if (binary_out)\r\n            {\r\n            /* Write images into binary files for further using in CNN training. */\r\n                ViewSphere.writeBinaryfile(filename, binaryPath, headerPath,static_cast<int>(campos.size())*num_class, label_class, static_cast<int>(campos.at(pose).x*100), static_cast<int>(campos.at(pose).y*100), static_cast<int>(campos.at(pose).z*100), rgb_use);\r\n            }\r\n            cnt_img++;\r\n        }\r\n    } while (cnt_img != campos.size());\r\n    imglabel.close();\r\n    return 1;\r\n};\r\n```\r\n\r\n## 6.1.3\t代码解释\r\n接下来对代码清单6-1中的程序进行详细介绍。\r\n\r\n-\t创建一个窗口。\r\n\r\n```cpp\r\n代码清单6-2：创建窗口\r\nviz::Viz3d myWindow(\"Coordinate Frame\");\r\n```\r\n-\t将窗口大小设置为64*64，我们使用这个尺寸作为它的默认值。\r\nmyWindow.setWindowSize(Size(64,64));\r\n\r\n-\t添加坐标轴。\r\n```cpp\r\n代码清单6-3：窗口中添加坐标轴\r\nmyWindow.showWidget(\"Coordinate Widget\", viz::WCoordinateSystem());\r\nmyWindow.setBackgroundColor(viz::Color::gray());\r\nmyWindow.spin();\r\n```\r\n\r\n-\t创建一个Mesh部件，加载.ply模型。\r\n```cpp\r\n代码清单6-4：添加.ply模型\r\nviz::Mesh objmesh = viz::Mesh::load(plymodel);\r\n```\r\n\r\n-\t获取生成的mesh部件的中心，生成一些.ply文件。\r\n```cpp\r\n代码清单6-5：获取部件中心位置\r\nPoint3d cam_focal_point = ViewSphere.getCenter(objmesh.cloud);\r\n```\r\n\r\n-\t使用makeCameraPoses获取相机的姿态。\r\n```cpp\r\n代码清单6-6：获取相机位姿\r\nAffine3f cam_pose = viz::makeCameraPose(campos.at(pose) * radius+cam_focal_point, cam_focal_point, cam_y_dir * radius+cam_focal_point);\r\n```\r\n\r\n-\t获取相机坐标系到全局坐标系的变换矩阵。\r\n```cpp\r\n代码清单6-7：获取变换矩阵\r\nAffine3f transform = viz::makeTransformToGlobal(Vec3f(1.0f,0.0f,0.0f), Vec3f(0.0f,1.0f,0.0f), Vec3f(0.0f,0.0f,1.0f), campos.at(pose));\r\nviz::WMesh mesh_widget(objmesh);\r\n```\r\n\r\n-\t将屏幕截图保存为图像。\r\n```cpp\r\n代码清单6-8：截屏\r\nmyWindow.saveScreenshot(filename);\r\n```\r\n\r\n-\t将图像写入二进制文件，以便在CNN训练中进一步使用。\r\n```cpp\r\n代码清单6-9：生成二进制文件\r\nViewSphere.writeBinaryfile(filename, binaryPath, headerPath,(int)campos.size()*num_class, label_class, (int)(campos.at(pose).x*100), (int)(campos.at(pose).y*100), (int)(campos.at(pose).z*100), rgb_use);\r\n```\r\n"
  },
  {
    "path": "chapter 6/分析训练模型.md",
    "content": "您将学习如何分析一个训练过的模型的性能。\r\n## 6.3.1\t目标\r\n在本节教程中，我们将学习如何如何分析一个训练过的模型的性能，主要内容包括：\r\n\r\n-\t如何从特定图像中提取特征\r\n-\t如何对提取的特征进行有意义的对比\r\n\r\n## 6.3.2\tC++代码\r\n```cpp\r\n代码清单6-18\r\n/*\r\n * Software License Agreement (BSD License)\r\n *\r\n *  Copyright (c) 2009, Willow Garage, Inc.\r\n *  All rights reserved.\r\n *\r\n *  Redistribution and use in source and binary forms, with or without\r\n *  modification, are permitted provided that the following conditions\r\n *  are met:\r\n *\r\n *   * Redistributions of source code must retain the above copyright\r\n *     notice, this list of conditions and the following disclaimer.\r\n *   * Redistributions in binary form must reproduce the above\r\n *     copyright notice, this list of conditions and the following\r\n *     disclaimer in the documentation and/or other materials provided\r\n *     with the distribution.\r\n *   * Neither the name of Willow Garage, Inc. nor the names of its\r\n *     contributors may be used to endorse or promote products derived\r\n *     from this software without specific prior written permission.\r\n *\r\n *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n *  \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\r\n *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r\n *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\r\n *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\r\n *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r\n *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r\n *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r\n *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r\n *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r\n *  POSSIBILITY OF SUCH DAMAGE.\r\n *\r\n */\r\n#include <iostream>\r\n#include \"opencv2/imgproc.hpp\"\r\n#include \"opencv2/cnn_3dobj.hpp\"\r\nusing namespace cv;\r\nusing namespace cv::cnn_3dobj;\r\nint main(int argc, char** argv)\r\n{\r\n    const String keys = \"{help | | this demo will have an analysis on the trained model, it will print information about whether the model is suit for set different classes apart and also discriminant on object pose at the same time.}\"\r\n\"{caffemodel | ../../testdata/cv/3d_triplet_iter_30000.caffemodel | caffe model for feature exrtaction.}\"\r\n\"{network_forIMG | ../../testdata/cv/3d_triplet_testIMG.prototxt | Network definition file used for extracting feature from a single image and making a classification}\"\r\n\"{mean_file | no | The mean file generated by Caffe from all gallery images, this could be used for mean value substraction from all images. If you want to use the mean file, you can set this as ../data/images_mean/triplet_mean.binaryproto.}\"\r\n\"{target_img | ../data/images_all/4_78.png | Path of image in reference.}\"\r\n\"{ref_img1 | ../data/images_all/4_79.png | Path of closest image.}\"\r\n\"{ref_img2 | ../data/images_all/4_87.png | Path of less closer image in the same class with reference image.}\"\r\n\"{ref_img3 | ../data/images_all/3_78.png | Path of image with the same pose in another class.}\"\r\n\"{feature_blob | feat | Name of layer which will represent as the feature, in this network, ip1 or feat is well.}\"\r\n\"{device | CPU | device}\"\r\n\"{dev_id | 0 | dev_id}\";\r\n    /* Get parameters from comand line. */\r\n    cv::CommandLineParser parser(argc, argv, keys);\r\n    parser.about(\"Demo for object data classification and pose estimation\");\r\n    if (parser.has(\"help\"))\r\n    {\r\n        parser.printMessage();\r\n        return 0;\r\n    }\r\n    String caffemodel = parser.get<String>(\"caffemodel\");\r\n    String network_forIMG = parser.get<String>(\"network_forIMG\");\r\n    String mean_file = parser.get<String>(\"mean_file\");\r\n    String target_img = parser.get<String>(\"target_img\");\r\n    String ref_img1 = parser.get<String>(\"ref_img1\");\r\n    String ref_img2 = parser.get<String>(\"ref_img2\");\r\n    String ref_img3 = parser.get<String>(\"ref_img3\");\r\n    String feature_blob = parser.get<String>(\"feature_blob\");\r\n    String device = parser.get<String>(\"device\");\r\n    int dev_id = parser.get<int>(\"dev_id\");\r\n    std::vector<String> ref_img;\r\n    /* Sample which is most closest in pose to reference image\r\n    *and also the same class.\r\n    */\r\n    ref_img.push_back(ref_img1);\r\n    /* Sample which is less closest in pose to reference image\r\n    *and also the same class.\r\n    */\r\n    ref_img.push_back(ref_img2);\r\n    /* Sample which is very close in pose to reference image\r\n    *but not the same class.\r\n    */\r\n    ref_img.push_back(ref_img3);\r\n    /* Initialize a net work with Device. */\r\n    cv::cnn_3dobj::descriptorExtractor descriptor(device, dev_id);\r\n    /* Load net with the caffe trained net work parameter and structure. */\r\n    if (strcmp(mean_file.c_str(), \"no\") == 0)\r\n        descriptor.loadNet(network_forIMG, caffemodel);\r\n    else\r\n        descriptor.loadNet(network_forIMG, caffemodel, mean_file);\r\n    cv::Mat img_base = cv::imread(target_img, -1);\r\n    if (img_base.empty())\r\n    {\r\n        printf(\"could not read reference image %s\\n, make sure the path of images are set properly.\", target_img.c_str());\r\n    }\r\n    std::vector<cv::Mat> img;\r\n    for (unsigned int i = 0; i < ref_img.size(); i++)\r\n    {\r\n        img.push_back(cv::imread(ref_img[i], -1));\r\n        if (img[i].empty()) {\r\n          printf(\"could not read reference image %s\\n, make sure the path of images are set properly.\", ref_img[i].c_str());\r\n        }\r\n    }\r\n    cv::Mat feature_test;\r\n    descriptor.extract(img_base, feature_test, feature_blob);\r\n    if (feature_test.empty()) {\r\n      printf(\"could not extract feature from test image which is read into cv::Mat.\");\r\n    }\r\n    cv::Mat feature_reference;\r\n    descriptor.extract(img, feature_reference, feature_blob);\r\n    if (feature_reference.empty()) {\r\n      printf(\"could not extract feature from reference images which is already stored in vector<cv::Mat>.\");\r\n    }\r\n    std::vector<float> matches;\r\n    for (int i = 0; i < feature_reference.rows; i++)\r\n    {\r\n        cv::Mat distance = feature_test-feature_reference.row(i);\r\n        matches.push_back(cv::norm(distance));\r\n    }\r\n    bool pose_pass = false;\r\n    bool class_pass = false;\r\n    /* Have comparations on the distance between reference image and 3 other images\r\n    *distance between closest sample and reference image should be smallest and\r\n    *distance between sample in another class and reference image should be largest.\r\n    */\r\n    if (matches[0] < matches[1] && matches[0] < matches[2])\r\n        pose_pass = true;\r\n    if (matches[1] < matches[2])\r\n        class_pass = true;\r\n    if (!pose_pass)\r\n    {\r\n        printf(\"\\n =========== Model %s ========== \\nIs not trained properly that the similar pose could not be tell from a cluster of features.\\n\", caffemodel.c_str());\r\n    }\r\n    else if (!class_pass)\r\n    {\r\n        printf(\"\\n =========== Model %s ========== \\nIs not trained properly that feature from the same class is not discriminant from the one of another class with similar pose.\\n\", caffemodel.c_str());\r\n    }\r\n    else\r\n    {\r\n        printf(\"\\n =========== Model %s ========== \\nSuits for setting different classes apart and also discriminant on object pose at the same time.\\n\", caffemodel.c_str());\r\n    }\r\n    return 0;\r\n}\r\n```\r\n\r\n## 6.3.3\t代码解释\r\n接下来对代码清单6-18中的程序进行详细介绍.\r\n\r\n-\t样例最接近参考图像且属于同一类别的样本。\r\n\r\n```cpp\r\n代码清单6-19：最相似\r\nref_img.push_back(ref_img1);\r\n```\r\n\r\n-\t样例与参考图像最不相似但属于同一类别的样本。\r\n\r\n```cpp\r\n代码清单6-20：同类不相似\r\nref_img.push_back(ref_img2);\r\n```\r\n\r\n-\t样例与参考图像非常接近，但不是同一类的样本。\r\n\r\n```cpp\r\n代码清单6-21：相似不同类\r\nref_img.push_back(ref_img3);\r\n```\r\n\r\n-\t使用Device初始化一个网络。\r\n```cpp\r\n代码清单6-22：初始化网络\r\ncv::cnn_3dobj::descriptorExtractor descriptor(device, dev_id);\r\n```\r\n\r\n-\t加载用caffe模型训练过的网络参数和结构。\r\n```cpp\r\n代码清单6-23：加载结构\r\nif (strcmp(mean_file.c_str(), \"no\") == 0)\r\n    descriptor.loadNet(network_forIMG, caffemodel);\r\nelse\r\n    descriptor.loadNet(network_forIMG, caffemodel, mean_file);\r\n```\r\n\r\n-\t比较参考图像和其他3个样本图像之间的距离，最接近参考图像的样本和参考图像之间的距离应最小，另一个样本和参考图像之间的距离应最大。\r\n```cpp\r\n代码清单6-24：比较结果\r\nif (matches[0] < matches[1] && matches[0] < matches[2])\r\n    pose_pass = true;\r\nif (matches[1] < matches[2])\r\n    class_pass = true;\r\n```\r\n"
  },
  {
    "path": "chapter 6/分类.md",
    "content": "## 6.2.1\t目标\r\n在本节教程中，我们将学习如何从图像中提取特征并使用描述符进行预测，主要内容包括：\r\n\r\n1.\t如何从图像中提取特征\r\n2.\t如何从给定根路径下的图像中提取特征\r\n3.\t如何利用参考图像和目标图像进行预测\r\n\r\n## 6.2.2\tC++代码\r\n```cpp\r\n代码清单6-10\r\n/*\r\n * Software License Agreement (BSD License)\r\n *\r\n *  Copyright (c) 2009, Willow Garage, Inc.\r\n *  All rights reserved.\r\n *\r\n *  Redistribution and use in source and binary forms, with or without\r\n *  modification, are permitted provided that the following conditions\r\n *  are met:\r\n *\r\n *   * Redistributions of source code must retain the above copyright\r\n *     notice, this list of conditions and the following disclaimer.\r\n *   * Redistributions in binary form must reproduce the above\r\n *     copyright notice, this list of conditions and the following\r\n *     disclaimer in the documentation and/or other materials provided\r\n *     with the distribution.\r\n *   * Neither the name of Willow Garage, Inc. nor the names of its\r\n *     contributors may be used to endorse or promote products derived\r\n *     from this software without specific prior written permission.\r\n *\r\n *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r\n *  \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r\n *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\r\n *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r\n *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\r\n *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\r\n *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r\n *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r\n *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r\n *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r\n *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r\n *  POSSIBILITY OF SUCH DAMAGE.\r\n *\r\n */\r\n#include <opencv2/cnn_3dobj.hpp>\r\n#include <opencv2/features2d.hpp>\r\n#include <iomanip>\r\nusing namespace cv;\r\nusing namespace std;\r\nusing namespace cv::cnn_3dobj;\r\nstatic void listDir(const char *path, std::vector<String>& files, bool r)\r\n{\r\n    DIR *pDir;\r\n    struct dirent *ent;\r\n    char childpath[512];\r\n    pDir = opendir(path);\r\n    memset(childpath, 0, sizeof(childpath));\r\n    while ((ent = readdir(pDir)) != NULL)\r\n    {\r\n        if (ent->d_type & DT_DIR)\r\n        {\r\n            if (strcmp(ent->d_name, \".\") == 0 || strcmp(ent->d_name, \"..\") == 0 || strcmp(ent->d_name, \".DS_Store\") == 0)\r\n            {\r\n                continue;\r\n            }\r\n            if (r)\r\n            {\r\n                sprintf(childpath, \"%s/%s\", path, ent->d_name);\r\n                listDir(childpath,files,false);\r\n            }\r\n        }\r\n        else\r\n        {\r\n            if (strcmp(ent->d_name, \".DS_Store\") != 0)\r\n                files.push_back(ent->d_name);\r\n        }\r\n    }\r\n    sort(files.begin(),files.end());\r\n};\r\nstatic int featureWrite(const Mat &features, const String &fname)\r\n{\r\n    ofstream ouF;\r\n    ouF.open(fname.c_str(), std::ofstream::binary);\r\n    if (!ouF)\r\n    {\r\n        cerr << \"failed to open the file : \" << fname << endl;\r\n        return 0;\r\n    }\r\n    for (int r = 0; r < features.rows; r++)\r\n    {\r\n        ouF.write(reinterpret_cast<const char*>(features.ptr(r)), features.cols*features.elemSize());\r\n    }\r\n    ouF.close();\r\n    return 1;\r\n}\r\nint main(int argc, char** argv)\r\n{\r\n    const String keys = \"{help | | This sample will extract features from reference images and target image for classification. You can add a mean_file if there little variance in data such as human faces, otherwise it is not so useful}\"\r\n    \"{src_dir | ../data/images_all/ | Source direction of the images ready for being used for extract feature as gallery.}\"\r\n    \"{caffemodel | ../../testdata/cv/3d_triplet_iter_30000.caffemodel | caffe model for feature exrtaction.}\"\r\n    \"{network_forIMG | ../../testdata/cv/3d_triplet_testIMG.prototxt | Network definition file used for extracting feature from a single image and making a classification}\"\r\n    \"{mean_file | no | The mean file generated by Caffe from all gallery images, this could be used for mean value substraction from all images. If you want to use the mean file, you can set this as ../data/images_mean/triplet_mean.binaryproto.}\"\r\n    \"{target_img | ../data/images_all/4_78.png | Path of image waiting to be classified.}\"\r\n    \"{feature_blob | feat | Name of layer which will represent as the feature, in this network, ip1 or feat is well.}\"\r\n    \"{num_candidate | 15 | Number of candidates in gallery as the prediction result.}\"\r\n    \"{device | CPU | Device type: CPU or GPU}\"\r\n    \"{dev_id | 0 | Device id}\"\r\n    \"{gallery_out | 0 | Option on output binary features on gallery images}\";\r\n    /* get parameters from comand line */\r\n    cv::CommandLineParser parser(argc, argv, keys);\r\n    parser.about(\"Feature extraction and classification\");\r\n    if (parser.has(\"help\"))\r\n    {\r\n        parser.printMessage();\r\n        return 0;\r\n    }\r\n    String src_dir = parser.get<String>(\"src_dir\");\r\n    String caffemodel = parser.get<String>(\"caffemodel\");\r\n    String network_forIMG   = parser.get<String>(\"network_forIMG\");\r\n    String mean_file    = parser.get<String>(\"mean_file\");\r\n    String target_img   = parser.get<String>(\"target_img\");\r\n    String feature_blob = parser.get<String>(\"feature_blob\");\r\n    int num_candidate = parser.get<int>(\"num_candidate\");\r\n    String device = parser.get<String>(\"device\");\r\n    int gallery_out = parser.get<int>(\"gallery_out\");\r\n    /* Initialize a net work with Device */\r\n    cv::cnn_3dobj::descriptorExtractor descriptor(device);\r\n    std::cout << \"Using\" << descriptor.getDeviceType() << std::endl;\r\n    /* Load net with the caffe trained net work parameter and structure */\r\n    if (strcmp(mean_file.c_str(), \"no\") == 0)\r\n        descriptor.loadNet(network_forIMG, caffemodel);\r\n    else\r\n        descriptor.loadNet(network_forIMG, caffemodel, mean_file);\r\n    std::vector<String> name_gallery;\r\n    /* List the file names under a given path */\r\n    listDir(src_dir.c_str(), name_gallery, false);\r\n    if (gallery_out)\r\n    {\r\n        ofstream namelist_out(\"gallelist.txt\");\r\n        /* Writing name of the reference images. */\r\n        for (unsigned int i = 0; i < name_gallery.size(); i++)\r\n            namelist_out << name_gallery.at(i) << endl;\r\n    }\r\n    for (unsigned int i = 0; i < name_gallery.size(); i++)\r\n    {\r\n        name_gallery[i] = src_dir + name_gallery[i];\r\n    }\r\n    std::vector<cv::Mat> img_gallery;\r\n    cv::Mat feature_reference;\r\n    for (unsigned int i = 0; i < name_gallery.size(); i++)\r\n    {\r\n        img_gallery.push_back(cv::imread(name_gallery[i]));\r\n    }\r\n    /* Extract feature from a set of images */\r\n    descriptor.extract(img_gallery, feature_reference, feature_blob);\r\n    if (gallery_out)\r\n    {\r\n        std::cout << std::endl << \"---------- Features of gallery images ----------\" << std::endl;\r\n        /* Print features of the reference images. */\r\n        for (int i = 0; i < feature_reference.rows; i++)\r\n            std::cout << feature_reference.row(i) << endl;\r\n        std::cout << std::endl << \"---------- Saving features of gallery images into feature.bin ----------\" << std::endl;\r\n        featureWrite(feature_reference, \"feature.bin\");\r\n    }\r\n    else\r\n    {\r\n        std::cout << std::endl << \"---------- Prediction for \" << target_img << \" ----------\" << std::endl;\r\n        cv::Mat img = cv::imread(target_img);\r\n        std::cout << std::endl << \"---------- Features of gallery images ----------\" << std::endl;\r\n        std::vector<std::pair<String, float> > prediction;\r\n        /* Print features of the reference images. */\r\n        for (int i = 0; i < feature_reference.rows; i++)\r\n            std::cout << feature_reference.row(i) << endl;\r\n        cv::Mat feature_test;\r\n        descriptor.extract(img, feature_test, feature_blob);\r\n        /* Initialize a matcher which using L2 distance. */\r\n        cv::BFMatcher matcher(NORM_L2);\r\n        std::vector<std::vector<cv::DMatch> > matches;\r\n        /* Have a KNN match on the target and reference images. */\r\n        matcher.knnMatch(feature_test, feature_reference, matches, num_candidate);\r\n        /* Print feature of the target image waiting to be classified. */\r\n        std::cout << std::endl << \"---------- Features of target image: \" << target_img << \"----------\" << endl << feature_test << std::endl;\r\n        /* Print the top N prediction. */\r\n        std::cout << std::endl << \"---------- Prediction result(Distance - File Name in Gallery) ----------\" << std::endl;\r\n        for (size_t i = 0; i < matches[0].size(); ++i)\r\n        {\r\n            std::cout << i << \" - \" << std::fixed << std::setprecision(2) << name_gallery[matches[0][i].trainIdx] << \" - \\\"\"  << matches[0][i].distance << \"\\\"\" << std::endl;\r\n        }\r\n    }\r\n    return 0;\r\n}\r\n```\r\n\r\n## 6.2.3\t代码解释\r\n接下来对代码清单6-10中的程序进行详细介绍。\r\n-\t使用Device初始化网络\r\n```cpp\r\n代码清单6-11：初始化网络\r\ncv::cnn_3dobj::descriptorExtractor descriptor(device);\r\n```\r\n\r\n-\t加载用caffe模型训练过的网络参数和结构。\r\n```cpp\r\n代码清单6-12：加载网络\r\nif (strcmp(mean_file.c_str(), \"no\") == 0)\r\n    descriptor.loadNet(network_forIMG, caffemodel);\r\nelse\r\n    descriptor.loadNet(network_forIMG, caffemodel, mean_file);\r\n```\r\n\r\n-\t列出给定路径下的文件名。\r\n```cpp\r\n代码清单6-13：获取文件名\r\nlistDir(src_dir.c_str(), name_gallery, false);\r\nfor (unsigned int i = 0; i < name_gallery.size(); i++)\r\n{\r\n    name_gallery[i] = src_dir + name_gallery[i];\r\n}\r\n```\r\n\r\n-\t从一组图像中提取特征\r\n```cpp\r\n代码清单6-14：提取特征\r\ndescriptor.extract(img_gallery, feature_reference, feature_blob);\r\n```\r\n\r\n-\t初始化一个使用L2距离的匹配器\r\n```cpp\r\n代码清单6-15：初始化匹配器\r\ncv::BFMatcher matcher(NORM_L2);\r\nstd::vector<std::vector<cv::DMatch> > matches;\r\n```\r\n\r\n-\t对目标图像和参考图像进行KNN匹配\r\n```cpp\r\n代码清单6-16：KNN匹配\r\nmatcher.knnMatch(feature_test, feature_reference, matches,num_candidate);\r\n```\r\n\r\n-\t输出参考图像的特征\r\n```cpp\r\n代码清单6-17：输出结果      \r\nstd::cout <<std::endl  <<” ---------- Features of target image:” << target_img << \"----------\" << endl << feature_test << std::endl;\r\n```\r\n"
  },
  {
    "path": "chapter 7/3",
    "content": "\n"
  },
  {
    "path": "chapter 7/计算机视觉应用的交互式可视化调试.md",
    "content": "调试计算机视觉应用程序的最常用方法是什么？通常答案是一些临时的、被混在一起的自定义代码并且在发布编译前必须从代码中删除这些自定义代码。\r\n\r\n由于编译器的存在，使得在程序中加入断点非常的容易，但是如果没有强大的编译器，例如Visual Studio，那么对于个完整程序进行调试将会变得非常的困难。因此本节将会介绍在linux系统中如何使用cvv模块进行程序调试。\r\n\r\n## 7.1.1\t目标\r\n在本节教程中，我们将介绍如何使用cvv模块（opencv2 / cvv.hpp）的可视化调试功能，主要内容包括：\r\n\r\n1.\t将cvv调试功能添加到你的应用程序中\r\n2.\t使用可视化调试GUI\r\n3.\t在编译期间启用和禁用可视化调试功能（禁用功能时不占用资源）\r\n\r\n## 7.1.2\tC++代码\r\n代码清单7-1中的代码可以实现如下的功能：\r\n\r\n-\t捕获图像（视频），例如通过网络摄像头\r\n-\t对每个图像（imgproc）进行滤波\r\n-\t检测图像特征并将其与上一张图像匹配（features2d）\r\n\r\n如果在没有使用视觉调试的情况下编译程序（请参见下面的CMakeLists.txt），唯一的结果是在命令行上打印了一些信息。我们想演示仅仅利用几行cvv命令就可以增加多少调试或开发功能。\r\n\r\n```cpp\r\n代码清单7-1\r\n// system includes\r\n #include <iostream>\r\n \r\n // library includes\r\n #include <opencv2/imgproc.hpp>\r\n #include <opencv2/features2d.hpp>\r\n #include <opencv2/imgproc/types_c.h>\r\n #include <opencv2/videoio.hpp>\r\n #include <opencv2/videoio/videoio_c.h>\r\n \r\n #define CVVISUAL_DEBUGMODE\r\n #include <opencv2/cvv/debug_mode.hpp>\r\n #include <opencv2/cvv/show_image.hpp>\r\n #include <opencv2/cvv/filter.hpp>\r\n #include <opencv2/cvv/dmatch.hpp>\r\n #include <opencv2/cvv/final_show.hpp>\r\n \r\n using namespace std;\r\n using namespace cv;\r\n \r\n template<class T> std::string toString(const T& p_arg)\r\n {\r\n   std::stringstream ss;\r\n \r\n   ss << p_arg;\r\n \r\n   return ss.str();\r\n }\r\n \r\n \r\n \r\n \r\n int\r\n main(int argc, char** argv)\r\n {\r\n   cv::Size* resolution = nullptr;\r\n \r\n   // parser keys\r\n   const char *keys =\r\n       \"{ help h usage ?  |   | show this message }\"\r\n       \"{ width W         |  0| camera resolution width. leave at 0 to use defaults }\"\r\n       \"{ height H        |  0| camera resolution height. leave at 0 to use defaults }\";\r\n \r\n   CommandLineParser parser(argc, argv, keys);\r\n   if (parser.has(\"help\")) {\r\n     parser.printMessage();\r\n     return 0;\r\n   }\r\n   int res_w = parser.get<int>(\"width\");\r\n   int res_h = parser.get<int>(\"height\");\r\n \r\n   // setup video capture\r\n   cv::VideoCapture capture(0);\r\n   if (!capture.isOpened()) {\r\n     std::cout << \"Could not open VideoCapture\" << std::endl;\r\n     return 1;\r\n   }\r\n \r\n   if (res_w>0 && res_h>0) {\r\n     printf(\"Setting resolution to %dx%d\\n\", res_w, res_h);\r\n     capture.set(CV_CAP_PROP_FRAME_WIDTH, res_w);\r\n     capture.set(CV_CAP_PROP_FRAME_HEIGHT, res_h);\r\n   }\r\n \r\n \r\n   cv::Mat prevImgGray;\r\n   std::vector<cv::KeyPoint> prevKeypoints;\r\n   cv::Mat prevDescriptors;\r\n \r\n   int maxFeatureCount = 500;\r\n   Ptr<ORB> detector = ORB::create(maxFeatureCount);\r\n \r\n   cv::BFMatcher matcher(cv::NORM_HAMMING);\r\n \r\n   for (int imgId = 0; imgId < 10; imgId++) {\r\n     // capture a frame\r\n     cv::Mat imgRead;\r\n     capture >> imgRead;\r\n     printf(\"%d: image captured\\n\", imgId);\r\n \r\n     std::string imgIdString{\"imgRead\"};\r\n     imgIdString += toString(imgId);\r\n         cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());\r\n \r\n     // convert to grayscale\r\n     cv::Mat imgGray;\r\n     cv::cvtColor(imgRead, imgGray, COLOR_BGR2GRAY);\r\n         cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, \"to gray\");\r\n \r\n     // detect ORB features\r\n     std::vector<cv::KeyPoint> keypoints;\r\n     cv::Mat descriptors;\r\n     detector->detectAndCompute(imgGray, cv::noArray(), keypoints, descriptors);\r\n     printf(\"%d: detected %zd keypoints\\n\", imgId, keypoints.size());\r\n \r\n     // match them to previous image (if available)\r\n     if (!prevImgGray.empty()) {\r\n       std::vector<cv::DMatch> matches;\r\n       matcher.match(prevDescriptors, descriptors, matches);\r\n       printf(\"%d: all matches size=%zd\\n\", imgId, matches.size());\r\n       std::string allMatchIdString{\"all matches \"};\r\n       allMatchIdString += toString(imgId-1) + \"<->\" + toString(imgId);\r\n       cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str());\r\n \r\n       // remove worst (as defined by match distance) bestRatio quantile\r\n       double bestRatio = 0.8;\r\n       std::sort(matches.begin(), matches.end());\r\n       matches.resize(int(bestRatio * matches.size()));\r\n       printf(\"%d: best matches size=%zd\\n\", imgId, matches.size());\r\n       std::string bestMatchIdString{\"best \" + toString(bestRatio) + \" matches \"};\r\n       bestMatchIdString += toString(imgId-1) + \"<->\" + toString(imgId);\r\n       cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, bestMatchIdString.c_str());\r\n     }\r\n \r\n     prevImgGray = imgGray;\r\n     prevKeypoints = keypoints;\r\n     prevDescriptors = descriptors;\r\n   }\r\n \r\n   cvv::finalShow();\r\n \r\n   return 0;\r\n }\r\n```\r\n下面是编译时需要使用的CmakeList.text文件\r\n\r\n```cpp\r\n代码清单7-2：CmakeLists.txt\r\ncmake_minimum_required(VERSION 2.8)\r\nproject(cvvisual_test)\r\nSET(CMAKE_PREFIX_PATH ~/software/opencv/install)\r\nSET(CMAKE_CXX_COMPILER \"g++-4.8\")\r\nSET(CMAKE_CXX_FLAGS \"-std=c++11 -O2 -pthread -Wall -Werror\")\r\n# (un)set: cmake -DCVV_DEBUG_MODE=OFF ..\r\nOPTION(CVV_DEBUG_MODE \"cvvisual-debug-mode\" ON)\r\nif(CVV_DEBUG_MODE MATCHES ON)\r\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DCVVISUAL_DEBUGMODE\")\r\nendif()\r\nFIND_PACKAGE(OpenCV REQUIRED)\r\ninclude_directories(${OpenCV_INCLUDE_DIRS})\r\nadd_executable(cvvt main.cpp)\r\ntarget_link_libraries(cvvt\r\n  opencv_core opencv_videoio opencv_imgproc opencv_features2d\r\n  opencv_cvv\r\n)\r\n```\r\n\r\n## 7.1.3\t代码解释\r\n我们可以使用上面的cmakeliss .txt和Option 命令CVV_DEBUG_MODE=ON (cmake -DCVV_DEBUG_MODE=ON)来编译程序。另外也可以将相应的宏定义CVVISUAL_DEBUGMODE添加到我们的编译器中(例如g++ -DCVVISUAL_DEBUGMODE)。\r\n\r\n-\t第一个cvv调用简单的显示图像(类似于imshow)，并使用imgIdString作为注释。\r\n\r\n```cpp\r\n代码清单7-3：简单的显示图像\r\ncvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());\r\n```\r\n\r\n图像被添加到可视化调试的GUI中cvv调用块下的Overview选项卡，具体如图7-1所示。\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226101731829.png\" height=\"300\">\r\n</p>\r\n\r\n然后可以选择和查看图像，具体如图7-2所示。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022610193981.png\" height=\"300\">\r\n</p>\r\n\r\n当你想继续执行代码，即取消cvv调用时，可以继续执行代码直到下一个cvv调用(Step)（绿色的按钮），继续到最后一个cvv调用(*>>*)（黄色）或者还可以直接运行应用程序，直到它退出程序(Close)（红色）。\r\n\r\n下面演示选择继续执行代码直到下一个cvv调用(Step)（绿色的按钮）。\r\n\r\n-\t下一个cvv调用用于调试各种滤波操作，即以图片作为输入并返回图片作为输出的操作。\r\n```cpp\r\n代码清单7-4：调试各种滤波操作\"to gray\"\r\ncvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, \"to gray\");\r\n```\r\n与每一个cvv调用一样，首先要进入Overview选项卡，此时选项卡如图7-3所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226102233572.png\" height=\"300\">\r\n</p>\r\n\r\n再次按下Step。\r\n```cpp\r\n代码清单7-5：调试各种滤波操作\"smoothed\"\r\ncvv::debugFilter(imgGray, imgGraySmooth, CVVISUAL_LOCATION, \"smoothed\");\r\n```\r\n如果打开了滤波器调用，最终将在“DefaultFilterView”中结束。两个图像显示在一起，我们可以同步放大它们，放大后如图7-4所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226102457489.png\" height=\"300\">\r\n</p>\r\n\r\n我们按两次Step，看看放大后的图像，当达到很高的缩放级别时，可以看到每个像素的灰度值，具体如图7-5所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226102603106.png\" height=\"300\">\r\n</p>\r\n\r\n查看边缘检测调试结果\r\n```cpp\r\n代码清单7-6：查看边缘\r\ncvv::debugFilter(imgEdges, imgEdgesDilated, CVVISUAL_LOCATION, \"dilated edges\");\r\n```\r\n此时，DefaultFilterView中显示了两个图像，形式如图7-6所示\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022610275344.png\" height=\"300\">\r\n</p>\r\n\r\n现在我们使用右上角的View选项卡选择“DualFilterView”。选择“Changed Pixels”作为滤波器，并应用在中间的图像上，得到如图7-7所示的结果。\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226103030594.png\" height=\"300\">\r\n</p>\r\n\r\n在我们仔细查看了这些图像(可能使用了不同的视图、滤波器或其他GUI功能)之后，决定让程序继续运行至结束。我们按黄色*>>*按钮。这个程序将阻塞在cvv::finalShow() 并显示概述以及传递给cvv的所有内容，结果如图7-8所示。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226103153100.png\" height=\"300\">\r\n</p>\r\n\r\n-\tcvv debugDMatch调用适用于以下情况：两个图像各有一组相互匹配的描述符。\r\n\r\n我们将这两个图像、这两组特征点集及其匹配传递给可视化调试模块。\r\n```cpp\r\n代码清单7-7：特征点匹配\r\ncvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str());\r\n```\r\n我们使用Overview中的滤波器功能(*#type match*)来只显示匹配结果，结果如图7-9所示。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226103414506.png\" height=\"300\">\r\n</p>\r\n\r\n如果想更详细地查看其中的一项，例如调整我们匹配时使用的参数。窗口中有各种设置来显示关键点和匹配项。此外，还有一个鼠标悬停提示工具。在图7-10中给出这步操作的结果。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226103509610.png\" height=\"300\">\r\n</p>\r\n\r\n\r\n可以看到它有很多不匹配的地方。因此我们可以只显示匹配距离最小的70％的关键点，操作结果在图7-11给出。。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226103602764.png\" height=\"300\">\r\n</p>\r\n\r\n成功减少视觉干扰后，我们希望更清楚地看到两个图像之间的变化。选择“ TranslationMatchView”，以不同的方式显示匹配关键点的位置，结果如图7-12所示。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226103731534.png\" height=\"300\">\r\n</p>\r\n\r\n很容易看出，在第二张图片中的杯子相对与第一张向左侧移动了一段距离。\r\n\r\n-\t尽管cvv的目的是使我们交互式地查看计算机视觉运算结果，但“RawView”对此进行了补充。它允许我们查看底层的数据，具体如图7-13所示。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/2020022610385539.png\" height=\"300\">\r\n</p>\r\n\r\n-\tcvv GUI中包含许多更有用的功能。例如，可以对Overview选项卡进行分组，结果如图7-14所示\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226104011120.png\" height=\"300\">\r\n</p>\r\n\r\n通过向我们的计算机视觉程序中添加可视化代码，便可以通过可视化工具交互地调试程序。如果我们完成了开发/调试，不必删除这些代码行。我们简单地禁用cvv调试(cmake -DCVV_DEBUG_MODE=OFF或删除*-DCVVISUAL_DEBUGMODE*)，程序便可以正常运行且没有任何调试开销。\r\n"
  },
  {
    "path": "chapter 8/使用CNNs进行目标检测.md",
    "content": "本章中的内容需要使用“dnn_objectect”模块，并且在编译的过程中需要构建示例程序，即在编译过程中选择BUILD_EXAMPLES选项，该选项在CMake界面中如图8-1所示，在Linux系统中通过BUILD_EXAMPLES=ON进行设置。\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226104443881.png\" height=\"200\">\r\n</p>\r\n \r\n**提示\r\n*设置BUILD_EXAMPLES=ON后，会编译所有模块的示例程序，因此如果条件允许的情况下，建议单独编译“dnn_objectect”模块。***\r\n\r\n## 8.1.1\t目标\r\n在本教程中，我们将利用自带例程模型对图像进行识别和分类，使用到的示例程序如下：\r\n\r\n1.\texample_dnn_objdetect_obj_detect\r\n2.\texample_dnn_objdetect_image_classification\r\n\r\n本教程中需要使用的模型定义文件和权值文件和可以在opencv_extra/dnn_objdetect中找到。文件的名称分别是在表8-1给出。\r\n\r\n<p align=\"center\">\r\n  表8-1 模型文件和权值文件名称\r\n</p>\r\n\r\n模型文件|权值文件\r\n-----|-----\r\nSqueezeDet_deploy.prototxt |SqueezeDet.caffemodel\t\r\nSqueezeNet_deploy.prototxt|SqueezeNet.caffemodel\r\n\r\n**提示\r\n*小白学视觉公众号后台回复“dnn_objdetect”同样可以获取本章教程使用的模型定义、权值文件以及测试使用的图片。***\r\n\r\n## 8.1.2\t目标检测\r\n对目标进行检测可以通过代码清单8-1中的格式实现。\r\n```cpp\r\n代码清单8-1：目标检测格式\r\nexample_dnn_objdetect_obj_detect  <模型定义文件>  <模型权值文件>  <图片>\r\n```\r\n\r\n本章中所有的的例子都是运行在Intel(R) Core(TM)2 i3-4005U CPU @ 1.70GHz(不含GPU)的笔记本上。\r\n\r\n在本教程中，该模型预测多个边界框的平均时间仅为0.172091秒，在不适用GPU的情况下，速度也十分的快。\r\n```cpp\r\n代码清单8-2：测试飞机\r\n<bin_path>/example_dnn_objdetect_obj_detect  SqueezeDet_deploy.prototxt  SqueezeDet.caffemodel  tutorials/images/aeroplane.jpg\r\nTotal objects detected: 1 in 0.168792 seconds\r\n------\r\nClass: aeroplane\r\nProbability: 0.845181\r\nCo-ordinates: 41 116 415 254\r\n------\r\n ```\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226110321167.png\" height=\"300\">\r\n</p>\r\n\r\n```cpp\r\n代码清单8-3：测试公交车\r\n<bin_path>/example_dnn_objdetect_obj_detect  SqueezeDet_deploy.prototxt  SqueezeDet.caffemodel  tutorials/images/bus.jpg\r\nTotal objects detected: 1 in 0.201276 seconds\r\n------\r\nClass: bus\r\nProbability: 0.701829\r\nCo-ordinates: 0 32 415 244\r\n------\r\n ```\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226110603905.png\" height=\"300\">\r\n</p>\r\n\r\n```cpp\r\n代码清单8-4：测试猫\r\n<bin_path>/example_dnn_objdetect_obj_detect  SqueezeDet_deploy.prototxt  SqueezeDet.caffemodel  tutorials/images/cat.jpg\r\nTotal objects detected: 1 in 0.190335 seconds\r\n------\r\nClass: cat\r\nProbability: 0.703465\r\nCo-ordinates: 34 0 381 282\r\n------\r\n ```\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226110709223.png\" height=\"300\">\r\n</p>\r\n\r\n```cpp\r\n代码清单8-5：测试人\r\n<bin_path>/example_dnn_objdetect_obj_detect  SqueezeDet_deploy.prototxt  SqueezeDet.caffemodel  tutorials/images/persons_mutli.jpg\r\nTotal objects detected: 2 in 0.169152 seconds\r\n------\r\nClass: person\r\nProbability: 0.737349\r\nCo-ordinates: 160 67 313 363\r\n------\r\nClass: person\r\nProbability: 0.720328\r\nCo-ordinates: 187 198 222 323\r\n------\r\n ```\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226110812788.png\" height=\"300\">\r\n</p>\r\n\r\n\r\n## 8.1.3\t改变阈值\r\n默认情况下，该模型的检测阈值为置信值0.53。它可以预测到一定数量的边界框，我们可以通过传递变量threshold的值来手动控制阈值，调用格式如代码清单8-6所示。\r\n```cpp\r\n代码清单8-6：改变阈值\r\n<bin_path>/example_dnn_objdetect_obj_detect  <模型定义文件>  <模型权重文件>   <测试图片> <阈值>\r\n```\r\n将阈值更改为0，再次检测飞机，可以如图8-6所示的结果。\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226111045592.png\" height=\"300\">\r\n</p>\r\n\r\n## 8.1.4\t图像分类\r\n-\t利用现有模型对图像进行分类的格式在代码清单8-7中给出。\r\n\r\n```cpp\r\n代码清单8-7：对图像进行分类\r\nexample_dnn_objdetect_image_classification  <模型定义文件 >  <模型权重文件>   <测试图片>\r\n```\r\n\r\n该模型的大小为4.9MB，对图像进行分类平均只需要0.136401秒。\r\n\r\n-\t代码清单8-8中给出了对飞机图像进行分类的代码及运行结果\r\n```cpp\r\n代码清单8-8：对飞机图像进行分类\r\n<bin_path>/example_dnn_objdetect_image_classification  SqueezeNet_deploy.prototxt  SqueezeNet.caffemodel  tutorials/images/aeroplane.jpg\r\nBest class Index: 404\r\nTime taken: 0.137722\r\nProbability: 77.1757\r\n```\r\n\r\n预测结果为404，通过查看synset_words.txt，可以找到它属于airliner类\r\n\r\n-\t继续对猫的图像进行分类，代码清单8-9给出示例代码和分类结果。\r\n\r\n```cpp\r\n代码清单8-9：对猫图像进行分类\r\n<bin_path>/example_dnn_objdetect_image_classification  SqueezeNet_deploy.prototxt  SqueezeNet.caffemodel  tutorials/images/cat.jpg\r\nBest class Index: 285\r\nTime taken: 0.136401\r\nProbability: 40.7111\r\n```\r\n\r\n预测结果为285，通过查看synset_words.txt，可以找到它属于Egyptian cat类\r\n\r\n-\t继续对航天飞机图像进行分类，代码清单8-10给出示例代码和分类结果。\r\n```cpp\r\n代码清单8-10：对航天飞机图像分类\r\n<bin_path>/example_dnn_objdetect_image_classification  SqueezeNet_deploy.prototxt  SqueezeNet.caffemodel  tutorials/images/space_shuttle.jpg\r\nBest class Index: 812\r\nTime taken: 0.137792\r\nProbability: 15.8467\r\n```\r\n\r\n预测结果为285，通过查看synset_words.txt，可以找到它属于space shuttle类\r\n"
  },
  {
    "path": "chapter 9/e",
    "content": "\n"
  },
  {
    "path": "chapter 9/放大图像：单输出.md",
    "content": "学习本章例程需要使用dnn_superres模块，该模块在Liunx系统中通过代码清单9-1中的命令进行安装，或者在CMake-GUI中对dnn_superres模块进行选择。\r\n\r\n```cpp\r\n代码清单9-1：安装dnn_superres模块\r\n1.\tcmake -DOPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules -Dopencv_dnn_superres=ON <opencv_source_dir>\r\n```\r\n\r\n在本节中，我们将学习如何使用'dnn_superres'，通过已有训练的神经网络对图像进行放大。\r\n\r\n## 9.1.1\tC++代码\r\n```cpp\r\n代码清单9-2\r\n// This file is part of OpenCV project.\r\n // It is subject to the license terms in the LICENSE file found in the top-level directory\r\n // of this distribution and at http://opencv.org/license.html.\r\n \r\n #include <iostream>\r\n \r\n #include <opencv2/dnn_superres.hpp>\r\n \r\n #include <opencv2/imgproc.hpp>\r\n #include <opencv2/highgui.hpp>\r\n \r\n using namespace std;\r\n using namespace cv;\r\n using namespace dnn;\r\n using namespace dnn_superres;\r\n \r\n int main(int argc, char *argv[])\r\n {\r\n     // Check for valid command line arguments, print usage\r\n     // if insufficient arguments were given.\r\n     if ( argc < 4 ) {\r\n         cout << \"usage:   Arg 1: image     | Path to image\" << endl;\r\n         cout << \"\\t Arg 2: algorithm | bilinear, bicubic, edsr, espcn, fsrcnn or lapsrn\" << endl;\r\n         cout << \"\\t Arg 3: scale     | 2, 3 or 4 \\n\";\r\n         cout << \"\\t Arg 4: path to model file \\n\";\r\n         return -1;\r\n     }\r\n \r\n     string img_path = string(argv[1]);\r\n     string algorithm = string(argv[2]);\r\n     int scale = atoi(argv[3]);\r\n     string path = \"\";\r\n \r\n     if( argc > 4)\r\n         path = string(argv[4]);\r\n \r\n     // Load the image\r\n     Mat img = cv::imread(img_path);\r\n     Mat original_img(img);\r\n     if ( img.empty() )\r\n     {\r\n         std::cerr << \"Couldn't load image: \" << img << \"\\n\";\r\n         return -2;\r\n     }\r\n \r\n     //Make dnn super resolution instance\r\n     DnnSuperResImpl sr;\r\n \r\n     Mat img_new;\r\n \r\n     if( algorithm == \"bilinear\" ){\r\n         resize(img, img_new, Size(), scale, scale, 2);\r\n     }\r\n     else if( algorithm == \"bicubic\" )\r\n     {\r\n         resize(img, img_new, Size(), scale, scale, 3);\r\n     }\r\n     else if( algorithm == \"edsr\" || algorithm == \"espcn\" || algorithm == \"fsrcnn\" || algorithm == \"lapsrn\" )\r\n     {\r\n         sr.readModel(path);\r\n         sr.setModel(algorithm, scale);\r\n         sr.upsample(img, img_new);\r\n     }\r\n     else{\r\n         std::cerr << \"Algorithm not recognized. \\n\";\r\n     }\r\n \r\n     if ( img_new.empty() )\r\n     {\r\n         std::cerr << \"Upsampling failed. \\n\";\r\n         return -3;\r\n     }\r\n     cout << \"Upsampling succeeded. \\n\";\r\n \r\n     // Display image\r\n     cv::namedWindow(\"Initial Image\", WINDOW_AUTOSIZE);\r\n     cv::imshow(\"Initial Image\", img_new);\r\n     //cv::imwrite(\"./saved.jpg\", img_new);\r\n     cv::waitKey(0);\r\n \r\n     return 0;\r\n }\r\n```\r\n\r\n## 9.1.2\t代码解释\r\n-\t包含头文件，设置命名空间\r\n\r\n```cpp\r\n代码清单9-3：包含头文件和命名空间\r\n#include <opencv2/dnn_superres.hpp>\r\nusing namespace std;\r\nusing namespace cv;\r\nusing namespace dnn;\r\nusing namespace dnn_superres;\r\n```\r\n-\t创建Dnn Superres对象\r\n\r\n```cpp\r\n代码清单9-4：创建Dnn Superres对象\r\nDnnSuperResImpl sr;\r\n```\r\n这只是为了创建对象，注册自定义dnn层，并访问类函数。\r\n\r\n-\t读取模型\r\n\r\n```cpp\r\n代码清单9-5：读取模型\r\npath = \"models/FSRCNN_x2.pb\"\r\nsr.readModel(path);\r\n```\r\n这段代码从.pb文件中读取TensorFlow模型。这里的“path”是预训练的Tensorflow模型的路径文件之一。我们可以从从OpenCV的GitHub上在“dnn_superres”模块中下载模型。也可以从小白学视觉公众号后台通过回复“dnn_superres”获取。\r\n\r\n-\t设置模型\r\n\r\n```cpp\r\n代码清单9-6：设置模型\r\nsr.setModel(\"fsrcnn\", 2);\r\n```\r\n根据想要运行的模型，我们需要设置算法和放大系数。这样，即使我们更改了.pb文件的名称，程序也能够知道算法和系数。例如，如果选择了FSRCNN_x2.pb，则算法和系数分别为'fsrcnn'和2。(其他算法选项包括“edsr”、“espcn”和“lapsrn”等)\r\n\r\n-\t放大图像\r\n\r\n```cpp\r\n代码清单9-7：放大图像\r\nMat img = cv::imread(img_path);\r\nMat img_new;\r\nsr.upsample(img, img_new);\r\n```\r\n\r\n现在我们可以放大任何图像。通过基础库的“imread”函数加载图像，并为目标图像创建一个新Mat。然后对图像进行方法，放大后的图像存储在'img_new'中。原图像在图9-1给出，通过FSRCNN标志放大后的图像在图9-2给出；通过Bicubic Interpolation（双三次插值）算法放大后的图像在图9-3给出。\r\n \r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226112711741.png\" height=\"150\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226112836749.png\" height=\"300\">\r\n</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226112902925.png\" height=\"300\">\r\n</p>\r\n\r\n"
  },
  {
    "path": "chapter 9/放大图像：多输出.md",
    "content": "在本章中，我们将学习如何使用'dnn_superres'通过多输出的预训练神经网络对图像进行放大。如果给出了节点名称，OpenCV的dnn模块支持一次推断访问多个节点。LapSRN模型可以在一次推断中提供更多输出，它支持2x，4x，8x和（2x，4x）和（2x，4x，8x）超分辨率输出。上传的已训练的模型文件应具有以下输出节点名称：\r\n\r\n-\t2x model: NCHW_output\r\n-\t4x model: NCHW_output_2x, NCHW_output_4x\r\n-\t8x model: NCHW_output_2x, NCHW_output_4x, NCHW_output_8x\r\n\r\n## 9.2.1\tC++代码\r\n使用代码清单9-8中的命令运行示例代码，示例代码在代码清单9-9中给出。\r\n\r\n```cpp\r\n代码清单9-8：运行示例\r\n./bin/example_dnn_superres_dnn_superres_multioutput path/to/image.png 2,4 NCHW_output_2x,NCHW_output_4x \\\r\npath/to/opencv_contrib/modules/dnn_superres/models/LapSRN_x4.pb\r\n```\r\n\r\n```cpp\r\n代码清单9-9\r\n// This file is part of OpenCV project.\r\n // It is subject to the license terms in the LICENSE file found in the top-level directory\r\n // of this distribution and at http://opencv.org/license.html.\r\n \r\n #include <iostream>\r\n #include <sstream>\r\n #include <opencv2/dnn_superres.hpp>\r\n \r\n #include <opencv2/imgproc.hpp>\r\n #include <opencv2/highgui.hpp>\r\n \r\n using namespace std;\r\n using namespace cv;\r\n using namespace dnn_superres;\r\n \r\n int main(int argc, char *argv[])\r\n {\r\n     // Check for valid command line arguments, print usage\r\n     // if insufficient arguments were given.\r\n     if (argc < 4) {\r\n         cout << \"usage:   Arg 1: image     | Path to image\" << endl;\r\n         cout << \"\\t Arg 2: scales in a format of 2,4,8\\n\";\r\n         cout << \"\\t Arg 3: output node names in a format of nchw_output_0,nchw_output_1\\n\";\r\n         cout << \"\\t Arg 4: path to model file \\n\";\r\n         return -1;\r\n     }\r\n \r\n     string img_path = string(argv[1]);\r\n     string scales_str = string(argv[2]);\r\n     string output_names_str = string(argv[3]);\r\n     std::string path = string(argv[4]);\r\n \r\n     //Parse the scaling factors\r\n     std::vector<int> scales;\r\n     char delim = ',';\r\n     {\r\n         std::stringstream ss(scales_str);\r\n         std::string token;\r\n         while (std::getline(ss, token, delim)) {\r\n             scales.push_back(atoi(token.c_str()));\r\n         }\r\n     }\r\n \r\n     //Parse the output node names\r\n     std::vector<String> node_names;\r\n     {\r\n         std::stringstream ss(output_names_str);\r\n         std::string token;\r\n         while (std::getline(ss, token, delim)) {\r\n             node_names.push_back(token);\r\n         }\r\n     }\r\n \r\n     // Load the image\r\n     Mat img = cv::imread(img_path);\r\n     Mat original_img(img);\r\n     if (img.empty())\r\n     {\r\n         std::cerr << \"Couldn't load image: \" << img << \"\\n\";\r\n         return -2;\r\n     }\r\n \r\n     //Make dnn super resolution instance\r\n     DnnSuperResImpl sr;\r\n     int scale = *max_element(scales.begin(), scales.end());\r\n     std::vector<Mat> outputs;\r\n     sr.readModel(path);\r\n     sr.setModel(\"lapsrn\", scale);\r\n \r\n     sr.upsampleMultioutput(img, outputs, scales, node_names);\r\n \r\n     for(unsigned int i = 0; i < outputs.size(); i++)\r\n     {\r\n         cv::namedWindow(\"Upsampled image\", WINDOW_AUTOSIZE);\r\n         cv::imshow(\"Upsampled image\", outputs[i]);\r\n         //cv::imwrite(\"./saved.jpg\", img_new);\r\n         cv::waitKey(0);\r\n     }\r\n \r\n     return 0;\r\n }\r\n```\r\n\r\n## 9.2.2\t代码解释\r\n-\t包含头文件，设置命名空间\r\n\r\n```cpp\r\n代码清单9-10：头文件和命名空间\r\n#include <opencv2/dnn_superres.hpp>\r\nusing namespace std;\r\nusing namespace cv;\r\nusing namespace dnn_superres;\r\n```\r\n\r\n-\t创建Dnn Superres对象\r\n\r\n```cpp\r\n代码清单9-11：创建Dnn Superres对象\r\nDnnSuperResImpl sr;\r\n```\r\n\r\n-\t读取模型\r\n\r\n```cpp\r\n代码清单9-12：读取模型\r\npath = \"models/LapSRN_x8.pb\"\r\nsr.readModel(path);\r\n```\r\n\r\n-\t设置模型\r\n\r\n```cpp\r\n代码清单9-13：设置模型\r\nsr.setModel(\"lapsrn\", 8);\r\n```\r\n\r\n设置算法和比例系数。最后一个(最大的)系数应该在这里给出。\r\n\r\n-\t给出节点名称和比例系数\r\n\r\n```cpp\r\n代码清单9-14：设置节点名称和比例系数\r\nstd::vector<int> scales{2, 4, 8}\r\nstd::vector<int> node_names{'NCHW_output_2x','NCHW_output_4x','NCHW_output_8x'}\r\n```\r\n\r\n-\t放大图像\r\n\r\n```cpp\r\n代码清单9-15：放大图像\r\nMat img = cv::imread(img_path);\r\nstd::vector<Mat> outputs;\r\nsr.upsampleMultioutput(img, outputs, scales, node_names);\r\n```\r\n运行程序，将输出图像存储在一个Mat中。\r\n"
  },
  {
    "path": "chapter 9/放大视频.md",
    "content": "在本章中，我们将学习如何使用'dnn_superres'通过预先训练的神经网络放大视频。\r\n\r\n## 9.3.1\tC++代码\r\n```cpp\r\n代码清单9-16\r\n// This file is part of OpenCV project.\r\n // It is subject to the license terms in the LICENSE file found in the top-level directory\r\n // of this distribution and at http://opencv.org/license.html.\r\n \r\n #include <iostream>\r\n \r\n #include <opencv2/dnn_superres.hpp>\r\n \r\n #include <opencv2/imgproc.hpp>\r\n #include <opencv2/highgui.hpp>\r\n \r\n using namespace std;\r\n using namespace cv;\r\n using namespace dnn_superres;\r\n \r\n int main(int argc, char *argv[])\r\n {\r\n     // Check for valid command line arguments, print usage\r\n     // if insufficient arguments were given.\r\n     if (argc < 4) {\r\n         cout << \"usage:   Arg 1: input video path\" << endl;\r\n         cout << \"\\t Arg 2: output video path\" << endl;\r\n         cout << \"\\t Arg 3: algorithm | edsr, espcn, fsrcnn or lapsrn\" << endl;\r\n         cout << \"\\t Arg 4: scale     | 2, 3, 4 or 8 \\n\";\r\n         cout << \"\\t Arg 5: path to model file \\n\";\r\n         return -1;\r\n     }\r\n \r\n     string input_path = string(argv[1]);\r\n     string output_path = string(argv[2]);\r\n     string algorithm = string(argv[3]);\r\n     int scale = atoi(argv[4]);\r\n     string path = string(argv[5]);\r\n \r\n     VideoCapture input_video(input_path);\r\n     int ex = static_cast<int>(input_video.get(CAP_PROP_FOURCC));\r\n     Size S = Size((int) input_video.get(CAP_PROP_FRAME_WIDTH) * scale,\r\n                   (int) input_video.get(CAP_PROP_FRAME_HEIGHT) * scale);\r\n \r\n     VideoWriter output_video;\r\n     output_video.open(output_path, ex, input_video.get(CAP_PROP_FPS), S, true);\r\n \r\n     if (!input_video.isOpened())\r\n     {\r\n         std::cerr << \"Could not open the video.\" << std::endl;\r\n         return -1;\r\n     }\r\n \r\n     DnnSuperResImpl sr;\r\n     sr.readModel(path);\r\n     sr.setModel(algorithm, scale);\r\n \r\n     for(;;)\r\n     {\r\n         Mat frame, output_frame;\r\n         input_video >> frame;\r\n \r\n         if ( frame.empty() )\r\n             break;\r\n \r\n         sr.upsample(frame, output_frame);\r\n         output_video << output_frame;\r\n \r\n         namedWindow(\"Upsampled video\", WINDOW_AUTOSIZE);\r\n         imshow(\"Upsampled video\", output_frame);\r\n \r\n         namedWindow(\"Original video\", WINDOW_AUTOSIZE);\r\n         imshow(\"Original video\", frame);\r\n \r\n         char c=(char)waitKey(25);\r\n         if(c==27)\r\n             break;\r\n     }\r\n \r\n     input_video.release();\r\n     output_video.release();\r\n \r\n     return 0;\r\n }\r\n```\r\n\r\n## 9.3.2\t代码解释\r\n-\t包含头文件，设置命名空间\r\n\r\n```cpp\r\n代码清单9-17：头文件和命名空间\r\n#include <opencv2/dnn_superres.hpp>\r\nusing namespace std;\r\nusing namespace cv;\r\nusing namespace dnn_superres;\r\n```\r\n\r\n-\t创建Dnn Superres对象\r\n\r\n```cpp\r\n代码清单9-18：创建Dnn Superres对象\r\nDnnSuperResImpl sr;\r\n```\r\n\r\n-\t读取模型，设置算法和比例系数。\r\n\r\n```cpp\r\n代码清单9-19：读取模型\r\npath = \"models/ESPCN_x2.pb\"\r\nsr.readModel(path);\r\nsr.setModel(\"espcn\", 2);\r\n```\r\n\r\n-\t放大视频\r\n\r\n```cpp\r\n代码清单9-20：放大视频\r\nfor(;;)\r\n{\r\n    Mat frame, output_frame;\r\n    input_video >> frame;\r\n    if ( frame.empty() )\r\n        break;\r\n    sr.upsample(frame, output_frame);\r\n    ...\r\n}\r\n```\r\n使用逐帧处理的方式对视频进行方大。\r\n\r\n"
  },
  {
    "path": "chapter 9/超分辨率基准测试.md",
    "content": "\r\n超分辨率模块包含用于基准测试的示例代码，可用来比较不同的模型和算法。本教程中给出了执行基准测试的示例代码和一些测试结果，示例代码在代码清单9-21中给出。测试时PC的配置如下：CPU：Intel i7-9700K CPU，操作系统： Ubuntu 18.04.02 。\r\n\r\n## 9.4.1\tC++代码\r\n\r\n```cpp\r\n代码清单9-21\r\n// This file is part of OpenCV project.\r\n // It is subject to the license terms in the LICENSE file found in the top-level directory\r\n // of this distribution and at http://opencv.org/license.html.\r\n \r\n #include <iostream>\r\n #include <opencv2/opencv_modules.hpp>\r\n \r\n #ifdef HAVE_OPENCV_QUALITY\r\n #include <opencv2/dnn_superres.hpp>\r\n #include <opencv2/quality.hpp>\r\n #include <opencv2/imgproc.hpp>\r\n #include <opencv2/highgui.hpp>\r\n \r\n using namespace std;\r\n using namespace cv;\r\n using namespace dnn_superres;\r\n \r\n static void showBenchmark(vector<Mat> images, string title, Size imageSize,\r\n                           const vector<String> imageTitles,\r\n                           const vector<double> psnrValues,\r\n                           const vector<double> ssimValues)\r\n {\r\n     int fontFace = FONT_HERSHEY_COMPLEX_SMALL;\r\n     int fontScale = 1;\r\n     Scalar fontColor = Scalar(255, 255, 255);\r\n \r\n     int len = static_cast<int>(images.size());\r\n \r\n     int cols = 2, rows = 2;\r\n \r\n     Mat fullImage = Mat::zeros(Size((cols * 10) + imageSize.width * cols, (rows * 10) + imageSize.height * rows),\r\n                                images[0].type());\r\n \r\n     stringstream ss;\r\n     int h_ = -1;\r\n     for (int i = 0; i < len; i++) {\r\n \r\n         int fontStart = 15;\r\n         int w_ = i % cols;\r\n         if (i % cols == 0)\r\n             h_++;\r\n \r\n         Rect ROI((w_ * (10 + imageSize.width)), (h_ * (10 + imageSize.height)), imageSize.width, imageSize.height);\r\n         Mat tmp;\r\n         resize(images[i], tmp, Size(ROI.width, ROI.height));\r\n \r\n         ss << imageTitles[i];\r\n         putText(tmp,\r\n                 ss.str(),\r\n                 Point(5, fontStart),\r\n                 fontFace,\r\n                 fontScale,\r\n                 fontColor,\r\n                 1,\r\n                 16);\r\n \r\n         ss.str(\"\");\r\n         fontStart += 20;\r\n \r\n         ss << \"PSNR: \" << psnrValues[i];\r\n         putText(tmp,\r\n                 ss.str(),\r\n                 Point(5, fontStart),\r\n                 fontFace,\r\n                 fontScale,\r\n                 fontColor,\r\n                 1,\r\n                 16);\r\n \r\n         ss.str(\"\");\r\n         fontStart += 20;\r\n \r\n         ss << \"SSIM: \" << ssimValues[i];\r\n         putText(tmp,\r\n                 ss.str(),\r\n                 Point(5, fontStart),\r\n                 fontFace,\r\n                 fontScale,\r\n                 fontColor,\r\n                 1,\r\n                 16);\r\n \r\n         ss.str(\"\");\r\n         fontStart += 20;\r\n \r\n         tmp.copyTo(fullImage(ROI));\r\n     }\r\n \r\n     namedWindow(title, 1);\r\n     imshow(title, fullImage);\r\n     waitKey();\r\n }\r\n \r\n static Vec2d getQualityValues(Mat orig, Mat upsampled)\r\n {\r\n     double psnr = PSNR(upsampled, orig);\r\n     Scalar q = quality::QualitySSIM::compute(upsampled, orig, noArray());\r\n     double ssim = mean(Vec3d((q[0]), q[1], q[2]))[0];\r\n     return Vec2d(psnr, ssim);\r\n }\r\n \r\n int main(int argc, char *argv[])\r\n {\r\n     // Check for valid command line arguments, print usage\r\n     // if insufficient arguments were given.\r\n     if (argc < 4) {\r\n         cout << \"usage:   Arg 1: image path  | Path to image\" << endl;\r\n         cout << \"\\t Arg 2: algorithm | edsr, espcn, fsrcnn or lapsrn\" << endl;\r\n         cout << \"\\t Arg 3: path to model file 2 \\n\";\r\n         cout << \"\\t Arg 4: scale  | 2, 3, 4 or 8 \\n\";\r\n         return -1;\r\n     }\r\n \r\n     string path = string(argv[1]);\r\n     string algorithm = string(argv[2]);\r\n     string model = string(argv[3]);\r\n     int scale = atoi(argv[4]);\r\n \r\n     Mat img = imread(path);\r\n     if (img.empty()) {\r\n         cerr << \"Couldn't load image: \" << img << \"\\n\";\r\n         return -2;\r\n     }\r\n \r\n     //Crop the image so the images will be aligned\r\n     int width = img.cols - (img.cols % scale);\r\n     int height = img.rows - (img.rows % scale);\r\n     Mat cropped = img(Rect(0, 0, width, height));\r\n \r\n     //Downscale the image for benchmarking\r\n     Mat img_downscaled;\r\n     resize(cropped, img_downscaled, Size(), 1.0 / scale, 1.0 / scale);\r\n \r\n     //Make dnn super resolution instance\r\n     DnnSuperResImpl sr;\r\n \r\n     vector <Mat> allImages;\r\n     Mat img_new;\r\n \r\n     //Read and set the dnn model\r\n     sr.readModel(model);\r\n     sr.setModel(algorithm, scale);\r\n     sr.upsample(img_downscaled, img_new);\r\n \r\n     vector<double> psnrValues = vector<double>();\r\n     vector<double> ssimValues = vector<double>();\r\n \r\n     //DL MODEL\r\n     Vec2f quality = getQualityValues(cropped, img_new);\r\n \r\n     psnrValues.push_back(quality[0]);\r\n     ssimValues.push_back(quality[1]);\r\n \r\n     cout << sr.getAlgorithm() << \":\" << endl;\r\n     cout << \"PSNR: \" << quality[0] << \" SSIM: \" << quality[1] << endl;\r\n     cout << \"----------------------\" << endl;\r\n \r\n     //BICUBIC\r\n     Mat bicubic;\r\n     resize(img_downscaled, bicubic, Size(), scale, scale, INTER_CUBIC);\r\n     quality = getQualityValues(cropped, bicubic);\r\n \r\n     psnrValues.push_back(quality[0]);\r\n     ssimValues.push_back(quality[1]);\r\n \r\n     cout << \"Bicubic \" << endl;\r\n     cout << \"PSNR: \" << quality[0] << \" SSIM: \" << quality[1] << endl;\r\n     cout << \"----------------------\" << endl;\r\n \r\n     //NEAREST NEIGHBOR\r\n     Mat nearest;\r\n     resize(img_downscaled, nearest, Size(), scale, scale, INTER_NEAREST);\r\n     quality = getQualityValues(cropped, nearest);\r\n \r\n     psnrValues.push_back(quality[0]);\r\n     ssimValues.push_back(quality[1]);\r\n \r\n     cout << \"Nearest neighbor\" << endl;\r\n     cout << \"PSNR: \" << quality[0] << \" SSIM: \" << quality[1] << endl;\r\n     cout << \"----------------------\" << endl;\r\n \r\n     //LANCZOS\r\n     Mat lanczos;\r\n     resize(img_downscaled, lanczos, Size(), scale, scale, INTER_LANCZOS4);\r\n     quality = getQualityValues(cropped, lanczos);\r\n \r\n     psnrValues.push_back(quality[0]);\r\n     ssimValues.push_back(quality[1]);\r\n \r\n     cout << \"Lanczos\" << endl;\r\n     cout << \"PSNR: \" << quality[0] << \" SSIM: \" << quality[1] << endl;\r\n     cout << \"-----------------------------------------------\" << endl;\r\n \r\n     vector <Mat> imgs{img_new, bicubic, nearest, lanczos};\r\n     vector <String> titles{sr.getAlgorithm(), \"Bicubic\", \"Nearest neighbor\", \"Lanczos\"};\r\n     showBenchmark(imgs, \"Quality benchmark\", Size(bicubic.cols, bicubic.rows), titles, psnrValues, ssimValues);\r\n \r\n     waitKey(0);\r\n \r\n     return 0;\r\n }\r\n #else\r\n int main()\r\n {\r\n     std::cout << \"This sample requires the OpenCV Quality module.\" << std::endl;\r\n     return 0;\r\n }\r\n #endif\r\n```\r\n\r\n## 9.4.2\t代码解释\r\n-\t读取并缩小（下采样）图像\r\n\r\n```cpp\r\n代码清单9-22：图像预处理\r\nint width = img.cols - (img.cols % scale);\r\nint height = img.rows - (img.rows % scale);\r\nMat cropped = img(Rect(0, 0, width, height));\r\nMat img_downscaled;\r\ncv::resize(cropped, img_downscaled, cv::Size(), 1.0 / scale, 1.0 / scale);\r\n```\r\n\r\n通过缩放系数调整图像大小。 在算法执行之前必须进行裁剪，以便图像能够对齐。\r\n\r\n-\t设置模型\r\n```cpp\r\n代码清单9-23：模型配置\r\nDnnSuperResImpl sr;\r\nsr.readModel(path);\r\nsr.setModel(algorithm, scale);\r\nsr.upsample(img_downscaled, img_new);\r\n```\r\n\r\n实例化一个Dnn Superres对象，读取并设置算法和比例系数。\r\n\r\n-\t执行测试\r\n\r\n```cpp\r\n代码清单9-24：进行测试\r\ndouble psnr = PSNR(img_new, cropped);\r\nScalar q = cv::quality::QualitySSIM::compute(img_new, cropped, cv::noArray());\r\ndouble ssim = mean(cv::Vec3f(q[0], q[1], q[2]))[0];\r\n```\r\n\r\n计算PSNR和SSIM。通过 PSNR (core OpenCV)和 SSIM (contrib OpenCV)比较图像。反复使用两个数值比较多种算法，如其他DL模型或插值方法(双三次插值，最近邻插值)。\r\n\r\n## 9.4.3\t基准测试结果\r\n<p align=\"center\">表9-1 扩大2倍</strong></center>\r\n\r\n\r\n\r\n\r\n方法|平均时间(s)|平均PSNR|平均SSIM\r\n---|---|---|---\r\nESPCN|**0.008795**|32.7059|0.9276\r\nEDSR|5.923450|**34.1300**|**0.9447**\r\nFSRCNN|0.021741|32.8886|0.9301\r\nLapSRN|0.114812|32.2681|0.9248\r\nBicubic|0.000208|32.1638|0.9305\r\nNearest neighbor|0.000114|29.1665|0.9049\r\nLanczos|0.001094|32.4687|0.9327\r\n\r\n\r\n\r\n<p align=\"center\">表9-2 扩大3倍</p>\r\n\r\n方法|平均时间(s)|平均PSNR|平均SSIM\r\n---|---|---|---\r\nESPCN|**0.005495**|28.4229|0.8474\r\nEDSR|2.455510|**29.9828**|**0.8801**\r\nFSRCNN|0.008807|28.3068|0.8429\r\nLapSRN|0.282575|26.7330|0.8862\r\nBicubic|0.000311|26.0635|0.8754\r\nNearest neighbor|0.000148|23.5628|0.8174\r\nLanczos|0.001012|25.9115|0.8706\r\n\r\n<p align=\"center\">表9-3 扩大4倍</p>\r\n\r\n\r\n\r\n方法|平均时间(s)|平均PSNR|平均SSIM\r\n---|---|---|---\r\nESPCN|**0.004311**|26.6870|0.7891\r\nEDSR|1.607570|**28.1552**|**0.8317**\r\nFSRCNN|0.005302|26.6088|0.7863\r\nLapSRN|0.121229|26.7383|0.7896\r\nBicubic|0.000311|26.0635|0.8754\r\nNearest neighbor|0.000148|23.5628|0.8174\r\nLanczos|0.001012|25.9115|0.8706\r\n\r\n<p align=\"center\">表9-4 扩大2倍后的图像</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226120400463.png\">\r\n</p>\r\n \t \t \t \r\n<p align=\"center\">表9-5 扩大4倍后的图像</p>\r\n\r\n<p align=\"center\">\r\n<img src=\"https://img-blog.csdnimg.cn/20200226120442832.png\">\r\n</p>\r\n"
  }
]