解决Qt中Qlabel显示OpenCV的Mat数据图像产生扭曲现象问题

前言

  曾写过一个程序,需要有一个界面,但 Shaun 不想使用 MFC,因缘巧合,在网上看到 Qt,就尝试用了一下,遂有此文。Shaun 的 Qt 版本为 qt-opensource-windows-x86-msvc2013-5.6.2,看其名字就知道该版本的 Qt 可以通过 Visual Studio 2013 开发 Qt 程序(各位看官猜的没错,Shaun 并没有直接使用 Qt Creator 开发 Qt 程序,而是通过 VS 开发 Qt 程序的 \(^o^)/),一来是熟悉 VS 开发,对 Qt Creator 完全没用过;二来是已经在 VS 配好全套的开发环境了(画外音:说白了就是懒嘛 ╭(╯^╰)╮)。但是在 VS 中开发 Qt 程序还需要一些其它的配置。

前言

  曾写过一个程序,需要有一个界面,但 Shaun 不想使用 MFC,因缘巧合,在网上看到 Qt,就尝试用了一下,遂有此文。Shaun 的 Qt 版本为 qt-opensource-windows-x86-msvc2013-5.6.2,看其名字就知道该版本的 Qt 可以通过 Visual Studio 2013 开发 Qt 程序(各位看官猜的没错,Shaun 并没有直接使用 Qt Creator 开发 Qt 程序,而是通过 VS 开发 Qt 程序的 \(^o^)/),一来是熟悉 VS 开发,对 Qt Creator 完全没用过;二来是已经在 VS 配好全套的开发环境了(画外音:说白了就是懒嘛 ╭(╯^╰)╮)。但是在 VS 中开发 Qt 程序还需要一些其它的配置。

准备篇

  在 VS 中开发 Qt 程序首先需要安装一个 addin 外接程序,下载并安装 qt-vs-addin-1.2.5.exehttp://download.qt.io/archive/vsaddin/),(网上说该程序已不支持 VS2013 及以上版本的 VS,原因是 VS2013 及其以上版本的 VS 都不支持该种类型的插件,新版本的 VS 需要安装新型插件 qt-vs-tools-msvc2013-2.1.1.vsixqt-vs-tools-msvc2015-2.1.1.vsix),但是经 Shaun 实测,Shaun 的 VS2013-update5 英文旗舰版通过 qt-vs-addin-1.2.5 编写 Qt 程序完全没问题,不过 VS2015 就不知道了,可能真需要安装新型插件。下载安装好相应的软件之后需要在 VS 中配置 Qt 环境,虽然不配置也能正常编译,但是会在 Qt 相关的语句下面出现红色波浪线,Shaun 轻微强迫症表示不能忍 ╭(╯^╰)╮。具体配置如下:

选中“VC++目录”,在“包含目录”中添加:

C:\Qt\Qt5.6.2\5.6\msvc2013\include

在“库目录”中添加:

C:\Qt\Qt5.6.2\5.6\msvc2013\lib

配置完成之后即可发现红色波浪线已消失。

使用篇

  VS 中如何开发 Qt 程序请详见参考资料,懒癌发作,不想写了 =_=(其实是因为要写的话只能贴图了,Shaun 表示不想使用图片 (╯﹏╰) )。

问题篇

问题描述:Shaun 在用 Qt 显示 OpenCV 的 Mat 数据图像时,有时会发生扭曲现象(图像从对角线分开,两边颠倒,扭曲),有时却不会,为了撤了解决问题,查阅了相关资料,终于发现症结所在,原来是图片数据格式不符合 Qt 的图片数据格式。

解决办法:正文来喽 ~\(≧▽≦)/~,就不说废话了,“Talk is cheap. Show you the code”,具体完整正确显示C++代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void showMatWithQtQlabel(const cv::Mat &img, QLabel *label)
{
// [Qt中用QLabel显示OpenCV中Mat图像数据出现扭曲现象的解决](http://lovelittlebean.blog.163.com/blog/static/11658218620125208212189/)
QImage q_img;
if(img.channels() == 3) // RGB image
{
q_img = QImage((const uchar*)(img.data), img.cols, img.rows, img.cols*img.channels(), QImage::Format_RGB888).rgbSwapped();
}else if (img.channels() == 4) // RGBA image
{
q_img = QImage((const uchar*)(img.data), img.cols, img.rows, img.cols*img.channels(), QImage::Format_RGB32);
}else // gray image
{
q_img = QImage((const uchar*)(img.data), img.cols, img.rows, img.cols*img.channels(), QImage::Format_Indexed8);
}

// -------------- 图片自适应label -------------------
QImage q_label_img = q_img.scaled(label->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); // 图片自适应label大小
label->setPixmap(QPixmap::fromImage(q_label_img)); // 将图片显示到label上

// -------------- label自适应图片 -------------------
/*label->setPixmap(QPixmap::fromImage(q_img)); // 显示在label中
label->resize(label->pixmap()->size()); // 改变label的尺寸以自适应图像
label->show(); */
}

  rgbSwapped() 函数是为了使 Qt 中显示图形颜色更自然,因为 OpenCV 的 Mat 数据 RGB 图像是以 BGR 的顺序排列,而 Qt 中是以 RGB 的顺序排列,所以需要 rgbSwapped() 交换一下颜色通道排列顺序。

附录

1、摄像头数据采集问题

注意:如果是从摄像头实时采集显示图像,在显示时需先判断图像有没有数据

1
2
3
4
5
6
7
if (image.data)
{
// 执行显示操作
showMatWithQtQlabel(mat, ui.label);

// 执行其它操作...
}

1
2
3
4
5
6
7
if (!image.empty())
{
// 执行显示操作
showMatWithQtQlabel(mat, ui.label);

// 执行其它操作...
}

具体原因可参考 Shaun 的一篇文章 解决OpenCV-2.4.11调用摄像头显示拍摄视频出错问题

2、信号与槽的连接函数问题

Qt4 中信号与槽的连接函数语法为:

1
connect(&theTimer,SIGNAL(timeout()),this,SLOT(getFrame()));	// 超时就去取下一帧

而 Qt5 中信号与槽的连接函数新语法为:

1
connect(&theTimer, &QTimer::timeout, this, &QtTest::getFrame);	//超时就去取下一帧 

推荐使用 Qt5 新语法,具体原因可参考 qt5中信号和槽的新语法

个人粗浅理解:信号函数一般是 Qt 中控件的库函数,比如按钮控件 QButton 的 QButton::clicked () 函数,定时器 QTimer 的 QTimer::timeout () 等函数;而槽函数是响应函数,一般由用户自己编写,也可以使用 Qt 中库函数。

  使用 Qt 中可能会遇到的一些错误请参考 使用VS2010开发Qt程序的一点经验http://www.cnblogs.com/csuftzzk/category/445772.html)。

后记

  本来其实就想把问题篇写出来的,毕竟主要就是想记录一下那个显示函数,但是感觉有点没头没尾,就把 VS 集成 Qt 开发环境也稍微写了一下,而使用篇确实是因为参考资料已经写的很详细了,所以就直接一笔带过了。

参考资料

[1] QT +openCV 实现摄像头采集以及拍照功能http://blog.csdn.net/llh318724/article/category/930663

[2] VS2010 + QT5.2+ QT-VS-Addin1.2.2开发环境配置http://blog.csdn.net/qqmindyourwill/article/category/5990841

[3] Qt+OpenCV界面http://blog.csdn.net/fm0517/article/category/1110960

[4] Qt中用QLabel显示OpenCV中Mat图像数据出现扭曲现象的解决http://blog.csdn.net/loveaborn/article/category/1164072

解决OpenCV-2.4.11调用摄像头显示拍摄视频出错问题

本文所用的 OpenCV 版本为 opencv-2.4.11,编程语言为 C++。

前言

  本文其实是以前在刚学 OpenCV 时遇到的一个问题,当时我的环境还是:Win7,VS2010,opencv-2.4.11。当初就记录了下来,现在再来重新梳理一下。

本文所用的 OpenCV 版本为 opencv-2.4.11,编程语言为 C++。

前言

  本文其实是以前在刚学 OpenCV 时遇到的一个问题,当时我的环境还是:Win7,VS2010,opencv-2.4.11。当初就记录了下来,现在再来重新梳理一下。

问题篇

问题描述:使用 OpenCV-2.4.11 调用摄像头显示拍摄视频时报 runtime error,控制台窗口出现 OpenCV Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file ……...cpp, line 261

解决办法:在显示图片时先判断是否有图像数据,如下:

1
2
3
4
if (!image.empty()) 
{
imshow("window", image);
}

1
2
3
4
if (image.data) 
{
imshow("window", image);
}

原因可能是:用 imshow() 显示图像时,其 image 必须有数据,如果它为空则程序会报错,而一般打开摄像头会有一定时间的延迟,这时程序已经启动,而摄像头由于启动延迟,不一定能及时获取图像,造成要显示的 image 为空,因此报错。个人粗浅理解,板砖轻拍 ⊙﹏⊙b。

而网上有人也认为:

  1. 我也是遇到这个问题,不过看到一个帖子写得不错(英文的),里面给出了一个可能的理由,就是我们用 opencv 打开视频的时候,会自动先监测摄像头有没有读到帧,如果没有,就会报错,然后再执行你的程序,加一个if判断就是跳过系统自己的判断,直接执行我们的程序。来自:https://zhidao.baidu.com/question/1831122325089024420.html
  2. 有人说的原因是在 VideoCapture 刚开始获取摄像头视频流的过程不返回信号,所以判断 Mat 是否为空,并不断循环去获取 Mat。来自:http://www.cnblogs.com/tiny656/p/3538115.html

附最终完整示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <opencv2/core/core.hpp>  
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

// 调用摄像头
void videoCaptureTest()
{
//cv::VideoCapture cap(0); // 打开默认摄像头,参数0代表默认摄像头的ID
cv::VideoCapture cap;
cap.open(0);
// 设置摄像头
cap.set(CV_CAP_PROP_FRAME_WIDTH,640);
cap.set(CV_CAP_PROP_FRAME_HEIGHT,480);
// 确认是否成功打开摄像头
if (!cap.isOpened())
{
printf("打开摄像头失败,退出!\n");
exit(-1);
}
cv::namedWindow("Capture", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
while (1)
{
cv::Mat frame;
cap >> frame; // 获取帧

// 对摄像头获取的帧进行各种处理
if (!frame.empty()) // 最好加上该判断,并在该判断中对帧进行处理
{
cv::imshow("Capture", frame);
}
if(cv::waitKey(30) >= 0) break; // 每30ms取一帧
}
}

int main(int argc, char *argv[])
{
videoCaptureTest();

return 0;
}

  其实也可以通过在获取帧时,反复获取帧,直到取到的帧有数据为止,这样就不需要判断语句了,直接显示即可,具体代码如下:

1
2
3
4
5
6
do
{
cap >> frame;
}while(frame.empty());

cv::imshow("Capture", frame);

参考自:https://stackoverflow.com/a/9285151

后记

  本文还是当初在国内某平台写博客时写的,但现在再回头看,又稍微有了点新的思路,温故确实能知新 (*^__^*) 嘻嘻……。

参考资料

[1] OpenCV2.3使用摄像头和视频http://blog.sina.com.cn/s/articlelist_2749877462_3_1.html

[2] OpenCV Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, fi 这个问题怎么办?

[3] OpenCV打开摄像头出现运行错误OpenCV Error:Assertion failed (size.width>0&&size.height>0)in cv::imshow,……http://blog.csdn.net/czl389/article/category/6381887

[4] [OpenCV]获取摄像头视频http://www.cnblogs.com/tiny656/category/550972.html

Hexo添加各种小部件

前言

  Shaun 目前还在使用对 Hexo 的主题 SPFK 自行魔改的那个主题(所谓的魔改也就是对照着 black-blue 主题修改了部分 CSS,然后又添加了一个站内搜索功能 (>^ω^<)),主题 SPFK 主体的东西其实都没改变。现在正逐渐将其完善中,遂有此文。

前言

  Shaun 目前还在使用对 Hexo 的主题 SPFK 自行魔改的那个主题(所谓的魔改也就是对照着 black-blue 主题修改了部分 CSS,然后又添加了一个站内搜索功能 (>^ω^<)),主题 SPFK 主体的东西其实都没改变。现在正逐渐将其完善中,遂有此文。

添加 QQ 邮箱联系

添加日期:2017-9-15

  进入 QQ邮箱开放平台,点击“获取邮我按钮”,登录 QQ 之后继续点击该按钮,因为 Shaun 不需要其样式,只需要其链接即可,所以就默认样式,直接点击“获取代码”即可,Shaun 默认的“HTML代码”为:

1
<a target="_blank" href="http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=qNvAyd3G0d3JxujOx9DFycHEhsvHxQ" style="text-decoration:none;"><img src="http://rescdn.qqmail.com/zh_CN/htmledition/images/function/qm_open/ico_mailme_01.png"/></a>

提取其中的 href,即http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=qNvAyd3G0d3JxujOx9DFycHEhsvHxQ,将该链接添加到 主题配置文件 中,具体如下:

1
2
subnav:
mail: "http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=qNvAyd3G0d3JxujOx9DFycHEhsvHxQ"

重新部署站点即可发现对应的邮箱图标,点击该图标可直接给 Shaun 发邮件。

添加 QQ 交谈链接

添加日期:2017-9-15

  进入 QQ推广,点击上方的“推广工具”,若没登录 QQ 则先登录 QQ,组件样式同样默认即可,这里需要注意的是,需要点击左边的“设置”,下滚页面,找到“安全级别设置”,如下

安全级别设置

完全公开(推荐商家,客服等用户使用,代码中显示QQ号码,易于推广)

安全加密(推荐博主,论坛用户等使用,代码中不显示QQ号码)

选中“安全加密”,不然该选项默认的为完全公开,这样 QQ 号码就直接会显示在代码中,不利于隐私保护,选中之后,点击“保存”。保存之后,再次点击“推广工具”,即可发现下方的复制代码区域的 HTML 代码已看不到明码显示的 QQ 号,(若还是能看到 QQ 号,没有任何变化,可关闭该界面,重启浏览器重新进入该界面),Shaun 的“复制这段代码并将其粘贴到您的网页上”下方区域的默认的代码为:

1
<a target="_blank" href="http://sighttp.qq.com/authd?IDKEY=b1afd83745b30922bc98e020847b86a5148d2114e62e8422"><img border="0"  src="http://wpa.qq.com/imgd?IDKEY=b1afd83745b30922bc98e020847b86a5148d2114e62e8422&pic=52" alt="点击这里给我发消息" title="点击这里给我发消息"/></a>

提取其中的 href,即http://sighttp.qq.com/authd?IDKEY=b1afd83745b30922bc98e020847b86a5148d2114e62e8422,将该链接添加到 主题配置文件 中,具体如下:

1
2
subnav:
QQ: "http://sighttp.qq.com/authd?IDKEY=4faf682653b3b7f5f47b9cb6d2bb8b81de8fa7a8fb8cee12"

重新部署站点即可发现对应的 QQ 图标,点击该图标可直接给 Shaun 发临时 QQ 消息。

添加用户访问统计信息小工具 —— RevolverMaps

添加日期:2017-10-12

  由于 Shaun 暂时不想搞 SEO,所以就没有搞站点地图,更没有将 Shaun 的站点提交到百度和 Google 的站长平台上。但 Shaun 又想查看用户访问信息(是不是很矛盾 o(╯□╰)o),而正好 Shaun 看到有个很酷炫的 3D地球 能满足 Shaun 的需求(其实很酷炫才是主要原因 O(∩_∩)O~),所以 Shaun 决定将其加入 Shaun 的站点中(当做一部分装饰品 ๑乛◡乛๑)。该插件的名称为 RevolverMaps,具体样式可以去其官网看,Shaun 就不贴图了。设置完前三步之后,第四步让用户复制代码到自己的站点上,注意第四步会让你选“new map”还是“update”,由于 Shaun 是初次使用,当然是选择默认的“new map”,如果是以前使用过,就选择“update”,并将原来使用的 script 代码输入出现的文本框并提交,这样就只是更改 3D 地球样式而不会丢失用户访问信息数据。   具体添加方法为:将复制的 script 代码放入想显示的某个 div 中。Shaun 得到的 script 代码为:

<script type="text/javascript" src="//rf.revolvermaps.com/0/0/8.js?i=50om5cdoa3h&amp;m=7&amp;c=ff0000&amp;cr1=ffffff&amp;f=arial&amp;l=49" async="async"></script>

  由于 Shaun 的博客是双栏的,Shaun 当然是把 RevolverMaps 放入左栏中,Shaun 刚开始是把得到的 script 代码放入主题文件夹下 \layout\_partial\left-col.ejs 文件末尾的

1
2
 </header>                
</div>

</header> 标签之前(即在 header 的最下端显示 RevolverMaps ),但实际用起来有点不好看;Shaun 又想干脆另外创造一个 div 放置地球,具体思路为:在 birdhouse 图标旁创建一个新的地球图标,再做一个像 birdhouse 图标一样的动画,鼠标移到地球图标时,出现一个 div,该 div 用来放置 RevolverMaps,这一步做到一半(即将一个新的地球图标并排放在 birdhouse 图标旁)发现这个效果感觉更不好看了,如果要改就需要大改了,有点麻烦 o(︶︿︶)o唉;于是 Shaun 看到鼠标放在 birdhouse 图标出现的菜单栏上,想到何不如将该菜单栏在添加一栏,创建一个 div 用来显示 RevolverMaps?事不宜迟,马上就动手添加该 div,具体添加步骤如下:

  1. 首先当然是添加一个“访问情况”的列表名称,在主题文件夹下 \layout\_partial\left-col.ejs 文件中 <ul class="tips-inner"> 下最后一个 <li> 后即 </ul> 前添加 <li>访问情况</li>

  2. 接着像其它的列表一样(点击该列表 birdhouse 图标就会改变成相应的图标),点击“访问情况”会将 birdhouse 图标改变成一个地球小图标,经查阅相应的 css 文件,其它的列表对应的图标好像是利用 div 的边框属性画出来的(某业余前端的猜测+_+),Shaun 目前还没有这样的才能,就只有投机的采用 Font Awesome 中的 globe 图标了。在主题文件夹下 \layout\_partial\left-col.ejs 文件中 <div class="icon-ctn"> 下最末尾即其对应的 </div> 前添加:

    1
    2
    3
    <div class="icon-wrap icon-globe hide" data-idx="4">
    <i class="fa fa-globe fa-spin fa-2x" aria-hidden="true"></i>
    </div>

    这样点击“访问情况”会将 birdhouse 图标变成一个旋转的地球小图标了;

  3. 接下来就需要创建“访问情况”对应的 div 了,在主题文件夹下 \layout\_partial\left-col.ejs 文件中 <div class="switch-wrap"> 下最末尾即其对应的 </div> 前添加:

    1
    2
    3
    <section class="switch-part switch-part5">
    <script type="text/javascript" src="//rf.revolvermaps.com/0/0/8.js?i=50om5cdoa3h&amp;m=7&amp;c=ff0000&amp;cr1=ffffff&amp;f=arial&amp;l=49" async="async"></script>
    </section>

    这样点击“访问情况”就能出现酷炫的 3D 地球了,才怪 :p。这样只能让 3D 地球出现在菜单界面,还需要添加修改相应的 css;

  4. 最后就是改 css 样式了,本以为这一步很简单,没想到这一步花费 Shaun 最多时间 ╮(╯_╰)╭,修改的样式位于主题文件夹下 \source\css_partial\main.styl 文件中,首先为 switch-part5 添加对应的样式,在 .switch-part4 样式后添加:

    1
    2
    3
    4
    5
    6
    .switch-part5{
    left: 400%;
    width: 100%;
    //height: 200px;
    //margin-left: 47px;
    }

    做完这一步会发现 3D 地球显示不完全,下面会缺一点,所以还需要继续修改,修改过程如下:Shaun 曾将该 width 减小(如上面代码中的注释),这样确实能让 3D 地球显示完全,但有点小,不是很好看;后面想到没显示完全可能是上层 div(.switch-area)太小且设置了 overflow: hidden;,于是这里 Shaun 首先增加了 .switch-area 的高度,这样确实能解决问题,但会使左栏的滚动条显示出来;所以 Shaun 接着尝试将 .switch-area 的 overflow: hidden; 注释掉,谁想注释掉之后出现了横向滚动条,这样更不好了,于是 Shaun 又更改为 overflow-x: hidden; ,谁想 .switch-area 又出现了竖直滚动条(感觉像拆东墙补西墙 -_-|||),查阅相关资料(CSS-overflow特性及总结)得知若 overflow-x 为 hidden,overflow-y 不为 hidden,则 overflow-y 将会自动重置为 auto,所以这里不能这样改,但 overflow: hidden; 还是得注释掉,不然上层 div 撑不开,而且不增加高度的话,还是不能完全显示 3D 地球,因为超出就隐藏了嘛;因为注释掉之后会出现横向滚动条,而又不能修改 .switch-area 的 overflow-x,所以就只能改更上层的 div,这里 Shaun 突然想起上次给左栏添加滚动条时,在 .left-col 下添加了 overflow: auto;,这次不如还修改这里,毕竟 Shaun 只想要竖直滚动条(其实不要滚动条却能滚动最好,但 Shaun 目前还没找到好的解决方案 (╯﹏╰)b),不要横向滚动条,于是将其修改为:

    1
    2
    overflow-y: auto;
    overflow-x: hidden;

    没想到这样也能解决问题,虽然还是会在左栏出现滚动条,但这样感觉比增加 .switch-area 的高度要好(嗯,应该要好吧 (~ o ~)Y)。看以后能不能改成点击“访问情况”时才出现滚动条,点击其它列表则不出现滚动条(其实把滚动条隐藏最好,但网上那个两个 div 嵌套的方法 Shaun 尝试过会出现一些奇怪的问题,等以后再试试吧 ↖((^ω^)↗)。


待续。。。

后记

  目前就添加这些小组件,以后应该会陆续添加一些其它的小东西 ↖(^ω^)↗。

参考资料

[1] 如何在自己网站上或者博客上放置QQ邮箱联系反馈http://jingyan.baidu.com/tag?tagName=%E9%82%AE%E7%AE%B1

[2] 如何在自己的博客添加QQ组件http://www.29mo.com/category/wltg

[3] 一步一步教你给自己博客添加QQ在线http://www.feizl.com/feizhuliu/QQbaodian/

OpenCV中显著性检测算法的使用

本文所用的 OpenCV 版本为 opencv-3.2.0,编程语言为 C++。

前言

  OpenCV 中实现了两种显著性检测算法,分别为 Spectral Residual 算法,出自 Xiaodi Hou and Liqing Zhang. Saliency detection: A spectral residual approach. In Computer Vision and Pattern Recognition, 2007. CVPR'07. IEEE Conference on, pages 1–8. IEEE, 2007. 和 Fine Grained Saliency 算法,出自 Sebastian Montabone and Alvaro Soto. Human detection using a mobile platform and novel features derived from a visual saliency mechanism. In Image and Vision Computing, Vol. 28 Issue 3, pages 391–402. Elsevier, 2010.。这两种算法同样是在扩展包 opencv_contrib-3.2.0 中,也是由于 opencv 官方示例程序对初学者不友好(主要是 Shaun 境界不够 o(╯□╰)o),所以 Shaun 对照其官方文档重新整理了一下。

本文所用的 OpenCV 版本为 opencv-3.2.0,编程语言为 C++。

前言

  OpenCV 中实现了两种显著性检测算法,分别为 Spectral Residual 算法,出自 Xiaodi Hou and Liqing Zhang. Saliency detection: A spectral residual approach. In Computer Vision and Pattern Recognition, 2007. CVPR'07. IEEE Conference on, pages 1–8. IEEE, 2007. 和 Fine Grained Saliency 算法,出自 Sebastian Montabone and Alvaro Soto. Human detection using a mobile platform and novel features derived from a visual saliency mechanism. In Image and Vision Computing, Vol. 28 Issue 3, pages 391–402. Elsevier, 2010.。这两种算法同样是在扩展包 opencv_contrib-3.2.0 中,也是由于 opencv 官方示例程序对初学者不友好(主要是 Shaun 境界不够 o(╯□╰)o),所以 Shaun 对照其官方文档重新整理了一下。

说明篇

  使用 OpenCV 中实现的显著性检测算法进行显著性检测十分方便简洁,利用以下三个函数就可以:

创建 Spectral Residual 算法显著性检测对象:static Ptr<StaticSaliencySpectralResidual> cv::saliency::StaticSaliencySpectralResidual::create();

Spectral Residual 算法计算显著性图:bool cv::saliency::StaticSaliencySpectralResidual::computeSaliency(InputArray image, OutputArray saliencyMap);

Fine Grained Saliency 算法显著性检测对应的函数声明同 Spectral Residual 算法类似。

计算显著性图的二值图:bool cv::saliency::StaticSaliency::computeBinaryMap(InputArray _saliencyMap, OutputArray _binaryMap) ;

具体使用方法可参考实例篇。

实例篇

  使用 OpenCV 中的显著性检测算法需要包含头文件#include <opencv2/saliency.hpp>,具体示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include <opencv2/opencv.hpp>
#include <opencv2/saliency.hpp>

//******************************************************
// [opencv_contrib/modules/saliency/src/saliency.cpp](https://github.com/opencv/opencv_contrib/blob/b7dcf141507edbe544e75820c76769a7769223ac/modules/saliency/src/saliency.cpp)
//
//Ptr<Saliency> Saliency::create(const String& saliencyType)
//{
// if (saliencyType == "SPECTRAL_RESIDUAL")
// return makePtr<StaticSaliencySpectralResidual>(); //computeSaliency返回的是32FC1
// else if (saliencyType == "FINE_GRAINED")
// return makePtr<StaticSaliencyFineGrained>(); //computeSaliency返回的是8UC1
// else if (saliencyType == "BING")
// return makePtr<ObjectnessBING>();
// else if (saliencyType == "BinWangApr2014")
// return makePtr<MotionSaliencyBinWangApr2014>();
// return Ptr<Saliency>();
//}
//
// [opencv_contrib/modules/saliency/src/staticSaliency.cpp](https://github.com/opencv/opencv_contrib/blob/41b0a71ac826b1489d3e5c208ac7a95e58556caf/modules/saliency/src/staticSaliency.cpp)
//computeBinaryMap()要求输入的saliencyMap为浮点数(eg:32FC1)
//*****************************************************

void spectralResidualTest()
{
cv::Mat src_img = cv::imread("../data/true.png", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); // 载入最真实的原始图像
cv::namedWindow("src_img", CV_WND_PROP_ASPECTRATIO);
cv::imshow("src_img", src_img);


// [OpenCV实现显著性检测中的谱残差法(Spectral Residual Method)涉及到了傅立叶正反变换](http://blog.csdn.net/kena_m/article/details/49406687)
if (src_img.empty())
exit(-1);
if (src_img.channels() == 3)
cv::cvtColor(src_img, src_img, CV_BGR2GRAY);
cv::Mat planes[] = { cv::Mat_<float>(src_img), cv::Mat::zeros(src_img.size(), CV_32F) };
cv::Mat complex_img; //复数矩阵
merge(planes, 2, complex_img); //把单通道矩阵组合成复数形式的双通道矩阵
dft(complex_img, complex_img); // 使用离散傅立叶变换

//对复数矩阵进行处理,方法为谱残差
cv::Mat magnitude, phase_angle, mag_mean;
cv::Mat real_part, imaginary_part;
split(complex_img, planes); //分离复数到实部和虚部
real_part = planes[0]; //实部
imaginary_part = planes[1]; //虚部
cv::magnitude(real_part, imaginary_part, magnitude); //计算幅值
phase(real_part, imaginary_part, phase_angle); //计算相角

float *pre, *pim, *pm, *pp;
//对幅值进行对数化
for (int i = 0; i < magnitude.rows; i++)
{
pm = magnitude.ptr<float>(i);
for (int j = 0; j < magnitude.cols; j++)
{
*pm = log(*pm);
pm++;
}
}
blur(magnitude, mag_mean, cv::Size(5, 5)); //对数谱的均值滤波
magnitude = magnitude - mag_mean; //求取对数频谱残差
//把对数谱残差的幅值和相角划归到复数形式
for (int i = 0; i < magnitude.rows; i++)
{
pre = real_part.ptr<float>(i);
pim = imaginary_part.ptr<float>(i);
pm = magnitude.ptr<float>(i);
pp = phase_angle.ptr<float>(i);
for (int j = 0; j < magnitude.cols; j++)
{
*pm = exp(*pm);
*pre = *pm * cos(*pp);
*pim = *pm * sin(*pp);
pre++;
pim++;
pm++;
pp++;
}
}
cv::Mat planes1[] = { cv::Mat_<float>(real_part), cv::Mat_<float>(imaginary_part) };

merge(planes1, 2, complex_img); //重新整合实部和虚部组成双通道形式的复数矩阵
idft(complex_img, complex_img, cv::DFT_SCALE); // 傅立叶反变换
split(complex_img, planes); //分离复数到实部和虚部
real_part = planes[0];
imaginary_part = planes[1];
cv::magnitude(real_part, imaginary_part, magnitude); //计算幅值和相角
for (int i = 0; i < magnitude.rows; i++)
{
pm = magnitude.ptr<float>(i);
for (int j = 0; j < magnitude.cols; j++)
{
*pm = (*pm) * (*pm);
pm++;
}
}
GaussianBlur(magnitude, magnitude, cv::Size(7, 7), 2.5, 2.5);
cv::Mat invDFT, invDFTcvt;
normalize(magnitude, invDFT, 0, 255, cv::NORM_MINMAX); //归一化到[0,255]供显示
invDFT.convertTo(invDFTcvt, CV_8U); //转化成CV_8U型
cv::namedWindow("SpectualResidual", CV_WND_PROP_ASPECTRATIO);
cv::imshow("SpectualResidual", invDFTcvt);

cv::Mat thresholded;
cv::threshold(invDFTcvt, thresholded, 0, 255, CV_THRESH_OTSU);
cv::namedWindow("Thresholded Image", CV_WND_PROP_ASPECTRATIO);
cv::imshow("Thresholded Image", thresholded);

cv::Mat eroded;
// 纵向腐蚀
cv::erode(thresholded, eroded, cv::Mat(5, 1, CV_8UC1, cv::Scalar(1)), cv::Point(-1, -1), 3); // cv::Point(-1,-1)为默认参数,代表原点(描点)为矩阵中心
cv::namedWindow("eroded Image", CV_WND_PROP_ASPECTRATIO);
cv::imshow("eroded Image", eroded);

//cv::Mat thresholded;
cv::threshold(eroded, thresholded, 60, 255, CV_THRESH_BINARY);
cv::namedWindow("Thresholded eroded Image", CV_WND_PROP_ASPECTRATIO);
cv::imshow("Thresholded eroded Image", thresholded);
}

// 显著性检测算法基类
void saliencyTest()
{
cv::Mat src_img = cv::imread("../data/true.png", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
cv::namedWindow("src_img", CV_WND_PROP_ASPECTRATIO);
cv::imshow("src_img", src_img);

if (src_img.empty())
exit(-1);
if (src_img.channels() == 3)
cv::cvtColor(src_img, src_img, CV_BGR2GRAY);

cv::Ptr<cv::saliency::Saliency> saliency_algorithm = cv::saliency::Saliency::create("SPECTRAL_RESIDUAL"); // FINE_GRAINED为Fine Grained Saliency算法
cv::Mat saliency_map;
if (saliency_algorithm->computeSaliency(src_img, saliency_map)) // 计算显著性图
{
cv::namedWindow("SR saliency map", CV_WND_PROP_ASPECTRATIO);
cv::imshow("SR saliency map", saliency_map);

cv::Mat saliency_map_show(saliency_map.size(), CV_8UC1);
normalize(saliency_map, saliency_map_show, 0, 255, CV_MINMAX); //归一化到[0,255]供显示
saliency_map_show.convertTo(saliency_map_show, CV_8U); //转化成CV_8U型
cv::namedWindow("saliency_map_show", CV_WND_PROP_ASPECTRATIO);
cv::imshow("saliency_map_show", saliency_map_show);

cv::Mat binary_map;
cv::saliency::StaticSaliencySpectralResidual spec;
if (spec.computeBinaryMap(saliency_map, binary_map)) // 对显著性图进行二值化
{
cv::namedWindow("binary map", CV_WND_PROP_ASPECTRATIO);
cv::imshow("binary map", binary_map);
}
}
}

// Fine Grained Saliency算法
void FGSTest()
{
cv::Mat src_img = cv::imread("../data/true.png", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
cv::namedWindow("src_img", CV_WND_PROP_ASPECTRATIO);
cv::imshow("src_img", src_img);

if (src_img.empty())
exit(-1);
if (src_img.channels() == 3)
cv::cvtColor(src_img, src_img, CV_BGR2GRAY);

cv::Ptr<cv::saliency::StaticSaliencyFineGrained> fgs = cv::saliency::StaticSaliencyFineGrained::create();
cv::Mat fgs_saliency_map;
fgs->computeSaliency(src_img, fgs_saliency_map);
cv::namedWindow("FGS saliency map", CV_WND_PROP_ASPECTRATIO);
cv::imshow("FGS saliency map", fgs_saliency_map);
//cv::imwrite("../data/T_S.png", fgs_saliency_map);

cv::Mat binary_map;
cv::threshold(fgs_saliency_map, binary_map, 0, 255, CV_THRESH_OTSU);
cv::namedWindow("binary map", CV_WND_PROP_ASPECTRATIO);
cv::imshow("binary map", binary_map);
//cv::imwrite("../data/T_S_B.png", binary_map);
}

// Spectral Residual算法
void SRTest()
{
cv::Mat src_img = cv::imread("../data/true.png", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
cv::namedWindow("src_img", CV_WND_PROP_ASPECTRATIO);
cv::imshow("src_img", src_img);

if (src_img.empty())
exit(-1);
if (src_img.channels() == 3)
cv::cvtColor(src_img, src_img, CV_BGR2GRAY);

cv::Ptr<cv::saliency::StaticSaliencySpectralResidual> sr = cv::saliency::StaticSaliencySpectralResidual::create();
cv::Mat sr_saliency_map;
sr->computeSaliency(src_img, sr_saliency_map);
cv::namedWindow("SR saliency map", CV_WND_PROP_ASPECTRATIO);
cv::imshow("SR saliency map", sr_saliency_map);

cv::Mat binary_map;
sr->computeBinaryMap(sr_saliency_map, binary_map);
cv::namedWindow("binary map", CV_WND_PROP_ASPECTRATIO);
cv::imshow("binary map", binary_map);
}

int main(int argc, char *argv[])
{
//spectralResidualTest();
//saliencyTest();
//FGSTest();
SRTest();

while (cv::waitKey(0) != 27) { }

return 0;
}

以上代码在 Win10 VS2013 中编译运行成功。

  这里面有个小东西需要注意,就是 computeBinaryMap() 函数,看其文档描述其中使用 K-means 算法和 Otsu 算法对显著性图进行二值化处理,其输入的显著性图数据类型应该为浮点数,OpenCV 中 Spectral Residual 算法 computeSaliency() 返回的结果为浮点数,而 Fine Grained Saliency 算法 computeSaliency() 返回的结果却是整型数据,所以这一点需要注意 Fine Grained Saliency 算法返回的结果不能直接使用 computeBinaryMap() 函数,一般对其结果直接使用 OTSU 算法进行阈值分割即可。

后记

  本文使用的这两种算法在 Shaun 的电脑上运行时间都较长,基本不可能用来处理视频流,而且在 Shaun 的这次实验中效果也不太理想,毕竟这是用来处理静态图像的两种显著性方法。不过 OpenCV 中也有用来处理视频流的显著性检测算法,其为 BING 算法,出自Ming-Ming Cheng, Ziming Zhang, Wen-Yan Lin, and Philip Torr. Bing: Binarized normed gradients for objectness estimation at 300fps. In IEEE CVPR, 2014.,实际上这是一种快速提取目标候选框的算法。

OpenCV中Selective Search算法的使用

本文所用的 OpenCV 版本为 opencv-3.2.0,编程语言为 C++。

前言

  OpenCV-3.2 中的 Selective Search 算法是在其扩展包中,所以要想使用该算法需自行编译 opencv_contrib-3.2.0。由于扩展包中的示例程序有点简陋,对初学者也不友好(Shaun 编程水平有限,粗浅评价,勿怪 (*^__^ *) 嘻嘻……),所以 Shaun 参考其官方文档及其官方示例程序写下此文。

本文所用的 OpenCV 版本为 opencv-3.2.0,编程语言为 C++。

前言

  OpenCV-3.2 中的 Selective Search 算法是在其扩展包中,所以要想使用该算法需自行编译 opencv_contrib-3.2.0。由于扩展包中的示例程序有点简陋,对初学者也不友好(Shaun 编程水平有限,粗浅评价,勿怪 (*^__^ *) 嘻嘻……),所以 Shaun 参考其官方文档及其官方示例程序写下此文。

说明篇

  该算法是选取 region proposal(一般翻译成候选区域 / 区域建议)领域中当时的 state-of-the-art。其算法具体思想出自 Jasper RR Uijlings, Koen EA van de Sande, Theo Gevers, and Arnold WM Smeulders. Selective search for object recognition. International journal of computer vision, 104(2):154–171, 2013.,若英文水平不够,还想了解其中文思想请参考文末参考资料。

  OpenCV中实现的相应函数:

void cv::ximgproc::segmentation::SelectiveSearchSegmentation::addGraphSegmentation(Ptr<GraphSegmentation> g);:添加相应的图割算法;

void cv::ximgproc::segmentation::SelectiveSearchSegmentation::addImage(InputArray img) ; :添加待处理的图片;

void cv::ximgproc::segmentation::SelectiveSearchSegmentation::addStrategy(Ptr<SelectiveSearchSegmentationStrategy> s); :添加相应的策略(颜色相似度、纹理相似度、尺寸相似度和填充相似度);

void cv::ximgproc::segmentation::SelectiveSearchSegmentation::process(std::vector<Rect> &rects);:结合图割算法和相应策略进行处理,返回候选框。

实例篇

  使用 Selective Search 算法需包含#include <opencv2/ximgproc.hpp>,完整示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <opencv2/opencv.hpp>
#include <opencv2/ximgproc.hpp>

void SSTest()
{
// [Image segmentation](http://docs.opencv.org/3.2.0/d5/df0/group__ximgproc__segmentation.html#ga5e3e721c5f16e34d3ad52b9eeb6d2860)

cv::Mat src_img = cv::imread("../data/true.png", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); // 载入原始图像
cv::namedWindow("src_img", CV_WINDOW_KEEPRATIO);
cv::imshow("src_img", src_img);

//// 转换为灰度图
//cv::Mat gray_img;
//cvtColor(src_img, gray_img, cv::COLOR_BGR2GRAY);

// 图割算法
cv::Ptr<cv::ximgproc::segmentation::GraphSegmentation> gs = cv::ximgproc::segmentation::createGraphSegmentation();
cv::Mat graph_segmented;
gs->processImage(src_img, graph_segmented);
normalize(graph_segmented, graph_segmented, 0, 255, CV_MINMAX); // 归一化到[0,255]供显示
graph_segmented.convertTo(graph_segmented, CV_8U); // 数据类型转化成CV_8U型
// cvtColor(graph_segmented, graph_segmented, CV_GRAY2BGR);
cv::namedWindow("graph_segmented", CV_WINDOW_KEEPRATIO);
imshow("graph_segmented", graph_segmented);

// 为selective search算法添加图割算法处理结果
cv::Ptr<cv::ximgproc::segmentation::SelectiveSearchSegmentation> ss = cv::ximgproc::segmentation::createSelectiveSearchSegmentation();
ss->addGraphSegmentation(gs);

ss->addImage(src_img); // 添加待处理的图片

// 自定义策略
cv::Ptr<cv::ximgproc::segmentation::SelectiveSearchSegmentationStrategy> sss_color = cv::ximgproc::segmentation::createSelectiveSearchSegmentationStrategyColor(); // 颜色相似度策略
cv::Ptr<cv::ximgproc::segmentation::SelectiveSearchSegmentationStrategy> sss_texture = cv::ximgproc::segmentation::createSelectiveSearchSegmentationStrategyTexture(); // 纹理相似度策略
cv::Ptr<cv::ximgproc::segmentation::SelectiveSearchSegmentationStrategy> sss_size = cv::ximgproc::segmentation::createSelectiveSearchSegmentationStrategySize(); // 尺寸相似度策略
cv::Ptr<cv::ximgproc::segmentation::SelectiveSearchSegmentationStrategy> sss_fill = cv::ximgproc::segmentation::createSelectiveSearchSegmentationStrategyFill(); // 填充相似度策略
// 添加策略
cv::Ptr<cv::ximgproc::segmentation::SelectiveSearchSegmentationStrategy> sss = cv::ximgproc::segmentation::createSelectiveSearchSegmentationStrategyMultiple(sss_color, sss_texture, sss_size, sss_fill); // 合并以上4种策略
ss->addStrategy(sss);

std::vector<cv::Rect> regions;
ss->process(regions); // 处理结果

// 显示结果
cv::Mat show_img = src_img.clone();
for (std::vector<cv::Rect>::iterator it_r = regions.begin(); it_r != regions.end(); ++it_r)
{
cv::rectangle(show_img, *it_r, cv::Scalar(0, 0, 255), 3);
}
cv::namedWindow("show_img", CV_WINDOW_KEEPRATIO);
imshow("show_img", show_img);



// -------忽略上述步骤,直接采用方便算法提取候选区域------------------------
/***************************************************************************
cv::Ptr<cv::ximgproc::segmentation::SelectiveSearchSegmentation> ss = cv::ximgproc::segmentation::createSelectiveSearchSegmentation();
ss->setBaseImage(src_img); // 采用switch* functions提取候选区域
ss->switchToSelectiveSearchFast(); // 快速提取区域

std::vector<cv::Rect> rects;
ss->process(rects);

int nb_rects = 10;

char c = (char)cv::waitKey();

while (c != 'q')
{

cv::Mat wimg = src_img.clone();

int i = 0;

for (std::vector<cv::Rect>::iterator it = rects.begin(); it != rects.end(); ++it)
{
if (i++ < nb_rects)
{
cv::rectangle(wimg, *it, cv::Scalar(0, 0, 255), 3);
}
}

cv::namedWindow("Output", CV_WINDOW_KEEPRATIO);
imshow("Output", wimg);

c = (char)cv::waitKey();

if (c == 'd')
{
nb_rects += 10;
}

if (c == 'a' && nb_rects > 10)
{
nb_rects -= 10;
}
}
********************************************************/
}


int main(int argc, char *argv[])
{
SSTest();

while (cv::waitKey(0) != 27) {}
return 0;
}

以上代码在 Win10 VS2013 中编译运行成功。

后记

  使用该算法,要想达到理想效果,一般需要调整图割算法的参数或注释中方法 switchToSelectiveSearchFast() 的参数。Shaun 的这次实验为了达到理想的选取的效果,其调整参数花了不少时间,而且该算法运行时间在 Shaun 电脑上略显长。GitHub 上也有大神自己用 opencv 实现了该算法,参考 watanika/selective-search-cpp,该算法的参数感觉比 OpenCV 自带的 Selective Search 算法要好调一些,但优化效果没有 opencv 好,其运行时间在 Shaun 电脑上更长,毕竟 OpenCV 是 Intel 的亲儿子,Intel 肯定针对处理器对 OpenCV 底层做了一定的优化。

参考资料

[1] 论文笔记:Selective Search for Object Recognitionhttp://jermmy.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/

[2] Selective Search for Object Recognition(阅读)http://blog.csdn.net/langb2014/article/category/5772811

[3] 论文笔记 《Selective Search for Object Recognition》http://blog.csdn.net/csyhhb/article/category/6048588

Win10以树形结构显示文件目录结构

前言

  本文其实可以算是标题党,Windows本身并不能以树形结构显示文件目录结构,一般需要借助第三方工具(后面去网上搜索了一下,发现 Windows 居然也有一个 tree 命令 o(╯□╰)o),Windows 虽然能用命令行显示树形结构文件目录,但不像 Linux 那样可以输入一些参数控制其输出。Win10 有个特殊的功能,就是可以使用 Ubuntu 的 bash,只需要开启这个有趣的功能,就可以将 Win10 当 Ubuntu 使用,从而像 Linux 那样只输入相关命令即可显示树形结构文件目录。

*注:值得注意的是 Win10 中的 bash 目前不支持中文输入,只能切换到英文输入才能正常输入。

前言

  本文其实可以算是标题党,Windows本身并不能以树形结构显示文件目录结构,一般需要借助第三方工具(后面去网上搜索了一下,发现 Windows 居然也有一个 tree 命令 o(╯□╰)o),Windows 虽然能用命令行显示树形结构文件目录,但不像 Linux 那样可以输入一些参数控制其输出。Win10 有个特殊的功能,就是可以使用 Ubuntu 的 bash,只需要开启这个有趣的功能,就可以将 Win10 当 Ubuntu 使用,从而像 Linux 那样只输入相关命令即可显示树形结构文件目录。

*注:值得注意的是 Win10 中的 bash 目前不支持中文输入,只能切换到英文输入才能正常输入。

准备篇

首先需要在 Win10 下开启 bash 功能。具体开启方法为:

  1. 打开 Win图标 ==》 设置 ==》 更新和安全 ==》 针对开发人员(左侧),选中开发人员模式
  2. 打开 Win图标 ==》 设置 ==》 应用 ==》 应用和功能(左侧) ==》 程序和功能(最下面的相关设置中) ==》 启用或关闭Windows功能(左侧),选中适用于Linux的Windows子系统(Beta)后点击确定。
  3. 重启计算机。打开 bash,打开 bash 的方法很多,这里列出三种:1、直接在微软小娜中输入关键字"bash"搜索 Bash on Ubuntu on Windows;2、Win键+R,输入 bash,点击确定即可打开 bash;3、Win键+R,输入 cmd,在 cmd 中输入 bash,回车即可打开 bash。打开 bash 后将会提示你是否下载安装 Ubuntu on Windows,输入 y 继续,稍等片刻即可完成下载安装。

设置篇

  安装完成后系统将会提示你设置用户名和密码。(如果这一步设置成功可以直接跳过设置篇直接看使用篇)。不知道怎的,Shaun 这一步没有完成,每次系统都是直接以 root 用户登录,而且没有密码,为了安全考虑,也幸好登录时是 root 用户,可以自由对系统修改。所以 Shaun 需要对 root 密码进行修改,并创建新的用户。具体过程需执行以下命令:

root 用户下,修改用户密码:

1
passwd 用户名 (修改密码)

  由于 Shaun 需要修改 root 密码,所以该用户名即为 root,执行之后需要输入新密码(在 *nix 哲学中,密码是不会显示在输入屏幕中的,所以如果在输入密码时发现屏幕没有任何变化是没关系的,只管输入即可 ↖(^ω^)↗),两次输入完成后会显示密码更新成功。

接下来需要创建新的普通用户,在 root 用户下执行:

1
adduser xxx # 这样的命令会在home目录下添加一个帐号

或者

1
useradd xxx #仅仅是添加用户,不会在home目录添加帐号

  推荐使用前者,这样可以很明确已经成功创建新用户,而且如果用户需要存放一些文件也更安全和方便。

  在 *nix 中,绝对不推荐直接使用root用户对系统执行各种命令,毕竟其权限太大,一旦误操作将造成无法挽回的后果。有些命令普通用户可能没有权限执行,这时需要提高其权限,普通用户临时获取 root 权限的方法为:在需要执行的命令前添加sudo,像上文中如果普通用户需要创建新用户 xxx 则需要执行sudo adduser xxx,执行以上命令后同样需要输入新用户的密码。

使用篇

  先切换至普通用户,执行su xxx切换用户,即可发现 shell 提示符由#变为$,前面的用户名由root变为xxx;执行cd ~切换至用户目录。由于 Ubuntu 系统中本身没有 tree 这个命令,需要执行以下命令安装 tree 命令工具:

1
sudo apt install tree

  直接输入tree命令,系统将会自动以树形结构列出当前目录中所有文件及文件夹;执行tree -L N 命令,以树形结构查看当前 N 级的目录和文件,eg:以树形结构查看当前目录二级文件结构,则执行tree -L 2。若想将输出的2级文件结构保存至上一层文件的tree.txt文件中,可执行tree -L 2 > ../tree.txt,进入上一层目录cd ..,打开 tree.txt 即可发现该目录的文件结构。

后记

  遇事还是需要多查证一下啊,想当然果然是会出问题的,文章开头差点就犯错误了 ~\(≧▽≦)/~。本文其实是在写 Win10+VS2013+CMake-gui编译和配置OpenCV-3.2.0 时,为了方便显示输出文件结构而查找的相关资料。

参考资料

[1]linux tree命令以树形结构显示文件目录结构http://jingyan.baidu.com/tag?tagName=linux

[2] win tree命令 tree导出目录 tree显示树形结构http://jingyan.baidu.com/tag?tagName=%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F

[3] win10下linux系统的安装(开启)和使用

[4] Ubuntu建立和删除用户

[5] linux修改root密码和linux忘记root密码后找回密码的方法

Hexo添加站内本地搜索

前言

  虽然 Shaun 博客目前数量不多,质量也不高,但抱着搞事的心态,先弄它一个站内本地搜索再说。

前言

  虽然 Shaun 博客目前数量不多,质量也不高,但抱着搞事的心态,先弄它一个站内本地搜索再说。

准备篇

  要想使用本地搜索功能,首先需要安装相应的搜索插件 hexo-generator-searchdb,网上可能大多数用的是 hexo-generator-search 这个插件,也有都装的,但 Shaun 就只安装这一个了,好像 hexo-generator-searchdb 更完善一点,由于 Shaun 前端接触的极少,所以就没有一一对比了,网上也没查到具体对比情况,有兴趣的童靴可以试试 (╯▽╰)。至于具体安装如下,在站点根目录执行:

1
npm install hexo-generator-searchdb --save

  安装完之后重新生成页面,将会发现 public文件夹 下多出一个 search.xml 文件。然后在配置文件 _config.yml 中添加:

1
2
3
4
# 站点本地搜索
search:
path: search.xml
field: all

其中:

  • path - 指定生成的索引数据的文件名。默认为 search.xml 。
  • field - 指定索引数据的生成范围。可选值包括:
    • post - 只生成博客文章(post)的索引(默认);
    • page - 只生成其他页面(page)的索引;
    • all - 生成所有文章和页面的索引。

  至于是在 主题配置文件,还是在 站点配置文件 中添加,个人觉得都没关系,附:Shaun 是在主题配置文件中添加的。

接下来就需要修改原主题的代码了。

改码篇

  由于 Shaun 博客主题是基于 SPFK 对照着 black-blue 进行修改的,而且因为 black-blue 是有搜索的(Shaun 不知道 black-blue 主题的作者是如何完成的,借助了什么技术),所以 Shaun 就看 black-blue 的搜索功能是修改了 SPFK 哪个地方,再将相应的代码添加至 SPFK 中(其中相应的代码来自让 Hexo 博客支持本地站内搜索),从而逐渐完成本次搜索功能。

首先找到 spfk 主题下的 left-col.ejs 文件,对其修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
<% if (theme.search_box){ %>
<!-- <form>
<input type="text" class="st-default-search-input search" id="search" placeholder=" Search...">
</form> -->

<form id="search-form"> <!-- 搜索框相关 -->
<input type="text" id="local-search-input" name="q" results="0" placeholder="Search..." class="search form-control" autocomplete="off" autocorrect="off"/>
<i class="fa fa-times" onclick="resetSearch()"></i> <!-- 清空/重置搜索框 -->
</form>
<div id="local-search-result"></div> <!-- 搜索结果区 -->
<p class='no-result'>No results found </p> <!-- 无匹配时显示,注意请在 CSS 中设置默认隐藏 -->
<%}%>

其次找到 spfk 主题下的 after-footer.ejs 文件,将其修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
<% if (theme.search_box){ %>
<!-- <script type="text/javascript">
window.onload = function(){
document.getElementById("search").onclick = function(){
console.log("search")
search();
}
}
function search(){
(function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){
(w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t);
e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e);
})(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st');

_st('install','A1Pz-LKMXbrzcFg2FWi6','2.0.0');
}
</script> -->

<script type="text/javascript">
// 激活搜索框时才搜索
var inputArea = document.querySelector("#local-search-input");
var getSearchFile = function(){
// 调用搜索函数
var search_path = "<%- config.search.path %>";
if (search_path.length == 0) {
search_path = "search.xml";
}
var path = "<%- config.root %>" + search_path;
searchFunc(path, 'local-search-input', 'local-search-result');
}
inputArea.onfocus = function(){ getSearchFile() }

// 搜索重置
var $resetButton = $("#search-form .fa-times");
var $resultArea = $("#local-search-result");
inputArea.oninput = function(){ $resetButton.show(); }
resetSearch = function(){
$resultArea.html("");
document.querySelector("#search-form").reset();
$resetButton.hide();
$(".no-result").hide();
}

// 屏蔽回车
inputArea.onkeydown = function(){ if(event.keyCode==13) return false}

// 无搜索结果
$resultArea.bind("DOMNodeRemoved DOMNodeInserted", function(e) {
if (!$(e.target).text()) {
$(".no-result").show(200);
} else {
$(".no-result").hide();
}
})

// 搜索函数
var searchFunc = function(path, search_id, content_id) {
'use strict';
$.ajax({
url: path,
dataType: "xml",
success: function( xmlResponse ) {
// get the contents from search data
var datas = $( "entry", xmlResponse ).map(function() {
return {
title: $( "title", this ).text(),
content: $("content",this).text(),
url: $( "url" , this).text()
};
}).get();
var $input = document.getElementById(search_id);
var $resultContent = document.getElementById(content_id);
$input.addEventListener('input', function(){
var str='<ul class=\"search-result-list\">';
var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
$resultContent.innerHTML = "";
if (this.value.trim().length <= 0) {
return;
}
// perform local searching
datas.forEach(function(data) {
var isMatch = true;
var content_index = [];
var data_title = data.title.trim().toLowerCase();
var data_content = data.content.trim().replace(/<[^>]+>/g,"").toLowerCase();
var data_url = data.url;
var index_title = -1;
var index_content = -1;
var first_occur = -1;
// only match artiles with not empty titles and contents
if(data_title != '' && data_content != '') {
keywords.forEach(function(keyword, i) {
index_title = data_title.indexOf(keyword);
index_content = data_content.indexOf(keyword);
if( index_title < 0 && index_content < 0 ){
isMatch = false;
} else {
if (index_content < 0) {
index_content = 0;
}
if (i == 0) {
first_occur = index_content;
}
}
});
}
// show search results
if (isMatch) {
str += "<li><a href='/"+ data_url +"' class='search-result-title' target='_blank'>"+ "> " + data_title +"</a>";
var content = data.content.trim().replace(/<[^>]+>/g,"");
if (first_occur >= 0) {
// cut out characters
var start = first_occur - 6;
var end = first_occur + 6;
if(start < 0){
start = 0;
}
if(start == 0){
end = 10;
}
if(end > content.length){
end = content.length;
}
var match_content = content.substr(start, end);
// highlight all keywords
keywords.forEach(function(keyword){
var regS = new RegExp(keyword, "gi");
match_content = match_content.replace(regS, "<em class=\"search-keyword\">"+keyword+"</em>");
})
str += "<p class=\"search-result\">" + match_content +"...</p>"
}
}
})
$resultContent.innerHTML = str;
})
}
})
}
</script>
<%}%>

最后找到 spfk 主题下的 main.styl 文件,在其末尾添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/*搜索框*/
.search {
width: 68%;
height: 18px;
margin-top: 1px;
padding: 0;
font-family: inherit;
border: 2px solid transparent;
border-bottom: 2px solid #d3d3d3;
border-radius: 2px;
opacity: 0.65;
background: none;
}
.search:hover {
border: 2px solid #d3d3d3;
opacity: 1;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
}

/*搜索重置按钮*/
#search-form .fa-times {
display: none;
padding: 1px 0.7em;
box-shadow: 0 0 3px rgba(0,0,0,0.15);
cursor: pointer;
color: #4094c7;
}
#search-form .fa-times:active {
background: #d3d3d3;
}
#search-form .fa-times:hover {
zoom: 1.1;
padding: 1px 0.6em;
border: 1px solid #d3d3d3;
box-shadow: 0 0 6px rgba(0,0,0,0.25);
}

/*搜索结果区*/
#local-search-result {
//margin: auto -12% auto -6%;
margin: 0;
font-size: 0.9em;
text-align: left;
word-break: break-all;

box-shadow: 4px 4px 6px rgba(0,0,0,0.46);
}

#local-search-result ul.search-result-list li:hover {
font-weight: normal;
}

/*单条搜索结果*/
#local-search-result li {
margin: 0.5em auto;
border-bottom: 2px solid #d3d3d3;
}
#local-search-result .search-result-list li:hover {
background: rgba(47,46,46,0.8);
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}

/*匹配的标题*/
#local-search-result a.search-result-title {
line-height: 1.2;
font-weight: bold;
color: #4094c7;
}

/*搜索预览段落*/
#local-search-result p.search-result {
margin: 0.4em auto;
line-height: 1.2em;
max-height: 3.6em;
overflow: hidden;
font-size: 0.8em;
text-align: justify;
color: #ffffffb3;
}

/*匹配的关键词*/
#local-search-result em.search-keyword {
color: #f58e90;
border-bottom: 1px dashed #f58e90;
font-weight: bold;
font-size: 1em;
}

/*无匹配搜索结果时显示*/
p.no-result {
display: none;
margin: 2em 0 2em 6%;
padding-bottom: 0.5em;
text-align: left;
color: #808080;
font-family: font-serif serif;
border-bottom: 2px solid #d3d3d3;
}

  这里请注意,当对 main.styl 文件做以上修改时,可能会发现有两个 .search 样式,而且相差不大,这时,不要对其原有的 .search 进行修改,更不要去注释掉它,只做上述修改就不用管了,不然可能会发生一些奇怪的事 o(>﹏<)o。Shaun 当时做以上修改时,将其原有的 .search 样式注释掉之后,整个页面的 css 布局全部都乱了 (╯﹏╰),不知道为什么 (⊙_⊙?),这两个同名样式看起来明明差不多的,最后只能维持现状了,等以后有机会再看看吧,业余前端伤不起啊! ╮(╯_╰)╭。

  至此整个站内本地搜索功能基本完成,勉强可以使用站内搜索功能了。

问题篇

  *注:以下问题于 2018-03-02 都已经解决 ╮(╯▽╰)╭。

1、搜索函数返回的 url 地址有问题。

  问题描述:当点击搜索结果时,新弹出的标签页地址栏中 url 地址会有部分乱码情况;当鼠标移到搜索的结果列表上时,浏览器左下角显示的 url 地址虽然没有乱码情况,但其中有一个重复的/符号。所幸这两个问题并没有造成浏览器解析错误,浏览器还是可以正常显示页面的。

================= 修改日期:2018-03-02 =================

  解决办法:\blog\node_modules\hexo-generator-searchdb\templates\xml.ejs 文件中的 <url><%- encodeURIComponent(config.root + post.path) %></url> 修改为 <url><%- encodeURI(post.path) %></url> ,使其中一些 url 中常见的字符(如:&, ?, /, =)不被十六进制的转义序列进行替换。

参考:escape,encodeURI,encodeURIComponent有什么区别?JavaScript encodeURIComponent() 函数

=====================================================

2、搜索结果区布局有问题。

  问题描述:当显示搜索结果时,搜索结果区会上下扩张,从而将其上下本来存在的一些布局挤开,造成布局混乱。这其实不算是一个 spfk 主题或者新添加的搜索功能的问题,而是新添加的一个东西又没有相应的和原本布局结合的布局文件,那就极大可能会有布局混乱的问题,至于这个要和原本布局契合的搜索结果区布局文件就只有等 Shaun 以后有机会有时间再完善去喽 ╮(╯▽╰)╭。

3、搜索框激活问题。

  问题描述:搜索框激活延迟很大,有时过很久或者需要切换站内页面它才能激活,给人的感觉就是好像没有搜索功能似的。添加搜索框激活功能据作者 MOxFIVE 所说是为了不让索引文件影响页面加载速度,MOxFIVE 同时也在文末指出了一些不足之处,如果索引文件太大,可能还是会造成一些问题,但 Shaun 的博客数量又不多,所以估计还是 Shaun 的代码混合问题,而且 MOxFIVE 的博客搜索功能好像没这个问题(至少 Shaun 目前没发现)。这个问题同样只有等以后再说了 (*^__^*) 嘻嘻……。

后记

  本文添加的本地搜索还很粗糙,还有很多地方需要以后去完善。但这好歹是一个好的开始,搜索功能至少勉强能够正常使用,总比以前是个空壳要好,以后有机会再慢慢去去完善吧 ↖(^ω^)↗。

参考资料

[1] jQuery-based Local Search Engine for Hexohttp://www.hahack.com/categories/codes/

[2] 让 Hexo 博客支持本地站内搜索http://moxfive.xyz/tags/Hexo/

[3] Hexo博客添加站内搜索https://www.ezlippi.com/categories/hexo/

[4] Hexo本地搜索及部分SEO优化https://www.oyohyee.com/categories/Note/

用OpenCV显示OpenGL图形

前言

  本文就是一个小实验,试验 OpenCV 到底能不能支持 OpenGL 图形显示。

前言

  本文就是一个小实验,试验 OpenCV 到底能不能支持 OpenGL 图形显示。

正文

  如果在 OpenCV 用 CMake 编译时勾选 WITH_OPENGL 且编译一切顺利的话,编译和配置的具体步骤和情况可以看 Shaun 写的一篇文档:Win10+VS2013+CMake-gui编译和配置OpenCV-3.2.0 ,那么就可以用 OpenCV 窗口显示 OpenGL 图形。

  在 VS 下使用 Windows 原有的 OpenGL 函数需要包含以下头文件和库文件:

1
2
3
4
5
6
#include <Windows.h>
#include <GL/gl.h>
#include <GL/glu.h>

#pragma comment(lib, "OpenGL32.lib")
#pragma comment(lib, "glu32.lib")

  在 OpenCV 中显示 OpenGL 图形需要 cv::namedWindow(openGLWindowName, cv::WINDOW_OPENGL),在 namedWindow 函数中添加 cv::WINDOW_OPENGL 参数说明该窗口支持 OpenGL 图形。

附示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#include <opencv.hpp>

#include <Windows.h>
#include <GL/gl.h>
#include <GL/glu.h>

#pragma comment(lib, "OpenGL32.lib")
#pragma comment(lib, "glu32.lib")

static const float vertex_list[][3] =
{
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
};

// 将要使用的顶点的序号保存到一个数组里面

static const GLint index_list[][2] =
{
{ 0, 1 },
{ 2, 3 },
{ 4, 5 },
{ 6, 7 },
{ 0, 2 },
{ 1, 3 },
{ 4, 6 },
{ 5, 7 },
{ 0, 4 },
{ 1, 5 },
{ 7, 3 },
{ 2, 6 }
};

static float rotate = 0;
static int times = 0;

GLint windowWidth = 800;
GLint windowHeight = 800;

GLfloat xRotAngle = -75.0f;
GLfloat yRotAngle = 0.0f;
GLfloat zRotAngle = -135.0f;

float MIN_X = -200;
float MAX_X = 200;

float MIN_Y = -200;
float MAX_Y = 200;

float MIN_Z = -200;
float MAX_Z = 200;

GLfloat coordinatesize = 200.0f;
GLfloat ratio = 1;

void drawLine(float x1, float y1, float z1, float x2, float y2, float z2)
{
glBegin(GL_LINES);
glVertex3f(x1, y1, z1);
glVertex3f(x2, y2, z2);
glEnd();
glFlush();
}

// 绘制立方体
void DrawCube(void)
{
int i, j;
glBegin(GL_LINES);
for (i = 0; i < 12; ++i) // 12 条线段
{
for (j = 0; j < 2; ++j) // 每条线段 2个顶点
{
glVertex3fv(vertex_list[index_list[i][j]]);
}
}
glEnd();
glFlush();
}

void reshapeOperate()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (ratio < 1)
glOrtho(-coordinatesize, coordinatesize, -coordinatesize / ratio, coordinatesize / ratio, -coordinatesize, coordinatesize);
else
glOrtho(-coordinatesize*ratio, coordinatesize*ratio, -coordinatesize, coordinatesize, -coordinatesize, coordinatesize);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void reshape(int w, int h)
{
if ((w == 0) || (h == 0))
return;

glViewport(0, 0, w, h);

ratio = (GLfloat)w / (GLfloat)h;

reshapeOperate();
}

GLfloat AngleX = 45.0f;
GLfloat AngleY = 315.0f;

void reshape1(int w, int h)
{
GLfloat aspect = (GLfloat)w / (GLfloat)h;
GLfloat nRange = 100.0f;

glViewport(0, 0, w, h);

glMatrixMode(GL_PROJECTION); //将当前矩阵指定为投影模式
glLoadIdentity();

//设置三维投影区

if (w <= h)
{
glOrtho(-nRange, nRange, -nRange * aspect, nRange * aspect, -nRange, nRange);
}
else
{
glOrtho(-nRange, nRange, -nRange / aspect, nRange / aspect, -nRange, nRange);
}
}

void onDraw(void*)
{
// Draw something using OpenGL here
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除所有的像素

//glMatrixMode(GL_MODELVIEW);
//glLoadIdentity();
//glPushMatrix();

////glTranslatef(-0.2, 0, 0); // 平移
////glScalef(2, 1, 1); // 缩放

//glRotatef(xRotAngle, 1.0f, 0.0f, 0.0f);
//glRotatef(yRotAngle, 0.0f, 1.0f, 0.0f);
//glRotatef(zRotAngle, 0.0f, 0.0f, 1.0f);

//glColor3f(1, 0, 0);
//drawLine(0, 0, 0, MAX_X, 0, 0); //x轴

//glColor3f(0, 1, 0);
//drawLine(0, 0, 0, 0, MAX_Y, 0); //y轴

//glColor3f(0, 0, 1);
//drawLine(0, 0, 0, 0, 0, MAX_Z); //z轴

//times++;
//if (times > 1)
//{
// times = 0;
//}

//if (times % 1 == 0)
//{
// rotate += 0.3;
//}

//glRotatef(rotate, 0, 1, 0);
//glRotatef(rotate, 1, 0, 0);

//glColor3f(0, 1, 1);

//DrawCube();

//glPopMatrix();

reshape1(windowWidth, windowHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
AngleX++;
AngleY++;
glPushMatrix();
{
glRotatef(AngleX, 1.0f, 0.0f, 0.0f);
glRotatef(AngleY, 0.0f, 1.0f, 0.0f);

glBegin(GL_POLYGON); //前表面
glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)255);//颜色设置为白色
glVertex3f(50.0f, 50.0f, 50.0f);

glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)0);//颜色设置为黄色
glVertex3f(50.0f, -50.0f, 50.0f);

glColor3ub((GLubyte)255, (GLubyte)0, (GLubyte)0);//颜色设置为红色
glVertex3f(-50.0f, -50.0f, 50.0f);

glColor3ub((GLubyte)255, (GLubyte)0, (GLubyte)255);//颜色设置为白色
glVertex3f(-50.0f, 50.0f, 50.0f);
glEnd();

glBegin(GL_POLYGON); //后表面
glColor3f(0.0f, 1.0f, 1.0f);//颜色设置为青色
glVertex3f(50.0f, 50.0f, -50.0f);

glColor3f(0.0f, 1.0f, 0.0f);//颜色设置为绿色
glVertex3f(50.0f, -50.0f, -50.0f);

glColor3f(0.0f, 0.0f, 0.0f);//颜色设置为黑色
glVertex3f(-50.0f, -50.0f, -50.0f);

glColor3f(0.0f, 0.0f, 1.0f);//颜色设置为蓝色
glVertex3f(-50.0f, 50.0f, -50.0f);
glEnd();

glBegin(GL_POLYGON); //上表面
glColor3d(0.0, 1.0, 1.0);//颜色设置为青色
glVertex3f(50.0f, 50.0f, -50.0f);

glColor3d(1.0, 1.0, 1.0);//颜色设置为白色
glVertex3f(50.0f, 50.0f, 50.0f);

glColor3d(1.0, 0.0, 1.0);//颜色设置为品红色
glVertex3f(-50.0f, 50.0f, 50.0f);

glColor3d(0.0, 0.0, 1.0);//颜色设置为蓝色
glVertex3f(-50.0f, 50.0f, -50.0f);
glEnd();

glBegin(GL_POLYGON); //下表面
glColor3ub(0u, 255u, 0u);//颜色设置为绿色
glVertex3f(50.0f, -50.0f, -50.0f);

glColor3ub(255u, 255u, 0u);//颜色设置为黄色
glVertex3f(50.0f, -50.0f, 50.0f);

glColor3ub(255u, 0u, 0u);//颜色设置为红色
glVertex3f(-50.0f, -50.0f, 50.0f);

glColor3ub(0u, 0u, 0u);//颜色设置为黑色
glVertex3f(-50.0f, -50.0f, -50.0f);
glEnd();

glBegin(GL_POLYGON); //左表面
glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)255);//颜色设置为白色
glVertex3f(50.0f, 50.0f, 50.0f);

glColor3ub((GLubyte)0, (GLubyte)255, (GLubyte)255);//颜色设置为青色
glVertex3f(50.0f, 50.0f, -50.0f);

glColor3ub((GLubyte)0, (GLubyte)255, (GLubyte)0);//颜色设置为绿色
glVertex3f(50.0f, -50.0f, -50.0f);

glColor3ub((GLubyte)255, (GLubyte)255, (GLubyte)0);//颜色设置为黄色
glVertex3f(50.0f, -50.0f, 50.0f);
glEnd();

glBegin(GL_POLYGON); //右表面
glColor3f(1.0f, 0.0f, 1.0f);//颜色设置为品红色
glVertex3f(-50.0f, 50.0f, 50.0f);

glColor3f(0.0f, 0.0f, 1.0f);//颜色设置为蓝色
glVertex3f(-50.0f, 50.0f, -50.0f);

glColor3f(0.0f, 0.0f, 0.0f);//颜色设置为黑色
glVertex3f(-50.0f, -50.0f, -50.0f);

glColor3f(1.0f, 0.0f, 0.0f);//颜色设置为红色
glVertex3f(-50.0f, -50.0f, 50.0f);
glEnd();
}
glPopMatrix();
}

void opencvWithOpenGLTest()
{
std::string openGLWindowName = "OpenGL Test";
cv::namedWindow(openGLWindowName, cv::WINDOW_OPENGL);
cv::resizeWindow(openGLWindowName, windowWidth, windowHeight);
cv::setOpenGlContext(openGLWindowName);
cv::setOpenGlDrawCallback(openGLWindowName, onDraw, NULL);

while (cv::waitKey(30) != 27)
{
cv::updateWindow(openGLWindowName); // when needed
}
}

int main(int argc, char *argv[])
{
opencvWithOpenGLTest();

return 0;
}

运行成功后可看到一个旋转的彩色立方体。

结论

  从实验结果来看,OpenCV 确实能支持 OpenGL 图形的显示,但其不足之处也很明显:没有提供鼠标和键盘的交互操作(可能是 Shaun 还没发现,毕竟只是尝试一下看它能不能显示),仅仅只是提供一个显示窗口。如果真想用 OpenGL 做一些好玩的东西,还是用 glut 和 glew 吧,不过 glut 已经停止更新许久,glew 在调试时可能会出现一些莫名其妙的错误,所以网上有人用 freeglut 代替 glut,glee 代替 glew,具体的东西 Shaun 也没试过,Shaun 目前还没做过 OpenGL 相关的事,这次用 OpenCV 显示 OpenGL 图形纯粹是为了好玩 (*^__^ *) 嘻嘻……。

后记

  本篇文档也是上次编译配置完 OpenCV-3.2 后做的一次小实验,但当时并没有记录,所以还有一些参考资料也已经不知道了 :-(。

参考资料

[1] OpenCV学习笔记(六十一)——建立支持OpenGL的OpenCV工程“Master OpenCV”chp.3http://blog.csdn.net/yang_xian521/article/category/910716

[2] 几个opengl立方体绘制案例http://blog.csdn.net/bcbobo21cn/article/category/3104565