0%

Pytorch安装教程(包含对齐cuda、cudnn版本,VSCode中Anaconda的设置)

安装一个编程环境/编程库的过程是无比烦人的。安装环境时不可避免地涉及一些和该环境有关的知识,而配环境的人多半是新手,面对未知的知识时总会碰到许多麻烦。比如第一次配和C++有关的库的时候,新手就不知道.dll应该加入环境变量,.h应该加入include。而在写教程的时候,也一般都是新手在写教程,老手们对配环境这一“简单”的事情不屑一顾。因此,我们一般只能在网上看到新手写的配环境教程,上面都是按部就班地介绍一步一步的操作,讲不出每一步操作为什么要这样做,那些操作可以更改或者跳过,碰到了问题又怎么样。教程的不详细同样是配编程环境令人感到烦人的原因。

基于上述情况,配编程环境考察的是人利用搜索引擎的能力。在配环境时,应该多参考几篇文章,有英文阅读能力的应该去参考官方给的配置教程。出了问题把问题的出错信息放到搜索引擎上去查。一般多踩几次坑,多花点时间,环境总能配好。

也正是基于这些原因,我觉得写一篇环境配置的文章很没有意思。没有技术性和原创性、繁琐而无趣。但恰逢我第三次配置pytorch环境,一切都轻车熟路了。我准备在等待文件下载安装之余截几张图,水一篇文章。

准备

去搜索引擎搜索pytorch安装 你的操作系统。比如我这次是在win10上安装,就选择输入”pytorch 安装 win10”.当然,一般会用到Anaconda来安装python上的一些库。在搜索中加入”anaconda”关键字可以搜到更多的教程。

当然,官方网站上也已经给出了不少信息了:https://pytorch.org/get-started/locally/

我的博客是随手写的,看这篇文章可能解决不了所有问题。正如前言中所说,应该多参考几篇教程。

安装Anaconda

Anaconda可以让用户更好地管理python包,配置pytorch需要先安装Anaconda。我说不出它的优点,反正大家都在用,我也一直在用。

https://www.anaconda.com/products/individual#Downloads 这里可以下载Anaconda

如果使用的是Windows,安装以后,点击任务栏中的搜索框,搜索Anaconda,打开Anaconda Powershell Prompt (Anaconda)或者Anaconda Prompt (Anaconda)。Ubuntu貌似进入命令行进入了Anaconda。

在conda命令行中输入conda create --name env_name python=3.7

env_name是虚拟环境的名字。装Python库,就好像在一个新房子里放家具一样,时间久了东西越来越乱。Anaconda给了你无限创造新房子的机会。你可以在一个完全没有“家具”,甚至连Python都没有的虚拟环境里,重新配置库文件。env_name就是虚拟环境的名字,我一般命名为pt,这样输入起来很快。后面的python=3.7是可选的,python版本可选,这句话加不加也可选。没加的话晚点安装python也行。

创建完环境之后,输入conda activate pt就可以进入新的虚拟环境。前面的(base)应该会变为(pt)

搞成上图那样就算配得差不多了。

CPU版本的Pytorch

Pytorch等深度学习框架分CPU版和GPU版。GPU版速度快,但装起来有点复杂。如果只想稍微学习一下编程框架,以后用GPU服务器跑正式代码,可以只装CPU版。

这个时候,为了避免GPU版教程的干扰,应该在搜索引擎中搜索Pytorch CPU 你的操作系统

一般输入conda install pytorch-cpu torchvision-cpu -c pytorch就可以安装好了。但这个时候,一般有一个坑会碰到:下载速度奇慢无比。

这个时候,应该去网上搜索conda 下载慢类似的关键字。网上一般会给出以下几个办法:换源、直接下载安装包、用pip下载。源就是下载的来源网站,在国内的话最好切换到国内的下载网站比较快。我选择换conda源的方法下载。明确了方法后,要继续换搜索的关键字,应搜索conda 换源。我搜索到的办法是:输入conda config --set show_channel_urls yes。之后在用户根目录(比如C:\Users\Yifan Zhou)下编辑.condarc,加入以下内容:

1
2
3
4
5
channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
ssl_verify: true

我发现换了源还是下载得很慢,于是直接挂了个VPN再下载(哈哈哈)

比较好的方式开始用换了源的pip

下好之后稍微验证以下。比如安装之后,继续在Anaconda的虚拟环境中输入以下内容

1
2
3
python
import torch
print(torch.__verion__)

GPU版本的Pytorch

确认显卡驱动

在安装GPU版本Pytorch之前,还要保证电脑中有版本正确的cuda和cudnn。cuda是一套GPU上的编程库,cudnn是基于cuda的深度学习库。Pytorch依赖这两个库,而cudnn版本依赖cuda版本。而由于cuda貌似更新比Pytorch快,出于向下兼容的考虑,应该先在电脑支持的CUDA版本和Pytorch支持的CUDA版本中选择一个较小者,再下载该版本的CUDA和CUDNN。

但是,在安装这些东西之前,还可能碰到一个坑——显卡没装驱动。这里默认电脑使用的是N卡。在控制台中输入nvidia-smi,如果出现了类似下面这种信息:

就说明驱动已经安装好了。如果没有,请搜索显卡驱动,适当加入”Nvidia Cuda Cudnn pytorch tensorflow python”等关键词,多看几篇教程总能把驱动装好。

输出信息中的CUDA Version就是电脑显卡最高支持的CUDA版本。到网上下CUDA也应该不超过这个版本。

CUDA

记录下nvidia-smi命令输出的CUDA版本(比如我的是11.1),再在 https://pytorch.org/get-started/previous-versions/ 查Pytorch是否支持该CUDA版本。如果不支持,就选一个网站上有,且版本小于命令输出的CUDA版本的。之后去 https://developer.nvidia.com/cuda-toolkit-archive 找到合适的CUDA版本,选择版本后选择自己对应的操作系统。下载了可执行文件后直接运行安装。

CUDNN

打开 https://developer.nvidia.com/rdp/cudnn-archive 这个网站。注册账号并登录。根据CUDA版本,找到合适版本在合适操作系统下的CUDNN。(注意是下载cuDNN Library,比如[cuDNN Library for Windows10 (x86)]

正如前面所描述的,CUDNN是个库,不需要安装,只需要把其文件复制到CUDA文件夹下即可使用。要把CUDNN中的bin,include,lib都复制到CUDA的对应目录下。

注意,CUDA的bin文件夹应该被添加进环境变量。用默认设置安装CUDA时一般该文件夹会自动被加入环境变量。

Pytorch

https://pytorch.org/get-started/locally/ 中,选择合适的操作系统和CUDA版本。参考CPU版本的Pytorch这一小节的内容,用conda一键下好pytorch。如果网速慢,请利用搜索引擎解决问题。

一切都下载完后,在环境中输入以下内容,看到True就是胜利。

1
2
3
4
python
import torch
import torchvision
torch.cuda.is_available()

风雨夜行记 ~ Adventure in a Rainy Night

初到上海,老天爷就隆重地请来嘉宾”烟花“迎接我的到来。一场突如其来的台风席卷了城市,大雨倾盆而下,把人们关在了家中,连多家知名餐馆都停止了外卖服务。傍晚,我在生活用品尚不齐全的新房间里,摸着空空如也的肚子暗自叹息。怎么办?是吃点零食,全身脏兮兮地回床上避难?还是一往无前,出门购买食物和所需的生活用品?向来喜欢追求成果上限的我自然是选择后者。

出门之前,我先在脑子里进行了任务计划。我的主要目标是吃饭,次要目标是购买生活物品。既然出了门,就要吃得好一点,并且去大超市买东西。我的住处附近恰好有一个大商场,附近必然有不错的餐馆。我在地图上看好了路线,背上用于运货的书包,带上心爱的遮阳挡雨两用自动伞,就头也不回地出门了。

刚一出门,我就意识到了今晚行动的困难性。屋外风雨交加,骤雨把漆黑的天空和昏暗的街道融为一体,雨点落地声甚至盖过了地铁启动时的轰鸣。今天晚上,我的行动恐怕和在RPG里做主线任务一样充满挑战吧。

果不其然,我很快就碰到了第一道关卡。在我的住处和大马路之间,有一条必经的小道。小道没什么人维护,平日里看起来幽雅宁静,下雨时还因积水形成了一条小溪,真多亏了它糟糕的排水系统。我把第一个场景命名为”积水巷道“。在积水巷道上,电动车驾驶员都得小心翼翼地行驶着,生怕把水溅到身体上,我一个撑着伞的行人又该怎么通过呢?我倒是不慌,想着:凡是游戏里的关卡,必然设置了通关的方法。很幸运,我发现路对面有一条较窄的人行道,上面没有积水,可以安稳地通过这条小道。想来这条路的建设者并不是没有注意到路面会有积水的现象,还非常贴心地建了一条下雨专用人行道。看得出这个建设者以前应该是个程序员,碰到解决不了的BUG,就给用户另一个更复杂的操作步骤来绕过BUG。凭借着细心的观察和对程序员的理解,我顺利利用人行道离开了积水巷道关卡。

离开了小道,来到大马路上,我立刻见识到了台风的威力。本来几阵夹杂细雨的微风就让我撑伞撑得十分吃力了,忽然的一阵狂风让雨伞飞舞起来,整个伞面都翻转了过去。不仅是我,街上其他撑伞的行人都受到了不小的影响。只见几个行人万般无奈地收起了伞,在雨点的敲打下叹息着。我碰到了新的挑战:“狂风事件”。狂风事件会在行动时随机发生,令行人失去撑伞能力,被迫淋雨,降低行动能力与士气。如果不是有不得不去做的事情,一般人遇到狂风事件都会退缩,立马跑回家去了。然而,我却十分冷静地面对着突然刮起的大风,使用我独创的“相对论遮风挡雨法”消除了狂风事件对我的影响。

什么是“相对论遮风挡雨法”呢?这里就要介绍一下背景信息了。之前出去旅游的时候,一向不喜欢撑伞的我,被太阳晒得没办法,买了现在这把遮阳挡雨两用自动伞。我发现遮阳的时候如果和遮雨一样正着撑伞,有的时候遮阳效果并不好,因为太阳的光往往是斜着照过来的。然而,不管你人朝着哪个方向走路,都把伞正对着太阳,就能起到非常好的遮阳效果。因此,我独创了一种“相对论遮阳法”,根据太阳的方位,在走路的时候固定手和伞的朝向,而不管自己朝着哪个方向走。今天碰到了台风后,我瞬间领悟了“相对论遮风挡雨法”,以应对狂风事件。这种撑伞法相比之下更加复杂,需要分两个步骤进行。在第一步中,需要用手无力地把伞举起,通过伞的运动方向感受风的方向;在第二步中,用类似相对论遮阳法的手法,把撑伞朝向固定为风吹来的方向。凭借这种方法,无论风有多大,只要握紧伞把,伞就不会翻转,还能把雨滴全部挡下来。使用相对论遮风挡雨法后,我自由自在地在风雨中走着。

由于我肚子实在太饿,我决定改变计划,立刻吃饭。马路对面有几家店还开着,我满怀期待地过了马路,希望能找到一家合适的餐馆。结果这边还在开门的只有几家装修平庸、看上去就不靠谱的餐馆,剩下还在开门的店就只有日式便利店、药店,还有一家写着“内有被褥”的杂货便利店。这些店都不能解决我的饮食问题。我有些低落地向前走了几步,突然眼前一亮,看到一家装修精美的餐厅。原来是一家看起来质量还不错的咖啡馆。但是这种店又过于正式了一些,不适合一个人随便解决一餐。一个人吃顿饭还是有些难啊,这样的店就适合几个人一起去,尤其是情侣。情侣?哼!这种店就是为了提供这样的氛围才装修得这么好的,太不为单身的人考虑了。一想到这,我就气饱了,也不管肚子饿不饿了,义愤填膺地从店门口晃了过去。

又走了一段时间,我发现店面越来越少,路也越来越窄,最后竟被一棵吹倒的大树拦住了去路。这感觉不对啊,是不是走错了路?我破天荒地打开手机看了下地图,反复确认了我走的路是正确的。这棵树是怎么回事?我很快给了自己一个合理的答案:RPG游戏里经常会出现第一次能走通的路在游戏后期被拦住的情况,玩家需要进入一个新的场景,通过更难的关卡,来绕过障碍物。想到这里,我就摩拳擦掌,对即将面临的新事件期待起来:我是不是要自己找一条路出来?结果,我又看了一眼地图,发现地图导航提示我在此处应下台阶。我这才发现,这条路本来就是死路,我应该走地下通道绕过这里。我刚刚提起的兴致一下就被浇灭了。

终于,商场的大楼近在眼前了,我只需要过一条比较宽的马路就行了。可是,我又碰到了新挑战:马路上有不浅的积水,穿凉鞋倒是可以不管不顾地走过去,可我穿了运动鞋出来。看来我不得不面对这“无伤通过积水公路”的挑战了。细心的我发现斑马线是有一定厚度的,踩着斑马线走几乎不会沾到水。绿灯一亮,我就使出”轻功水上漂”绝技,大踏步地踩着斑马线走。最后一条斑马线离人行道还有一段距离,这之间还有不少积水。为了保证自己是“无伤通关”,我一扭头,令积水恰好能反射路灯的灯光。这样,我就掌握了所有的危险地带和安全地带的位置。踏过几个较干的路面,我成功抵达了马路对面。

地图上显示商场的关门是十点,而现在是八点多,时间还算充裕。我看商场对面开着的店铺很多,决定先找个地方吃饭。有好几家店看起来都不错,该去哪一家呢?正当我准备开始分析时,突然发现一个外卖员从一家面馆走了出来。能做外卖,这个点还有人点餐,说明这家店应该还不错。特别是我发现这家店里还有一个正在用餐的人,这让我对这家店的质量更加放心了。进店之后,我直接点了最贵的红烧牛肉面。面端上来用了七、八分钟,这对饥肠辘辘的我来说就像45分钟的语文课一样漫长。加几滴醋,用筷子微微搅拌,捞出一大把筋道的面条,配上大块牛肉,我享受这绝妙的用餐体验。一想到吃一辈子红烧牛肉方便面也吃不到这么多牛肉,我就吃得更香了。我吃得过于投入,以至于身上的雨水渐渐变成了汗水。吃完面条,我没有休息,立刻走出店门,吹了下风才发现身上出汗了。

马上,我的旅途就要抵达终点了。在明亮的商场里挑选商品,而不必四处躲雨,这就是我即将获得的通关奖励。买完东西之后,我还要打出租车回家,结束这冒险之旅。一边想着这些幸福的事情,我很快来走到了商场门口。商场里面倒确实是十分明亮,可大门却是锁着的。十点没到,商场怎么关门了呢?看来今天台风的影响确实很大,出于人道主义的考虑,商场的工作人员都早早下班避雨了。这么大的雨,也没人来买东西,商场也赚不到钱啊。想到商场关门的种种原因,我顿时觉得自己十分草率,竟然出门之前不去从商场的角度考虑,忽视了商场有提早关门的可能。但也不亏,我的主要任务——吃饭已经完成,买生活用品的支线任务就放一边吧。站在商场前的路灯下,我偶然抬头一望,针状的雨在光线下清晰可见,猛烈地扎在我伞上。看见无色的雨本身算是一件有趣的事,现在的我却只能感叹今天雨真大。

忽然,我发现自己似乎错过了什么东西。购买生活用品这一任务还不能宣告失败!我又再次充满了动力,沿回家的路走去。

回程时,我走在马路的另一侧。非常烦人,我又碰到了新的挑战:马路这侧的人行道中间的路砖因修地铁而全部挖掉了,人行道外的马路又全是积水,马路上时不时有汽车经过,走马路中间也不太好。我唯一的选择,就是走人行道外侧残留的石砖。石砖非常狭窄,仅能恰好放下我的鞋子。石砖的一边是积水的马路,一边是坑坑洼洼的泥土——还好不是掉下去就死的万丈深渊,起码给我留了一些容错率。于是,我开始挑战“平衡木”关卡了。虽然我天生缺乏运动细胞,但我使用了极佳的前进策略:我一只手使用相对论遮风挡雨法撑伞,为了平衡,另一手略微向外伸出。我出乎意料地顺利通过了平衡木挑战,虽然中间有几次失去平衡踩到了泥土上。

路过“倾倒之树“景点,我再次感叹起了今天行程的不易。仔细想来,今天好像遇到了不少有趣的挑战。于是,我一边走路,一边构思起了这篇文章。

走到了”积水巷道“的入口,我没有进去,而是又过了一次马路,走到了那一排仅有少数亮光的店面前。原来,在发现大商场关门后,我的潜意识一直没有停止思考,一直寻找着购买生活物品的方法。那时,我忽然想起这里有一家便利店写着”内有被褥“。为什么一家便利店要在牌子写这些东西?是真的提示顾客里面卖被子吗?不是这样的,真相只有一个!其实”内有被褥“的意思是,这家店不仅是一个卖饮料零食的普通便利店,还是一个卖生活用品的杂货店!!虽然我没有进店确认,但我利用心理学思考推理出了这一结论。有了这一发现,我才满怀希望地踏上了回程的路,觉得宣告任务失败还为时尚早。

那么结果如何呢?我走进杂货便利店一看,果然,在被子旁边,还有洗衣液、碗筷、垃圾桶这些生活用品,甚至还有电饭锅。我的推理完全正确!我最近一直在看柯南,感觉学到的推理能力一直没有用武之地。但非常巧,今天恰好有一个舞台,让我把推理能力淋漓尽致地展现了出来。不仅是看柯南学到的推理能力,我在之前参与的很多项目、经历中已经掌握了极强的逻辑思考和求解问题的推理能力,并且在玩LOL时锻炼了从对手的角度反向思考的”千层饼“心理分析能力。今天我把这些能力都用了出来,实在值得吹嘘一番。

考虑到台风可能一时半会儿不会走,我不仅把日常要用的清洁用品买了,还买了个电饭锅准备回去自己做东西吃,买了一大堆东西。最后,我一只手撑伞和拎锅,另一只手抱着刚买的装得满满当当的垃圾桶,吃力地走出了杂货店。虽然拎这么多东西很麻烦,但我看着这些”战利品“,感到十分充实与开心。

雨势渐小,积水巷道里的水却依然没有消退。和出来的时候不同,我熟练地走上了人行道,回忆起了今天的奇遇。今天我的行动称不上旅行,用”冒险“来形容恐怕更恰当一些。正如我以前写道的,旅行的意义在于旅行的体验。其实今天吃饭、买东西的任务都在其次,最令我难忘的是一路的见闻、经历。虽说老天不是很给面子,让台风来迎接我的到来,但也多亏了这风雨交加的夜晚,给了我这独一无二的体验。这换成其他任何人,或者其他任何时候,都很难有这样的情况出现吧。再往大了想,人生的快乐也不是那么奢侈的事情。都说”偷得浮生半日闲“,再平淡的生活,亦或是再艰苦的生活,也是能通过一些方法找到乐趣的吧。

满载着战利品,我回到了家中。今天采取了这么多应对下雨的策略,应该取得了不错的效果。结果我一坐下,屁股一凉,才发现我的短袖裤子和短袖T恤都湿了,鞋子也湿了个透心凉。不能说我的撑伞法、过街法不好,只能说雨下得太大,要挑战的难度太大。今天我的主要任务和次要任务都完成了,虽然没有做到无伤,但也是不小的成就了。今天过得非常好,希望以后我在自我挑战中能够再接再厉。

初夏,一片绿叶悄然飘落

一片绿叶

六月,北京的气温开始升高。风和日丽的春天突然离去,闷热的空气充斥了整栋宿舍楼。傍晚,热气依然没有离开宿舍的意思,我只好先它一步,跑到宿舍外散步乘凉。

散步时无事可做,我只好欣赏起校园里那些不起眼的花草树木起来。经历了漫漫长冬后,在第一阵春风的引导下,植物开始竞相生长。大树又一次长满了树叶,和草坪一起,构成了一幅放眼望去只有绿色的单调画面。好在有各色的花朵在路旁点缀,给枯燥的以理工科为主的大学勉强增添上了一分色彩。

经历了一个春天的滋补,初夏的植物都茂盛地生长着。我偶然低头看去,突然发现树下竟然有几片落叶。与秋天落木萧萧的美景不同,此时地上的落叶只有零星的几片,树上也看不到有任何树叶要落下的迹象。想必这几片落叶只是不成器的失败者,看它们枯黄的颜色就知道,和正在生长的绿叶相比,它们并没有得到足够的养分,最后体力不支被风吹到地上。

忽然,一阵风吹过,又一片树叶从树上落下。我的内心一阵欣喜,因为自己的猜想算是得到了证明:哪怕在夏天,依然会有些发育不良的树叶被吹落。但我定睛一看,却又是一阵震惊:树叶从大树深处的阴影中落下,却在灯光下显得绿得发亮;明明是从高处跌落,最终必将驻留在地上,却在风中迟迟不肯落地。

这片落叶是与众不同的吧。和地上那些已经一动不动,几近腐烂的落叶相比,它绿得那么鲜艳,在空中飞舞得那么轻盈。明明马上就会落地,失去活动的机会,却依然奋力地展示着自己的生命力。

和树上那些长得正旺的树叶相比,它也是不同的。它本来应该可以继续在树上生长,用最好的姿态去迎接盛夏的骄阳的吧。那它是不小心从树上跌落,还是自己想离开大树的呢?它是见惯了花开花落,厌恶了大多数植物同样的命运?还是只是想随风飘摇,看一看树外不同的景色?它最好的归宿是哪里呢?

还未等那片绿叶落地,又刮起了一阵更强的风。眨眼间,绿叶就从我的视野里消失了。我想,大自然已经给了我最好的答案:那片绿叶心向远方。我的视野之外,就是它最好的去处。

迷茫与挣扎

说来丢人,马上就要毕业了,我还没有一个明确的去向。

过去的一年半里,我都是在同样一种不安的心情下度过的。我没有踏上迈向未来的“正轨”,我担心自己的未来会陷入迷茫、找不到方向的境地。事实上,到了即将毕业的今天,我的一切担忧变成了现实。

在这种担忧之下,我很难完全集中于眼前的事情,无论它是否会对我的未来产生功利的影响。我在各种事情上犯了很多错误,或者说有很多做得不好的地方。这些错误或失误长久地驻留在我的脑中,自责不断地刺痛着我,让我在做类似事情回忆起这些错误时都倍加折磨。在正反馈调节下,自责引发进一步的错误,事情向越来越糟糕的方向发展。

我很早就意识到了我的所有问题的原因,并尝试去解决。我通过一系列思考分析清了自己的现状,一直保持着十分自信的心态。但是,我只是发现了问题并分析了问题,却没有解决问题,并付出行动。

我一直在迷茫中挣扎,明白了自责不是解决错误的方法。只有从错误中学习,并提取经验为未来服务,才是利用错误的最好方法。在找寻未来方向的这段经历中,我学到了什么呢?经过一段时间的发现和思考,我得到了这样的结论:人生本身就是混沌的,需要自己去寻找方向。我能找到大致的方向,却缺乏主动为方向铺路并沿着道路前进的能力。

温室里的流水线

两年半前,我参加了某个留学中介的讲座。主讲人提到了一个案例:有一个计算机专业的学生,大学前三年都对专业知识一窍不通,但是在大三暑假依然找到了一个和艺术、信息技术相关的暑研,最后大四申请到了该方向上的硕士。主讲人想通过这个例子说明,留学中介可以极大地帮助想留学的同学,哪怕专业能力再差,也能申请到学校。主讲人把这个故事当成了宣传中介的绝佳示例,却让我彻底断绝了向留学中介寻求哪怕一丁点帮助的想法:一个这样的人都能成功留学,那这种形式的留学的意义在哪,又有何含金量可言呢?这样是“没有意义”的。我宁可自己申请不到学校,也不想通过留学中介“成功”申请到某所学校。

我一直坚持着自己的观念,由于种种原因我也确实没有申请到学校。一开始,我还没有相同自己潜意识里想表达的“没有意义”是什么意思。现在,我总算想通了自己申请留学的意义所在:出国留学,是对自己未来的一次主动选择。这其中有许多信息、方向上的困难,而不是思考、智力上的困难。这个过程考验人对于方向的掌握,并为某个方向努力的能力。留学中介帮你完成了许多中间步骤,让你失去了这次锻炼自己掌控自己未来的机会。将来,或许是留学毕业后,肯定还会再次碰到未来方向的选择问题。侥幸绕开了一次考验,只会把问题全部积攒起来滞留到下一次考验上。因此,当时的我就在潜意识中认识到了:选择未来,掌控未来是一种重要的能力,比能否在毕业那年成功留学重要得多。我要利用这次机会主动选择未来的方向并朝着方向迈进。

我在一所还不错的大学里上学。仔细回想,我和周围的人一样,从小到大不过是在温室里的流水线上,在他人的呵护下推动着移动罢了。绝大多数出生普通的孩子都被灌输“万般皆下品,唯有读书高”的信念,巴不得鲤鱼跃龙门,一朝金榜题名改变命运。我和周围这些幸运的同学,在家长和老师的照料下,十二年考场厮杀搏斗,千军万马中闯到名牌大学。我们人生的轨迹是早早地就决定好了的,人生的价值甚至是可以被量化的:用那光彩照人,却又污秽不堪的考试成绩来表示。考试越来越难,周围的人越来越强,流水线上的人越来越少,我们嬉笑地看着跌落出流水线的人,暗自骄傲。

到了大学,父母的呵护少了些,世界那狰狞的本性逐渐向我们展露出来。我们慢慢离开了温室,却依然没有离开流水线,而是逐渐被机器划分到了不同的支流上。有的支流叫做“保研”,有的叫“出国”,还有叫“工作”、“考研”和“创业”什么的。有人会跟你说,每条支流上的风景都很好,有哪些优秀的零件获得了许多奖项,保持着顶尖的成绩,收获了“精彩”的人生。大家趋之若鹜,纷纷在流水线上赛跑着,继续争夺着那几条看起来更体面的支流。

但是,人生绝对不是只有铺好的道路,或是说那些自动运行的流水线的。人生的大部分地方是混沌、黑暗、未经探索而充满危险的。在混沌中前行,会迷茫、犹豫;会陷入深坑,难以挣脱;会因害怕和痛苦而回头逃避;会因找不到同行的人而寂寞惆怅;会对着无人的夜空哀嚎。但是,正是在无人探索的深处,有光明,有宝藏,有能让你忘却路上所有的苦难,并且骄傲地向全世界炫耀的事物。危险而困难,却充满了令人向往的事物,这才是人生的全貌。流水线,只是人生的一隅,一个被前人故意建好的设施而已。

每个人在人生中的某个阶段,总会走下这流水线,被迫去用自己的双脚探索这个世界。亦或是流水线的终点已到,仅需在附近寻找光照,就可以停滞不前却又安稳地生活着;亦或是从某处落下,无法立刻安定,在担惊受怕地瞎逛一阵后,建立起一个勉强供自己生活的根据地;亦或是凭自己的意志离开,不断前行,根据自己脑中的印象,去寻找世界上最危险、最美丽的宝藏。

正是意识到了这些,我对流水线积怨已久。我想凭自己的双脚走路,却又对远处的黑暗感到恐惧,只好暂且跟随有光照的流水线行走。我畅想有一天能逐渐远离流水线,去往更有趣的地方。但十分不巧,一座高山出现了。我的面前是一座垂直的悬崖,高不见顶,给人高山仰止之感。我屡次伸手想借突出的石块攀岩上去,却又屡屡跌落。摔伤的次数越多,我越恐惧,离下次尝试的时间就越久。我看到附近有不少人登上了山:有人乘着流水线,缓缓上了山;有人沿着铺好的、略有陡峭的台阶,很快跑上了山;哪怕是荒凉的周围,也有人攀登上了较矮的山峰,踏上了登山之旅。我开始责问自己为什么没有长得更高一点,手脚为什么没有更有力气一点,上次攀登的时候为什么没有再小心一点。最终,我开始驻足不前。

在温室的流水线里呆久了,我不过是一个大号的婴儿而已。凭借叛逆的本能,逃离了流水线,却发现自己根本没有掌握足够的运动能力。不肯低下头,看着路前行;倒下后,不肯治疗伤口,躺着逃避下一次挑战;不肯向周围张望,挪动双脚,换一条上山的道路。周围的一切都看得清清楚楚,却就是因为种种原因毫无行动。

现在,我不仅看清了周围,还看清了自己。既然向往远处的宝藏,那么再高的山、再陡的崖也无法阻止的我前行。体能不行,就去锻炼身体;一个石块被我抓断了,就去抓旁边那块;跌倒了,就尽快投入下一次尝试。凭借着正确的战略,征服眼前的山只是时间问题。

后记

在外面转了一圈后,我不仅身上凉快了,脑子也清醒了,回宿舍后立刻把所见所想记录了下来。

我又想起那片树叶了。它的颜色那么鲜艳,不是春风赏赐给它的,而是它从树的枝干里汲取营养,自己长出来的。既然它心向远方,哪怕暂时落到地上无法移动,风儿或许也会因欣赏它的美丽而响应它的呼唤,载着它继续飞翔。

人在前进时需要动力,谨以此文自勉。

真·后记

写完这篇文章后,我觉得内容有些羞耻,就立刻把文档关掉,再也没打开过。一个多月后重读这篇文章,还是觉得内容有点不太适合发表出来。不过仔细分析了一下后,我觉得这篇文章里并没有过多地反映我个人的信息,而是很好地刻画了一个人的心理活动,并且表达出了一些思想,对于他人来说还是有一些阅读价值的。照相记录画面,录音记录声音,那么文字则是能把思想、情感保存下来,供愿意欣赏的人品味。我最终还是准备把这篇文章发表出来。

如今我已经有了毕业的去向。我不会嘲笑自己之前的焦虑,毕竟这些都是宝贵的人生体验;我也不会因有了去处就得意忘形,因为这本来就是我应得的。这件事其实不是本文的重点。最重要的是文章中提及的对人生的态度,即如何决定自己未来的发展。不论何时,我都会坚持这样的态度。

“悬梁刺股”不如“乐此不疲”

正文

大学四年中,我有了许多新的感悟。其中,我认为最重要的一项收获是:与其“悬梁刺股”般督促自己艰苦地奋斗,不如在任务中寻找令自己快乐的成分,从而乐此不疲地达成目标。我在大学中印象最深刻的两段经历印证了这一道理。

一、因为热爱,所以成功

第一段经历是我在大一暑假参加的算法竞赛集训。当时,我有幸受到了学长学姐的邀请,提前加入了集训队。我既要和其他大一同学一样,学习新的竞赛知识,又要和其他高年级集训队的同学一起参加模拟赛。作为大一的学生,我要每两天上一次课,每次完成数十道习题;作为集训队的一员,我每周要参加四场训练赛,每场比赛持续五小时,赛后还需要回顾比赛中没写出来的题目。在旁人眼中,我每天都要完成大量的题目,日子过得十分辛苦。然而,我自己知道,我从小就不是一个刻苦的人。我之所以能每天坚持写完那么多题目,是因为我乐在其中。当我思路深陷泥潭,绞尽脑汁也想不出解题之道时,屏幕上一行行的“答案错误”就仿佛是对我的付出所发出的一声声嘲笑;而当我柳暗花明,总算把题目解出来时,屏幕上那一行“答案正确”就是对我最好的盛赞。这句对我的赞美,胜过了之前所受的所有嘲笑,让我收获到了极大的成就感。我享受学习新算法时的恍然大悟,享受想题时的冥思苦想,享受解题失败后的心烦意乱,享受解题时的灵光一闪,享受题目最终通过时的开怀大笑。我热爱算法竞赛的一切。

正是因为我对算法竞赛的这份热爱,让我不知不觉中完成了大量的习题,竞赛水平突飞猛进。在暑假集训前,由于我在高中没有竞赛基础,我的水平在同年级中算不上拔尖。而集训结束后,我的水平得到了质的飞跃,在大大小小的比赛中都取得了不错的成绩。暑假结束后,在程序实践课的期末考试中,所有同学要求组队在两个小时里完成五道题目。我单人参赛,50分钟不到就以全年级最快速度写完了所有题目。之后,我首次参加正式的算法比赛,我们团队获得了银牌。一个月后,我参加国际大学生程序设计竞赛亚洲区域赛。凭借着我通过的一道中等难度的题目,我们队伍拿下了北理2018年第一块算法竞赛金牌。

我在算法竞赛中获得的金牌,为我带来了许多利益上的收获。但是,在参与算法竞赛中最令我难忘的经历,不是登上金牌领奖台的那一刻,而是我大一暑假集训时,那乐在其中的每一天。我的热爱为我带来了成功,但我不是为了获得成就而去热爱,而只是单纯觉得参加竞赛很有趣而已。在参加算法竞赛的过程中,我不仅每天都过得十分开心,更是学会了一个道理:热爱自己的事业,能够取得成功。

二、抛开压力,寻找目标

第二段经历是我在大三暑假开始参与的研究项目。我和另一位同学一起,在本校明振军老师和与本校有合作关系的Farrokh,Janet教授夫妇的指导下进行科研。两位外国教授不仅学术成果斐然,更是善于教学,他们的许多学生已经成为了教授。现在,这两位教授年事已高,却依旧思路清晰、逻辑缜密,还心胸宽阔,毫无保留地指导着各个国家的学生。他们不仅在科研上指导我们,还常常分享生活和学习的心得。有一次,我们向他们请教,在和同龄人的竞争中感到压力太大怎么办。Farrokh教授回答道:“时常保持快乐。成功的人不一定快乐,但快乐的人总能成功。我喜欢我的事业,我做着我喜欢的事情,别人的成就又与我何干呢?我每天勤恳地工作,是因为我热爱我做的事情啊!”

教授的一番话点醒了我,让我开始重新审视自己的行为。在遇到这两位教授之前,我就已经参与了一些科研项目。但是,我感觉自己付出了很多时间与心血,却一直没有什么收获。回过头来一想,我之前参与科研,只不过是为了发论文,提升自己的背景,获取利益而已。我根本没想清楚为什么要去参与科研,只会关注有没有产出,所以参与科研时感到十分辛苦,却又毫无收获。我根本不喜欢我正在做的事情。

我参与的暑研课题离我的专业比较远,导致我对这个课题并不是很感兴趣。但是,在这次的科研项目中,我开始转换思维,试图去发掘这个科研项目中令我感兴趣、能让我有动力去奋斗的目标。结果,还是两位教授的教诲帮助了我。他们告诉我,科研不一定是要做出什么惊天动地的成就。发掘一些新的知识,能给其他研究者带来启发,这就够了。因此,我确立了我在这次科研中要达到的目标:做为本科生,想做出太大的创新十分困难。不如提出一些简单的创新点,并尽可能给别人的研究提供一些灵感。确立了能让我获得成就感的目标后,我在毫无压力的心态下投入到了这个科研项目中,最终完成了一篇论文,该论文目前正在投稿中。在这段经历中,我并没有像算法竞赛一样那么热爱我正在从事的工作,但我依旧去挖掘任务中能让我感到兴奋、愿意为之奋斗的因素。最终的结果证明,在压力下为了功利匍匐而行,不如看向远方,慷慨高歌着踏步而行。

三、不再“努力”,快乐人生

我的算法竞赛经历,让我明白从事自己所热爱的工作,能够既享受过程,又收获成功。我的暑研经历,让我明白哪怕是面对自己不是很喜欢的事情,艰苦地奋斗依旧比不过为了任务中某个热爱的成分而奋斗。但是,我的收获和大部分人的认知有所出入。从小到大,我们都被灌输了要努力学习的概念,也听过种种因努力而成果的例子。诚然,是有很多人凭借着过人的意志,在艰苦的奋斗后取得了成功,比如“头悬梁,锥刺股”的故事。但是,又有多少人倒在了正在努力的路上呢?我们不得而知。从另一个角度来讲,什么是成功呢?在人生的一站中凭借辛苦的努力取得成功,是否又能在下一站成功呢?什么时候能停下来,欣赏人生的大好风景呢?努力,本来是表示尽力去完成一件事,引申为一种积极的工作态度。然而,在诸多艰苦奋斗事迹的渲染下,努力成了抑制自己的欲望、让自己在一种十分不情愿的态度下,依然拼尽全力去做事的代名词。这种对于“努力”的阐释,完全是自相矛盾的:自己都感到辛苦,说明自己的内心根本不认同自己正在做的事情,那哪里来的动力去把事情做好,取得更高的成就呢?因此,在目前从事的事情中感到辛苦的人,要么是因为无法失败而勉强自己

,把任务以最低的标准达成,要么是为了诸如功名的其他目标而强迫自己奋斗,在达到短期目标后就会立刻抛弃当前这项令自己辛苦的工作。正是因为这种对努力的错误阐释,导致有些人盲目努力,倒在了半路上;有些人通过“努力”获得了一时的成功,却在下一件事情中顺从自己远离压力的本能,放弃了努力;有些人持续地“努力”,一直小有成就,却发现自己的努力是没有尽头的;有些人深谙事故,“努力”一时得到了自己想要的事情后,开始鼓吹“努力”,通过他人的努力获利。不管每个人经历如何、结果如何,这些所谓压制自己的欲望进行艰苦奋斗的人,在“努力”时是不幸福的。

“努力”的反面,是快乐地从事一件事。保持快乐的心态能否取得世俗上的成功,这难以下一个定论。但是,保持这种心态的人,有着其他层面上的优势。这种优势体现在整个人生的意义上。人生的意义是什么呢?简单来讲,就是要让自己过得开心。与“努力”相比,热爱自己所从事的事情,能让人持久地获得动力,同时获得享受。更进一步地,不管当前从事的工作是否热爱,都应该从中寻找一些值得奋斗的目标。人生中,除了成绩、背景、金钱、地位、粉丝、异性缘这些世俗、公认的目标,还有着更多精彩的事物。视频游戏胜利时的一幅画面、沙滩上的一块贝壳、茂密草丛中的一束红花、父母的一声叮咛、久别重逢时的一阵寒暄、对视时躲闪的一缕目光、半夜的一段深思、聚光灯下的一场演讲、求知者抛来的一句提问、万千人幸福的笑脸。这些事物,远比世俗的目标重要得多,更能让人士气高昂地前行。“乐此不疲”的人,不需要在人生的路上休息,因为他们一直是快乐的,更是有意义、无悔的;甚至因为他们对于事情有着更多的投入,反而更容易收获其他人眼中难以企及的成功。

“悬梁刺股”不如“乐此不疲”,这是我从大学中印象最深刻的两段经历中得到的收获。未来,我依然会保持这份信念,在自己热爱的领域中取得令自己满意的成就。“悬梁刺股”不如“乐此不疲”

吐槽与评价

其实,现在我有很多时间了,应该静下心来反思一下,并发一些博文了。我有好多东西想总结,并且表达、分享出来的。可惜自己太懒了。

这篇文章是为了学校最终的德育答辩而写的。正常来说,我随便写一下糊弄过去就完事了。但是,这篇文章可能可以发表到学校的”前辈经验分享“书刊上,供新生阅读。我十分想向别人分享一些想法,尤其是对于比我年轻、可能会重新走我的一些弯路的人。考虑到我的文章可能会帮助别人,甚至改变他人的人生,我决定认真写一篇文章。

写文章之前,我的潜意识里就已经制定了一些写文章的注意事项(我的大脑的思考能力实在太强,以至于我不需要刻意地思考,就已经制定了完成事情的策略)。一些策略是为了能让文章被顺利录用,一些策略是为了更好地帮助后辈们。

  • 立意不能过于反常理。显然,抨击学校的不合理制度、告诉大家在大学不应该做些什么的是不允许发表的。但是,如果写一些常见甚至错误的立意,就对他人没什么帮助,我写起来也没劲。因此,我选择了我演讲时最常用的,有点反常理却能让人接受的一个立意:努力学习不如快乐学习。
  • 让审稿的人知道你很厉害。我必须吹一下我取得的成就,告诉审稿人我很强,这篇文章很有参考性。这是我擅长的领域,我在文章中也花五成功力吹嘘了自己一番。
  • 告诉后辈们一些重要的信息。讲大道理谁都会讲,但我特意在文章中写了一些十分具体的东西。聪明的新生会利用文章中提到的信息来提升自己。

同时,文章的字数要求在3000字左右。为了不让自己受到字数限制的影响,我特意没有用word和其他软件,用了看不见字数的记事本来写这篇文章。可谓是殚精竭虑,面面俱到。

制定完了策略后,我就开始写文章了。一开始,我写得非常不顺利,一方面是因为这是篇正式文章,虽然没有题材要求,但还是不能写起来太奔放的;另一方面,文章要花一些笔墨来写事,我只擅长写理和对事情客观而准确的描述,单纯写事对我来说有点不熟悉。结果文章写完,导入word一看,只有2200多字。没办法,我要说的话就这么多,该怎么办呢?

没办法,我开始顺从本心,在文章的总结阶段加入自己擅长的分析说理的文字,瞬间提升了文章的深度,也让文章从各种意义上看起来不够自然了。写完之后,导入word一看,3000字,刚好。我把想说的话说完了,发到博客上了。最终能不能被学校录用我已经不在意了,我已经把我在这几件事上想说的东西都说出来了,我的目的已经达到了。这篇文章中,我已经对我的这种态度进行了一定的说明。我讨厌无意义的结果,讨厌为了不纯粹的目的花费时间。我的目标是分享有用的经验,我认为这篇文章对后辈有着非常大的帮助。如果因为文章的某些观点不太能够被理解,导致学校不发出来,那我也不愿意去为了迎合审稿人而修改内容。

讲完了我写文章的心路历程,现在来评价一下这篇文章。评价之前吐槽一句,我发现自己的文学素养实在太差了。我有着灵活的思维,天马行空的创意。这不仅仅体现在其他方面,还体现在文学创作上。可惜,我没有足够的文学积累,无法做到用巧妙的方式来表达我的意思。但我清楚地知道,只要我有一定的文学积累,我一定写出具有文学性的文章,即用词优美华丽,却不失内涵,有深意的文字。有时间我会去尝试养成阅读文学书籍的习惯。

文章中前几段描写的是我的两段经历。其中第一段经历确实是我印象最深刻的经历,我也时常与别人分享,两次演讲中都分享了这个经历。第二段经历有点生搬硬凑的感觉,与实际情况有些出入,但为了写文章举例子不得不歪曲了一下真实的情况。这几段没什么出彩的地方,不过没有这些经历的人看完这些经历,应该能体会到一些东西。

后面三段总结段本来是很短的,最后我在放开手脚后往里面加入了很多内容。倒数第三段批判努力的内容我本来想单独用一篇文章来写,从本质上分析原因。可惜我实在太懒了。这篇文章浅谈了努力的部分内容,有一点点深度。当然,写这一段时我非常爽,因为我讽刺了非常多的东西。如果稍微对生活有一些观察,就能看懂这一段具体在讲生活中的哪些现象。

倒数第二段虽然比前一段短,却包含了更多的深意。这里的内容其实已经和本文没关了,应该在另一个主题里提及。但为了让自己写得爽一些,我直接就开始把想写的东西全写了。这一段的内容不知道读起来是不是很奇怪,但内容绝对不是乱写的,而是有逻辑,有一些文字背后的深意的。

总结来看,这篇文章是一篇要提交上去的文章,虽然我认为质量已经足够,但缺点还是很多。文笔不够好,这是自然的。文章后面有点违和,这是由于我为了凑字数,转换了写作思路导致的。评价起任何一种艺术作品来何其容易,但真做起来还是很难的。比如这篇文章,我知道有哪些地方不够好,但就是不好改,甚至我没有能力去改。不管怎么说,我认为这篇文章还算是合格,可以提交上去,也可以发到博客上分享一下。

“Poisson Image Editing”论文方法实现

上上篇博文中(现在是上上上了),我介绍了一下这篇图像融合的经典论文。今天,我将记录一下这篇论文的C++代码实现。我已经三个多月没碰C++了,手很生。但是,我会凭借着我高超的编程底力和过人的天赋,三小时内完成代码实现。稍微对工作量有一点了解的人都会产生疑问:“你在开玩笑吧?论文实现不比写算法题,你要先去看懂论文,再调一调别人的库函数,写代码之前有很多准备工作要做。你这不可能完成。”肯定有人这样想。但是,我可以说,包括学习前置知识在内,我可以三小时完成实现。这是一个七年编程王者应该有的自信。这篇博文的正文和会按之前的格式对项目的实现做一个比较全面的记录,最后一个章节会写下我完成此项目的实况。

代码仓库:https://github.com/SingleZombie/Gradient-Domain-Image-Processing-Cpp

由于种种原因,在记录了编程的实况后,我就把这篇博文搁置下来了。但马上要写毕业论文了,这篇博文和论文的内容有很多重合之处,我打算先把博文写完。

知识准备

在代码实现前,我们先整理一下实现方法的整体思路,再提取出实现中涉及的知识点,对每个知识点有关的实现方法和具体的实现技术进行介绍,最后对方法的核心——结果图像求解进行一个详细的介绍。

方法思想

整个方法的总流程图如上。和普通的图像复制类似,方法需要输入一幅源图像、一幅目标图像及复制区域(步骤1),输出一幅融合好的图像(步骤4)。为了得到最终的图像,需要先计算出源图像的梯度(步骤2),再根据目标图像在复制区域边缘的像素值和源图像复制区域的梯度值对结果图像求解(步骤3)。实际上,其他步骤都十分简单,方法的关键就在于第三步结果图像求解。

方法中设计的知识点有:

  1. 图像输入/输出:这项操作的内容很显然:把操作系统中的图像文件读入到程序的一个数组中,再把一个数组输出成一个图像文件。这个可以通过OpenCV库来轻松实现。图像输入输出是OpenCV最基本的操作,网上随便搜一搜OpenCV的教程就能找到。我自己也有一篇博文讲了这项知识。
  2. 图像梯度计算:所谓图像的梯度,就是每个像素与它左边和上边像素的颜色值之差,某处的梯度值表示此处颜色变化速度的快慢。由于后续操作需要源图像的梯度,在实际进行结果图像求解前需要先预处理出图像梯度。实现的时候用一个滤波器对图像做一次滤波即可,OpenCV包括了滤波功能。我的这篇博客简单介绍了OpenCV滤波函数fillter2D的用法。
  3. 结果图像求解:图像融合方法的思想是在融合区域边缘的颜色和目标图像一样的前提下,让源图像的梯度尽可能不变。这是一个最优化问题,最后问题可以转换成求解线性方程组。因此,这一步的目的就是通过求解线性方程组来得到我们想要的结果图像。这是本程序最关键、困难的一步,C++的Eigen库提供了高性能的矩阵运算函数。
  4. 图像拼接:最后输出前,要把处理过的图像区域和整幅目标图像拼起来。OpenCV提供了方便的图像区域覆盖函数。

本程序设计的图像处理操作都十分基础,用一些简单的OpenCV库函数即可。如前面多次强调的,该程序的关键是第三步。下一节将对第三步进行详细介绍。

结果图像的问题建立与求解

(导向插值标记,来自论文[1])

一般来说,用公式描述一件事是方便描述者,而折磨倾听者的。但是,为了把问题准确无误的描述出来,有时不得不使用严谨的公式标记。

在此问题中,我们把图像看成一个函数。如图所示,$S$是一个二维点的集合,即一堆可以用$(x,y)$这样的坐标描述的点。集合$S$就是图像函数的定义域。图像的值域呢?自然是图像的像素值了,这取决于实际情况,比如RGB像素值的范围是$[0, 2^{24})$。

现在,理解了图像其实可以表达为定义域是二维点,值域是颜色空间的值域的一个函数后,就可以继续理解符号标记了。令$g$是源图像,$f^*$是目标图像,$f$是我们把源图像复制到目标区域后,经过图像融合,得到的结果图像。按上一节的话说,$g$和$f^*$是输入,$f$是输出的结果图像。我们还有一个输入,就是进行图像融合的区域$\Omega$。既然我们把图像看成了函数,那么区域$\Omega$就是整个定义域$S$的一个子集。

$v$是$g$的梯度,$\partial\Omega$是区域$\Omega$的梯度,这两个量都是计算得到的。

我们还要引入一个符号——梯度算子$\triangledown$。$v$是$g$的梯度,可以写成$v=\triangledown g$。

重申一下结果图像求解的思想:在融合区域边缘的颜色和目标图像一样的前提下,让源图像的梯度尽可能不变(最小化差值)。用数学语言就是:

如果理解了求解的思想,那么看懂,或者说推导出这个公式是十分简单的。

这个公式是理想情况:图像在一个连续的二维数集上定义。但实际上,我们图像是离散是,只有在整数的位置处有值。我们要把问题用离散的形式表达出来。这里又要提出一些标记,对于$S$中的每个像素$p$,令 $N_p$ 为其4邻域中仍在 $S$ 的像素集合。也就是说,这个符号表示的某个像素上下左右这四个像素的集合,在这些像素没有跑出整幅图像的前提下。令 $(p,q)$ 为一个像素对,其中 $q \in N_p$ 。

再令 $v_{pq}$ 表示 $pq$ 两点间的像素值之差,即p到q方向的梯度值。则上面那个离散化的式子可以被转换成:

能看懂之前的式子,看懂新的式子也不难。

经过一系列数学推导,上面这个离散域的最优化问题可以被转换成一个线性方程组:

这个方程组就比较难懂了。不过,可以用一个直观的方法描述出来:待求解的结果图像的每个像素与周围四个像素的像素值之差(梯度)的求和等于源图像与周围四个点的像素值之差(梯度)的求和。公式中结果图像的每个像素与周围的梯度的求和分了两部分,是因为如果这个像素来自图像边界,则这个值是指定的(来自约束条件,边界像素值等于目标图像的像素值),在等式右边;如果像素不来自边界,则这个值是未知的,在等式左边。

有了联立的线性方程组,问题就变成了纯粹的数学问题,有很多的工具和方法来求解。

程序实现

程序结构图

在理想情况下,程序的结构图如下:(目前的代码很乱,没有严格按照这个图来)

整体分成输入、输出、和图像处理模块,非常简明。

如方法思想中所介绍的,图像处理可以分成图像梯度计算和目标图像求解。而求解又可以分成方程组建立和方程组求解。

这里仅介绍图像处理模块的实现细节。该模块整体的伪代码如下:

这份伪代码只能算是把整个流程用英文表达出来了而已,前文的梯度计算、方程组建立、方程组求解等内容均有体现。接下来几节介绍这三个重要步骤的细节。其他诸如获取图像的一个区域、填充图形一个区域的操作十分简单,可以用OpenCV轻松实现。OpenCV用Rect表示一个矩形区域,假设其有实例rect。令mat为任意一个图像,则mat[rect]就是图像的一个区域,可以执行读写操作。

图像梯度计算

梯度计算是学数字图像处理时的一个基本作业,由于有库函数的帮助,实现起来很方便,其伪代码如下:

拉普拉斯滤波器就是

1
2
3
[[0, -1, 0],
[-1, 4, -1],
[0, -1, 0]]

这样一个3X3的滤波器。手动建立一个滤波器后,调用OpenCV的filter2D就可以完成滤波了。

方程组建立

问题的方程组就是“结果图像的问题建立与求解”一节中最后一个公式所表示的方程组。只有彻底理解了那个公式中每一项的由来,才能把方程组建好。方程组求解时用到了矩阵,这其实就是把方程组的所有有关参数塞入了一个二维的存储结构中,没有太深奥的东西。

方程组主要有3项:系数矩阵项,边缘像素值项和梯度项。系数矩阵是方程组的左端项,后两者之和就是方程组的右端项。这一步的伪代码如下:

系数矩阵项lhs只由融合区域的大小决定。设矩阵的第i行表示第i个像素有关的方程,则第i列就是表示i像素自己的系数,由公式可知系数值是$|N_p|$;而其他至多4个在这个像素上下左右,且在融合区域内部的像素的系数是$-1$。

边缘像素值来自原问题的限制条件:融合区域的边缘像素值等于目标图像在此处的像素值。因此,这一部分的值要根据目标图像及融合区域得到。由于边缘项的出现没有什么规律,实际实现时可以在计算系数矩阵的同时计算边缘项:对每个像素四周判断,如果这一项在边缘上,则更新返回的边缘项;否则更新系数矩阵。

梯度项则完全来自源图像区域的梯度,甚至每个元素的位置都没有变,只要把它的形状改成列数为1的矩阵。

方程组求解

有了所有参数,调库求解方程组就是几行的事情了。下面是Eigen求解Ax=B方程组的代码:

1
2
3
4
5
6
7
Eigen::SparseLU<Eigen::SparseMatrix<float>> solver;
solver.analyzePattern(A);
solver.factorize(A);
Eigen::VectorXf tmpRes = solver.solve(b);Eigen::SparseLU<Eigen::SparseMatrix<float>> solver;
solver.analyzePattern(A);
solver.factorize(A);
Eigen::VectorXf tmpRes = solver.solve(b);

我只有在这一部分贴了代码,因为其他步骤都是比较灵活的,每个人都有自己的实现方式。只有这一步调库的写法是固定的。

求解完方程组,程序其实就基本写完了。

UPD:SparseLU并不是求解此问题的最佳方法。SimplicialLDLT适用于正定对称矩阵,在此问题中有更高的性能(我并不知道其中的原因,但是官网上明确写出这个方法适用于2D泊松问题,我就把结论拿来用了,哈哈)。只要把上述代码的SparseLU替换掉即可。

1
2
3
4
5
6
7
Eigen::SimplicialLDLT<Eigen::SparseMatrix<float>> solver;
solver.analyzePattern(A);
solver.factorize(A);
Eigen::VectorXf tmpRes = solver.solve(b);Eigen::SparseLU<Eigen::SparseMatrix<float>> solver;
solver.analyzePattern(A);
solver.factorize(A);
Eigen::VectorXf tmpRes = solver.solve(b);

结果展示

如图,(a)是源图像,(b)是目标图像,(c)是直接复制的结果,(d)是图像融合的结果。从视觉上来看,图像融合的效果还是不错的。如果能够用套索工具代替方框工具选择区域。用套索工具的话,难点在于实现套索工具本身,这已经脱离了本程序的主要任务了。有了套索的选定区域后,就是获取边缘项烦了一点,大部分的步骤还是一样的。

UPD:

程序里发现了一个BUG!!!方程右端的边缘项应该是相加,我写成了直接赋值。上面的结果图片是错的,正确的图片如下:

后续工作

在下面的”实况记录“一节中,你会发现,我5个小时不到就完成了没有bug的程序,基本完成了整个毕业设计。得知了这一事实,你能得到哪些结论?

首先,你会说:”哇,你好强啊,这么快就把一个看上去那么复杂的项目写完了。“确实,这说得很正确。但这对于我来说是基本操作,甚至我还对自己有些不够满意,写代码的时候急躁了一点,不然可以更快写完。比起学东西,写论文和博客等文档的时间,实际编程的时间确实占比很小。

层次高一点的人,会评论道:”你这项目太水了吧!你干了什么?不久求个线性方程组?高中生都会解这种问题,叫一个大一新生学一学也能写出求解线性方程组的程序。“这说得不错。确实这个项目实现起来很简单。但是,你要注意到,这是一个科研性质的毕业设计。什么是科研?写代码是科研吗?不是。科研是要针对一个问题,提出某些解法,或者说改进现有的解法。重点是思路。我刚刚也说了,我的大多数时间其实花在了看文章、学习上面。这十分合理。

对科研有一点经验的人,又能找到我的漏洞了:”你刚刚说科研的定义,说得很好。但你做了什么呢?你复现了别人的方法。这是科研吗?这有任何创新吗?有任何改进吗?“能说出这种话的人,一定是高手。确实,本文的内容是不太够的。我的创新点在其他地方。这篇文章,只是我毕业设计的核心部分,还有一些内容没有展示出来。敬请期待以后的博客。

真正善于见微知著的人,还能得到其他的结论:”这就是本科教育吗?本科论文就这么水吗?“我只能说:“是的。”本科教育基本没有给学生们提供接触科研的机会,而学校为了方便大家毕业,不会在毕业设计上为难大家。没有了GPA,奖学金这些功利的东西,大家也没有认真做这个项目的理由了。本科毕业设计,就是一个从学校、导师到学生,基本没有人会认真对待的东西,从动机的角度来说,十分合理。

要是你再想得透彻一点,会发现这一切都是不是事。还是我最喜欢强调的,重点是你学到什么,你能得到什么。对于我来说,本科毕设强化了我的科研能力,让我找回了一点写代码的熟练度,锻炼了逻辑表达能力。虽然我花费的很多心思没有任何功利的作用,没有人会注意到我做的那些东西,但我知道我能得到什么就够了,不需要得到别人的认可。本科教育也是这样。本科究竟是为了什么呢?说来可笑,一部分去就业,一部分人继续科研。无论是工业界的知识,还是科研能力,本科一概不教。对于大学来说,能通过高考把优秀的人才招进来,本科设立的意义就已经达到了。既然如此,毕业设计怎么搞不都一样了吗。

实况记录

3.22 18:13

现在,游戏开始。先稍微讲一下我已经做好了哪些准备:我已经建立好了VS项目,导入了之前了OpenCV环境,并成功实现了普通的图像复制。我下好了别人实现论文的代码,以及代码中将用到的Eigen数学库。我还没有学过Eigen的用法,也没有把整个方法的实现流程完全想好。

18:26

理解了此方法中如何构建最后的求解矩阵。现在学习如何用Eigen求解稀疏矩阵。

18:36

知道如何用Eigen创建矩阵,求解矩阵了。可以考虑写代码了。先想办法把Eigen导入项目,并且测试一下Eigen解方程代码。

18:39

很烦!Eigen还要编译,计划中断。我准备一边编译一边盲写代码(写的代码无法进行测试)。

18:57

一边复习OpenCV,一边写完了拉普拉斯滤波代码。同时Eigen似乎编译完了。

19:09

Eigen编译到一半,出了问题,开始向搜索引擎求教。最烦人的配环境时间开始了。

19:21

气死了,这个库可以不编译直接使用。重新开始Eigen的使用测试。

19:36

写到一半,OpenCV配置又出错了。原来是之前写的OpenCV配置文件少包含了一个库文件。批评一下过去的自己,太坑了吧。

19:47

OK,Eigen测试完了,游戏结束。现在所有不确定的东西都搞定了,前置知识学习完毕。接下来我可以展示王者级别的写代码能力了。

20:26

潇潇洒洒写了几十行,突然发现我不会对OpenCV和Eigen两个库的矩阵进行互相转化,又开始查东西了。时间还很充裕,问题不大。

20:30

原来OpenCV提供了转换函数。好,工作继续。

20:37

虽然我没有花半秒钟在程序的软件工程设计上,但我潜意识里已经把代码的结构、实现顺序给想好了。凭借着强大的功底,我本能地使用了自底向上的思想,先把每个细节的实现函数写好了。现在我准备把整个程序拼起来。

20:46

好,程序拼完了,已经开始编译了。说实话,第一遍能不能过(没有BUG)?肯定过不了。无论多强的高手,都会写BUG,都有大意的时候。BUG本身并不可怕,可怕的是不会调试BUG。编程能力展示结束,现在展示我发现BUG的洞察力和解决BUG的决断力。

20:54

代码还是倒在了OpenCV和Eigen两个库的互相兼容上。三个小时快到了,你会不会觉得我很慌?你会不会觉得我已经写不完了?不好意思,现在我才刚刚登上展示实力的舞台。只有在逆境中绝处逢生,才能体现出一个人的过人之处。

20:58

突然发现代码漏写了!图像有RGB三个颜色通道,该方法要对每个颜色通道单独处理,我忘了拆开颜色通道了!

21:03

OpenCV和Eigen相互转换的库函数不能用,我就自己动手!总算,稍加调试之下,代码可以运行了!只不过代码的运行效果明显不对……这下调试起来就麻烦了。很好,最尴尬的情况出现了。我不会放弃的!

21:17

时间已经过了。但我有大约10分27秒花费在了写博文上,这些时间应该不算。继续计时。

21:45

我发现我对论文方法的理解出现了问题。

22:25

更改对论文理解上的逻辑BUG后,又发现8位颜色通道溢出了。

22:34

历经千辛万苦,程序总算是成功实现了。总耗时约260分钟。多花费了约50%的时间,你肯定说我很菜,说我写代码写得烂。但是这种说法并不对。仔细看一看这份文字实况,你能看出什么?没错,我一直在强调我纯粹的编程实力,并没有强调我有快速把论文转换成代码的能力,也没有强调我有快速学会调用那些坑爹的公共库的能力。我写代码写了多久?19:47~20:26,39分钟;20:30~20:46,16分钟,一共55分钟。第一版本的代码,我花55分钟就写完了。后面又调试了一下,20:54~22:34,100分钟,减去重看论文的21:17~21:45这28分钟,又花了72分钟调试,最后也就改了5%不到的代码。我说这么多数据是为了什么?是为了说明,恰恰是这份实况记录,强调了我编程能力之强,强调了时间其实都耗费在一些编程之外的时间上。虽然没有在规定时间内写完,但我感觉虽败犹荣。

当然,你要说我每个方面的表现都很好吗?也不是这样的。我自己也承认,理解论文的时候急躁了一些,学库函数的时候学的不仔细了一些,导致我这个第一版的程序,出现了一点小小的偏差。不过真要说起来,我今天确实有点急于求成。因为立下了3小时完成的flag,所以我做事火急火燎,忽略了工作质量,结果败给了自己。如果一开始没有这个flag,我能不能3小时内完成呢?你可能会认为我会说:“会”。好,实际情况是,我不立这个flag,我今天会玩一个晚上,不会来做这个项目,哈哈。

参考文献

[1]. Pérez P, Gangnet M, Blake A. Poisson image editing[M]//ACM SIGGRAPH 2003 Papers. 2003: 313-318.

最近买了台新电脑,所有编程都需要重新配置,可以写很多水博文了。

Visual Studio 2019(VS2019)安装教程(解决VS scanf问题)

准确来说,这是VS2019在Windows 10下为了配置C++编程环境而写的教程。

在下载前,请务必保证C盘的空间比较充裕,因为不论安装在哪,VS都会挤占C盘的很多空间。

下载

  1. 打开官方网站https://www.visualstudio.com/vs,点击网站中间那个醒目的“下载 Visual Studio”按钮,再选择”Community 2019”(即社区版)。社区版是免费的,其他几个版本都要付费。作为普通的程序员,使用社区版就够了。1

  2. 由于我们点击了下载按钮,为VS贡献了宝贵的+1下载量,我们得到了来自微软公司的感谢。浏览器此时会自动开始下载。

    2下载很快就会结束。规模这么庞大的编程软件,几分钟就下载完了?其实我们现在只下载了一个下载器,待会才会正式开始下载VS的本体。

安装

  1. 下载完成后,在文件夹里找到下载好的可执行文件并点击执行。处理掉操作系统弹出的烦人的提示后,我们可以看到如下界面:

    3

    这说明安装正式开始了。

  2. 点击继续后,下载器会读一个进度条。读条结束后,可以看到如下的安装配置界面:

    4刚看到这个界面,第一次用VS的人肯定会有点懵:哎呀!怎么这么多选项啊!该点哪个呀?

    但如果你是一个有推理能力的人,你看到这个界面,哪怕不去网上查询VS的使用方法,也能猜出这个页面的意思:VS是一个集成开发环境(IDE),可以进行不同语言的开发。这个界面是用来让用户选择要开发的语言的。

    5所以,我们应该根据自己的需要选择要开发的语言。只进行C++的开发的话,只点击”使用C++的桌面开发“即可。上图中,我多点了几个选项。

    6

    下载之前,如果C盘空间吃紧的话,可以点击“安装位置”选项卡,把VS的部分文件装在其他盘上。不过在右下角可以看到,VS的大部分依赖文件还是得装在C盘的。

    点击安装按钮后,VS就会开始下载安装了。

  3. 7

    下载结束后,就可以正式使用VS了。使用之前,会弹出如上的登录界面。可以点击“以后再说”不登录直接使用程序,也可以创建账户并登录。创建账户的好处是,你在一个账户上配置了VS的界面布局后,无论在哪台电脑上都可以直接使用自己这套界面布局。

    通过了登录界面后,还有一个主题选择界面,可以按自己的喜好定义VS的“外貌”。

使用VS写C++的Hello World

第一次打开VS2019,应该会看到如下的界面:

8

又是琳琅满目的按钮。我们不必感到慌乱,只需点击最后一个选项“创建新项目”即可。

9

10

要在一堆选项中选择项目模板。作为一个初学者,我们要从零开始学习。因此,我们先在所有语言下拉栏中选择”C++”,再选择“空项目”.

11

终于,激动人心的编程就要开始了。再次之前,我们要给项目取一个好听的名字。这里我们取名叫做”Hello World”。

12

好了,总算正式进入VS了。代码需要写到文件里,而我们现在还没有创建文件呢。所以,我们要创建一个源代码文件。右键点击右边的“源文件”文件夹,点击添加->新建项。

13

在新窗口里点击”C++文件(.cpp)”,然后在下面的“名称”输入框中输入一个响亮的名字。按照传统,我们给第一个源代码的文件取名为”main.cpp”。

14

接下来,第一份源代码文件就打开了,我们可以编辑这份文件了。无论你是否学过C++,可以往文本编辑器中输入以下代码,达到和上图一样的效果:

1
2
3
4
5
6
7
8
9
#include <iostream>

using namespace std;

int main()
{
cout << "Hello World" << endl;
return 0;
}

按下F5就可以编译并调试这份代码,如果得到了下图的结果,就说明VS能成功编译运行C++程序了。

15

当然,如果使用老的VS版本,或者因为其他一些原因,按下F5后控制台会一闪而过。这个时候。你需要在return 0这一行左边的灰色部分点一下,加一个断点,让程序运行到这行的时候停下来。这样就可以看清控制台的输入了。

16

在VS中使用scanf

理论上,这篇教程已经写完了,大家可以快乐地用VS写C/C++程序了。但是,有些人会用VS写纯C语言代码。为了保险起见,我们不妨测试一下一份C语言的“A + B“程序。输入以下代码并按F5调试运行:

17

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", a + b);
return 0;
}

结果编译器会报错,不让这份代码通过。

18

这是为什么呢?事实上,VS不支持纯C语言的编写,只支持C++的编写。很多早期C标准库有安全问题,VS不允许使用这些库函数。但在学习C语言,尤其是要把C语言代码提交到在线评测平台(Online Judge)的时候,不得不去使用这些C标准库的函数。为了解决这个矛盾,我们需要对VS的设置进行一些更改。

19

在右边”解决方案资源管理器“窗口中,在项目名”Hello World”上点击右键,再点击”属性”。

20

在左侧点击C/C++,把右边的SDL检查改成“否”。这样就可以使用C语言中scanf等不太安全的标准库函数了。

用Mendeley进行文献管理

在做一个有关科研的项目时,我常常把该项目相关的论文全部放到同一文件夹下。一个pdf文件刚下载下来的时候,我还能分辨出这个文件是哪篇论文。但过了一两天,我还想找这篇论文时,就只能望着文件夹里乱七八糟的pdf文件名而不知所措了(论文pdf的命名方式五花八门)。我深知对论文进行管理的重要性,但由于之前的项目涉及的论文数量较少,我勉勉强强没有在搜索文献时碰到太多困难,出于懒惰,我没有对文献进行管理。最近,我要开始写毕业论文了,恰好有了一个让自己开始学习文献管理的理由。

我用极快的速度在网上搜索文献管理软件有关信息,在知乎上稍微浏览一会儿后,选择了Mendeley这款软件。我没用过其他软件,无法给出不同软件之间的优劣,只能根据别人的信息,学习一下Mendeley这款文献管理软件的用法,并把这些有用的信息分享出来。本文将包括以下内容:

  • Mendeley基本安装使用方法
  • 如何用Mendeley达成自己的文献管理目标

Mendeley下载安装

开始使用Mendeley前,要完成以下几件事:

  • 注册账号
  • 下载软件
  • 安装word引用插件

直接在搜索引擎中搜索“Mendeley”即可找到官网(https://www.mendeley.com )。官网上有一个很大的“DOWNLOAD”,可以跳转到软件版本选择界面,有各个平台的软件版本以供下载。官网上还有一个很显著的“Create Account”,设置邮箱密码,不需邮箱验证即可注册。

软件下载安装输入账号密码后即可开始使用软件。

初次点开软件后,有一个添加word文献引用插件的提示,可以在关闭了所有word文档后点击安装。

Mendeley基本使用方法

Mendeley包括以下功能:

  • 文档层级管理
  • 文档导入
  • 文档重命名
  • 文档辅助标记
  • 文档同步
  • 文档搜索

文档层级管理保证了文档能按一定逻辑结构进行存储,这是一切后续文档操作的基础。文档经导入后,可以按照你熟悉的方式进行重命名,也可以对文档添加一些标记,方便从文献的检索和管理。文档同步保证了一台电脑上导入的文档能够在其他设备上方便地访问。

文档层级管理

Folder

软件左上角My Library就是文件夹列表。你可以自己创建文件夹并重命名,并在文件夹中再创建文件夹,实现对论文的分项目、分类管理。

除了自定义文件夹外,还有一些预定义文件夹,从这些文件夹的名字基本就可以知道它们的用途。比如Recently Added文件夹,就和win10系统下资源管理器左上角的”快速访问“一样,能找到最近访问的文档。

文档导入

Import

为了把pdf格式的论文导入,可以点击左上角Add在操作系统的资源管理器中添加文档,也可以直接在资源管理器(此电脑)中框选一堆文档拖入中间的空白处。注意到中上处有一个文件夹的提示(本图中是Undergraduate Capstone),表明论文是被导入了一个具体的文件夹中。

文档导入不仅仅是让软件”知道“了你有哪些论文,在文档导入后,软件还会根据pdf自动分析出论文的发表时间、作者、期刊。

文档重命名

重命名功能能够把重新把文档在操作系统里的文件名按一定格式进行修改,而不只是在软件中取了一个虚拟的名字。当然,为了保证重命名的正确运行,需要关掉正在打开的pdf文件。重命名的具体操作如下:Rename

选择一些文档后,点击右键,可以在选项中找到“重命名文档”这一功能。

Rename2

重命名功能能让每篇论文的文件名以任意的顺序包含期刊、发表年份、作者、标题这四种信息。比如在上面的截图中,我是按“年份-作者-标题”这样的格式对所有文件重命名。

重命名结束后,可以再次右键点击文档选择”打开文档所在文件夹“(Open Containing Folder),在操作系统里查看这些文件。

文档辅助标记

右键点击文档,可以看到一个叫做”标记为“(Mark As)的选项,该选项能给每个文档一些标记,包括是否读完,是否喜欢,是否需要回顾。

界面右端有三个选项卡,其中笔记(Note)一栏可以对每一个文档做一些简要的笔记。

界面右端Details选项卡里有一个标签(Tags)选项,可以给文章添加标签。

文档同步

界面上方有一个循环箭头图标,点击该图标即可同步。理论上只要是登录同一个账号,就可以在不同的电脑上访问同样的内容。

文档搜索

界面右上角有一个搜索框,输入东西就可以搜索。

理论上,不管是论文名、文章内容、还是添加的一些辅助标记,都可以通过搜索功能搜索到。这也是添加辅助标记的意义所在。

使用文献管理软件的目标

使用工具一般有两种出发点:知道有哪些需求,而去寻找工具;得知了工具有哪些功能,再去看这些功能能做什么。前几节只介绍了Mendeley这个工具本身能做什么,而这一节将按照第一种使用工具的出发点,忽视工具而只谈论文献管理有哪些需求。下一节将结合工具和需求分析一下该怎么使用Mendeley。

我的阅读论文经验尚浅,这里仅能给出两个文献管理的需求:快速检索、快速预览。

在读某一篇论文的时候,经常会碰到一个子问题已经有了一个成熟的解决方法,作者直接贴了一篇参考文献的情况。这个时候,你肯定很希望能够立马看到这篇参考文章,和刚刚阅读的内容关联起来。如果这篇参考文献没有下载下来,你还可以发出一两句怨天尤人的抱怨:”世界上论文怎么这么多啊“,”下载个论文怎么这么麻烦啊“。但如果这篇论文已经下载了,你在电脑里找不到,你只能怪自己道:”我之前下载论文的时候怎么没有好好整理一下啊!“我们希望脑中想到一篇论文,屏幕上就能立刻出现哪篇论文。

还有一些情况,我们看到一篇以前读过的论文,突然忘记了它讲了什么。打开文档、浏览全文、关闭文档可要花费不少时间。一个最简单的方法,就是对每一篇文章做一些自己的笔记。看着自己对论文的描述,一般就能想起论文的内容。问题是,电脑里有那么多论文,存在文件夹的各个角落里,该把笔记写哪里,怎么写呢?

使用Mendeley完成文献管理

有了Mendeley,完成文献的快速检索和快速预览讲不再是梦想。Mendeley本身提供了多种给文档添加标记的方式,利用搜索功能能方便快捷地定位到你想要的文章。同时,利用软件的笔记功能,能够给每篇文章添加自己的描述,配合上搜索功能,达到快速找到文章,并快速想起文章的”combo“。

根据我现在对Mendeley的认知,一个合理的使用该软件的流程如下:

  1. 为当前项目创建新的文件夹,让自己有一种”我要开始做大事了“的仪式感
  2. 找一堆论文,下载并导入Mendeley
  3. 重命名所有论文,使得哪怕不打开Mendeley也能快速在文件系统中找到文章
  4. 每看完一篇论文后,添加一些能让自己看得懂的简短笔记,视心情添加标签
  5. 如果对该项目相关的论文有了一定的认识,建立子文件夹,对文献进行一级分类
  6. 动态维护添加这个文件夹,根据需要对文献进行搜索

总结

我向来觉得写一篇大部分都是信息(比如软件安装方法)的文章没什么意义。网上参考资料那么多,何必要再写一些原创性较低的东西呢?但最后我还是完成了这样一篇”工具使用指南“的文章,介绍了Mendeley这个文献管理软件的用法。其原因是,我写这篇文章的目的是为了让我自己有动力去学习这个软件的使用方法,并通过费曼学习法,利用向他人重述来加深自己对信息的记忆。

这篇文章主要面向的是没有用过Mendeley的读者,尤其是从未用过文献管理软件的读者。读者通过文章前半部分,在没有下载软件的情况下,能大致知道Mendeley有哪些功能;下载了软件后,可以知道如何使用这些功能。文章后半部分对科研经验尚浅的人较有帮助,提出了一些简单的文献管理需求,并探讨了如何结合软件达成这些需求。

Poisson Image Editing 图像融合/图像无缝编辑经典文献阅读

想不想把自己的头P到别人的图片中?

想不想把别人图片里的彩虹放进自己拍的风景照中?

想不想自己修掉脸上的皱纹?

掌握图像融合技术,以上的一切图像编辑任务都可以轻松完成。

fig1

(图片来自论文)

如图所示,利用图像融合技术,可以把一幅图片的部分区域复制到一幅图片(可以是同一幅图片)上,并且让复制过去的图片块保持之前的色彩风格。因此,这种技术有时可以叫做图像无缝复制(image seamless cloning)

论文整体结构

该论文介绍了一种图像编辑的方法,其主要作用是图像边缘的无缝拼合。该方法可以应用到图像融合和图像风格变换等任务中。论文的结构如下:

fig2

从结构图可以看出,这种方法的输入是待修改图像、待修改区域及待修改区域的目标梯度,输出是编辑后的图像。基于该图像编辑方法,能产生各式各样的图像融合结果,产生不同结果的方法是修改输入中目标梯度的获取方法。

具体介绍一下这种图像融合方法。这种方法的思想是让图像编辑后的梯度域尽可能和源图像(图像编辑是从源图像复制一块到目标图像)一样,并且编辑区域边缘的梯度值和目标图像一样。图像的梯度可以理解为图像相邻像素的变化量。有研究指出,比起绝对的像素值,人眼对梯度值更敏感,因此维持编辑区域内部梯度不变,边界和目标图像“接轨”能让图像融合的质量更高。梯度值尽可能不变,就是让图像编辑前后梯度域的差值尽可能小,问题就变成了一个纯数学问题。通过相关的数学工具,可以求解出编辑后的图像来。

论文细节

优化目标的建模与求解

该论文方法的核心是建立了梯度域的优化目标后,如何用数学方法对这个问题求解。

首先,稍微介绍一下这个优化问题是如何建模的。输入图像、输出图像都可以看成一个定义域在平面上的一个函数。对输出图像的求解即对一个未知函数的求解。求解未知函数,涉及泛函的知识,需要用到欧拉-拉格朗日方程,有一篇非常好的博客[2]介绍了有关的数学知识。

有了对问题的建模后,论文直接给出化简后的待求解方程。该方程是一个泊松方程,即与函数二阶导数相关的偏微分方程。对该方程求解,即可得到最终的图像。

图像的数值都是离散的,对一个图像的求解,最终可以转化成对该图像每个像素值的求解,即求解一个多元方程。此问题中,该方程是一个线性方程。利用求解多元线性方程的方法即可解出此问题。

事实上,该问题的建模和求解中涉及了很多数学定义,这些数学的细节是否理解对整篇文章的理解、方法的创新不是十分重要。对于一个研究计算机技术的人来说,优化目标的设定才是最重要的。

利用方法产生不同的编辑效果

论文的所有图像效果都是基于同一种方法。也就是说,只要理解了最基本的把一幅图片无缝融合进另一幅图片的原理,其他图像编辑效果都可以通过同样的原理推导而来。

如果把源图像的待修改区域的梯度做为目标梯度直接输入进方法,由于图像边缘梯度和目标图像一样,内部和源图像一样,复制过去的图像可以无缝地对接到目标图像上。利用该图像融合方法,可以实现“图像隐藏”:比如把脸上没有皱纹的地方复制到有皱纹的地方,达到去皱纹效果。

只用源图像的梯度,而忽视目标图像待修改区域的梯度,则会完全抛弃待修改区域原来的信息。在复制一些半透明的图像(如彩虹)时,如果抛弃掉目标图像待修改区域本身的性质,就会产生很差的效果。论文中提出,可以把源图像梯度和目标梯度的较大值做为方法的目标梯度,这样经过图像编辑后图像的待修改区域能融合两幅图像的性质。

通过上述例子,可以得知目标区域的梯度是如何决定图像编辑效果的。对目标区域的梯度进行一些“魔改”,就能得到不同的结果,比如结构图中提到的扁平化、光照修改和地砖无缝拼接等。

论文实现

理论上,我会对论文进行实现。以后我会更新此文,加入实现的代码仓库链接。

其实随便一搜就能找到很多该论文的实现,毕竟这篇论文是图像融合的一个非常经典的方法。很多matlab代码技术性较弱,很多东西都写好了,因此我附上了一个该论文的C++实现[3]。当然,用C++的话也要调用不少库函数,最烦人的线性方程组求解是必须得调一下库的。

总结

本文对论文“Poisson Image Editing”进行了一个简要介绍,其本意是通过总结论文大意来加深我自己对论文的认识,并且方便之后对其他图像融合领域的对比阅读。

当然,这篇文章对读者来说也有一定可读性。本文并没有对原文进行死板的翻译,而是从整体到局部,大致介绍了论文中的主要内容。对于只是想浏览该文章的读者,可以学到一种新的P图方法,图一乐;对于在精读论文之前看到这篇文章的读者,可以大致知晓文章是在讲什么,解决什么问题,用了什么方法,产生什么结果;对于读完论文之后的读者,可以把这篇文章和自己的收获进行对比,看看哪些内容是读到了的,哪些内容文章没有提到,甚至哪些地方写错了,写得不够好,以加深自己对论文的理解。当然了,如果你觉得我讲的你都读到了,你也可以写出一样的东西,那说明你是个高手,学得很不错,毕竟想要掌握我这种概述能力是十分困难的。

参考资料

[1] Pérez P, Gangnet M, Blake A. Poisson image editing[M]//ACM SIGGRAPH 2003 Papers. 2003: 313-318.

[2] https://www.cnblogs.com/bigmonkey/p/9519387.html

[3] https://github.com/cheind/poisson-image-editing


2023.4 更新:
最近在重新温习Poison Image Editing,看了看自己以前写的文章,发现这篇文章写得一塌糊涂。写得这么简单,谁看得懂啊?或许我当时为了便于理解,没有在文章里用数学符号。但这文章也写得太不清楚了吧。我现在来稍微重新总结一下这个方法。

泊松图像编辑要解决的基本问题是图像融合:输入源图像、源图像上的编辑区域、目标图像、目标图像上的编辑区域,我们想把源图像的编辑区域「贴」到目标图像的编辑区域上。相比于最朴素的复制粘贴,我们希望图像贴过去后看起来没有那么突兀,就像文章展示出来的一些融合结果一样。

作者把这种图像融合看成了一个优化问题:在编辑区域边界处的像素值应该保持不变(保持原来目标图像在此处的像素值)的前提下,令编辑区域内部的梯度值与源图像该处的梯度值尽可能相似。求解这个优化问题,就能得到编辑区域内的像素值。

归根结底,泊松图像编辑的输入是一幅目标图像、一个梯度值、一个编辑区域。图像融合只是应用之一。通过修改输入的来源,可以完成多种任务。

泊松图像编辑的原理就是这么简单。更详细的介绍在那篇代码实现的博文里有介绍。

这篇文章确实是一篇实实在在的「博文」:只有我自己能看懂,别人都看不太懂。可见,我当时的描述能力比现在差远了。反过来说,没过几年,我的描述水平就大有提升。这真是太可怕了。

套磁,据传来自北京方言,意为“套近乎”,特指在申请出国留学时,提前给导师发邮件推荐自己,以增加录取几率。套磁信都是拿英文写的,我写起来很不顺手。现在,我打算解开封印,用中文写出一篇套磁信,让任何阅读者仅通过文字就能感觉到我的强大之处。

如果套磁信可以用中文写

尊敬的教授:

您好。

我是北京理工大学一名计算机专业的大四学生。我正在申请你们学校的博士。我对您的研究十分感兴趣。我没有名列前茅的排名或顶会的论文的来证明我的实力,但我依然敢断言,我是一名非常优秀的学生,我能够为您的研究做出贡献。请允许我介绍自己的背景,以及我为什么很适合加入您的研究团队。

为什么有那么多优秀的学生在博士期间退学呢?一个科研工作者最重要的品质是什么呢?在我看来,参与科研的动机是最重要的。许多人从小知晓科学家的事迹,抱有改变世界的理想,寒窗苦读,披荆斩棘,登堂入室,成为博士在读生。然而,现实与理想产生了割裂,博士生每天的工作是读论文、做实验。不知何时,儿时的幻想逐渐破灭,绞尽脑汁地发论文毕业成了最重要的事。我认为自己已经认清了博士生在做什么,并依然渴望成为一名博士生。在两名美国教授(一对老夫妇,已经培养出了数十个知名教授)的教导下,我找到了令我持之以恒的动力:与有思想的人交流合作,创造新知识并把我的思想传递给其他人。在这强大的动力下,我能克服读博士期间的一切挑战,成为一名优秀的研究者。

光有斗志,没有实力是不够的。据我所知,在科研中,最重要的实力不是考高分、拿奖的能力。科研,需要发现分析解决问题的思考能力,以及把自己的想法和他人分享沟通的交流能力。这两种能力都是我的强项。可惜的是,这两种能力难以用客观的标准来衡量,只能通过某些事情来体现。我打算用一些例子和事实来证明我这两方面的能力。我常常把编程的思想用于生活:在学习新知识的时候,我会建立一颗“树”来梳理知识的自浅到深的结构;在解决一个较为具体的问题的时候,我会像写程序先写主函数再实现各个子函数一样,先把问题划分成互不相交的子问题,梳理整理的流程,再逐个解决子问题。我在演讲和分享知识上颇有经验:我的中国导师听了我的两次演讲,他感到“印象深刻”;我给同年级和低年级的同学都上过算法课,得到了广泛好评。

通过以上的介绍,您应该知道我是一个有科研潜力的人了。接下来,我将从我的专业技能和科研经历来分析为什么我适合加入您的团队。我自认为我的编程水平堪称顶尖:我在ACM中拿过金牌,有算法实现经验;我实现过C编译器、路径追踪渲染器、光线跟踪算法、图像分割算法……等程序,有复杂项目编程经验。我能够又快又准地实现一个功能复杂的程序,同时保证所使用的算法与数据结构尽可能高效。图形学研究涉及的算法十分复杂,经常需要编写大型程序。我有图形学知识基础,也有过硬的编程经验。我的专业技能定能在研究中发挥重要作用。

通过看您的网站,您之前的研究是………………………………………………。我之前参与过图形学的研究 …………………………………………。我认为我们的研究方向非常匹配。对于您的……………………论文,我也许可以…………………………,我的……………………技能或许能够对改进这篇论文提供帮助。

从您的介绍中,我认为您是一个………………的人,我也是………………的人,我觉得我加入您的团队十分合适……………………………………这是我的CV与个人网站。如果您对我的背景感兴趣,欢迎进行进一步的交流。

Best,

周弈帆

套磁信评析

严格来说,这不能算一个套磁信模板。里面的文字太多了,老师没那么多时间来看的。也正因为这样,我拿英文写起套磁信来的时候浑身不适,感觉手脚都被束缚了。今天用中文写一封套磁信,并发到这里,就是为了能够在完全自由的情况下用文字展示我的科研能力。我相信,如果你是一个高手,你读完了我的这封信,一定会明白我是有多么强大;如果你没有这种感觉,说明你还年轻,还有很多事需要想清楚。这里我也不能说得太明白,如果我把道理全部说出来,你就无法得到成长。

以下是套磁信的赏析:

正文第一段开门见山,用最少的文字介绍自己的背景和目的。之后,别出心裁地先提自己的缺点,与绝大多数先提自己优越客观条件的套磁信形成对比,吸引老师的眼球,激发阅读兴趣。最后,介绍邮件的目的:介绍自己,并用议论的说法证明自己时候老师的科研。

第二段,从科研动机证明自己的科研能力。因为我认为,科研的动机的最重要的一件事,所以放到第二段介绍。我先使用较为夸张的口吻,描述了一般的博士生的悲惨经历,再使用对比的手法,写出自己与他们的不同,以证明自己对于读博士的心理准备。

第三段,再从一个刁钻的角度证明自己的能力。思想能力、沟通能力都是难以量化的。但不可否认,它们都是重要的能力。老师看到你对于这两种能力的重视,首先就会认为你很有思想,已经把事情看透了。之后,我论证我能力的方法堪称绝妙:我用例子证明思考能力,用事实证明沟通能力。短短几句话,一个睿智、高傲、善辩的形象逐渐树立起来。

其实,整篇文章都使用了欲扬先抑的手法,从第一段的“没有客观成就”开始,一直是“抑”。从第三到第四段开始,是“扬“。ACM金牌、项目经验,证明自己的手牌一张一张打出。我用一些简历里不是很明显的客观事实来证明我的专业能力。前面几段算是论证,这一段就算是在举例子辅助论证了。

再接下来一段讲自己和导师的科研经历,这一段要具体问题具体分析,我在这里没有认真写。

最后一段,如果导师的个人网站的内容写得多,我认为可以写一些有关导师个人的东西,以证明自己的导师的重视,有强烈地进行合作的意愿。最后一句话点名整封邮件的意图:如果老师对我感兴趣,快点给我回信。

通篇看下来,整篇套磁信抑扬顿挫、起承转合,一应俱全。主要思想言简意赅,文章结构清晰明了,复杂想法的表达别出心裁,不可谓不是一篇优秀的文字作品,值得赏析和玩味。

抛开信本身的写作方法不谈,光谈内容。我认为,一个厉害的导师,看到了我的信,一定会认为我是一个厉害的学生;如果他看了我的信没有什么反应,说明他不是一个厉害的导师,那么我不被接受也没有任何遗憾。有人读到这,可能会说,你太狂妄了吧?你本科还没毕业吧?你成绩不是很好吧?你凭什么这么说啊?作为一个读者,你读完后会这样想吗?对此,我只能说,因为你不会,所以你才会。你”不会“对这件事进行深刻的分析,所以你”才会“得出这样的观点。首先,我想说,什么是厉害的导师?没有一个这样的定义。那么,可以说,我认为一个导师厉害,那么这个导师就厉害,因为是我在选导师,不是把这个导师推荐给别人。一个导师没有通过我的筛选,那么我通不通过他的筛选已经无所谓了。有点逻辑思维的人看了我这段解释,又会说:”看了你的邮件接受了你的导师,就是厉害的导师,你就跟着导师干,这不是废话吗?导师不接受你,你想跟着他干也没用啊。“我承认我的第一个解释不太对。那么,我真正想说的是,通过和我的美国导师以及一个优秀的同学的交流,我得出了评价博士录取的最重要的因素。一个人的科研潜力远比客观的证明材料重要,虽然这种潜力难以被挖掘。我对自己的语言表达能力十分自信,我通过文字很好地展示了自己的能力。那么,一个当上了教授的人,自然知道该如何选择学生,自然会被我的语言所打动。

扯了这么多,我是否能被最终录取还是个未知数。这只能算一个套磁信的分享与分析。我说的对不对,我想的能不能成,已经不重要了。做为读者,你学到了什么,才是最重要的。当然对于我来说,能不能成功录取还是有点重要的;哪怕事情没那么顺利,也问题不大。总之,我认为这封套磁信非常有趣,非常欢迎大家来阅读。

最近有好多东西想写,今天先写一篇比较短的文章。

想得越多,干得越少,做得越差

我发现,我在做事的时候,经常会考虑太多东西。考虑得太多,反而在事情开始干活的时候畏手畏脚,不知道该怎么下手。最后,什么事情都没有做成,还不如少想一点。今天,我想讨论一下这种现象的具体表现及其负面影响,以及这种现象的解决方法。

写这篇文章的时候我头脑不是很清楚,很多地方不是很严谨。在描述一个事情/例子的时候,如果用的人称是”你“,这表示我认为这种现象比较普遍,大家可能都会碰到;如果用的人称是”我“,表示这种现象可能比较特别一点,且确确实实发生在了我自己身上。

我的“过载思考”

我在认真对待一项任务的时候,会全方位深入思考这件任务的完成方法,我把我的这种思考方法叫做”过载思考“。过载思考本身是一个非常复杂的话题,我以后可能会专门花一篇文章来总结,现在就简要介绍一下这种思考方法。

在做事情之前,我要求自己明确知道自己的目标是什么。比如在大学里上一门课,正常来说大家的目标是获取更高的分数。我会去问自己:获取高分真的就是我的目标吗?是不是获取这门课的知识才是我的真正目标?还是通过完成这门课的一个项目,来提升项目经验,而不是掌握这门课的知识?只有搞清楚了自己真正的目标,才能针对目标选择最优的每部操作。搞清楚了真正目标后,你会发现一些看似正确的做法,其实并不正确。比如我们学校的操作系统课,拿着一本烂书,讲着没有用的知识,考试考背诵,老师上课在大谈自己的人生经历。在这样一门课上,认真听课,认真准备考试获取高分,在我看来都是浪费时间。我在这门课上学不到任何知识,所以我的目标是获得一个看得过去的分数,我要花最少的时间,让自己在背最少的东西的情况下达成目标,而不是所谓的“认真学习”。

确定了目标之后,要思考达成目标的过程中,哪些是重要的东西,哪些是看似重要的东西。很多时候,你不自己实际经历一遍这件事,踩一遍坑,你可能就不知道哪些事是重要的。但一旦你能提前把可能遇到的坑想好,提前发现完成一件事的最重要的因素,你就能节约大量时间,以及减少陷入负面情绪的时间和可能。比如你为了获得一个好成绩,天天去占前排的座位,去做漂亮的笔记,以”认真的态度“去面对学习,结果学习还是很吃力。其实,学好一门课的重点根本不是一些形式上的东西,不是一个主观上努力认真的态度。要取得更好的成绩,首先要改变思考方法,要去主动地学习;其次,一些技巧还尤为关键,比如获取前几年的题目。凭着主观的意识,去在意那些自己以为重要的东西,取得了不好的结果,最终只能怪自己没有早点看清完成目标的过程中,最重要的某些因素。

有了目标(一个目标函数),有了重要因素(目标函数中权重较大的变量),现在就要考虑计划了。事实上,有了重要因素,计划是一件很显然的事,不如说重要因素的提炼就是为了计划。还是拿刚刚的例子,你知道取得好成绩,要去获取前几年的卷子,你去努力向学长学姐要题目就行了。

准备工作结束,在开始做事情之后,我会不自觉地,以批判的角度看待自己的每一个行为。我会去考虑诸如这样问题:我的每一步行动是否能让我更靠近目标?我是否有的时候在凭借着主观看法一厢情愿地做事?我的哪些做法是不够好的,可以改进的?我是不是做错了一些事?

以上是我的”过载思考“的一个大致介绍。之所以取这个名字,是因为在完全开启这个能力的之后,我会消耗大量脑力在推进任务进度之外的思考上,大脑有一种过载的感觉。过载思考帮助我成功完成了很多事,避开了许多弯路。但是,有的时候我过度使用这种思考方式,导致了很多负面的结果。

过载思考的弊端

犹豫

使用过载思考时,要花很多时间在准备上,这意味着不能及时地开展行动。而有的时候,时间又是任务中重要的限制因素。比如在棋类比赛中,先有一个总的保留用时,再会对每步棋进行读秒。如果思考时不注意时间,因为时间的限制而导致思考中断,只会导致产生极差的做事成果。

另外,有些任务随着时间的变化,其性质也会发生变化。这种事情一般发生在抢占资源的任务上。比如你看上了一个人,思考太久,结果别人都结婚了。由于思考得太久,任务本质发生了变化,使得你不得不又去重新思考,陷入了一种无法开始做事的死循环中。这样往往会导致机会的错失。

自责

我在使用过载思考时,会批判地看待自己。结果我经常会产生某些不必要的,对于过去已经无法改变的事情的批判。比如一手牌打错了,我会对自己说:”这也太菜了吧。“一些事情做得慢了,拖到ddl前才完成,我会想:”我为什么这么拖拉啊。“这些想法是完全没有意义、没有建设性的,不能对未来产生任何利处,只会凭空损害自己的内心,让自己产生负面情绪。

其实,我完全知道这种现象产生的原因:在一件事做的不够好的时候,人会下意识地感到难受。为了掩盖这种难受,人会产生一些习惯性的应对方法。对我来说,所谓的自责,只是掩盖这种难受的下意识反应。这可能和我童年生活有关:我小时候有事情做好时会挨骂,以至于长大后事情没做好后,会自己责怪自己。我潜意识里虚伪地认为,自责就能让自己看上去意识到了自己的错误,能让自己更好一点。但从理性的角度来看,自责没有任何意义。

大脑崩溃

人的大脑是是有极限的。我从短暂的人生中学到这样一件事……越是工于心计,越会发现人的大脑是有极限的……除非超越人类。

当我也想不做人时,却发现自己大脑崩溃了。

过载思考,意味着需要对任务建模,深刻了解任务的一些细节、原理。当这个任务过于复杂,超过了某个阈值后,大脑就无法处理了。这和电脑内存不够大概是一个道理。这样的例子不太好找,我举一个可能不容易理解的,在我身上发生的例子。我在高考前,发现自己经常考试时间不够。于是,我开始尝试对考试用时安排进行建模。我去思考了一个叫思考效率的东西:人在刚开始思考的时候,思考不是很集中,思考效率不是很高。当思考一段时间后,思维逐渐集中,思考效率较高。真正的思考成果,是思考效率在思考时间上的积分。不同学科的不同类型的题目,有着不同的思考效率-时间曲线。但是,这里面的工作量实在是太大了,而且很多东西是不能量化的,只能是定性地看待,我的大脑完全处理不了这么多东西。结果,我想了一些很没用的东西,对我的高考一点帮助也没有。

大脑崩溃可能会产生严重的后果。如果在一件无关紧要的事情上崩溃,可能只会让我放弃这件事。但最近,我在一些重要的事情上产生了崩溃。结果我突然完全丧失了思考和行动的能力,陷入了不知所措中。好在我吃了个饭,扩充了大脑内存,立马kill了之前的进程,想出了暂时解决问题的方法。

自我怀疑

我一直依靠我的过载思考完成重要任务,以至于我忽略了这种思考方式不是万能的。一旦我没能成功地完成任务,我就会下意识对自己进行全面的否定。一旦陷入了自我怀疑,就会害怕思考,从而失去了正常工作的能力。

解决方法

其实,从弊端的分析中,基本就能得到解决方法了。针对每种弊端,给出对应的方案即可。

为了避免犹豫,应该把时间纳入考虑范围内,不能不加约束地过分细致地计划事情。

为了解决自责,要从理性上认识到自责的原因,克服长期以来的潜在的思考方式,不去做没有意义的事情。如果自己想被责怪,就痛痛快快地骂自己一顿,再向前方看去就好。

为了解决大脑崩溃,首先要意识到世界的复杂性,不要考虑把什么事情都建模清楚。再考虑事情之前,要先考虑事情的规模,考虑可行性。之后,在具体执行任务的时候,可以不看那么远,先尝试把任务拆成能力范围内的子任务,先把子任务完成,再考虑进一步的事情。

为了解决自我怀疑,只需要告诉自己过载思考不是万能的即可。一两次的失败,不能代表这种思考方式的错误,反而代表自己有更多的提升空间。