hexo-theme-chi主题更新小记

前言

  Chi 主题的大体结构功能算是写完了,但是还有一些个性化的东西需要添加,所以以后有关于 Chi 主题更新的部分就都写在这里吧。但是由于是 Shaun 个性化定制的一些东西,所以如果不是大 Bug 或大优化的更新,一般就不进 Chi 主题仓库 中了。

前言

  Chi 主题的大体结构功能算是写完了,但是还有一些个性化的东西需要添加,所以以后有关于 Chi 主题更新的部分就都写在这里吧。但是由于是 Shaun 个性化定制的一些东西,所以如果不是大 Bug 或大优化的更新,一般就不进 Chi 主题仓库 中了。

功能篇

1. 脚注提示功能

功能描述: 鼠标悬停在脚注上即可显示对应脚注内容。 Shaun 的脚注由于是采用 pandoc 渲染的,所以也是属于个性化定制,就不将这个功能放进 Chi 主题仓库中了。

解决方案: 还是利用 Bootstrap 的 tooltip 提示插件,具体实现代码如下:

1
2
3
4
5
6
7
$('a.footnote-ref').each(function (index, elem) {
let post_id = $(this).parents('article').attr('id');
let fn_href = $(this).attr('href');
elem.setAttribute('data-toggle', 'tooltip');
elem.setAttribute('data-html', 'true');
elem.setAttribute('title', $("#" + post_id + " " + fn_href).html());
});

遍历脚注,先获取文章 id,再获取对应文章下的对应脚注内容,使用 tooltip 提示。

Bug 篇

1. 图片没居中

问题描述: 上次那篇翻译的文章有几张图片,在放置的时候发现图片没有居中,查看代码后发现居中样式没写,但由于其图片标签 <img> 是放在一个 <figure> 标签中,由于不确定是不是 pandoc 渲染的问题,所以就没将这个修正放进 Chi 主题仓库中了。

解决办法:style.styl 文件中添加样式:

1
2
3
figure {
text-align: center;
}

即可让图片居中。

动画篇

1. 鼠标跟随动画

  其实一直都想把『奥日与黑暗森林』中的鼠标轨迹特效移植过来,但是苦于水平有限,一直没法做到,恰好 19 年 StackOverflow 的愚人节彩蛋中有个鼠标跟随动画很有意思,有好事者还专门将该彩蛋做了个脚本:Will there be an option to permanently keep this year's April Fools design active? 。查看代码,知道实现原理后,发现用 jQuery 和 CSS3 实现一个类似的效果也不算很难,于是 Shaun 就尝试做了一下,并简单的美化了一下,感觉效果还行,就加到自己的个性化主题上了。至于在 Chrome 中的小尾巴和 Firefox 中的卡顿现象,是 Shaun 故意的,因为 Shaun 觉得这个小尾巴很有意思,从这个动画看,Chrome 确实比 Firefox 要流畅一点。最终实现代码如下:

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
27
28
29
30
.cursor-trail--item {
display: inline-block;
line-height: 1px;
position: fixed;
pointer-events: none;
touch-action: none;
z-index: 9999999;
will-change: transform;
font-size: 10px;
color: rgba(186, 227, 240, 0.1);
text-shadow: 0 0 2px #6CC2F8;
-webkit-animation: cursorTrail 0.9s ease;
animation: cursorTrail 0.9s ease;
}

@keyframes cursorTrail {
0% {
opacity: 1;
}

20% {
opacity: 0.5;
transform: scale(5);
}

100% {
opacity: 0;
transform: translate3D(0, -20px, 0) scale(1) rotate(90deg);
}
}

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
$(document).on('mousemove', function (e) {
e.preventDefault();
if (this.time && (Date.now() - this.time) < 16) return;
this.time = Date.now();
let trail_character = '•';
let mouse_x = e.originalEvent.x || e.originalEvent.layerX || 0;
let mouse_y = e.originalEvent.y || e.originalEvent.layerY || 0;
mouse_x = mouse_x + 20;
mouse_y = mouse_y + 26;


$('#cursor-trail').append(
'<span class="cursor-trail--item" style="left:'
+ mouse_x
+ 'px;top:'
+ mouse_y
+ 'px;">'
+ trail_character
+ '</span>'
);

$('.cursor-trail--item').each(function () {
let item = $(this);
setTimeout(function () {
$(item).remove();
}, 900);
});
});

若不要小尾巴,则只需要将移除元素的时间改快一点就行了,除开上面的代码,还需要在 html 页面中添加一个元素 <span id="cursor-trail"></span>

2. 点击波纹

  一次偶然的机会,看到一篇这样的文章:还原一个 Windows 10 Metro 布局 。感觉其中的点击波纹动画很有意思,Shaun 决定把这个动画也放进自己的个性化主题中。至于代码就不贴了,毕竟就是上面文章提供的代码,只是用这个动画的时候发现了 Chrome 的一个问题,就是在进行模糊动画的时候会出现正方形的右边框和下边框,单个模糊不进行动画,不会有边框,进行动画但没模糊也不会有边框,两个同时一起就会出现问题,Firefox 没有这个问题,但 Firefox 存在另一个问题,就是鼠标快速连点的时候,鼠标跟随动画可能会出现问题。浏览器的问题 Shaun 暂时没法解决了,就先这样吧 ╮(╯▽╰)╭。虽然对 Chrome 为啥会出现这样的问题有些猜想,但还是不说出来丢人了,万一不是 Shaun 想的这样就尴尬了 ,,ԾㅂԾ,, 。

后记

有更新再继续更新吧。

[译]为什么深度学习没有取代传统的计算机视觉

前言

  这是一篇译文,译自:Why Deep Learning Has Not Superseded Traditional Computer Vision,原作者为:Zbigatron 。Shaun 水平有限,仅供参考学习,更多内容还请自行查看原文。

至于为什么要翻译这篇文章,算是回答别人的一个问题吧。

前言

  这是一篇译文,译自:Why Deep Learning Has Not Superseded Traditional Computer Vision,原作者为:Zbigatron 。Shaun 水平有限,仅供参考学习,更多内容还请自行查看原文。

至于为什么要翻译这篇文章,算是回答别人的一个问题吧。

为什么深度学习还没有取代传统的计算机视觉?

scale-cv-dl

  编写这篇文章的原因是在论坛中经常有人问:深度学习是否取代了传统的计算机视觉?或者类似的问题:深度学习效果这么好,还有继续研究传统计算机视觉的必要?

  这是个好问题,深度学习(DL)确实彻底改变了计算机视觉(CV)和人工智能,许多曾经看起来不可能解决的问题都解决了,甚至达到了 机器的结果比人类更好 的程度,比如图像分类。正如 我之前讨论的深度学习确实为计算机视觉做了很大的贡献

  但是深度学习只是计算机视觉的一个工具,并不是解决所有问题的万能药,所以,在这篇文章中,我想详细说明一下为什么传统的计算机视觉仍然非常有用,并且应该继续学习。

这篇文章主要有一下几个观点:

  • 深度学习需要大量数据;
  • 深度学习有时大材小用了;
  • 传统的计算机视觉可以辅助深度学习。

  但是在我开始讨论这些观点之前,需要先解释一下什么是传统的计算机视觉,什么是深度学习,以及深度学习为何这么 dio。

背景知识

  在深度学习之前,如果要实现图像分类这样的功能,需要首先进行 特征提取。特征即为图像中的信息块,能代表图像的部分信息,可以通过 边缘检测角点检测物体检测 等技术来提取特征。

  在进行特征提取和图像分类之类的工作时,一个想法是从一类对象(例如椅子,马等)的图像中提取尽可能多的特征,并将这些特征视为对象的一种定义(比如 词袋模型),然后在其他图像中查找这些定义,若另一个图像中的特征和定义的特征很相似,则该图像可能包含该特定的对象(如椅子,马等)。

  在图像分类中,这种特征提取的难点在于每个图像都必须选择哪些特征进行查找,当需要分类的类别开始增加时,比如 10 或 20 种类别,这种方式将变得很麻烦以至于不可能实现。当然也可以考虑角点、边缘和纹理信息,使用不同的特征可以更好的描述不同类别的对象,但是如果选择使用很多特征,则必须处理大量参数,所有这些参数都必须微调。

  深度学习中有一个端到端学习的概念,其含义为告诉机器要针对每个特定类别对象学习需要查找的特征,它为每个对象设计了最具描述性和显著性的特征,换言之,告诉神经网络要发现图像中每个类别的基本模式

  因此,通过端到端学习,不需要再决定使用哪种传统的计算机视觉技术来描述特征,机器自动选择好了,Wired magazine 说过:

如果想教一个神经网络识别一只猫,不需要让它寻找胡须、耳朵、皮毛和眼睛,只需要给它大量猫的图像,它就会自动识别猫,若它将狐狸错认成猫,不需要重写代码,只需要继续训练即可。

下图表示了特征提取(使用传统计算机视觉)和端到端学习之间的差异:

traditional-cv-and-dl

  以上就是需要的背景知识,下面开始深入探讨为什么传统的计算机视觉仍然有存在的必要。

深度学习需要大量数据

  首先,深度学习需要大量的数据,那些著名的图像分类模型就是在海量数据集上训练的,训练数据集中最大的三个是:

  一般的图像分类任务不需要这么多图片,但仍然需要很多,如果没办法获得这么多图片怎么办?我们还是得训练我们所有的数据(有些方法可以增多我们的训练数据,但这些都是人工方法)。但是,没有足够的数据支持,训练出来的模型可能会在训练集之外表现不好,因为机器没有洞察能力,无法对没有的数据进行分类。而且无法直观查看训练好的模型并手动调整里面的数据,因为深度学习模型里面有数百万个参数,并且这些参数在训练时会自动微调,某种程度上,深度学习模型就是一个黑盒子。

  传统的计算机视觉完全透明,可以很清楚的判断自己的解决方案在训练数据之外是否可行,而且可以深入了解算法中存在的问题。如果有没法解决的问题,也可以更容易的找出原因并调整。

深度学习有时大材小用了

  这可能是我支持传统计算机视觉技术研究的最佳理由。

  训练深度神经网络需要很长时间,而且需要专门的硬件(高性能 GPU),如果想在普通的笔记本上训练最先进的图像分类模型,可以去外面玩一个星期,回来之后应该还没训练完 :) 。而且,如果训练好的模型表现不好怎么办?必须调整训练参数并重新开始训练,这个过程有时会重复数百次。

  但有时候使用深度学习是完全没有必要的,因为有时传统的计算机视觉技术可以比深度学习更有效的解决问题并且代码更少。比如,我曾经做过一个项目来检测传送带上每个罐头是否都有红色的勺子,解决这个问题可以训练深度神经网络来检测勺子,也可以针对红色编写一个简单的阈值分割算法(红色的某个范围内的像素点为白色,其它像素点为黑色),然后计算有多少个白色像素点,后者明显简单的多,一个小时就完成了。

  了解传统的计算机视觉有时会节省大量时间和避免不必要的麻烦

传统计算机视觉提高改进深度学习技能

  了解传统的计算机视觉可以帮助我们更好地进行深度学习。

  例如,计算机视觉中使用的最常见的神经网络是卷积神经网络。但什么是卷积?它实际上是一种广泛使用的图像处理技术(例如 Sobel边缘检测)。了解这一点可以帮助我们了解神经网络正在做什么,并因此可以更好的设计和调整神经网络来解决问题。

  深度学习中还可以对图像进行预处理,所谓的预处理是指对训练的数据进行一定的处理(Shaun 注:比如图像增强,图像去噪等),这些预处理操作一般由传统的计算机视觉技术完成,比如:当没有足够的训练数据时,可以使用一种叫数据增强的技术,使用数据增强让图像进行旋转,平移,裁剪等操作,从而增加“新”图像,通过执行这些操作,可以成倍的增加训练数据集。

总结

  在这篇文章中,我解释了为什么深度学习还没有取代传统的计算机视觉技术,因此还需要研究后者。首先我发现了深度学习要想表现的足够好需要大量数据的问题,有时候没法获得大量数据,这时只能用传统计算机视觉技术代替;其次,对于特定的任务,使用深度学习可能大材小用了,传统计算机视觉有时比深度学习更有效且代码量也更少;最后了解传统的计算机视觉可以更好的学习深度学习,因为这可以使我们更好的了解深度学习的内部机制,并且可以使用某些预处理操作来改善深度学习结果。

  简而言之,深度学习只是计算机视觉的一个工具,不是万能药,不要只是因为它现在很流行所以使用它,传统的计算机视觉技术仍然十分有用,了解它可以节省时间和避免许多麻烦。

后记

  翻译这篇文章的原因在于,因为某些原因,Shaun 不得不回答一个 “为什么不用深度学习?” 的问题,虽然这里面有极大的因素是客观原因(设备不够 ╮(╯▽╰)╭),但 Shaun 不能明说,正好在网上看到这篇文章,加上也看到过一个问题(类似于 “既然已经能用深度学习做高层次的工作,为何还要用深度学习做底层的工作?”,比如深度学习能做全景分割(可以说是基本完成了图像理解),为何还要做目标检测,图像分割),预计以后有很大的概率也会被问到,所以就需要借用文中的一些观点来进行回答。至于为啥还要用深度学习做底层的工作,可以这样认为,如果用深度学习做底层工作效果不错的话,应该可以对上层工作进行一些辅助,并且可以为上层工作的做法提供一些新的思路。

hexo-theme-chi主题开发小记

前言

  2019 年 2 月 20 日,Shaun 的站点主题终于完成了,终于能用上自己的主题了,开心 ~( ̄▽ ̄~)。

前言

  2019 年 2 月 20 日,Shaun 的站点主题终于完成了,终于能用上自己的主题了,开心 ~( ̄▽ ̄~)。

  在网上看到的建议都是多写内容少整主题,但强迫症表示受不了,Shaun 就是想要一个自己的主题,自己怎么看着舒服就怎么来,即使没有一个访客,至少也要让自己看着舒服,自己想怎么设计就怎么设计,想用什么技术就用什么技术,想怎么排版就怎么排版,尽量少受写约束,总而言之,Shaun 喜欢自由。

回顾篇

  自己写主题的想法由来已久,应该是从 17 年 12 月份开始的吧,当时就感觉自己魔改的 spfk_x 主题只能勉强符合自己的需求,但是因为已经用的是别人已经完成的框架,大体框架也不好做太多修改(如果要改的话还不如自己重新写一个),而且里面由于经手太多人的修改,里面的代码结构在 Shaun 看起来有点混乱,而且有很多冗余的代码,有些代码甚至完全没有用上,导致修改起来有点麻烦。

  至于为什么不在 17 年寒假期间就开始写,而要拖到 18 年 12 月份才开始动手,一部分原因是 Shaun 的拖延症,一部分原因是还没具体想好该怎么布局,该用什么技术,还有一部分就是外在客观原因了。

  18 年 12 月份开始动手写,是因为这段时间稍显空闲,虽然白天还是一样的忙,但至少没那么多心里负担了,而且此时 Shaun 对于自己想要的大体布局已经十分清楚了(虽然一些具体细节还没不是那么清楚),所以就趁着晚上有时间写写,有时候晚上高兴或没事干就写写,有时候没想法或还没想好具体怎么实现就不写,就这样断断续续的写,总算在 19 年 2 月完成了。

  本来想在 19 年之前完成的,可是由于本人的拖延症和一些客观原因,还是超期了很久,侯世达定律总是存在的( sigh~😔),不过总的开发时间也差不多是一个月吧。感觉花费这一个月的晚上时间还是挺值得的,虽然从中学到的东西不多,但总算也有点自己的东西了。

实现篇

  既然要开始写主题了,首先当然要想一个主题名字,本来最初是直接取原来主题的首尾字母作为名字,但后面想了想感觉有点不对劲,有点骂人的意味在里面 (´ ; ω ; `),后面又想了想,就用尾字母 X 算了,反正站点图标也是那个,后面感觉还是不好,单字母太单一了,后面突然想到希腊字母,就以希腊字母的英语命名了。

  名字想好之后,就要决定用什么技术了,由于 Shaun 只是一个前端小白,无论用什么技术对 Shaun 来说难度都差不多,Shaun 理想的主题是能够支持响应式的,所以为了尽可能简单的支持响应式,CSS 方面的库就采用 Bootstrap4,CSS 预处理器当然是用 Stylus 了, JS 方面最令 Shaun 纠结,到底是采用现代的 Vue 还是传统的 jQuery,最终从相对熟悉程度和相关资料广度方面考虑,Shaun 最终选择了 jQuery3(以后有机会再尝试一下 Vue 吧),接下来要选择的是 Node.js 模板引擎,候选的主要有三个 EJS、Swig 和 Jade(现已更名Pug),Shaun 原来的主题是用 EJS 的,著名的 NexT 主题用的 Swig,而 Pug 的语法目前有点接受不能,所以直接放弃了,而 Swig 早已停止更新,所以也放弃了,所以最终选择的还是相对熟悉的 EJS(而 EJS 最近的一次提交时间为 2018 年 11 月 28 日,看来应该也是要停止更新了,以后有机会试试 Pug 吧)。

  技术决定之后,就要开始写了,最开始写的时候,由于不清楚 hexo 的工作机制,导致完全无法显示页面,后面查阅了几篇关于如何写 hexo 主题的文章,加上自己的慢慢摸索,终于弄懂了,hexo 的最初页面需要有一个 index 文件,在 index 中写入的东西,将会显示在 http://localhost:4000/ 页面中,好了,终于能显示内容了,接下来要解决的是布局问题,hexo 的所有页面渲染都会通过一个叫 layout 的文件,layout 文件中必须存在一个 <%- body %> 语句,在渲染时,hexo 会用其它页面的内容替换 layout 中的 <%- body %> 语句,包括 index 中,所以在 layout 中写好 HTML 框架之后,其它页面只需要负责写内容就行。实现一个最简单的主题(只有纯文字版网页),只需要下面几步:

  1. 在 index.ejs 文件中写入 <%- include('_partial/archive', {item: page, is_index: true}) %>
  2. 然后在 _partial 文件夹中 archive.ejs 文件中写入:
1
2
3
4
5
6
7
8
9
10
<% page.posts.each(function(item) { %>
<%- include('article', {item: item, is_index: true}) %>
<% }) %>

<% if (page.total > 1){ %>
<%- paginator({
prev_text: '&laquo; Prev',
next_text: 'Next &raquo;'
}) %>
<% } %>
  1. 在 _partial 文件夹中 article.ejs 文件中写入 <div><%- item.content %></div>
  2. 最后在 layout.ejs 文件中写入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
</head>

<body>
<div>
<%- body %>
</div>
</body>

</html>

即可将文章内容显示出来,前提是已有文章。当然也可把上面的1~3步合起来算作一步,直接在 index.ejs 文件中写入:

1
2
3
4
5
6
7
8
9
10
<% page.posts.each(function(post) { %>
<div><%- post.content %></div>
<% }) %>

<% if (page.total > 1){ %>
<%- paginator({
prev_text: '&laquo; Prev',
next_text: 'Next &raquo;'
}) %>
<% } %>

这样就算是实现了一个最简单的主题,只是这个主题十分丑陋,文章也没有对应的标题。

※注: 在写这几步的时候,Shaun 在这里发现 hexo-browsersync 插件和 hexo s 命令有冲突,会造成一个bug:hexo s 启动后,页面加载错误,大BUG,具体表现为:当直接使用 <%- post.content %> 在首页显示文章时,index.html 将会无法加载完全,后面的一部分会加载错误。解决办法为:卸载 hexo-browsersync 插件。

  完成上面几步,就算是写主题已经入门了,万事开头难,入门之后就相对简单了,只要按部就班的一步步走下去就行了,接下来就是慢慢进行排版布局问题,就这样,Shaun 一边参考其它一些主题的排版和写法,一边查阅网上资料实现自己的想法,一步一步的解决问题,直到完成该主题。

PS: 主题配置文件 _config.yml 中的配置变量的命名和其它编程语言一样,不能以数字作为第一个字符

文档篇

  Chi 主题涉及到的技术主要为:Bootstrap4、jQuery3、Stylus、EJS、HTML5、CSS3、ES6 等;运行环境为:node-v8.11.1、Hexo-3(hexo-cli-1.1.0)等;测试浏览器为Firefox 60.5.0 esr 和 Chrome 72.0.3626.109,其它浏览器没测过,也不考虑测,有兴趣的童鞋可以自己测自己改。

  Chi 主题的配色主要参考 Shaun 原来的主题 spfk_x,排版部分参考了 hexo-theme-freemind,部分代码也是来自这两个主题。Chi 主题的一些动画实现优先采用 CSS,除了那些无法用 CSS 实现或 Shaun 不知道如何用 CSS 实现的,所以 JS 代码部分不算很多,网页加载速度也勉强能够接受吧。

  Chi 主题的代码高亮部分本来想参考 为hexo博客加入prettify高亮插件 使用prettify替换默认的代码高亮,但使用了之后发现效果不是很好,就还是使用默认的样式了。

  Chi 主题的使用文档暂时就不写了,毕竟这个主题只是 Shaun 个人使用(应该也不会有其他人使用了,万一真有人要用却不太会用,欢迎提 issue,以后酌情考虑添加使用文档 🙃),由于 Shaun 只是前端小白,这个主题也只是当学习练手开发,里面肯定会有各种各样的问题,欢迎提 issue 或 fork 或 clone 后自行修改,提 issue 的话,Shaun 不保证一定能解决,毕竟水平有限,当然如果有代码优化想法还望不吝赐教 (。・ω・。)ノ♡ (至于 px 换 em 或 rem 就不用提了,没什么原因,主要是 Shaun 懒,更深层的原因在于 Shaun 手头上还没有更高分辨率的显示器,不知道显示差距有多大,不好进行测试 😓)。

PS: Shaun 使用了 Hexo 博客美化代码块| 梦魇小栈 中的脚本对 Markdown 代码部分进行了美化渲染,但是该脚本有时候渲染会出现一些问题,无法进行美化,可能是代码语法格式有错误,这时需要自己手动调整一下 Markdown 代码格式

使用 Chi 主题推荐安装插件:

  • hexo-renderer-pandoc # 使用pandoc渲染markdown,安装前需卸载默认渲染器hexo-renderer-marked
  • hexo-generator-feed # RSS订阅
  • hexo-generator-searchdb # 本地搜索
  • hexo-abbrlink # 文章唯一永久链接
  • hexo-all-minifier # 快速压缩优化代码

好了,暂时就写到这里了。

后记

  当 Shaun 有一天使用其它 blog 框架时,这个主题可能就不再维护更新了,但其中的排版样式应该还会或保留或更新。下面要做的事就不立 Flag 了,因为立了多半会完不成,这就很尴尬了,但不立又没有动力,真是让人脑壳疼啊 (๑•ั็ω•็ั๑),反正是一件长期要做的事了,长到可能接下来的业余时间可能都在做这件事,或开源或不开源,看以后的心情和完成程度了 (↖(^ ω ^)↗)。

Matlab和OpenCV混合编程小结

本文所用的 Matlab 版本为 Matlab R2017b,OpenCV 版本为 opencv-3.4.3,C++ IDE 为 Visual Studio 2017,系统环境为 Windows 10_x64。

前言

  秋招告一段落了,又要回到最初的起点,继续搞(qu)科(hua)研(shui)了,由于前人的代码主要是用 C++ 和 Matlab 混编实现的,而 Shaun 比较熟悉的是 C++ 和 OpenCV,而用 OpenCV 完全重写前人的代码工作量又太大了而且有些 API 不是很好互换,为了方便站在巨人的肩膀上继续前进,所以只能学习一下 OpenCV 和 Matlab 的混合编程了,这样在前人的基础上实现 Shaun 自己的想法相对来说更容易一些。

本文所用的 Matlab 版本为 Matlab R2017b,OpenCV 版本为 opencv-3.4.3,C++ IDE 为 Visual Studio 2017,系统环境为 Windows 10_x64。

前言

  秋招告一段落了,又要回到最初的起点,继续搞(qu)科(hua)研(shui)了,由于前人的代码主要是用 C++ 和 Matlab 混编实现的,而 Shaun 比较熟悉的是 C++ 和 OpenCV,而用 OpenCV 完全重写前人的代码工作量又太大了而且有些 API 不是很好互换,为了方便站在巨人的肩膀上继续前进,所以只能学习一下 OpenCV 和 Matlab 的混合编程了,这样在前人的基础上实现 Shaun 自己的想法相对来说更容易一些。

准备篇

  由于目前主要用 C++ 实现的是一些小功能,也不需要调试,所以就直接使用 VSCode 进行编程了(或许以后还是会用 VS 进行一些简单的调试),而在没有配置相关环境的前提下,VSCode 无法实现自动补全,所以需要在 VSCode 中配置相应环境。具体添加方法如下:在 VSCode 中点击菜单栏 “查看” ==》“命令面板...” ==》选择 “C/Cpp: Edit Configurations...”==》在出现的 c_cpp_properties.json 文件中 "includePath" 对应的值中添加 OpenCV 的 include 目录和 Matlab 的 include 目录,添加之后的 "includePath" 如下:

1
2
3
4
5
6
7
{
"includePath": [
"${workspaceFolder}/**"
, "D:/ProgramFiles/OpenCV/3.4.3/build/include/**"
, "C:/Program Files/MATLAB/R2017b/extern/include/**"
]
}

如此就能在 VSCode 中写 OpenCV 和 Matlab 相关函数时实现自动补全了。

Matlab 和 C++ 混编篇

  由于 Shaun 使用的是 OpenCV 的 C++ 接口,所以需要先知道 Matlab 和 C++ 混合编程如何进行。以实现两个数的加法为例,首先创建一个 mexAdd.cpp 文件,其中 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
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <mex.h>    // 必须包含头文件 mex.h
#include <iostream>

// 检查输入是否合法
void checkInputs(int nrhs, const mxArray *prhs[])
{
if (nrhs != 2)
{
mexErrMsgTxt("Incorrect number of inputs. Function expects 2 inputs.");
}

if (!mxIsDouble(prhs[0]))
{
mexErrMsgTxt("Input number must be double.");
}
}

double add(double x, double y)
{
return x + y;
}

/**
* nlhs:matlab 函数左边变量个数,即返回值参数个数
* plhs: matlab 函数左边变量,即返回值参数
* nrhs: matlab 右边变量个数,即函数输入参数个数
* prhs: matlab 函数右边变量,即函数输入参数
*/
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
checkInputs(nrhs, prhs);

// 输入参数可以不使用指针,但输出参数必须使用指针
double *a = nullptr; // 输出参数
double b = 0.0, c = 0.0; // 两个输入参数
plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL); // 创建1x1的实数矩阵用作输出第一个参数
a = mxGetPr(plhs[0]); // 用指针a指向第一个输出
b = *(mxGetPr(prhs[0])); // b作为第一个输入
c = *(mxGetPr(prhs[1])); // c作为第二个输入
*a = add(b, c); // 计算b、c之和得到a
}

  若要使用 Matlab 混合编译 C++,必须要添加头文件 mex.h,使用 void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 函数接收输入输出参数,如此编译完成之后,就和使用普通的 Matlab 函数一样了。具体编译调用方法如下,新建 addTest.m 文件,其中 Matlab 代码如下:

1
2
3
4
5
6
7
8
9
clc, clear, close all;  % 清空变量和关闭所有打开窗口

current_folder = pwd; % 获取当前文件路径
addpath(genpath(current_folder)); % 添加matlab临时搜索路径,并包含子文件夹(matlab退出后该路径不存在)

mex mexAdd.cpp; % 混合编译C++,得到matlab可识别的函数

a = 3.1; b = 2.6;
c = mexAdd(a, b);

其中 mex mexAdd.cpp 可以直接在 Matlab 命令行窗口下预先执行编译动作,编译成功后会输出一个 mexAdd.mexw64 文件,若是 32 位系统则后缀为 mexw32,之后直接执行 ans = mexAdd(3.1, 2.6); 即可在 Matlab 中调用该函数。在 Matlab 首次执行 mex 命令时,Matlab 会自动选择 VS 编译器作为默认 C++ 编译器,也可以执行 mex -setup 初始化或更换默认编译器。

BTW: 最好在安装 Matlab 之前安装 Visual Studio,否则使用 mex 编译时,可能会出现找不到编译器的情况。

Matlab 和 OpenCV 混编篇

  Matlab 和 OpenCV 混编大体上和 C++ 混编差不多,最大的区别在于如何利用 OpenCV 的 cv::Mat 对象和相关的库函数,Matlab 良心的提供了 OpenCV 接口以实现 mexArray 和 cv::Mat 格式之间的互相转化,使用这些接口需要包含头文件 opencvmex.hpp 。如果不使用 Matlab 提供的这些接口而是自己写转换过程的话有点麻烦,因为 Matlab 的数据是以列优先方式存储的,而 OpenCV 的数据是以行优先方式存储的。至于如何进行混编,主要有以下三种方式:

  1. 第一种是自己写 make.m 文件,相当于 gcc 编译时需要的 Makefile 文件,需要手动拼接各种编译命令和添加相应的附加依赖库;
  2. 第二种是通过 Matlab 官方提供的 Computer Vision System Toolbox OpenCV Interface 功能,Matlab 没有默认安装该功能,这个功能需要另外安装,具体安装方法为:在 Matlab 命令行窗口输入 visionSupportPackages,即可弹出“附加功能资源管理器”窗口选择对应附加功能安装即可,安装完之后可通过 mexOpenCV 命令对包含 OpenCV 库函数的 .cpp 文件进行编译,查看 mexOpenCV.m 的源码可知,mexOpenCV 其实是对 mex 命令进行了封装,其调用的 OpenCV 库也是其工具箱自带的 OpenCV,而且有些库还没有包含,有一定的局限性,不过该附加功能自带了些示例程序,可以参考学习一下;
  3. 第三种是使用第三方的 mexopencv,不过需要安装与该工具对应的 OpenCV 版本,并需要进行一定的配置工作,略显麻烦。

  Shaun 这里直接使用的是第一种方式,自己写 make.m 文件,比较灵活,想怎么配置就怎么配置。下面具体以 RGB 转 GRAY 为例,首先新建 mexRGB2GRAY.cpp,其中 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
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
#include <opencvmex.hpp>

#define _DO_NOT_EXPORT
#if defined(_DO_NOT_EXPORT)
#define DllExport
#else
#define DllExport __declspec(dllexport)
#endif

/**
* Usage: [img_matrix] = mexRGB2GRAY('img.jpg');
* Input: a image file;
* Output: a matrix of image which can be read by Matlab
**/

// 检查输入是否合法
void checkInputs(int nrhs, const mxArray *prhs[])
{
if (nrhs != 1)
{
mexErrMsgTxt("Incorrect number of inputs. Function expects 1 inputs.");
}

if (mxGetNumberOfDimensions(prhs[0]) != 3) // 获取Matlab图像总的维度个数(灰度图为2维,RGB彩色图为3维)
{
mexErrMsgTxt("Incorrect number of dimensions. First input must be a RGB image.");
}

// 检查图像数据类型
if (!mxIsUint8(prhs[0]))
{
mexErrMsgTxt("Template and image must be UINT8.");
}
}

void exit_with_help()
{
mexPrintf("Uasge: [image_matrix] = mexRGB2GRAY('image_file.jpg');\n");
}

static void fakeAnswer(mxArray *plhs[])
{
plhs[0] = mxCreateNumericMatrix(0, 0, mxDOUBLE_CLASS, mxREAL); // 创建一个0x0的空双精度matlab矩阵
}

cv::Mat RGB2GRAY(const mxArray *prhs[])
{
cv::Ptr<cv::Mat> img_cv = ocvMxArrayToMat_uint8(prhs[0], true); // 将unit8数据类型的matlab矩阵转换为OpenCV的mat对象智能指针
if (img_cv.empty())
{
return cv::Mat_<double>(0, 0);
}

// 将RGB转化为GRAY图
cv::Mat gray((*img_cv).size(), CV_8UC1);
if ((*img_cv).channels() == 3)
{
cv::cvtColor(*img_cv, gray, CV_RGB2GRAY);
}
else if((*img_cv).channels() == 4)
{
cv::cvtColor(*img_cv, gray, CV_RGBA2GRAY);
}
else
{
(*img_cv).copyTo(gray);
}

return gray;
}

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
checkInputs(nrhs, prhs);

if (nrhs == 1)
{
cv::Mat gray = RGB2GRAY(prhs);
plhs[0] = ocvMxArrayFromMat_uint8(gray); // 将unit8数据类型的OpenCV的mat对象转换为matlab矩阵
}
else
{
exit_with_help();
fakeAnswer(plhs);
return ;
}
}

然后新建 makefile.m 文件,自己配置相关编译环境, Shaun 这里具体如下:

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
function makefile()

% 选择相应计算机系统版本
is_64bit = strcmp(computer, 'MACI64') || strcmp(computer, 'GLNXA64') || strcmp(computer, 'PCWIN64');

% 配置OpenCV编译环境,如果系统是64位的,则OpenCV也需要是64位的
out_dir = '.'; % 输出目录,这里为当前目录
CPPFLAGS = ' -O -DNDEBUG -I./ -ID:/ProgramFiles/OpenCV/3.4.3/build/include'; % OpenCV “include” 目录
LDFLAGS = ' -LD:/ProgramFiles/OpenCV/3.4.3/build/x64/vc15/lib -LC:/PROGRA~1/MATLAB/R2017b/extern/lib/win64/microsoft'; % OpenCV “lib” 目录 和 MatLab 附加库目录
LIBS = ' -lopencv_world343 -lmwocvmex'; % 添加OpenCV相关库和Matlab libmwocvmex.lib库

if is_64bit
CPPFLAGS = [CPPFLAGS ' -largeArrayDims'];
end

% 需要编译的 cpp 文件
compile_files = {
'mexRGB2GRAY.cpp'
'mexAdd.cpp'
};

% 开始编译
for k = 1 : length(compile_files)
str = compile_files{k};
fprintf('compilation of: %s\n', str);
str = [str ' -outdir ' out_dir CPPFLAGS LDFLAGS LIBS];
args = regexp(str, '\s+', 'split');
mex(args{:});
end

end

其中 Matlab 配置路径中的 PROGRA~1 是指 Windows 下的 C 盘中的 Program Files 文件夹,为了使用 Matlab 提供的转换接口,libmwocvmex.lib 是必须要添加的一个库。最后具体使用示例 Matlab 代码如下:

1
2
3
4
5
6
7
8
9
10
11
clc, clear, close all;  % 清空变量和关闭所有打开窗口

current_folder = pwd; % 获取当前文件路径
addpath(genpath(current_folder)); % 添加matlab临时搜索路径,并包含子文件夹(matlab退出后该路径不存在)

makefile();

image = imread('lena.jpg');

I = mexRGB2GRAY(image);
figure, imshow(I);

也可以将 makefile(); 函数预先执行。※注: 这里如果出现编译报错 “缺少依赖共享库” 的情况可能还需要把 OpenCV 的 bin 目录加到系统环境变量 Path 中,Shaun 这里是路径 D:\ProgramFiles\OpenCV\3.4.3\build\x64\vc15\bin,然后重启 Matlab

调试篇

  若要对写的 mexAdd.cpp 文件进行调试,则需要

  1. 先使用 mex -g mexAdd.cpp 编译该文件,由于添加了 -g 参数,此时除了会生成 mexAdd.mexw64 文件之外,还会生成一个 mexAdd.mexw64.pdb 文件,该文件即包含调试信息;

  2. 然后在相应 .m 文件中调用 mexAdd 函数的位置设置断点,运行该相关文件,matlab 程序会在调用 mexAdd 函数之前停下;

  3. 此时使用 VS2017 打开 mexAdd.cpp 文件,在需要调试的地方设置好断点,选中菜单栏中的“调试” ==》“附加到进程 ”,或者直接点击菜单栏上的绿色三角 附加...,选中 MATLAB.exe ,点击附加,即可看到 VS2017 调试程序已启动;

  4. 最后回到 matlab 中继续运行相关文件,即可看到程序跳转到 VS2017,并执行到设置断点的地方,此时即可在 VS2017 中按调试 C++ 程序一样对其进行调试。

  完成调试并执行完 mexAdd.cpp 之后,程序将回到 matlab 界面继续执行,直到整个 matlab 程序执行完成。

后记

  这次主要是记录 Matlab 如何调用 C++ 编写的函数,其实还可以用 C++ 调用 Matlab 编写的函数,不过那是另一种混编方式了,以后有机会碰到的话再继续记录吧。

参考资料

[1] Matlab与C++混合编程(依赖OpenCV)

[2] 更改默认编译器

[3] OpenCV Interface Support

[4] Matlab OpenCV混合编程

[5] vc与matlab连接的实用函数简介

Android实践小结

实践环境为:android-studio-bundle-162.4069837-windows(Android Studio 2.3.3 带 Android SDK 版)、该 Android Studio 自带的 JRE,系统环境为 Win10-1607。

前言

  由于上一届没有更新任何项目文档和学习文档,Shaun 只能自己去网上查找相关的资料,从零开始学习,顺便留下一些文档,正所谓:「代码未动,文档先行」,也算是实践出真知吧。

PS: 本次实践的项目主要来自 12.1Android 实战 :DrySister看妹子应用(第一版) — 项目搭建与简单实现http://www.runoob.com/w3cnote_genre/android) 以及目前手头上正在维护的项目。

实践环境为:android-studio-bundle-162.4069837-windows(Android Studio 2.3.3 带 Android SDK 版)、该 Android Studio 自带的 JRE,系统环境为 Win10-1607。

前言

  由于上一届没有更新任何项目文档和学习文档,Shaun 只能自己去网上查找相关的资料,从零开始学习,顺便留下一些文档,正所谓:「代码未动,文档先行」,也算是实践出真知吧。

PS: 本次实践的项目主要来自 12.1Android 实战 :DrySister看妹子应用(第一版) — 项目搭建与简单实现http://www.runoob.com/w3cnote_genre/android) 以及目前手头上正在维护的项目。

布局篇

  首先打开 Android Studio ,新建项目,一路默认下去即可,等待片刻,“MainActivity.java” 文件的错误提示就会自然消失,将左侧栏上方的 “Android” 切换为 “Project”,打开 app -> src -> main -> res -> layout -> activity_main.xml,由于其默认是以 “Text” 的模式打开(Android Studio 右侧有个 “Preview” 标签可以进行当前布局预览),这对于小白来说不大好控制布局,所以需要在该文件底部将 “Text” 切换为 “Design”,如此可以进行拖拽式布局,以下正式开始进入 Android UI 的布局。至于具体如何进行布局设置,可以参考 Android ConstraintLayout 使用指南Android实现拖拽式布局开发----约束性布局 以下两篇资料。

  而若要使控件大小根据屏幕大小自适应,一般可使用相对布局,但如今的 Android Studio 默认新建的页面就是一种类似于相对布局的页面,所以直接在控件中设置 android:layout_width="match_parent" 以及 android:layout_height="match_parent" 属性即可,再设置 layout_margin 属性进行调整即可,而若要让控件大小随控件内的内容自适应,则只需要将 match_parent 更改为 wrap_content 即可。

编码篇

  首先新建自己的业务逻辑 java 代码,具体新建方法可参考 Android studio怎么创建一个Java类文件 ,在 MainActivity.java 文件所在目录上鼠标右键 New -> Java Class,新建完成之后即可编写自己的业务逻辑。若要新建文件夹,则在目录上鼠标右键 New -> Package 。若要使级联目录展开,例如 com.example.admin.myapplicationtest,则需要在该目录的父级目录新建一个 com.example.admin.test Package 即可将com.example.admin 目录展开。

页面跳转

  首先新建一个页面 jump_test_activity,在 \MyApplicationTest\app\src\main\res\layout,即 layout 文件夹上鼠标右键,“New” ==》“Activity” ==》“Empty Activity” (有多种 Activity 样式可供选择,Shaun 这里就以 Empty Activity为例了),随后弹出窗口,在 Activity Name 栏填写页面逻辑控制代码文件名 JumpTestActivity,在 Layout Name 栏填写页面 UI 设计代码文件名 jump_test_activity,在 Package name 以及页面逻辑控制代码文件所在在包名 com.example.admin.myapplicationtest.ui.activity,其它设置为默认即可,这样新建的页面的有个问题就是会有一个丑陋的标题栏,所以还需要去掉该标题栏,具体方法为:

  1. MyApplicationTest\app\src\main\res\values\styles.xml 文件中添加:

    1
    2
    3
    4
    <style name="AppTheme.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
    </style>
  2. MyApplicationTest\app\src\main\AndroidManifest.xml 中的 <activity android:name=".ui.activity.JumpTestActivity"></activity> 更改为 <activity android:name=".ui.activity.JumpTestActivity" android:theme="@style/AppTheme.NoActionBar"></activity> ,再次编译运行即可看到标题栏已消失。

好了,准备阶段已经搞完,接下来就是正式的页面跳转了,一般页面跳转是用户点击事件发生的,所以需要添加一个具有点击事件的控件,一般而言就是 Button 了,这里设该 button 的 id 为 page_jump_btn;该 button 所在页面为 MyApplicationTest\app\src\main\res\layout\activity_main.xml,则在对应的逻辑控制文件MyApplicationTest\app\src\main\java\com\example\admin\myapplicationtest\ui\activity\MainActivity.java 中的页面跳转代码为:

1
2
3
4
5
6
7
8
page_jump_btn = (Button) findViewById(R.id.page_jump_btn);
page_jump_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, JumpTestActivity.class);
MainActivity.this.startActivity(intent);
}
});

如此,在主界面中点击跳转按钮,即可跳转到新建页面。

URL 中的坑

  在学 12.2 的时候,由于请求的URL地址中有中文“福利”,所以其返回的字符串为:

1
2
3
4
{
"error": false,
"results": []
}

可以看到 “results” 的值为空,这显然是错的,后面调试发现需要对 URL 地址转义(这都 8102 年了,为什么 URL 地址中还要有中文,或者说为什么 URL 地址还不支持解析中文 ╮(╯▽╰)╭),具体转义代码如下:

1
2
3
fetch_url = Uri.encode(fetch_url);  // 将URL地址转义
fetch_url = fetch_url.replace("%3A", ":"); // 将%3A替换为:
fetch_url = fetch_url.replace("%2F", "/"); // 将%2F替换为/

主要参考资料为:Android url中文乱码问题及解决办法Android URL encode 空格处理

SQL 语句中的坑

  在使用字符串拼写 SQL 语句时,一定要注意 SQL 语句中的空格,要不然拼起来的 SQL 语句可能语法不通而导致 APP 崩溃。如在使用创建表的 SQL 语句时,可能的错误写法(不注意空格)如下:

1
2
3
4
5
6
7
8
9
10
11
12
String create_table_sql = "CREATE TABLE IF NOT EXISTS" + TableDefine.TABLE_FULI + "("
+ TableDefine.COLUMN_ID + "INTEGER PRIMARY KEY AUTOINCREMENT,"
+ TableDefine.COLUMN_FULI_ID + "TEXT,"
+ TableDefine.COLUMN_FULI_CREATEAT + "TEXT,"
+ TableDefine.COLUMN_FULI_DESC + "TEXT,"
+ TableDefine.COLUMN_FULI_PUBLISHEDAT + "TEXT,"
+ TableDefine.COLUMN_FULI_SOURCE + "TEXT,"
+ TableDefine.COLUMN_FULI_TYPE + "TEXT,"
+ TableDefine.COLUMN_FULI_URL + "TEXT,"
+ TableDefine.COLUMN_FULI_USED + "BOOLEAN,"
+ TableDefine.COLUMN_FULI_WHO + "TEXT"
+ ")";

以上写法无法正确建表,甚至会因为错误 SQL 语句而导致 APP 闪退,正确的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
String create_table_sql = "CREATE TABLE IF NOT EXISTS " + TableDefine.TABLE_FULI + " ("
+ TableDefine.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ TableDefine.COLUMN_FULI_ID + " TEXT, "
+ TableDefine.COLUMN_FULI_CREATEAT + " TEXT, "
+ TableDefine.COLUMN_FULI_DESC + " TEXT, "
+ TableDefine.COLUMN_FULI_PUBLISHEDAT + " TEXT, "
+ TableDefine.COLUMN_FULI_SOURCE + " TEXT, "
+ TableDefine.COLUMN_FULI_TYPE + " TEXT, "
+ TableDefine.COLUMN_FULI_URL + " TEXT, "
+ TableDefine.COLUMN_FULI_USED + " BOOLEAN, "
+ TableDefine.COLUMN_FULI_WHO + " TEXT"
+ ")";

调试篇

  编码完成之后,一般而言需要进行调试,Android Studio 的调试可参考 【Android 开发入门】android studio 控制台打印输出日志 ,进入调试模式具体方法为:点击上方工具栏中的 Debug 'app' 图标,而不是直接 Run 'app',进入调试模式之后,点击下方的 “Android Monitor”,切换到 “logcat” 标签,即可查看调试信息。

  华为手机打印调试信息可参考 华为手机logcat不出日志解决方案。这里由于 Shaun 还没有对 AndroidManifest.xml 进行修改,所以该 APP 在点击 Button 的时候会直接闪退,调试窗口出现 java.lang.SecurityException: Permission denied (missing INTERNET permission?) 错误信息,参考 android菜瓜笔记之missing INTERNET permissionSecurityException: Permission denied (missing INTERNET permission?) 可知,该 APP 没有网络权限,所以需要在 AndroidManifest.xml 中添加网络权限,具体在 manifest 标签中添加语句 <uses-permission android:name="android.permission.INTERNET" />,如此该 APP 就能正常执行了。

PS: 如果是对应用发生闪退或崩溃的原因进行调试,建议直接在 logcat 中搜索 fatal 关键字

附录

更换马甲重新发布为另一个 app

  即一样的代码却编译出另一个相同的 app,在某些特殊的需求(使两个功能大致一样的 app 共存在一台手机上)上可能需要这个技巧,具体步骤如下(以 MyApplicationTest 更名为 MyApplication 为例):

  1. 首先将 MyApplicationTest 文件夹(即 APP 根目录)更名为 MyApplication;(这一步不是刚需)
  2. MyApplicationTest\app\src\main\res\values\strings.xml 文件中 <string name="app_name">MyApplicationTest</string> 更改为 <string name="app_name">MyApplication</string>
  3. MyApplicationTest\app\build.gradle 文件中 applicationId "com.example.admin.myapplicationtest" 更改为 applicationId "com.example.admin.myapplication" 。(这一步是刚需)

如此,即可另外安装一个全新 APP,同时保留原有 APP,至于 applicationId 的更多作用和用法可参考 设置应用 ID 。更换 APP 图标只需在 app 文件上鼠标右键, “New” ==》“Image Asset” ,即可弹出设置 app 图标窗口,或者直接更改 AndroidMainfest.xml 文件中的 android:icon 也可。

后记

  排版可能有点乱,毕竟是随便写的,碰到问题就简单的记录一下,Shaun 这次的实践过程可在 AndroidLearning 中查看,页面跳转的方法来自手头上正在开发维护的一个项目,附录中第一个问题的来源是对方提出的一个特殊需求。

C++数组中的坑

前言

  在写快排的时候偶然发现了 C++ 数组中的一个坑,具体表现为:对数组元素进行无临时变量的自交换时竟然会将数组该元素置为 0,这应该是 C++ 的一个 BUG Shaun 脑子抽了。

前言

  在写快排的时候偶然发现了 C++ 数组中的一个坑,具体表现为:对数组元素进行无临时变量的自交换时竟然会将数组该元素置为 0,这应该是 C++ 的一个 BUG Shaun 脑子抽了。

交换函数篇

  据参考资料 [1] 中, C++ 的交换函数可以有如下三种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第一种:使用模板,创建临时变量
template <class T> void swap(T &a, T &b)
{
T temp(a); a = b; b = temp;
}

// 第二种:无临时变量,针对int,double等内建数值类型的基本运算(以double为例)
void swap(double &a, double &b)
{
a = a + b;
b = a - b;
a = a - b;
}

// 第三种:无临时变量,针对int的异或运算
void swap(int &a, int &b)
{
// a ^= b ^= a ^= b;
a = a ^ b;
b = b ^ a;
a = a ^ b;
}

  其中,第一种是通用的交换方法,无论做什么交换都能用第一种,但需要创建一个临时对象;而第二种不需要创建一个临时对象,只能用在 int,double 等内建数值类型上,且存在溢出的风险;第三种同样不需要创建临时对象,只能用在 int 类型上,由于采用位运算,所以不存在溢出的风险,且效率最高。

BUG 复现篇

  bug 复现代码如下:

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
#include <iostream>

template <class T> void swap_1(T &a, T &b)
{
T temp(a); a = b; b = temp;
}

void swap_2(int &a, int &b)
{
a = a + b;
b = a - b;
a = a - b;
}

void swap_3(int &a, int &b)
{
a ^= b ^= a ^= b;
}

int main(int argc, char *argv[])
{
int data[] = { 0, 3, 8, 2, 9, 4, 6, 1, 7, 5 };
int a = 1, b = 2, c = 3;
swap_1<int>(data[a], data[b]);
printf("%d\t%d\n", data[a], data[b]); // 输出 8 3
swap_1<int>(data[a], data[a]);
printf("%d\t%d\n", data[a], data[a]); // 输出 8 8
swap_2(data[a], data[b]);
printf("%d\t%d\n", data[a], data[b]); // 输出 3 8

swap_2(data[a], data[a]);
printf("%d\t%d\n", data[a], data[a]); // ***输出 0 0***

swap_3(data[b], data[c]);
printf("%d\t%d\n", data[b], data[c]); // 输出 2 8

swap_3(data[b], data[b]);
printf("%d\t%d\n", data[b], data[b]); // ***输出 0 0***

std::swap(data[4], data[5]);
printf("%d\t%d\n", data[4], data[5]); // 输出 4 9
std::swap(data[4], data[4]);
printf("%d\t%d\n", data[4], data[4]); // 输出 4 4

return 0;
}

  目前没有找到什么好的解决方案,只能老老实实的用第一种创建一个临时变量的交换方法,或者在交换之前先判断一下是不是同一个元素,若不为同一个元素,才进行交换,否则不交换,即避免自交换

后记

  刚开始出现这个问题的时候,Shaun 还纳闷了,怎么写个快排把数组元素都改变了,刚开始根本没想到这茬,还以为是 Shaun 代码的问题,倒着检查了好几遍,换第一种交换方式以及换个冒泡排序用一样的交换方式进行排序输出结果都没问题,后面使出终极调试法,一个个的输出看看才知道原来是自交换的锅。

  如果有大佬知道为什么会出现这个问题还望不吝赐教 ◔ ‸◔?。经 @ Magnesium12 大佬提醒,这个问题由于引用引起的,采用第二种方法进行交换时如果两个数是完全一样的(地址也一样),则在进行两数相减时,由于是引用,所以这两个数完全一样,则最后会导致这两个完全一样的数,即自交换的该数置为 0 。

参考资料

[1] 谈谈C++中的swap函数

Android开发环境配置

由于一些历史原因,本篇配置的 Android 开发环境所需的软件对应版本号为:jdk-7u80-windows-x64、android-studio-bundle-162.4069837-windows(Android Studio 2.3.3 带 Android SDK 版),系统环境为 Win10-1607。

前言

  没想到,时隔两年之后又要重新捡起 Java,还要学基本没怎么做过的 Android,而且还是在这节骨眼上,真是造化弄人 _(´ཀ`」 ∠)_。

由于一些历史原因,本篇配置的 Android 开发环境所需的软件对应版本号为:jdk-7u80-windows-x64、android-studio-bundle-162.4069837-windows(Android Studio 2.3.3 带 Android SDK 版),系统环境为 Win10-1607。

前言

  没想到,时隔两年之后又要重新捡起 Java,还要学基本没怎么做过的 Android,而且还是在这节骨眼上,真是造化弄人 _(´ཀ`」 ∠)_。

Java 环境篇

  先在 Oracle Java Archive 下载对应的 Java 版本,下载完成后去 这里 校验对应的 Hash 值(若是其它 JDK 版本,则只需将 url 末尾的 7u80 改成相应的版本号即可),并安装,安装时需要注意,在安装完 JDK 之后,该安装器还会继续弹出让安装 JRE 的窗口,此时直接点取消即可,因为 JDK 中已包含 JRE ,所以没必要也不需要再继续安装,安装了之后,就相当于有两个 JRE ,还可能会为以后的工作造成一些麻烦。具体系统环境变量配置如下(若没有相应的变量名则新建):

变量名变量值
JAVA_HOMEC:\Program Files\Java\jdk1.7.0_80 (PS:此为JDK安装目录,后面不能加分隔符分号)
CLASSPATH.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; (PS:最前面的 .; 必须要有)
Path%JAVA_HOME%\bin;%JAVA_HOME%\jre;

配置完之后,键入 Win+R ==》cmd ==》 Enter,在终端输入 “java -version”,“java”,“javac” ,这几个命令,若有正确的响应,则表示配置成功。

Android 篇

※注: 在安装和配置 Android Studio 时最好先自行找好梯子,这其中有一些步骤可能需要连接外网

  自然还是先在这里http://www.android-studio.org/)下载安装 Android Studio ,为避免再下载安装配置 SDK 的麻烦,推荐直接下载带 SDK 版本的 Android Studio 。直接默认安装,其中安装 SDK 的时间略长。安装完成之后,推荐把 SDK 目录下的 tools 和 platform-tools 子目录也添加到系统的 PATH 环境变量中

  在第一次打开 Android Studio 的时候,可能需要连接外网以更新 SDK ,所以需要自行设置好代理,更新又要花一段时间 ╮(╯▽╰)╭,当然也可以选择不更新,不更新的办法为:

在AS启动前,打开安装目录,请先将bin目录的idea.properties文件中增加一行:disable.android.first.run=true就行了,避免第一次打开AS时自动重新下载SDK。

  第一次运行时,首先需要配置 SDK 路径和 JDK 路径,配置 SDK 路径方法为:“Configure” —> “SDK Manager”,编辑 “Android SDK location” ,其会自动找到安装的 SDK 路径;配置 JDK 路径方法为:“Configure” —> “Project Defaults” —> “Project Structure”,编辑 “JDK location”(这里它有个默认内置的 jre,但推荐还是使用自己的 JDK)。

  Android Studio 默认的编辑器方案无法更改字体(若真想在默认的方案上更改字体,可以先将其另存为一个新方案),而且个人认为其默认的主题(配色,字体等)不好看,所以推荐自行去 Color Themes 选择合适的主题。最终 Shaun 选择 Wombat 主题。至于导入主题的方法为:“Configure” —> “Import Settings”,将下载好的 jar 包导入即可。

  为了测试方便,就直接装了个 网易MuMu模拟器 ,用起来感觉还可以,至于 Android Studio 连接 MuMu 模拟器的方法为:先打开 MuMu 模拟器,在 Android Studio 底下的 Terminal(终端) 中输入命令:adb connect 127.0.0.1:7555 ,响应 connected to 127.0.0.1:7555 则说明连接成功,这时就能愉快的使用 MuMu 模拟器调试 Android app 了。

后记

  以后有碰到什么坑再继续记录吧 ╮(╯▽╰)╭。

参考资料

[1] Android Studio安装配置、环境搭建详细步骤及基本使用

[2] 第一次使用Android Studio时你应该知道的一切配置http://www.cnblogs.com/smyhvae/category/587732.html

[3] Android Studio连接不到MuMu模拟器

Windows实用技巧

前言

  本篇主要用来记录在 Windows 下使用命令行能做到的一些特殊技巧。

前言

  本篇主要用来记录在 Windows 下使用命令行能做到的一些特殊技巧。

Windows 粉碎文件技巧

  有时候 Windows 下会莫名出现文件无法删除的现象,即使通过 Shift+Delete 组合键也还是无法删除,这时可能需要通过一种 “文件粉碎机” 的工具才能删除,但是为了偶尔出现的这种现象而安装一个这样的工具又略显麻烦,这时只需要新建一个 Windows 批处理文件,即新建一个 txt 文本文件,并将后缀改为 .bat 即可,文件内容输入:

1
2
DEL /F /A /Q \\?\%1
RD /S /Q \\?\%1

其中

DEL 表示删除文件,命令参数为: del /?

/F:表示强制刪除

/A:选择文件的属性

/Q:静默模式,在删除时不会弹出提示信息

RD 表示刪除目录,命令参数为: rd /?

/S:连带删除子目录下的文件

/Q:静默模式,在删除时不会弹出提示信息

  具体用法为将待删除的文件拖拽到该 .bat 文件图标上,用该 .bat 文件打开待删除的文件即可。

  但有时候即使使用上面的方法,也无法删除文件,同时还会弹出“无法删除文件,文件或目录损坏且无法读取”这样的提示,这可能是文件存储时发生错误造成的,此时需要在 Windows 命令提示符界面中输入命令 CHKDSK 盘符:/F,盘符为需要删除的文件所在的磁盘或 U 盘,比如在 D 盘,则输入命令 CHKDSK D:/F,如出现无法锁定的提示信息,则输入 Y 强制卸载该卷,等待一段时间,等磁盘扫描和修复完成后即可删除待删除的文件。

Windows 校验文件技巧

  为确保从网络上下载的文件为原文件,一般需要对其进行 hash 校验(如 SHA1、SHA256、SHA384、SHA512、MACTripleDES、MD5、RIPEMD160 值等),一般随原文件一起释放的比较常见的 hash 值有 SHA1、SHA256 或 MD5 等,但是为了校验下载文件的 hash 值可能需要专门的第三方工具,但其实 Windows 、Linux 和 macOS 都自带了校验 hash 值的命令,通过这些命令可以直接对文件的 hash 值进行校验,而不需要使用额外的第三方工具,这里只介绍 Windows 的哈希值校验命令,其命令为 Get-FileHash,具体使用方法如下:

1
2
# []内为待校验的文件 和 所用的hash算法
Get-FileHash [文件路径及名称] -Algorithm [校验的Hash值类型]| Format-List

  如果觉得每次这样输入比较麻烦,可以使用参考资料[2]提供的注册表文件(.reg 后缀),双击运行,即可在鼠标右键菜单添加「文件哈希校验」,如此可以通过鼠标右键直接对文件进行 hash 校验。附其中代码如下:

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
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\文件哈希校验]

"SubCommands"="MACTripleDES;MD5;RIPEMD160;SHA1;SHA256;SHA384;SHA512"

"MUIVerb"="文件哈希校验"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\MACTripleDES]

@="MACTripleDES"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\MACTripleDES\command]

@="PowerShell Get-FileHash -Algorithm MACTripleDES \\\"%1\\\" | format-list;“按任意键退出...”;[Console]::Readkey() | Out-Null;exit"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\MD5]

@="MD5"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\MD5\command]

@="PowerShell Get-FileHash -Algorithm MD5 \\\"%1\\\" | format-list;“按任意键退出...”;[Console]::Readkey() | Out-Null;exit"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\RIPEMD160]

@="RIPEMD160"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\RIPEMD160\command]

@="PowerShell Get-FileHash -Algorithm RIPEMD160 \\\"%1\\\" | format-list;“按任意键退出...”;[Console]::Readkey() | Out-Null;exit"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA1]

@="SHA1"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA1\command]

@="PowerShell Get-FileHash -Algorithm SHA1 \\\"%1\\\" | format-list;“按任意键退出...”;[Console]::Readkey() | Out-Null;exit"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA256]

@="SHA256"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA256\command]

@="PowerShell Get-FileHash -Algorithm SHA256 \\\"%1\\\" | format-list;“按任意键退出...”;[Console]::Readkey() | Out-Null;exit"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA384]

@="SHA384"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA384\command]

@="PowerShell Get-FileHash -Algorithm SHA384 \\\"%1\\\" | format-list;“按任意键退出...”;[Console]::Readkey() | Out-Null;exit"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA512]

@="SHA512"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\SHA512\command]

@="PowerShell Get-FileHash -Algorithm SHA512 \\\"%1\\\" | format-list;“按任意键退出...”;[Console]::Readkey() | Out-Null;exit"

Windows 隐写文件技巧

  在 Windows 下可以通过 copy 命令实现简单的文件合并,即隐写,例如:将压缩文件隐写入 jpg 文件中,将 txt 文件隐写入 jpg 文件中。这样在没改后缀名的情况下,该文件是以图片形式存在,若要恢复原有隐写文件信息,则只需要将 jpg 后缀名更改为相应文件后缀名,例如:若隐写的是 rar 文件,则只需将后缀名 .jpg 改为 .rar ,再用解压缩软件打开即可,也可以直接用解压缩软件打开相应 jpg 文件;若隐写的是 txt 文件,则需要用记事本打开该文件,通过 ctrl+end 组合键让光标定位到文件最末尾,即可看到隐写的 txt 文件内容。

具体命令如下:

1
2
3
4
5
6
7
8
9
10
11
# 将 b.txt 隐写进 a.jpg 中,并输出为 c.jpg
copy/b a.jpg+b.txt c.jpg

# 将当前目录下 2.rar 隐写进 1.jpg 中,并输出为 3.jpg
copy /b ./1.jpg + ./2.rar ./3.jpg

# 将 b.txt 隐写进 a.jpg 中,并输出为 c.jpg
copy a.jpg /b + b.txt /a c.jpg

# 将 b.rar 隐写进 a.jpg 中,并输出为 c.jpg
copy a.jpg /b + b.rar c.jpg

※注:图片文件要放在前面,需要隐写的信息放在后面,不然输出的图片无法正常查看和显示

  其中参数 /b 表示以二进制格式复制、合并文件,在合成图片和压缩文件等二进制文件时必须使用该参数,不然会丢失信息,从而造成合成失败,当然合成 txt 文本文件时也可以使用该参数;参数 /a 表示以 ASCII 码格式复制、合并文件,参数 /a 只适用于 txt 文本文件合并。

  至于其它需要注意的就是:1、要合成的两个文件最好放在同一目录下,不然输入路径有点麻烦; 2、txt 文本文件前面最好空三行,这样它头部的内容就不会丢失

  输出的图片 c.jpg 和原图片 a.jpg 显示是一样的,看起来就是同一张图片,因此达到隐写的目的。

Win10 开启休眠方法

  不知道巨硬是怎么肥事,居然 Win10 中默认电源选项中没有「休眠」选项,需要手动开启。虽然睡眠和休眠有重叠的地方,但是休眠是完全关机,再次开机时会恢复原有工作状态,更多的情况是必须要关机(因为需要考虑电量情况),相反如果有充足电力的情况下,还不如使用锁定代替睡眠,从这里的需求看还不如在默认电源选项中去掉「睡眠」选项。好了,抱怨吐槽的话就说这么多了,具体开启「休眠」的方法为:只需要在命令行(需要以管理员身份打开「命令提示符」)中输入一下命令:

1
powercfg /H on

回车 执行即可在电源选项中发现「休眠」选项。

Windows 新建用户命令

  前一段时间电脑系统升级之后崩了,资源管理器损坏,没有任务栏,没有桌面,Win 键都无法使用,还好可以使用任务管理器,通过任务管理器调出 cmd,输入以下命令新建一个用户:

1
2
3
4
5
# 添加用户tmp、密码123  
net user tmp 123 /add

# 设置tmp为管理员
net localgroup administrators tmp /add

  通过 tmp 用户可以正常使用电脑,因此猜测应该是原来的用户系统配置文件(例如注册表文件,系统设置文件等等)损坏。系统损坏之后捣鼓了两天,这没办法修复了,即使新建用户也还是有些软件没法使用,还不如直接重装系统(系统出现问题果然重装系统才是最快的解决方案),还好可以新建用户以备份原有系统盘文件,不然就是真 gg 了。既然新建命令记录了,也顺便记录一下删除命令吧,删除用户 tmp 命令为:net user tmp /del

Windows 解除 U盘或移动移动硬盘被占用技巧

  Windows 在弹出 U盘或移动硬盘时,有时会出现 “该设置正在使用中,请关闭可能使用该设备的所有程序或窗口,然后重试。” 的提示,导致 弹出 USB 大容量存储设备时出现问题,无法弹出。解决该问题需要知道是那个或哪些程序占用该存储设备,然后结束该进程即可。查看存储设备占用程序 Shaun 知道的有两种方法:

  1. 通过 “Windows 日志”。Windows 每次弹窗提示都会有相应的日志,右键计算机 ==》管理 ==》事件查看器 ==》Windows日志 ==》系统,然后在窗口左侧查找其中有黄色警告的日志信息,找到来源为 Kernel-PnP 的日志,双击该日志即可看到 “进程 ID 为 XXX 的应用程序 XXX 已停止删除或弹出设备 XXX。” 之类的信息,从而知道是哪个进程占用该存储设备,停止该进程即可正常弹出。停止进程一般通过 任务管理器 ==》详细信息,找到对应的 名称 和 PID,右键 ==》结束任务即可。
  2. 通过 “资源监视器”。右键计算机 ==》管理 ==》性能,打开资源监视器。然后在 “CPU” 栏中找到 “关联的句柄”,在后面的搜索栏中输入存储设备的盘符(D: 、E: 或 F: 等 ),即可显示占用程序的 名称 和 PID,随后在任务管理器中停止该进程即可。

  ※注: 有时只有 System 进程阻止存储设备弹出,这时可以直接关机了 :),因为 System 进程是系统核心进程,不能结束,结束之后系统会崩溃,并自动关机。当然,如果是其他进程阻止,可以用上述方法正常弹出,一般阻止存储设备弹出的都是 explorer.exe,在停止该进程后,Windows 的任务栏将会消失,所以需要在任务管理器中重新运行该任务(任务管理器 ==》文件 ==》运行新任务, 输入 “explorer” 即可)。

后记

  如果以后碰到更多有意思的小技巧再继续和大家分享吧 ↖(^ω^)↗。

参考资料

[1] windows文件夹删不掉怎么办

[2] 巧在Win10右键菜单添加校验文件Hash值命令(MD5、SHA1/256等)

[3] 【命令行copy命令】将txt文档与jpg图片合并

[4] 升級Win10後,為何筆電專有的休眠選項消失了

[5] CMD命令(添加删除管理员账户)