利用回调函数计算函数运行时间

前言

  曾有一段时间在写一个小程序,由于其对运行时间有要求,所以每写一段代码就要测试一下运行时间,如果超出就需要优化一下代码或换一种方法和算法。但是每次都需要插在某两个位置插两段代码感觉有点烦,也有点浪费时间,毕竟浪费时间就是浪费生命,本着保尔柯察金关于生命的言论,Shaun 不愿虚度年华,所以只得寻找一个方便简洁的方法计算运行时间(说了这么多,说到底其实就是懒吧 */ω\*)。后面就想到了回调函数,将想要计算运行时间的代码段放入一个函数中,并将其作为回调函数,用事先写好的计算时间函数调用它,从而方便计算该代码段的运行时间。

前言

  曾有一段时间在写一个小程序,由于其对运行时间有要求,所以每写一段代码就要测试一下运行时间,如果超出就需要优化一下代码或换一种方法和算法。但是每次都需要插在某两个位置插两段代码感觉有点烦,也有点浪费时间,毕竟浪费时间就是浪费生命,本着保尔柯察金关于生命的言论,Shaun 不愿虚度年华,所以只得寻找一个方便简洁的方法计算运行时间(说了这么多,说到底其实就是懒吧 */ω\*)。后面就想到了回调函数,将想要计算运行时间的代码段放入一个函数中,并将其作为回调函数,用事先写好的计算时间函数调用它,从而方便计算该代码段的运行时间。

正文

Show u 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
25
26
27
28
#include <ctime>
#include <cstdio>

#define _CALLED_ printf("The function %s", __FUNCTION__);

// 使用回调函数计算一段代码执行时间
void computeTotalTime(void(*processingCallback)() = 0)
{
clock_t start_time = clock();
processingCallback();
clock_t end_time = clock();
printf(" takes: %fs.\n", (double)(end_time - start_time) / CLOCKS_PER_SEC);
}

void test()
{
for (int i = 0; i < 1000; i++)
{
printf("Hello World!\n");
}
_CALLED_;
}

int main(int argc, char *argv[])
{
computeTotalTime(test);
return 0;
}

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

后记

  本来是想在网上找一个的,谁知道并没有找到,就只有自己动手实现一个了 ╮(╯_╰)╭。后面使用了一下该函数,发现好像并没有提高生产力 o(╯□╰)o,所以就没人放在网上?-_-!,不过确实从实现过程中学到了一些东西 ↖(^ω^)↗。

参考资料

[1] C/C++之回调函数http://www.cnblogs.com/danshui/category/345046.html

[2] c/c++在windows下获取时间和计算时间差的几种方法总结http://blog.csdn.net/coder_xia/article/category/837943

[3] (转)用宏获取函数名http://www.cnblogs.com/steady/category/264974.html

论如何科学的上网

科学式上网推荐组合:Chrome,Proxy SwitchyOmega,Lantern 等代理工具。

前言

  所谓的科学式上网,懂的自然懂,Shaun 也就不做过多解释了。本来一直在用别人免费提供的 pac 代理,但最近可能别人关掉了,上不了 google 了,就只能另寻他路了。所谓的另寻他路也就是尝试云端框架网站站长 枂下 提供的另外几种科学式上网攻略。本文只是对 枂下 站长的攻略做一下试验记录,若想看原滋原味的攻略,还请移步 云端框架

科学式上网推荐组合:Chrome,Proxy SwitchyOmega,Lantern 等代理工具。

前言

  所谓的科学式上网,懂的自然懂,Shaun 也就不做过多解释了。本来一直在用别人免费提供的 pac 代理,但最近可能别人关掉了,上不了 google 了,就只能另寻他路了。所谓的另寻他路也就是尝试云端框架网站站长 枂下 提供的另外几种科学式上网攻略。本文只是对 枂下 站长的攻略做一下试验记录,若想看原滋原味的攻略,还请移步 云端框架

科学的上网方法

  尽量使用 Chrome 进行科学式上网,因为其有一个代理管理插件 Proxy SwitchyOmega,该插件称之为代理切换神器也不为过,网上大量的教程和配置文件也是基于该神器做的。使用 Proxy SwitchyOmega 需要进行配置,这对初学者有一定的难度,这里 Shaun 推荐直接使用站长 枂下 提供的配置文件,至于 SwitchyOmega 的配置文件可以去站长的 云端框架 网站上去下,也可以联系 Shaun 。至于代理工具请看下文,Shaun 目前也只尝试过使用以下几种工具。

lantern

  其实 Shaun 最先尝试的工具是 XX-NET,但是其配置起来稍显繁琐,而且在第一步的时候必须处在科学式上网环境,而 Lantern 就比较简单了,只要装上之后再稍微动动手脚就可以了,所以就把 lantern 写在第一位了。从站长 枂下 那下载【蓝】灯电脑破解版压缩包,不过 Shaun 觉得应该随便在哪里下载个原版 lantern-2.2.5 安装都可以,只要后续的破解方法一样即可。

  具体破解方法为:主要是令 lantern 一直保持在 2.2.5 版本不变。但是一般来说 lantern 在安装之后会自动更新到最新版(Shaun 在两台电脑上都安装过 lantern,其中一台安装完之后打开 lantern 安装文件夹发现其已更新,而另一台却没有更新,这就有点玄学了 -_-!),至于判断 lantern 有没有更新的办法是:首先进入 lantern 的安装文件夹:C:\Users\XXX\AppData\Roaming\Lantern(将XXX改成自己的用户名),1、看 lantern.exe 文件的修改日期,如果还是 2016 年的,就说明其还没更新;2、显示隐藏文件,看有没有 .lantern.exe.old 文件,如果没有,则也还没更新。如果已经更新了,参考站长 枂下 的说法:

删除lantern.exe文件,修改.lantern.exe.old为lantern.exe

这样就又可以回退至 lantern-2.2.5 版。如果没更新的话就不用进行删除回退这一步,直接进行下一步。

  下一步为修改 lantern-2.2.5.yaml 文件中的更新路径 updateserverurl,使 lantern 永远不再更新,一直维持在 2.2.5 版本不变。具体更改方式为:

将其中的

updateserverurl: https://update.getlantern.org

修改为

updateserverurl: https://pic.black1ce.com

修改完之后保存退出。这里 Shaun 觉得可以随便将其修改成其它路径即可,毕竟只是让其不更新而已,这个路径应该除了更新就没有其它作用了,这纯属 Shaun 拙劣的猜测,有(ai)兴(gao)趣(shi)的童靴可以试试 :-P。

  原本以为到这一步就完成了,但是 枂下 站长后来又补发了一步,就是上面几步只是让 lantern 不再更新,而 500M 流量之后限速的问题仍然存在(Shaun 目前还没超过 500M,所以不知道这个问题,但抱着有备无患的心态先把 枂下 站长的攻略记一下 O(∩_∩)O~),所以接下来才是上正菜,破解“限制500M流量”问题的具体方法为:当使用 lantern 流量超过 500M 时,打开 lantern 的安装目录,打开 lantern-2.2.5.yaml 文件,

修改其中第九行的设备号,随意更换一个数字或者字母即可。

枂下 站长的说法是 8 位随机字母数字大小写均可,只是为方便起见推荐只改动某位即可。

eg:Shaun 目前第 9 行为:deviceid: Gu25Sfoz,一旦 500M 流量用完了,Shaun 就只需要将其修改为 deviceid: Gu25Sfoa 即可。

到这一步 lantern 的破解算是基本完成了吧,如果 枂下 站长有新的更新且被 Shaun 看到的话再进行实验更新吧。


XX-NET

  该工具应该是 Shaun 尝试配置的首款代理工具,不得不说其配置和 lantern 相比实在是太复杂了,而且其中有一步还必须处在科学式网络环境中,Shaun 还是借助别人的 VPN 上的(当时还没用 lantern,所以没用其 500M 免费不限速的流量 ~~o(>_<)o~~)。Shaun 经过实测 XX-NET 无法在 Firefox 中用 google 搜索,一用 google 搜就会报错:

您的连接并不安全

www.google.com 的网站管理员未正确配置网站。为避免您的信息被窃,Firefox 没有与该网站建立连接。

此网站采用了 HTTP 严格传输安全(HSTS)机制,要求 Firefox 只能与其建立安全连接。正因如此,您也不能将此证书加入例外列表。

www.google.com 使用了无效的安全证书。 该证书因为其颁发者证书未知而不被信任。 该服务器可能未发送相应的中间证书。 可能需要导入一个额外的根证书。 错误代码: SEC_ERROR_UNKNOWN_ISSUER

https://www.google.com/search?q=test&ie=utf-8&oe=utf-8

对等端的证书颁发者不受认可。

HTTP 严格传输安全(HSTS):false HTTP 公钥钉扎:true

证书链:

-----BEGIN CERTIFICATE----- MIIDkzCCAnugAwIBAgIQSu4RvcIwnqiEQE6Z68FlaDANBgkqhkiG9w0BAQsFADBz MQswCQYDVQQGEwJDTjERMA8GA1UECAwISW50ZXJuZXQxDzANBgNVBAcMBkNlcm5l dDEQMA4GA1UECgwHR29BZ2VudDEVMBMGA1UECwwMR29BZ2VudCBSb290MRcwFQYD VQQDDA5Hb0FnZW50IFhYLU5ldDAeFw0xNzA5MTYxNDIzMjRaFw0yNzA5MTQxNDMz MjRaMHgxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhJbnRlcm5ldDEPMA0GA1UEBwwG Q2VybmV0MRcwFQYDVQQLDA5Hb0FnZW50IEJyYW5jaDEVMBMGA1UEAwwMKi5nb29n bGUuY29tMRUwEwYDVQQKDAwqLmdvb2dsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDL3K1OgwalKOJPtO4urpAiu+lioGNax/EIaYR1D2kH66AJ lpal0pYFhXF6MOYCUNfpZIqP5qAQs7JGuRmFdo7rWaLHZ+3S+TlIHdZkoLvyYBcX ENVBcLQvZ7IL7DDUZObK/R7OOKz82dEoITQnT+q/lecR9wQ7QNdNVNqn0xS0NPt7 bS76irMxkJcO2q7Lu4R56ImCox/G7dUEepjL0Po516l6fLKG3qi5org2z6ap0yl2 Etu8cRfqiqaqhO0HI1Twz+Rbp/8KUdUBgnNkjcod83HE+jJKxIUDmn18+l7J8sBi a0JvWSIYy2ccFXoR8L4lfvIa8PhTuMmpxyDkwDdPAgMBAAGjHjAcMBoGA1UdEQEB /wQQMA6CDCouZ29vZ2xlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEADiM6yWCaGNLn ggirjN0b34j5JmjgYYx3bRaKDe4We2emjlLsdskBo2ztkd/tPBfUa7DWExgFPvVq B2FeEf85Zj72kMmc2JikJBtPF1qK9fa4O1gST4VE0xIF99zGrgkDhGaYd1ocElWS qfBNQfzwsO+nl2OQf99ATMqMSCGacN7z+LJBLn65de+ODzYUkIHzhU5/xJMian3y fQzNFCAgK8OMf16excqRUcX8zfGPfvtAafDrdOYEXcGayLIvt4tGr8T+tii+MtCR O5hXK8/ABMLGI74zgLYloVFjJv21VsLrNCvvD43T5E3c+8d1MENozdEnsyzWkTkp knP4aEiLOQ== -----END CERTIFICATE-----

Shaun 不知道为什么 (+﹏+)~。所以为了能正常使用科学式网络,还是老老实实的用 Chrome 吧,何况其还有 Switchyomega 神器,并且建议把 Chrome 设置为默认浏览器。具体配置流程如下:

  从 枂下 站长那下载 XX-NET(Shaun 其实最先是直接在 GitHub 上下载最新的 XX-NET,但 Shaun 由于在 Firefox 上尝试失败了,当时也没在 Chrome 上尝试,以为最新版 XX-NET 有问题,后来用上 枂下 站长那下的 XX-NET 在 Chrome 上试了下可以,而 Firefox 不行,就知道可能是 Firefox 的问题,而既然已经能用了,Shaun 也没用最新的 XX-NET 在 Chrome 上尝试了),直接解压到某个文件夹,然后将 XX-Net-3.3.6 文件夹重命名为 XX-Net,即去掉末尾的版本号,据 枂下 站长说法是为了减少后续 XX-NET 出错(Shaun 这里老老实实的照 枂下 站长说的做了,所以也不知道如果没去掉版本号会有什么后果)。接着以管理员身份运行 XX-Net 目录下的 start.vbs 文件(这里右键是没有“以管理员身份运行”选项的,要想以管理员身份运行就只有使用 Windows 命令行了,具体做法就是以管理员身份运行“命令提示符”,再在其中运行 start.vbs 文件即可),运行成功后将弹出

已经导入GoAgent证书,请重启浏览器.

点击确定即可。再次启动默认(Chrome)浏览器,将打开 127.0.0.1:8085 页面,即为 XX-NET 的配置界面。将看到 GAEProxy 状态信息(可能是由于 google 取消了公共 APPID,所以 Shaun 看到的不是“您正在使用公共APPID,....”这条消息,而是另外一条消息(具体什么消息 Shaun 忘记了 o(╯□╰)o)),打开显示详细信息(其实也没用,Shaun 并看不懂这么多 -_-|||),先放这里吧,部分信息以后再说,先进入正式配置步骤。

  1. 首先点击左边的“高级”选项,据枂下站长说,将自动调整扫描线程数关掉,最大扫描线程数设为 200,点击提交(可能这样连接速度更快一些)。Shaun 这里这里没有照做,而是保持默认设置,Shaun 只是要求能上就可以了,对速度要求可以稍微放松一点(或许以后会调成站长推荐的配置)。
  2. 接下来点击“部署服务端”选项,填写 AppID,点击“开始部署”。但是这一步,Shaun 并没有 AppID,所以只能上 google 申请,这样最麻烦的一步就来了。照着 枂下 站长的指引,Shaun 一步步的申请了 AppID。具体申请步骤如下(这一步需要登录 google 账号,必须处于科学式网络环境):
    1. 点击 打开Appid申请页面,登录 google 账号,创建项目,并在新建项目中修改项目 ID(这就是第一个 AppID ),为方便,建议直接以“项目名-00”的方式按顺序命名 AppID,创建成功后继续点击“Google Cloud Platform”旁的一个小三角,点击弹框的“+”,按上述方式进行创建新的 AppID,如此重复,Shaun 总共创建 10 个 AppID,10 个之后会提示配额已用完,硬是要创建只会覆盖掉第一个 AppID。
    2. 接下来就是需要选择语言和地区,只有为每个 AppID 选择语言和地区之后,该 AppID 才会生效。语言和地区的选择界面可以从“App Engine”界面中进入,也可以直接在添加 AppID 界面的那个弹框中点击相应的 AppID 进入,当然如果是第一个 AppID 选择语言可以直接点击界面上的“选择一种语言” 。语言选择 Python,地区选择亚洲(asia-northeast1),选错了后果自负(当然可以覆盖掉该 AppID 重新设置)。其实可以照 枂下 站长那样设置完第一个 AppID 的语言和地区之后直接修改浏览器的地址栏的 url 以快速设置 AppID 的语言和地区。具体修改方法为:将 url 地址末尾的 project 参数的值改为你想要设置的下一个 AppID,eg: Shaun 当前 AppID 为 test-00,对应的浏览器 url 地址末尾的参数为 lang=python&project=test-00,Shaun 想设置的下一个 AppID 为 test-01,则只需将其修改为 lang=python&project=test-01 回车即可快速设置 test-01 的语言和地区。如此重复,就可以设置完全部的 AppID 语言和地区。设置完之后,这些 AppID 就能进行部署了(据说每个 AppID 每天有 1G 的流量可以使用,并于每天下午三点更新,也就是 Shaun 每天有 10G 流量,一般是够用了 :-D)。
  3. 将上文申请并设置完成的 AppID 放入“GAE AppID”文本框中,多个 AppID 可以按这样的格式放入:test-01|test-02|test-03,两个 AppID 以|分隔即可。点击“开始部署”,会弹出一个登录 google 账号的标签页,登录并允许即可,等待 2~5 分钟,可发现日志页面出现 Done!Deploy 10 appid successed. 等字样即表示服务端部署成功。
  4. 点击“部署”选项,将上面部署服务端的 AppID 以相同格式输入“GAE AppID”文本框中,点击“保存”即可。保存完之后,即可在状态信息界面显示详细信息中 Appid 发现当前工作 AppID 就是部署的 AppID ,至于那个配置下的监听代理就是设置代理的地址和端口。

这样 XX-NET 就算是配置完成了,至于 枂下 站长说的扫描 ip,Shaun 就没做实验了,因为这样配置完就可以科学式上网了。


  以上代理工具配置完成后,即可在 Chrome 中畅游 Internet 了,但是正确的科学式上网姿势应该是:国内的网站走本地连接,而国外被屏蔽的网站才走代理。这就需要 Proxy SwitchyOmega 这款插件了,它能按照一定的规则自动选择走本地还是走代理,这样既不会浪费流量,也能使国内的网站联网速度不受影响。导入前文推荐的配置文件后,就可选择对应的代理方式。这里当然是选择自动切换,至于虚拟切换是选择 Lantern for 8787 还是 XXNET for GAE 就随便个人的喜好了(在走代理的时候别忘了把相应的代理工具开启);如果直接选择其中一种代理方式就相当于全局代理,这也就失去这款插件的作用,只有自动切换加上虚拟切换才能充分发挥这款神器的真正作用。

匿名网络

  要想使用匿名网络,当然少不了专用的浏览器:Tor Browser,下载并安装(下载时需要身处科学式网络环境,安装时最好改变一下目录,而且路径中最好不要有中文)。接下来就是配置了 Tor 网络了。具体配置流程如下:

  首先,它问直接连接 Tor 网络还是配置网桥或代理,这里当然是选择配置;其次它问互联网服务提供商( ISP )是否对 Tor 网络连接进行了封锁或审查,这里选,据枂下站长所说因为国内网桥大部分已失效,连接网桥没有意义还会拖慢速度;然后它问是否本地代理访问互联网,这里当然选择;最后填写本地代理配置,这里需要注意,枂下站长提供的部分代理配置是:

SSR/SS Socks5//127.0.0.1 : 1080

Seed HTTP//127.0.0.1 : 1080

Lantern Socks5//127.0.0.1 : 8287(2系列),三系列的在Lantern设置页面查看

Psiphon 可以在配置页面自定义

  其中经 Shaun 实测,上面 Lantern 的代理配置是连接不上的,Shaun 后来参考 SwitchyOmega 配置文件中 Lantern 的代理为 HTTP//127.0.0.1 : 8787,经尝试如此配置可以连接 Tor 网络,所以 Lantern 的正确配置应为:

Lantern HTTP//127.0.0.1 : 8787(2系列),三系列的在Lantern设置页面查看

设置完成后等待片刻就能连上 Tor 网络了,最好就保持原来的 DuckDuckGo 搜索引擎,不要更改,接下来就可随心所欲的畅游 Internet 了。

  至于想访问暗网,可以参考Hacking/整理的暗网网址Tor.txtWorking Links to the Deep Web或者直接用站长枂下给的网址:torlinkbgs6aabns.onionxmh57jrzrnw6insl.onion

枂下站长回答:

XX-Net可以作tor的前置代理吗?不行的,xx-net是假http协议

所以 XX-NET 不能用作 Tor 的代理配置。

  最后再简要记录一下 Chrome 调用 Tor Browser 的代理吧。Shaun 没有像 枂下 站长那样用命令行去实验,只是享受了一下 ta 的试验成果(O(∩_∩)O谢谢)。总而言之,还是利用 SwitchyOmega,代理方式选择 Tor for 9150,就可以在 Chrome 中调用 Tor Browser 的代理,畅游 Internet 了。


更新于:2017-11-27

  时久达期间及之后,lantern 的那种破解方式从时灵时不灵,到完全失效,而 XX-NET 则一开始就失效了,这见证了 Google 和 GFW 的斗智斗勇(๑乛◡乛๑),但很明显,google 失败了,事实证明没有 GFW 封不了的,只是看它想不想封 (๑•ั็ω•็ั๑)。至于 lantern 和 XX-Net 的复活方式请移步 枂下 站长的网站,Shaun 这里就不再赘述了。这里需要更新的一点是:Tor 的网桥配置采用“meet-amazon”(亚马逊的云计算平台)或者“meet-azure”(微软的云计算平台)传输也能实现科学式上网,但速度很慢,仅能浏览网页而已,可以当做备选临时用用。


更新于 2017-12-24:今天使用了下 赛风(Psiphon),其操作完全傻瓜式,简直不要太好用,而且为单个绿色文件,携带也方便,用的时候只要设置一下端口就行。不出意外的话,妈妈再也不用担心 Shaun 搞科研了(؏؏☝ᖗ乛◡乛ᖘ☝؏؏)。


更新于 2018-03-14:现在最新版的 Firefox 59.0 也有 Proxy SwitchyOmega 插件了,虽然还是测试版,但还是能够正常使用,既然 Firefox 有了这款神器,Shaun 也就有动力将 Firefox 使用 XX-Net 连接 Google 搜索出现的问题解决了,首先进入 Firefox 的 「选项」或设置 ==》「隐私安全」,下滑到最下面的「证书」,点击「查看证书」,在弹框中选中「证书机构」,点击「导入」,添加 XX-Net\data\gae_proxy目录下的 CA.crt 证书,在导入中出现的弹框全部选中信任,将会在上方的证书栏中出现 GoAgent XX-Net 证书,这样就能解决上文出现的问题,参考自:【已解决】Firefox报错:github.com 使用了无效的安全证书。 证书因为未提供证书发行链信而不被信任。 (错误码: sec_error_unknown_issuer)


后记

  最后的匿名网络是 Shaun 弄着好玩的,像暗网这种东西 Shaun 这种遵纪守法的好公民才不会访问呢 (ಡωಡ)。等以后时机到了再去买个国外 VPS 自己搭建一个科学式上网环境吧。最后感谢 枂下 站长的无私分享。

附录

  原本还以为 Shaun 搭建的 Hexo+GitHub 个人博客站点还是个深网,没想到搞完科学式上网后用 google 搜索竟然能搜到,虽然 Shaun 没做什么,但 google 仍然能搜到,google 的蜘蛛还挺厉害的,不过如果百度的蜘蛛没被 GitHub 屏蔽的话百度可能也能搜到(从某些原因上来说,GitHub 把百度屏蔽掉也好 O(∩_∩)O~)。既然已经被 google 收录了,Shaun 也就不去搞那个站点地图了,等以后想搞 SEO 了再去做吧。

参考资料

[1] Switchyomega超详细教程之Chrome与Firefox版本

[2] 【蓝】灯电脑破解版之2系列禁止自动升级最终办法

[3] XX-NET史上最详细完整教程

[4] XX-NET史上最详细完整教程之第一部分:Appid创建部分

[5] Tor Browser在国内Windows平台下的超详细教程

[6] Chrome等其他程序如何完美调用Tor Browser的代理来上网

[7] “如何翻墙”系列:TOR 已复活——meek 流量混淆插件的安装、优化、原理https://program-think.blogspot.com/search/label/IT

[8] 如何翻墙?——写在 BlogSpot 被封之后 {2015-08-28}

Hexo的SPFK主题修改小记

前言

  Shaun 一直在对 Hexo 的 SPFK 主题进行持续修改以符合 Shaun 自己的需求,在修改当中也会遇到一些小问题,以防遇到重复问题,特此记录所遇小问题,至于大问题可能会另外开篇。

前言

  Shaun 一直在对 Hexo 的 SPFK 主题进行持续修改以符合 Shaun 自己的需求,在修改当中也会遇到一些小问题,以防遇到重复问题,特此记录所遇小问题,至于大问题可能会另外开篇。

修改篇

1、修改 aboutme 排版问题

修改日期:2017-09-16

需求描述:Shaun 为了使 aboutme 排版好看一点,使“关于我”的内容更有段落感,Shaun 尝试在主题配置文件中 aboutme 对象的内容添加各种换行转义符号均于事无补,如 \n\r\n&#13;&#10;<br /> 等,站点不仅不会换行,还会直接将转义符号都显示出来 (╯﹏╰)。

解决办法:既然 Shaun 基本把所有的换行方法都试过了,还没有任何作用,那就只能是问题出在其它地方了。Shaun 首先找到显示 aboutme 内容的地方,其位于主题文件夹下 \layout\_partial\left-col.ejs,显示 aboutme 内容的代码为 <div id="js-aboutme"><%=theme.aboutme%></div>,查阅相关资料,具体为 与大家分享ejs源码阅读心得,其中有这样一段话:

关于ejs模板的五种模式对应几种指令

ejs主要提供了如下几种指令:

  • <%, 该指令主要通过js中的eval来执行js代码, 如上模板代码<% [1,2].forEach(function(v){ %>将通过eval编译成; [1,2].forEach(function(v){即直接可执行的js代码, 并且不会存放到__output函数中输出.
  • <%=, 该指令主要用于输出变量内容, 如上模板代码<%= v %>将通过escape函数编译成__append(escape(v)), 可以看到该指令用于输出变量内容, 最后将通过__output输出内容.
  • <%-, 该指令与<%=区别是, <%=指令使用escape函数来对特殊字符进行编码, 如将>转为%3E, 查看关于escape函数.
  • <%#, 该指令主要用于模板内注释, 既不会执行也不会输出.
  • <%%, 主要用于输出字面值%.

关于以上各个指令对应的解析, 可参考ejs源码根目录lib/ejs.js文件中的scanLine函数.

从中可得知 <%= 指令会将变量内容中一些特殊字符先转义,再原封不动的输出,所以 Shaun 无论怎么修改主题配置文件中 aboutme 对象的值,其输出内容都会是原封不动的 aboutme 对象的值。为了让其输出内容可以有相应的特殊格式,就不能让其转义,只能用 <%- 指令,将其修改为 <div id="js-aboutme"><%-theme.aboutme%></div>,这样就能使输出内容可以自定义特殊格式,Shaun 最后在 aboutme 对象的内容中需要换行的地方添加了 <br />,实测如此修改后可以换行。

2、给左栏添加滚动条

修改日期:2017-09-18

需求描述:SPFK 主题是双栏的主题。因为左栏主要是用来显示一些菜单和头像等内容,这些内容也不多,所以原作者就没有添加滚动条。但是由于 Shaun 添加了个本地搜索功能,在刚开始文章少的时候还不受影响,但是随着文章的增多,搜索功能就会影响左栏的布局,这是就必须添加一个滚动条了。本以为添加滚动条很简单,就是添加一个 overflow: auto;,谁知道还没这么简单 ╮(╯﹏╰)╭。

解决办法:Shaun 对问题的定位没问题,就是修改主题文件夹下的 \source\css\_partial\main.styl 文件中 .left-col 样式,问题在于怎么修改,本想直接在其中加入 overflow: auto;,按道理说问题就能解决的,但是 Shaun 去搜索试试,发现搜索框上方的头像,文字等全部消失了,滚动条没起到作用,而下方的菜单可以通过滚动条看到。于是 Shaun 觉得可能是 div 上界没撑开,而超出的地方却隐藏了,但下界为什么能撑开,Shaun 这里还是很不明白 ?_?。既然是这里隐藏了,Shaun 就去看相关标签有没有 overflow: hidden; 属性,谁知道要么是没有,要么是即是关闭了也没有作用,那问题应该不是出在这里。就只能是这些元素所在的子 div 里了,Shaun 找到其子 div 属性 .intrude-less,其中虽有 overflow: auto; 但没设置 height 属性,所以就不能发挥其作用,Shaun 于是给它加上 height 属性,搜索后发现有两个滚动条,这显然不简约,于是 Shaun 把 .intrude-less 的 overflow: auto; 属性注释掉,没想到居然能完美解决问题,可能是因为加上高度属性之后就能撑大父元素 div 了吧(来自某业余前端的猜测 (⊙_⊙))。后面为了更美观,Shaun 把下方菜单区域的 div 样式 .switch-area 高度 min-height 改小了一点,顺便也把主题文件夹下的 \layout\_partial\left-col.ejs 文件中首行注释掉 <!-- <div class="overlay"></div> --> 。Shaun 也曾想把 height 改为 min-height,谁知道又出现相同的问题,不得不又改回去。虽然这次已经解决了问题,但有些细节问题还是不太明白,只有等以后前端水平上去了再去想了,如有大佬知道还望不吝赐教 (^人^)。

3、更换鼠标指针

修改日期:2017-9-26 ~ 2017-9-27

需求描述:Shaun 在玩《Ori and the Blind Forest》这款游戏的时候觉得其鼠标指针很酷炫,于是想把其鼠标指针放在 Shaun 的博客站点中 (๑´ڡ`๑) 。

解决办法:要想更改指针,首先需要找到对应的指针文件,最终在万能的贴吧得到指引,在 RealWorld Graphics 上找到两个 ori指针 文件,一个是 动态的 ani指针文件,还有一个是 静态的 cur指针文件(好像该游戏的作者也在 steam 上的评论中提供了游戏中的指针文件,详见:I wanna use this games cursor. )。既然已经找到了指针文件,就可以开始更换炫酷的鼠标指针 (•̀ᴗ•́)。具体更改方法如下:将下好的指针文件放在主题文件下的 \source\img 文件夹中,在主题配置文件中添加 cursor 属性:

1
2
3
4
5
# set cursor | 设置鼠标指针图标
cursor:
on: true
cursor_0: img/cursor.ani # 首选指针
cursor_1: img/cursor.cur # 备选指针

其中 cursor_0 和 cursor_1 代表使用哪个指针,因为 firefox 和 chrome 不支持 ani文件 的指针(好像是 ani 文件有很大的漏洞),所以 ani 动态指针是用不了的,只能用 cur 格式的静态指针,而 IE 是可以加载 ani 格式的动态指针,所以 Shaun 这里就将两个指针文件全放上去了,首选加载动态指针;最后增加相应的代码调用 cursor 属性,加载指针文件,在主题文件夹下 /layout/_partial/background.ejs 文件末尾添加:

1
2
3
4
5
6
7
8
<% if (theme.cursor.on){ %>
<style>
body{
background: #3f3f3f;
cursor: url(<%- config.root %><%- theme.cursor.cursor_0 %>), url("<%- config.root %><%- theme.cursor.cursor_1 %>"), auto;
}
</style>
<% } %>

如此更新站点之后即可使用新鼠标指针样式,可能需要先进行 hexo clean 再发布。

BTW:这次修改是一个月之前的了,当时不知怎么的忘记记录了,还好 Shaun 的 git 提交记录比较详细,对应的提交记录为:add a function -- change cursorupdate set cursor function

4、修改打赏问题

修改日期:2017-10-13

问题描述:Shaun 突然想玩一下那个打赏小东西,但照配置文件中指示的那样在文章开头 ymal 格式中加入 reward: true 属性,没有任何作用,于是去主题文件夹搜索 reward 属性相应的代码,结果是“找不到结果”(坑爹了这是,摔!(╯‵□′)╯︵┴─┴ )。

解决办法:既然 reward 属性找不到就只有搜索 reward_type 属性,最终在主题文件夹下 \spfk_c\layout\_partial\article.ejs 文件中找到这样一条语句 <% if ((theme.reward_type === 2 || (theme.reward_type === 1 && post.toc)) && !index){ %>,其下面就是打赏相关的代码,查看 SPFK 主题原作者介绍信息(Hexo 主题:SPFK)发现 toc 属性是用来显示目录的(一个用来打赏的代码怎么与文章目录相关了 -_-#),所以上面的 toc 应该改成 reward,修改后的代码为 <% if ((theme.reward_type === 2 || (theme.reward_type === 1 && post.reward)) && !index){ %>,这时照配置文件中指示的那样在文章开头 ymal 格式中加入 reward: true 属性就能在相应的文章后面看到一个大大的“”字。

本来写到这里应该打赏这玩意应该完结了,但 Shaun 无意中在该文件的下面发现这样一段代码:

1
2
3
<% if (!index && post.toc != false && !is_page()){ %>
<%- partial('_partial/toc') %>
<% } %>

这是和 toc(即文章目录)真正相关的代码,功能大概就是判断是否加载文章目录相关的代码,如果在文章开头设置 toc: false,则该文章不会显示目录,但是如果在文章中不加 toc 属性,也会显示文章目录,但上面的打赏却不会显示,看起来 post.toc != falsepost.toc 应该逻辑差不多,这里是 Shaun 感到十分奇怪的一个地方?后面查阅相关资料(JavaScript undefined 属性)得知:

注释:null 表示无值,而 undefined 表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性。

而本文这里因为没有在文章开头设置 toc 属性,所以其为 undefined,其既不为 false 也不为 true,只为 undefined,当在 if 语句中做判断,会执行 else 分支,作 ! 运算,结果则为:true。所以 if(post.toc) 不能执行其下代码,因为 post.toc 为undefined,不为 true 也不为 false,而 if(post.toc != false) 能执行其下代码,因为 post.toc != false 为真。至于 javascript 中 if(a == ture)if(a) 的区别具体为:前一种是 a 必须为 1 或者 true 才执行;而后一种只要 a 不为 false undefined null 0 -0 NaN "" 这 7 个字符中的其中任何一个都能执行。

5、交换内容栏和左侧栏位置

修改日期:2017-11-22 ~ 2017-11-23

需求描述:Shaun 最近逛网站时发现,好像一些博客网站基本都是把内容放在左侧,百度和 google 的搜索结果也是在左侧,可能是内容在左侧要好一点吧,于是 Shaun 略微修改之后,将内容放在左侧,而原来的左侧栏放到最右侧,好像是顺眼了一点(不排除是心理作用 (ಡωಡ)),如果以后还觉得不错的话,再把相关的变量名换掉吧(此次修改仅仅是将 CSS 相关的值改变,div 类名没变)。

解决办法:首先当然是定位左侧栏 .left-col,它在主题文件夹下 source/css/_partial/main.styl 文件中,为其添加 right: 0px; 属性,使左侧栏靠右侧停放;在定位内容栏 .mid-col ,将 right:0; 改成 left:0;,将 left: 300px; 改成 right: left-col-width;,使内容栏靠左侧停放,同时使其距离右侧有左侧栏的宽度。最后就是再修改其他一些小东西(比如目录按钮和目录内容 div 等)的 css 值,关于这个 Shaun 就不细述了,反正也就是更改 left、right、bottom 以及 top 属性及其值,具体修改了哪些内容可以见 Shaun github 提交记录。

BTW:诶呀呀!昨天忘记测试手机端,今天用手机打开一看,手机端页面也距右边 left-col-width 宽度,这使得内容全挤在一起了,完全没法看 ರ_ರ ...。所以不得不添加手机端样式,定位手机端 .mid-col,它在主题文件夹下 /source/css/_partial/mobile.styl 文件中,为其添加 right: 0; 属性值;后面看见回到顶部、回到底部的导航栏也有点问题,就在该文件中 /*导航*/ 下面 .scroll 上面添加:

1
2
3
#scroll{
right: 0;
}

使得该导航栏靠右停靠。

6、更新站点部分 CSS 文件和代码结构

修改日期:2017-12-04 ~ 2017-12-06

更新日志:

  1. Shaun 将 left-col 相关的东西(比如 css 样式和 ejs 文件)全部重名为 right-col,毕竟经过几天的适应,感觉放在右侧还不错,就干脆也将其重命名算了,所以原左侧栏 left-col 从现在开始就完全变成右侧栏 right-col 了;

  2. 原来的本地搜索框有两个 .search 样式,本次修改将两个 .search 样式合并了,删除重复的样式,只留下一个合并后的 .search 样式;Shaun 同时还优化了一下本地搜索功能的结构,将原来主题配置文件中的 search_box 属性删掉,给 search 属性添加一个 on 的属性来代替 search_box 属性,这样让结构不那么混乱,只由一个 search 属性决定本地搜索功能的开启和关闭及功能的实现,而不是像以前那样由 search_box 属性决定右侧栏搜索框的显示,而 search 属性决定本地搜索功能的实现;

  3. 更改右侧栏 right-col 的 overflow 样式,原来是右侧栏 right-col 垂直超出滚动,水平超出隐藏,这样在屏幕比较窄的情况在右侧会出现两条滚动条,很不美观。现在 Shaun 将 overflow 样式改成 &:hover {overflow-y: auto; overflow-x: hidden;} ,这样只有在鼠标指针悬浮在右侧栏 right-col 上时才会再右侧栏出现滚动条,这样虽然不能从根本上解决问题,但稍微缓解了一下,等以后再看能不能彻底解决滚动条的问题 ರ_ರ ...;

  4. Shaun 以前添加 RevolverMaps 这个小部件的时候只是简单粗暴的添加 div 及对应的样式,完全没考虑到主题的扩展性和易修改性。于是 Shaun 将其改成配置文件的形式,在主题配置文件中添加 visual_visitor 属性,只要将其值设置为 RevolverMaps官网 获取的那串 script code,eg:

    <script type="text/javascript" src="//ra.revolvermaps.com/0/0/8.js?i=0lpycb5p234&amp;m=7&amp;c=ff0000&amp;cr1=ffffff&amp;f=arial&amp;l=49" async="async"></script>,即可在右侧栏菜单下的访问情况中看到一个 3D 地球实时显示访客的位置信息,Shaun 为了优化异步访问信息,将其中的 async="async" 改成 defer="defer",这样好像能优化加载次序。这两者的区别可参考 defer和async的区别,好像是都能异步加载,只是 async 是该 script 加载完立即执行,而 defer 是该 script 加载完之后在整个页面结束加载之前执行,也就是最后执行的;

  5. 最后还修改了 MathJax 的 CDN 地址及配置属性。MathJax 的配置属性可参考 加载和配置MathJax,具体如下:

    第一种配置Mahtjax的方法就是使用配置文件。MathJax附带了很多种预制配置文件。它们存储在MathJax/config 目录。主要有其中以下几个:

    • default.js:这个文件包含了所有MathJax可用的配置选项,并附有注释和说明,你可以编辑它们来满足你的需要。
    • TeX-AMS-MML_HTMLorMML.js:允许使用 TeX, LaTeX, 或者MathML 符号书写公式。如果浏览器支持就处理为MathML,否则就使用Html和Css渲染。
    • TeX-AMS_HTML.js:允许使用 TeX 或者 LaTeX 符号书写公式。使用Html和Css渲染。
    • MML_HTMLorMML.js:允许使用 MathML 符号书写公式。如果浏览器支持就处理为MathML,否则就使用Html和Css渲染。
    • AM_HTMLorMML.js:允许使用 AsciiMath 符号书写。如果浏览器支持就处理为MathML,否则就使用Html和Css渲染。
    • TeX-AMS-MML_SVG.js:允许使用 TeX, LaTeX, 或者MathML 符号书写公式。使用SVG产生输出。
    • TeX-MML-AM_HTMLorMML.js:允许使用 TeX, LaTeX,MathML,或者 AsciiMath 符号书写公式。如果浏览器支持就处理为MathML,否则就使用Html和Css渲染。

    第一个文件是提供给你修改的。它基本上包含了MathJax的所有配置选项,同时有注释解释。其他的文件就是我们联合配置文件。它们不仅仅配置Mathjax,还预加载了一些配置所需的文件。这些文件内容在 联合配置 中有详细的解释。

    原来的 CDN 地址 cdn.mathjax.org 已经在 2017-04-30 日关闭,所以必须更新 CDN 地址,其推荐的 CDN 地址为 cdnjs.cloudflare.com/ajax/libs/mathjax,而新的 MathJax 也提供一种一种新的配置文件 TeX-MML-AM_CHTML(允许使用 TeX, LaTeX,MathML或者 AsciiMath 符号书写公式,使用 CommonHTML 产生输出),新的 MathJax 推荐使用的就是这种配置文件,因为它计划在 V3.0 将 HTML-CSS 输出格式丢弃,只留下 CommonHTML 和 SVG 这两种输出格式。而且新的 CDN 地址不支持 /latest/MathJax.js 这种格式,必须指定一个确定的版本,截止 Shaun 此次修改日期之前,最新的版本为 2.7.2,所以比较推荐的一种加载格式为:

    <script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML"></script>

7、添加 404 页面和一些插件等

修改日期:2017-12-09 ~ 2017-12-10

更新日志:

  1. 修改文章内 a 标签的高度,即 .article-entry p a 中 padding 的上下边距。文章内的 a 标签在外面加个虚线框本来就很突出了,还设置上下 padding 为 8px,这就显得有点浮夸了 :) 。最后将其上下边距设为 0px;

  2. 添加 404 页面,具体参考自:在 Hexo 中创建匹配主题的404页面

    1. 启动 Git Bash,进入 Hexo 所在文件夹,输入 hexo new page 404
    2. 打开刚新建的页面文件,默认在 Hexo 文件夹根目录下 /source/404/index.md
    3. 在顶部插入一行,写上permalink: /404,这表示指定该页固定链接为 http://"主页"/404.html
    1
    2
    3
    4
    5
    title: 404 Not Found:该页面无法显示
    toc: false
    comments: false
    permalink: /404
    ---
  3. 添加 hexo-abbrlink 插件,使文章生成唯一永久链接。这个插件最好是在建站之初就加上,不然写了很多文章之后又都得重新生成链接,搜索引擎需要再次抓取新链接,不利于 SEO,Shaun 这里也就只有等 Google 慢慢抓取更新,还好写的不多,不算太迟。安装完之后 Shaun 在站点配置文件中添加:

    1
    2
    3
    4
    permalink: posts/:abbrlink.html # 需安装hexo-abbrlink插件
    abbrlink:
    alg: crc32 # 算法:crc16(default) and crc32
    rep: hex # 进制:dec(default) and hex

    ※BTW:需要将原来的 permalink 注释掉或直接删除。

    更多设置可参考 hexo-abbrlink

  4. 添加 hexo-all-minifier 插件,快速压缩代码,分别对 html、css、js、images 进行优化。Shaun 这里就直接使用推荐的配置了,直接在站点配置文件中添加:

    1
    all_minifier: true  # 需安装hexo-all-minifier插件

    更多设置可参考 hexo-all-minifier

  5. 原来文章标题不可点击,反而日期可点击,这有点奇怪 ◔ ‸◔?。本次修改之后,点击文章标题即为刷新页面。

更新日期:2017-12-19 ~ 2017-12-19

具体修改内容:主要更新了 404 页面上面的动图,个人偏好喜欢一些星系漩涡之内的动图,偶然发现这个东西(HTML5+Three.js实现的3D可拖拽银河星系旋转动画特效源码),于是将它的源码略作修改放进 Shaun 的 404 页面,将相关的 js 文件放入主题文件夹中 \source\js 文件夹里,最初时是将相关 js 文件引入路径当成相对路径引入,没想到这样造成有的 404 页面会显示旋转动图,而有的 404 页面则不会显示,后面参考网上资料(解惑页面中的相对路径和绝对路径)了解到:

  1. html 中引入的资源(包括jscssimg
    • 相对路径:相对的是 网页本身的 URL
    • 绝对路径:相对的是 网页 URL 的根路径
  2. css 中引入的资源
    • 相对路径:相对的是 css 文件本身的 URL
    • 绝对路径:相对的是 网页 URL 的根路径

结论: html 中引入资源的相对路径与 网页的 URL 有关,而css中则与 css 资源本身 URL 有关。但使用绝对路径时,不管是在 html中,还是css中,都只与 网页 URL的根路径有关。

将相对路径改为绝对路径,即可在 Shaun 博客域名下所有 404 页面正常显示旋转动图。

8、添加 Gitalk 评论系统

修改日期:2017-12-16

  Gitalk 是一款类似 gitment 的评论系统,Shaun 先是照着它提供的配置添加之后,发现居然与 spfk 主题中的 require-2.1.6,jquery-1.9.1.min.js 冲突,显示不了 Gitalk,Shaun 以为是 bug,所以就去提了个 issue,作者 booxood 还是挺认真负责的(°Д°)Ъ,耐心的解决 Shaun 的问题,原来是 Shaun 的引用方式有问题,需要用 require 方式引用,大佬就是大佬 ○| ̄|_,小白还是小白,萌新完全没见过还有这种操作,也算是开眼界了 ✪ω✪。由于大佬解决完问题之后就直接把 issue 关闭了,所以 Shaun 就只有在这里表示感谢了 /つ∇T)。Shaun 最后添加 gitalk 的 ejs 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="comments" class="gitalk">
<div id="gitalk-container" class="article article-inner article-entry"></div>
<script type="text/javascript">
require(['https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js'], function (Gitalk) {
var gitalk = new Gitalk({
clientID: '<%= theme.gitalk.client_id%>',
clientSecret: '<%= theme.gitalk.client_secret%>',
id: window.location.pathname,
repo: '<%= theme.gitalk.repo%>',
owner: '<%= theme.gitalk.owner%>',
admin: '<%= theme.gitalk.admin%>',
// facebook-like distraction free mode
distractionFreeMode: true
})
gitalk.render('gitalk-container')
})
</script>
</div>

还有其它的一些修改也是仿照 gitment 的代码添加的,我这上面没有添加 css 文件是因为 gitalk 原有的 css 文件与 Shaun 的主题不相符,所以就稍微修改了一下。

※BTW:上次修改文章内a标签的高度后突然发现打赏的“”字背景圆形变成椭圆了 o(╯□╰)o,后面发现原来它也继承文章内 a 标签的属性,没有自己的 padding,后面只有给 .dashang 添加个独立的 padding: 8px;

9、Fix Bugs

修改日期:2017-12-20

修复bug:鼠标悬浮 a 标签之上会出现显示 a 标签 title 内容的气泡,当 title 内容过多时,会造成气泡位置下调,从而遮住相应 a 标签内容的 bug。

解决方案为:定位气泡文件为主题文件夹下 \layout\_partial\post\TipTitle.ejs 文件,将其中气泡出现的位置改变,原来气泡的位置确定由 top 和 left 决定,现改为 bottom 和 left,毕竟气泡是出现在 a 标签上方,如果将 top 确定,则 title 内容过多时,其只能向下扩张,造成气泡位置下移现象,从而遮住原来的 a 标签内容,改为 bottom 确定之后,气泡只会向上扩张,气泡位置相对稳定,不会遮住原来的 a 标签内容。具体修改内容为,将 top: offset.top - a.outerHeight() - 15 替换为 bottom: window.innerHeight - offset.top + 10 ,其它内容保持不变。

修改日期:2017-12-26

BUG描述:由于 spfk 主题启用的是百度分享,而原作者没有为其添加邮件分享,Shaun 为了好玩就添加个邮件分享,但是在添加过程中,Shaun 发现了个 BUG,就是为其添加的 title 属性没有作用,它会自动更改 title 内容,而 spfk 主题有显示 title 的气泡 TipTitle,所以不需要用默认的东西显示 title 内容,但是,这个百度分享还是强制默认显示,TipTitle 并不能消除其 title 内容。

解决方案:定位百度分享强制添加 title 的代码,其位于主题文件夹下\source\static\api\js\view\share_view.js 中,具体代码如下:

1
2
3
4
function(e){
var i=e.partners,s=i[n]?"\u5206\u4eab\u5230"+i[n].name:"";
!r(t).attr("title")&&s&&r(t).attr("title",s)
}

因为 TipTitle 会首先将 title 去掉,所以这里会强制添加百度分享自己的 title,所以需要将其注释掉,具体注释代码为:/*&&r(t).attr("title",s)*/。本来这样就可以了,但是 Shaun 发现添加邮件分享之后,布局又不对,所以定位主题文件夹下 \source\css\_partial\baidushare.styl 文件,发现它的居中布局居然是有宽度决定的,于是 Shaun 为其添加 text-align: center; 谁知还是没变,不能自动居中,后来在主题文件夹下找到 \source\static\api\css\share_style2_24.css 文件,发现 .bdshare-button-style2-24 a 设置了浮动样式,难怪(⊙﹏⊙),最后将其注释掉就能自动居中了。最后还需要更改移动端 \source\css\_partial\mobile.styl 中 .bdshare-button-style2-24 的样式设置为自动居中就可以了。

10、使文章目录可折叠

修改日期:2017-12-28

首先声明,本次修改完全参考:为 Hexo 添加可折叠的文章目录,所用代码也来自其文(Shaun 只是做了点微不足道的修改),在此表示感谢 Yelee 主题的作者 MOxFIVE 👍 。

具体修改如下:首先在主题文件夹下 \layout\_partial\toc.ejs 中添加 js 代码:

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
<!-- 折叠目录 -->
<script type="text/javascript">
// -------------添加小图标--------------
var $itemHasChild = $("#toc .toc-item:has(> .toc-child)");
var $titleHasChild = $itemHasChild.children(".toc-link");
$itemHasChild.prepend("<i class='fa fa-caret-down'></i><i class='fa fa-caret-right'></i>");

var $iconToFold = $(".toc-item > .fa-caret-down");
var $iconToExpand = $(".toc-item > .fa-caret-right");
$iconToExpand.addClass("hide");

// --------------点击小图标--------------
var clickIcon = function () {
$("#toc .toc-item > i").click(function () {
$(this).siblings(".toc-child").slideToggle(100);
$(this).toggleClass("hide");
$(this).siblings("i").toggleClass("hide");
})
}()

// 默认展开目录,所以隐藏掉表示“目录已展开”的图标(向下的小三角)
var $iconToFold = $(".toc-item > .fa-caret-down");
$iconToExpand.addClass("hide");

// ------------点击大标题-----------------
var clickTitle = function () {
$titleHasChild.dblclick(function () {
$(this).siblings(".toc-child").hide(100);
$(this).siblings("i").toggleClass("hide");
})
// After dblclick enent
$titleHasChild.click(function () {
var $curentTocChild = $(this).siblings(".toc-child");
if ($curentTocChild.is(":hidden")) {
$curentTocChild.show(100);
$(this).siblings("i").toggleClass("hide");
}
})
}()

// ---------点击总标题-----------------
var clickTocTitle = function () {
var $iconToExpand = $(".toc-item > .fa-caret-right");
var $iconToFold = $(".toc-item > .fa-caret-down");
var $subToc = $titleHasChild.next(".toc-child");

var $tocTitle = $("#toc .toc-title");

// 当包含多级目录时再执行
if ($titleHasChild.length) {
$tocTitle.addClass("clickable");
$tocTitle.click(function () {
if ($subToc.is(":hidden")) {
$subToc.show(150);
$iconToExpand.removeClass("hide");
$iconToFold.addClass("hide");
} else {
$subToc.hide(100);
$iconToExpand.addClass("hide");
$iconToFold.removeClass("hide");
}
})
}
}()
</script>

然后添加相应的 css 样式,在主题文件夹下 \source\css\_partial\article.styl#toc 样式里添加 css 样式:

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
ol.toc li.toc-item i {
display: inline-block;
margin-left: -0.9em;
width: 0.9em;
color: #b3b3b3;
font-weight: bold;
cursor: pointer;

&:hover {
color: #000;
}

&.hide {
display: none;
}
}
.toc-title.clickable {
cursor: pointer;

&:hover {
color: #88acdb;
}
&:active {
color: #d3d3d3;
}
}

以上两步做完之后,点击目录前的小三角符号或双击目录名就能折叠相应目录,点击“文章目录”就能折叠所有目录。


待续。。。

后记

  先就写到这里,如后续修改中发现问题再继续记录吧 ↖(^ω^)↗。

解决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密码后找回密码的方法