0%

从去年搬家以来,我就保持着极简的局域网环境。光纤入户后,用客厅里的路由器拨号上网,除了和路由器放在一块的nas以外,其他所有设备,包括电视、电脑以及其他智能设备,都是通过wifi接入,结构上非常扁平,简单。而且过去的这一年多时间里也很少遇到实际问题,就一直这么用着。

虽然大问题没有,但是我也清楚这套结构是有很大的不足的。主要是受限于单路由器的信号限制,放在书房的主力电脑,连接到路由器的速度大概只有10~20MB/s。虽然在重启或关闭wifi重新打开后,速度可以短暂的恢复到60MB/s,但是之后又会降到不足一半的程度。我推测的原因是书房和客厅之间虽然没有实体墙堵着,但是书房的门如果关上了,对于信号敏感的5G频段的wifi还是会受到干扰,导致降速。除此之外,像笔记本电脑连着wifi备份也会碰到网络断开导致备份失败。因为我差不多一周才会把电脑带回来备份一次,备份失败多少还是有点影响的。

而促使我下决定进行调整的直接原因,则是偶然发现当电脑和nas之间进行大文件传输的时候,网络延迟已经会严重影响游戏上的延迟时间了。看起来内网的带宽已经比连接公网的带宽可能还小了,这确实太影响上网体验了。

这里简单说下网络环境,如图所示:

img

虽然各个房间之间与弱电箱都通过交换机布置了网线,但是遗憾的是每个房间都只有一条网线,包括客厅。因此如果要保持由客厅的路由器拨号的话,那么是没有第二条线能把信号回传给交换机,再与各个房间连接的。对于网络拓扑我也是个外行,大概了解到有两个办法可以解决,一个是客厅里加一个交换机,设置vlan;另一个是把弱电箱的交换机换成路由器。前者实施起来有一点门槛,后者则受限于弱电箱的格局,要加设备也比较困难。

在网上发帖求助之后,看到一个有意思的方案。就是把客厅的路由器升级成mesh wifi路由,并在每个需要较高带宽的房间里单独部署一个mesh路由。好处自然是部署简单,而且完全不受环境约束。当然mesh路由也支持无线回程,如果路由器之间还是通过网线连接的话,带宽和延迟保障会更好一点。但是这个方案最吸引我的地方还是在于可以全部使用无线的方式解决问题。某种意义上,这也算是以科技进步的方式,影响了房子的物理结构吧。

整个过程实施起来也很方便,我从京东上下单了两台小米的ax6000路由器作为试验。先用一台测试取代了现有的主路由进行测试。wifi的连接速率在60~70MB/s左右,和我原先的路由器在刚连接上wifi时差别并不大。接着再激活第二台路由器,并配置好与第一台路由器结成mesh网络,整个过程操作都比较简单,照着说明书操作即可。配对好以后把第二台路由器放到书房里并接好电源。这里我分别测试了电脑与路由器通过wifi的方式和网线的方式连接,两者相差不大,实测从nas上拷贝文件,速率在110MB/s左右。考虑到这可能已经是受限于nas硬盘的读取速度了,我觉得这个速率毫无疑问已经是让我满意的了。后续如果卧室或其他房间对于网速较高要求的话,也都可以考虑通过这种方式解决。

对于网络的稳定性我也在如此布置了一两周后又测试了几遍,速率依旧能稳定在110MB/s左右,没有像之前的路由器一样出现降速的情况,稳定性上来说,也算是满足了我的需求。

值得一提的是,由于路由器之间依旧是通过无线连接的,因此在位置的布置上就比较随意了。客厅的路由器依旧受限于与光猫之间的连接,因此我放在了电视机柜上,也还算比较隐蔽。书房里的路由器则被我放在书架的底层。我个人很喜欢这种把这类基础设施类设备摆放隐蔽的做法。毕竟我需要的只是网络信号,而不是在家里哪个显眼的位置摆放一个祭台,像供奉神灵一样把wifi信号请来。mesh路由对于布置位置的随意性也是相当好,只要能接上电,放在哪里都行。我想这类设备最好的做法可能是像电灯一样装在天花板上。不过眼下这样一台可以随意摆放的设备也已经能满足我的需求了,就不多折腾了。

起因

我第一次接触量化自我是在2010年的时候,读到李笑来先生的《把时间当做朋友》,里面提到的《奇特的一生》这本书,书中记录了柳比歇夫是如何几十年如一日地将自己的时间开销以数字化的方式记录下来的。当时出于好奇进行了实践,采用与柳比歇夫类似的方式,把时间开销详细地记录下来。

说实话这并不是一件很容易的事情,实践过后会发现很多问题。我自己总结来说,最大的问题在于这种记录方式对于精神的集中度要求会很高,在进行一些纯粹的工作时也许影响不大,但是在进行很多琐碎事情时,很容易让人举步维艰。

举例来说,如果一天的时间只投入在5~10件事情上,那记录的成本就很低,诸如投入一到两小时以后,回过头来记录,1小时35分,写博客。但是如果投入的事情很多,时间比较发散,没有计划性,那这件事情的成本就会很高,每5~10分钟就要回过头去想想自己刚才在做什么。

最终我的第一次尝试在实践了大约2周时间后终止了。因为在这个实践的过程中,自己反而被锁在了时间的牢笼里,每分每刻都在想着自己要做什么。一旦规划好了,就投入到目标的事情上去。等事情做完,又想着马上该接上的下一件事情是什么。

这次尝试终止以后,很长一段时间里我没再把这件事情当回事。对于一般的任务管理来说,采用GTD工具对我来说已经够用了。直到工作后的一些年份里,我开始意识到时间的开销管理开始成为一个问题,我觉得有必要像管理收支一样,对时间的开销进行记录。于是我又回到了量化自我的实践上来。

当然,实际上量化自我所收集的数据,并不仅限于时间的开销,我整理的方法也会包含其他各方面的数据。只不过对于时间的记录和分析,是我做这件事情的初衷。

收集数据

线上时间开销数据

在现在这个时代,人们花在线上,或者说屏幕前的时间真的越来越多了。这也使得这部分的时间统计变的非常的简单。

针对桌面端来说,有非常多的应用可以自动记录时间的开销。目前来说,在pc和mac上,我都会使用RescueTime这个应用。只要设置成开机自启动,这个应用就可以在后台静默地记录所有的时间花费。(除了桌面端以外,也支持android。)另外,除了记录,RescueTime提供的在线服务还可以查看时间开销的统计报告,其中比较有价值的部分在于,它还会自动根据活动的类型进行(积极/消极)分类。

RescueTime还提供了付费服务,会有更多的时间管理和数据管理上的功能。不过这部分我没有接触过,出于对数据自主性的考虑,我写了一个简单的程序,每月通过RescueTime开放的API接口,把自己的数据下载回本地,以csv文件存储,并保存在私有的代码仓库里。

它的缺点在于,本地运行的静默程序不包含任何数据交互的功能,所有的数据都是上传到服务器上,在web端才能查看。如果觉得个人私密数据不想上传,ActivityWatch是一个可以考虑的替代品,不过我没有具体用过,不好对其功能做具体评价。

另外如果使用的是纯粹的苹果生态圈,也可以考虑系统自带屏幕时间(ScreenTime)。缺点是对于第三方浏览器(chrome、edge、firefox),无法统计到具体网站,只有自带的safari是会将花费在浏览器上的时间,穿透到具体站点上。这一点对于通常使用定制app的移动端无所谓,但是对于浏览器使用较为频繁的桌面端还是有些影响。假以时日各大浏览器若是都能支持屏幕时间的话,倒是一个不错的方案。

另外,使用系统自带的屏幕时间,想要获取原始的数据,也是需要费一些手段的。这个项目似乎可以做到这一点,不过根据sof上的这个帖子的说法,可能还需要关闭SIP才行。一个更合乎常理的途径是通过这个地址直接向苹果索要数据,不过我尝试了两回向苹果索要数据,其中并不包含屏幕时间的数据。

对于移动端,在android上我也使用的是RescueTime。不过即使有精神洁癖不想用,问题也不大,android系统本身也会记录应用的使用时间,想必找个法子记录在本地或者是导出都是比较简单的。

至于iOS系统,和上面提到的mac一样,虽然有现成的系统功能会记录这些数据,但是却无法便利的拿到。因为我目前iOS设备使用并不多,所以在这件事情上并不是太关注。后续如果需要的话,我打算的做法是用一台旧iOS设备越狱,通过iCloud同步主设备的数据,然后从文件系统中拿到相关数据,以此来尽量避免对日常使用的影响。不过即使如此,通过iCloud同步的数据依旧存在因越狱引起泄露的风险。最好还是由苹果提供这些数据,毕竟这下数据本来就属于用户,希望以后苹果能完善这块数据的供给吧。

线上其他数据

数字世界的数据,除了上面这些常规的记录以外,另外一项补充的是截图。在Windows上我使用TimeSnapper,而在mac上则是之前提到过的一个自己编写的定时脚本,并不是多么特别的功能,不过作为对于数字记录的补充,有时候留有几张简单的截图,能够更好的理解自己在那个时候在做什么。

移动端似乎没有类似的软件,确实是一个遗憾。不过我想android端倒是存在自己编写程序进行记录的可能,有必要的话倒是什么时候可以试着写一个。

线下时间开销数据

线下的记录不像线上那么方便,可以自动记录,返璞归真的做法当然可以用纸笔。稍微便利一点则是可以用手机app。

android上我长期使用过的是TimeMeter,通常我会给事前就做好分类,设置成桌面快捷方式,在开始前点一下,结束的时候再点一下,就完成了基础的记录,需要有额外的信息则再添加一点备注。

ios上与之类似的app是TimeLogger,虽然iOS本身不支持设置快捷方式这种操作,不过这款app搭配apple watch使用的话,倒是可以做到不用解锁手机就能进行时间记录。而且某种意义上,这似乎也确实是手表应该具备的理所应当的功能。

运动与生理数据

在可穿戴设备丰富的今天,这些数据的收集也变成理所当然,甚至可能是量化自我的一大重点。我个人从最早的智能手环时代开始就有使用穿戴设备,到目前为止,华为的智能手表gt2在产品层面上是足够让我满意的。能收集的数据包括运动记录(我常用的是室内室外跑步、游泳、骑车、划船),睡眠时间,日常热量消耗,心率(并不是很准确)等等,应该算是覆盖全面了。而手表相对较好的续航能力,也使得我不用时常操心什么时候要给手表充电。这一点在我原来使用apple watch时是个麻烦事,频繁的充电使得我必须把充电时间选择晚上,但这么一来就会使得睡眠的记录变的不完整。

值得一提的是,华为提供的数据记录app运动健康本身并不支持数据导出,我不知道是出于封闭生态圈的考虑还是什么。当然也有一些hack的手段,比如github上的这个项目有提到。不过由于华为官方提供了用户自主索要数据的通道,我个人更倾向于采用这种合乎规矩的做法。唯一的缺点就是从提出申请到提供数据,通常需要等待一周左右。从我个人经验上来说,比向苹果索要数据要短一点。另外从两家公司的实际做法上来说,华为在个人数据的保护上似乎也做的更妥当一些。在提出申请时,会需要用户提供账号密码、手机验证、邮箱验证、身份信息验证等四道验证。在提供的数据包上,也会要求用户提供密码进行加密保存。

具体数据的索要方式是在app中按账户中心->隐私中心->获取您的数据副本。获取的数据相当齐全,不过由于是json格式,在作为分析数据前,通常还需要进行一些处理。

财务收支数据

我个人认为这是挺重要的一个数据,至少在财务自由之前,我还是关心钱赚了多少,花了多少的。

比较意外的是,我发现要做这个统计并不容易。举例来说,我能不能通过支付宝账单统计我花了多少钱呢?不能,因为支付宝上的钱有的来自银行卡,有的来自信用卡,有的来自花呗,有的来自余额宝,还有的钱我是不通过支付宝花出去的……而有时候通过支付宝的支出是偿还信用卡,有的是消费,有的是转账,这其中关系错综复杂,而更关键的问题,支付宝是不会记录这笔钱从哪来,花到哪去的……

最终为了终结这些混乱的资金流向,我对个人的资产账户做了一系列精简,包括关掉花呗,关掉不必要的信用卡,互联网金融产品等等。最终只保留一个存款账户作现金收支,以及一张信用卡作为信用消费使用。

这样一来,我只要简单的统计存款账户的资金流向,和信用卡账单,就可以统计到全部的收支情况。所幸这两项数据,在招行的专业版网银客户端上都可以比较容易的拿到。

最后的记录则是通过简单的数据清洗脚本,转成标准csv文件,在经过人工复核(主要是确认开销类别)后,再通过定制脚本,转化成beancount账本。

beancount是一个用于复式记账的领域专用语言,对于复式记账,这里就不展开介绍了,有兴趣可以自行了解一下。单纯的使用来说,并不需要太多的财务知识,使用beancount的好处,我个人认为就和使用markdown写作一样,数据的自主性较好,仅此而已。

当然在财务这块,我个人感觉做的还不是太好。就目前来说,我的记录效果也仅限于能够分析自己当前的净资产状况是怎么样的,过去几个月里自己的开销主要是什么。我个人更希望能够将数据用于对于未来开销预算的分析上,不过因为每个月都有些不同的开销和收入项目,感觉并不是那么容易。

分析与回顾

除了财务数据是单独使用fava这个专用于beancount的可视化工具,以及截图这种非常规格式数据以外,剩余的数据在收集完之后,都是按标准化格式进行保存,以便于分析。

这里我使用的分析工具是tableau,这是一个专用的BI领域工具,不过在数据分析领域也非常合适。相对于制作固定的可视化图表,使用BI工具在进行数据分析上更具有灵活性,可以快速制作图表,必要时可以针对特定数据进行定制分析。早先我也试过使用excel,主要缺点在于每个月制作新的excel文件需要花费一些精力,而tableau的dashboard在制作好以后,后续只要将新的数据文件存放在特定的目录下,就可以自动读取、展示。

缺点的话,一方面是软件本身的授权价格并不便宜,如果制作的图表或是仪表盘想要在移动端查看,那还需要单独部署 tableau server,server端的授权本身就是一笔不小的开支,而要支撑这种量级的软件部署,服务器的开销也并不小。而若是使用公开的tableau public则必须将数据公开才行。由于这类数据涉及过多私人数据,我个人并不太倾向于在公开网站上发布。

好在即使只在桌面端使用,对于我的分析场景来说,也已经足够了。我通常会每月进行一次数据收集和清洗,并进行分析和回顾,一方面看看有没有什么可以改进的地方,另一方面也是让自己重新感受一下,过去的一个月我都做了什么。

前言

大概两年前,vscode推出了以SSH为基础的远程开发插件,当时还只是略作了解,然后拿来作为远程文本编辑的一个备选项。相当于是ssh环境下vimemcas的一个替代品。作为GUI基础的文本编辑器,在访问未经配置远程环境下,相比使用全默认配置的vim还是要更好用一点,不过也就仅限于此了。

最近翻出来研究的主要原因,是自己的办公电脑配置慢慢跟不上开发需要。虽然4c16g的配置不算低,不过办公机需要开启大量的网页窗口、各类文档、IM软件等等,再加上IDE和大量依赖服务,而且本身所处的开发环境还需要运行额外的监控软件、防病毒软件等等,实际在开发过程中和代码编辑时还是能明显感受到拖慢的。测试一个完整的java项目编译,server端2分钟可以跑完,本地要跑上十几分钟,确实浪费了不少生命。于是一不做二不休,捣腾一下全给搬到server上,通过远程访问方式进行开发。

总结一下的话,远程开发带来的好处大概有这么几个

  • 更可控的机器配置,云端虚拟机可以比较方便的做在线扩容。目前我使用的配置是8c32g,如果有需要完全可以扩到32c256g甚至更高。
  • 便利的环境快照。基于虚拟机的开发环境,折腾前先做个快照,发现什么问题可以随时回退。或者机器更换,基于快照恢复也很方便。
  • 纯原生的linux环境。没啥好说的,可以完整使用linux的开发生态。使用docker镜像啥的也比较方便
  • 上下文环境保持。因为server端基本可以一直挂着不用关,不用烦恼每次重启后又要打开一大堆调试窗口或者是启动一堆背景服务。

另外还有一些可以算作远程开发的优势,不过在我的使用场景下不涉及的

  • 对客户端性能要求较低,具有一定移动开发友好度。比如在阿里云上申请台高配server,然后本地使用mba或者surface开发,也基本不会受限于cpu或内存等硬件桎梏。
  • 低成本的多设备切换。在公司开发到一半了,出差或回家了,用笔记本远程连上就可以继续工作。当然这点要看具体的网络环境了。

至于缺点也有一些,不过我觉得核心就是一条,就是vscode作为一个主打文本编辑器的轻量级开发环境,能否满足开发效率上的需求。

我个人感觉还是有点难度,当然其中也有一些使用习惯上的问题。比如对于版本管理,我个人更习惯intellij中提供的version control界面。基于命令行的tig或是vscode 的插件git lens,个人习惯上还是觉得差一点。

另外对于java项目来说,idea的代码补全(code complete)还是要比vscode更好一点。虽然现在新一代的基于AI模型的代码补全工具在很多时候确实也非常好用,不过短期内感觉还很难取代传统基于语言模型的代码补全。当然最致命的一点是,由于java ee项目的技术栈深度问题,诸如mavenspring bootjunitlombok等等,idea已经提供了很完备的插件生态来支持开发,而vscode在这方面则多还处于起步阶段。

总体而言,给人的感觉是,在使用IDE时,通常开发者不需要对其背后的运作原理有太深入的理解,只需要专注在自己的代码上就好了。而基于vscode的开发环境,则需要开发者对代码的编译、调试、打包等流程都需要有一定了解才能顺利进行。

如果上面说的这些都不成问题,那么搭建一个server端的开发环境还是非常实用的。

环境搭建

前置准备

这里要准备的东西倒不是整个开发环境必备的,倒不如说是作为开发环境,我个人习惯会准备的一些开发工具。

  1. zsh&oh my zsh

    习惯使用zsh,自然是第一时间会装上

  2. tmux

    基本是server环境下必装的,用于会话管理和终端窗口管理,也是减轻vscode本身的terminal在窗口管理上功能孱弱带来的影响。

  3. docker

  4. pet

    因为windows端我没有用类似dash这样的工具管理命令行snippet,因此选择在linux环境装了pet用来做日常命令的收集。

  5. tig

    虽然vscode本身也有git插件,不过纯粹的命令行交互式的git工具在很多时候还是很方便的。

  6. Ncat

    比较通用的网络工具,在远程环境下我拿来作为一个比较简单粗暴的转发工具

  7. Ant、maven、gradle、java、gcc、python等开发工具

说实话,如果有人能把这些开发环境统统装好打成镜像,我觉得还挺有用的。不过就算自己准备也不算太费事吧,linux环境下通过包管理工具通常都可以很便利的装好。(我使用的server环境是rhel7.4,因此使用的是yum进行包管理)。

vscode remote安装

本身remote插件的安装其实很简单,在vscode中打开extension然后搜索Remote SSH就能装上。需要额外一提的是,本身这个插件是作用于本地的vscode的。对于server端而言,还需要单独安装vscode-server。当然如果是处于互联网环境的话,那么第一次使用vscode连接到远程服务器的时候,就会自动去下载server端安装包。

不过如果你的server是处于非互联网环境下,那么注意这个安装过程虽然不能顺利进行,但是可以通过提前下载部署server包来跳过,具体可以参考这个帖子。**另外需要注意的是,vscode-server版本是要与vscode匹配的,因此如果vscode更新过了,那么也需要注意更新vscode-server**。

除此之外,基于remote模式,vscode只有界面渲染等与UI相关的部分是在客户端执行的,编辑器内的计算部分是在服务端完成。因此,想要使用vscode的插件,需要在server端安装才能正常使用。

除了基础的编辑、保存等功能外,remote模式提供了一个非常实用的功能就是端口映射。这是由于,因为编辑的代码都是实际存储在server端,编译和调试自然是在server端进行。那么比如我在开发一个前端项目时,写完代码想在界面上调试看看效果,那么自然是要npm start把网站跑起来。可是此时网站是启动在server上,虽然应用会提示说请通过localhost:xxxx来访问,但实际在本地自然是访问不到的。remote插件提供的端口映射功能,就是通过侦测远程端口的开启情况,将本地对应的端口映射到远程端口上,来达到本地访问远端服务,实际体验上就和在本地调试一样。

另外就是我在上面提到的装的ncat。这是因为某些代码需要访问本地客户端的一些服务,距离来说,由于某些原因,我没有在server端安装mysql数据库,此时server端访问本地的mysql就会访问不到。于是我干脆通过ncat --sh-exec "ncat <ip> <port>" -l <port> --keep-open把server上访问mysql的请求转发到本地。这当然不是必须的,不过在过渡期懒得折腾的时候倒也能省不少事。

IDEA&Rsync

虽然理论上采用这种方式,就可以把代码全部搬到server端进行开发。不过正如上面我提到的,使用vscode进行开发,在java ee这种特定场合下,效率上还是不如使用ide来的更方便快捷。于是我想到一个折中妥协的法子,就是在本地用ide开发后,将代码同步到服务端进行编译调试。实际体验中,采用rsync进行同步,其效率还是很高的。

对于单纯的代码同步,rsync命令如下

rsync -avz --filter=':- .gitignore' --delete -e "<ssh_path>" -r <source_dir> <user>@<server_id>:<target_dir>

除了第一次代码的全量同步以外,rsync在后续的同步中会自行进行增量对比,整体效率还是相当不错的。使用.gitignore文件则是把编译、构建过程中不必要的文件排除掉,某种程度上也能加快同步效率。

后记

这通折腾我大概在3月中旬的时候折腾完毕,把整套开发环境顺利搬到了服务器上,也顺利用了近一个月。

意料之外的是,想不到就在我折腾完没过两周,jetbrains就发布了projector产品,同样是通过远程渲染的方案,将基于swing的IDE工具搬到server上,而本地只需要通过浏览器或瘦客户端来访问。

可能未来的趋势,就是IDE慢慢云端化吧。我个人倒是还挺待见这种趋势的,有空的话再折腾一下部署projector,若是可以把idea也搬到服务器上,本地开发就可以彻底成为过去式了。

最近碰到一个蛮有意思的问题. 我原来在windows平台下有使用一款叫timesnapper的软件. 这个软件的作用是可以在间隔一段固定的时间后做一次屏幕截图, 将图片保存下来. 我用这个功能主要是用来做时间开销的记录. 此外, 之前在hackernews上看到过一个有意思的帖子是对比20年来的桌面的变化. 所以我觉得定期记录一下自己的电脑也算是蛮有意思的一件事情.

不过在我换到mac平台的时候, 没有找到一款类似的软件. 于是我写了一个很简单的定时脚本, 每隔几分钟调用一下macos自带的截屏工具保存图片. 这里的代码很简单, 大致如下

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
#!/usr/local/env python
#coding=utf8
import datetime
import subprocess
import os


def main():
path = '/Users/xdsoar/Documents/ScreenRecord/'
time = str(datetime.datetime.now())
date = time[0:10]
month = date[0:7]
path = path + month + '/'
newPath = path + date + '/'
if not os.path.exists(path):
os.mkdir(path)
if not os.path.exists(newPath):
os.mkdir(newPath)
time = time.replace(' ', '_').replace(':','.')
file1 = newPath + time + "_1.png"
file2 = newPath + time + "_2.png"
file3 = newPath + time + "_3.png"
param = "screencapture -x " + file1 + " " + file2 + " " + file3
print("calling the cmd" + param)
subprocess.call(['/usr/sbin/screencapture', '-x', file1, file2, file3])

if __name__ == '__main__':
main()

写完以后放了一个定时任务在crontab里就算完工了. 这几年来一直运转正常. 不过有一个小毛病过去倒是一直困扰着我. pc上的timesnapper有一项功能就是检测到画面没有变化时, 就不会截屏. 我猜测原理可能是识别如键盘、鼠标的活动时间, 或者干脆就是识别画面. 这个功能可以节省非常多的冗余图片. 而我自行写的这个截图小程序自然是没有这个功能的.

于是这个周末稍微花了点时间研究了一下图像查重的方法. 很容易查到了一个叫做ssim的算法, 用于判断图片之间的相似度. 这里的相似度是基于结构相似度, 虽说可能和我的场景不是完全一致, 不过实际测试过以后发现还是比较符合预期的. 基于方便的原则, 还是用python写了一个, 基于opencv的ssim实现

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
import os
from skimage.measure import compare_ssim
import cv2
import cProfile

def get_file_list(path):
files = os.listdir(path)
gray_files = []
file_compare_result = []
times = 0
for file in files:
image_file = cv2.imread(path+'/'+file)
gray_image = cv2.cvtColor(image_file, cv2.COLOR_BGR2GRAY)
is_dedup = False
for i in range(0, len(gray_files)):
gray_file = gray_files[i]
if gray_file.shape != gray_image.shape:
continue
(score, _) = compare_ssim(gray_image, gray_file, full=True)
if score > 0.99:
gray_files.pop(i)
gray_files.insert(0, gray_file)
is_dedup = True
break
file_compare_result.append((file, is_dedup))
if len(gray_files) > 10:
gray_files.pop()
gray_files.insert(0, gray_image)
print('done once ' + str(times))
times += 1
return file_compare_result



if __name__ == "__main__":
cProfile.run('result = get_file_list("/Users/xdsoar/Documents/project/image-dedup/sample")')
print(result)

这段代码里用了cProfile, 是因为写完以后对性能有点好奇, 不知道是花在图像加载上, 还是实际的ssim算法上. 跑了一下发现基本都是花在图像加载上. 因此代码里对于图片的比较稍微做了点优化. 虽然说只比较前后两张图片的话, 时间复杂度就会很美好了. 不过实际上会遇到一种情况就是电脑锁屏以后的待机画面经常被不连续的截进来. 因此还是希望尽可能放大对比的范围. 当然如此一来, 时间复杂度就可能上升到o(n²). 对于本身就比较耗时的ssim算法而言, 时间开销就会相当庞大.

顺带一提, 我原来在mac上运行这个代码的时候, 基本上cpu都被吃满了, 因此我推测ssim算法是可以用到多线程的. 于是我尝试同样的代码和文件搬到了台式机上, 因为台式机用的是8核cpu而笔记本2核, 预期是可以有数倍的性能提升的. 结果台式机上运行反而慢过笔记本, 而且cpu并没有吃满, 只使用了10%左右. 这里我的猜测是笔记本上的opencv可能是使用了pyopencl, 用核显做了加速. 而台式机因为显卡是nvidia的, 可能并不支持加速.

于是我开始考虑如果能使用nvidia的显卡(我用的是1060)来做计算的话, 或许还能快一些. 于是开始找支持n卡加速的ssim算法库. 看了一圈发现pytorch支持n卡的cuda框架, 并且也有ssim算法的实现. 于是几经波折在pc上装上了pytorch. 代码也改成了如下版本

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
import os
from pytorch_msssim import ssim, ms_ssim
import torch
import numpy as np
from PIL import Image
import shutil


def get_file_list(path):
files = os.listdir(path)
gray_files = []
file_compare_result = []
times = 0
for file in files:
if not (os.path.isfile(path+file)):
continue
image_file = Image.open(path + file)
size = image_file.size
resize_image = image_file.resize((int(size[0]/2), int(size[1]/2)),Image.ANTIALIAS)
image_array = np.array(resize_image).astype(np.float32)
image_torch = torch.from_numpy(image_array).unsqueeze(0).permute(0, 3, 1, 2) # 1, C, H, W
image_torch = image_torch.cuda()
is_dedup = False
for i in range(0, len(gray_files)):
gray_file = gray_files[i]
if gray_file.shape != image_torch.shape:
continue
score = ssim(image_torch, gray_file)
if score.item() > 0.99:
gray_files.pop(i)
gray_files.insert(0, gray_file)
is_dedup = True
file_compare_result.append((file, is_dedup))
if len(gray_files) > 100:
gray_files.pop()
gray_files.insert(0, image_torch)
print('done once with ' + str(times))
times += 1
return file_compare_result


def compare(file1, file2):
ima1 = to_cuda(Image.open(file1))
ima2 = to_cuda(Image.open(file2))
score = ssim(ima1, ima2)
print(score.item())

def to_cuda(ima):
image_array = np.array(ima).astype(np.float32)
image_torch = torch.from_numpy(image_array).unsqueeze(0).permute(0, 3, 1, 2) # 1, C, H, W
return image_torch.cuda()


if __name__ == "__main__":
path_arg = "C:\\Users\\qwerp\\Documents\\source\\dedup\\sample\\"
results = get_file_list(path_arg)
os.mkdir(path_arg + 'dedup')
for result in results:
if result[1]:
shutil.move(path_arg+result[0], path_arg+'dedup/'+result[0])
print(results)

结果是速度上确实有了非常感人的提升, 体感应该有5到10倍吧. 以后如果换用更好的显卡, 应该会有更好的效果. 另外在做优化的时候小开了一个脑洞, 用PIL将图像长宽都缩小到原来的一半, 结果不仅在计算速度上有非常大的提升, 而且显存能存储的图片数量也是有了一个量级的提高. 是的, 这里依旧限制了只保存100张图片进行对比, 原因就是将大量图片转换成向量以后保存在显存里还蛮大的, 未缩减大概是单张80mb. 最后的对比结果也基本符合预期, 重复的图片都找了出来. 只是这里阈值设置低一点的话(比如95), 那么一些稍微有点区别的截图也会被抓进来. 很难说哪样取舍更好. 我想最终我会使用的方案是删掉那些完全重复(重合度超过99%)的截图释放空间, 而把那些高重复度的截图单独建目录存放.

做完以后我又想到一个点子. 同样用ssim算法, 不知道能不能找出所有截图里最另类的一批? 因为图片存着以后我还是会定期回顾的. 但是大量图片的回顾确实也非常累, 如果能从大量图片中找到相对高价值的那些, 应该能节省不少精力. 关于这一点, 接下来有时间我想我会琢磨琢磨.

发布地址, 以及github的链接.

本身是在这个月初的时候做的东西, 初版发布的时候还在nga发了帖子. 起因是dnf新的100级版本是个深渊版本, 想大致算一下需要投入的时间.

周末某天早上的时候突发奇想, 用python写了点代码用蒙特卡洛来算概率. 算了几组以后发觉对界面操作的需求还挺大的, 于是决定gui重构一版. 因为要画界面, 因此也就不想用python了, 于是盯上了vue. 本来是打算用vue+electron做一个跨平台的客户端版本的. electron也一直是我想了解的一项技术, 用vue只是单纯觉得更轻量一点, 上手可能可以快一点.

最后整套技术栈用的就是vue+typescript+electron(可有可无). 实际开发中基本感知不到electron, 包括调试和发布最后其实也只是基于传统web的形式. 开始趟了点小坑, 用的electron-vue的模板, 结果这玩意连启动hello world都会报错……后面改用vue-clielectron-builder, 总算是顺利多了. 改typescript稍微花了点时间, 主要是vue本身的教程都是基于js的, 用ts的思路来理解的话多少有点不顺, 这篇博客看看还是蛮有收获的.

最后发布用了和博客类似的基础设施. 只不过之前博客用的是hexo的s3插件, 这次是更通用的npm插件, 以后如果有类似实验性质的项目, 应该都可以用类似的方式来发布. 我特意做了这个lab的二级域名, 也是希望以后能再往里面填一些东西. 因为原来用的就是s3, 所以这次发布也还是在s3, 然后用cloudfront做加速, 国内访问确实算不上非常快, 后面有机会再看是不是迁到阿里云之类国内的服务商吧. 本次部署的时候遇到了一些坑, 基本都是我在这篇博文里提到的, 想不到完全适用, 还是挺意外的.

发布以后我想着针对像多选, 更复杂的计算条件做了一些优化. 不过本身这个应用的使用场景还是蛮有限的, 后续是否继续更新, 就看心情了…..不过作为vue的上手项目, 还算不错.

我一直习惯用笔记本外接dock的用法,特别是最近几代的笔记本(不管是pc还是mac)配备了thunderbolt3接口以后。tb3接口的高带宽,实现了单一接口外接多个高分辨率显示器,使用体验上感觉还是非常好的。像是我带笔记本在公司里用。只要在工位上放置一个tb3的扩展坞,使用的时候只要连接一根tb3数据线,就能满足电脑充电、外接显示器等功能。外设的连接,像鼠标和键盘当然也是可以的。不过因为这样一来桌面的线材会非常多,所以键鼠一类低速设备我还是倾向于使用无线的。

不过这种使用方式在我的实际使用中存在一个不大不小的痒点。就是在笔记本连接了扩展坞以后,显示器并不能自动的从台式机切换到笔记本上。而且自动切换也不现实,因为设备当然不知道我什么时候想用笔记本,什么时候想用台式机。在只有单个屏幕的时候,这件事还不算很麻烦,我只要通过显示的OSD菜单切换输入源就可以了。但是当我连接了多个显示器以后,这个问题就愈加麻烦。直到某次在论坛偶然看到有人提起DDC协议,我才知道这个问题是可以解决的。

简而言之,显示器的数字接口(如DisplayPort和HDMI),是可以双向通信的,因此从主机向显示器发送命令也就是可行的。DDC协议也就因此应运而生。像笔记本上的功能键控制屏幕亮度等功能,其实本质上也是通过这个协议完成。知道了协议以后我就去找了找是否有现成的软件。还算顺利的找到了。

Windows下我使用的是一款别人推荐的免费软件ClickMonitorDDC,有图形界面,也有命令行接口。虽然CLI命令和常用的UNIX系命令不太像,不过使用上还算比较简单。以我用的LG显示器为例,要将输入源切换成HDMI,可以用命令ClickMonitorDDC_7_0.exe s LGUltraHD HDMI1。显示器的命令可以事先通过图形界面的控制面板拿到。最后我选择的用法是将切换命令存到我的AHK脚步里,实现一键切换。

Mac下则是搜索到了github上的一个开源项目,ddcctl。需要自行下载源码编译安装。编译安装好了以后,就可以通过如ddcctl -d 1 -i 15的命令,将指定显示器切换成指定的输入源。对比windows平台的软件,有一个小的不足是显示器是通过编号识别的。但是我实测通过tb3连接外接显示器时,每个显示器的编号并不固定,想要准确切换还需要自己写脚步识别每个编号的显示器具体是什么。当然如果每个显示器的连接方式都一样,那就没有这个烦恼了。Mac上的快捷工具我习惯用Alfred,因此就把相关的命令写成Alfred workflow来用就可以了。

一些小坑

  1. Dell的显示器(以我用的U2312为例)只能接收当前输入源的主机输入的命令。举例来说,我的台式机通过HDMI连接显示器,笔记本通过DP连接。当显示器输入源为HDMI时,只有台式机发送的DDC命令才会被执行。反之,当输入源为DP时,只有笔记本发送的命令会被执行。
  2. 几年前买的EIZO显示器,不支持DDC命令,那自然是没有办法控制了,比较遗憾。我觉得以后我会把这一条放在购买的要求里。如果不支持DDC,可能就不会考虑购买了。
  3. Mac我有两个dock,在家里用的是thinkpad的tb3 dock,公司里是belkin的tb3 dock。其中通过thinkpad的dock连接显示,ddcctl会提示发送命令失败,而belkin的则没有问题。本身显示器端的问题我也已经排除了,是支持ddc的。看来应该是dock本身对ddc支持的问题。本来买thinkpad的dock是出于兼顾window和mac的考虑。如此看来,以后还是要针对专项的笔记本选购最适用的dock才行。如果下一台笔记本还是用mac的话,就要考虑把家里的dock换掉了。

前阵子游戏上录了几个视频, 传到b站, 顺便学了点视频编辑和压制的知识. 主要是x264ffmpeg相关的. 不过浅尝辄止, 这部分就先不展开说了.

由于b站最高能支持1080p的视频上传, 而录的游戏视频原始分辨率是720p的. 720p一方面本身分辨率不如1080p, 另一方面对码率的限制更严.(1080p能上传6k码率的视频, 720p最高只能上传3k码率). 根据我压视频的经验, 码率上升带来的画质提升是大于分辨率上升带来的负担的. 因此有条件当然是传1080p的更好.

这就涉及到视频upscale的问题. 几年前用过waifu2x做一些图片的放大, 效果还不错, 一些比较老的低分辨率图片放大到高分辨率后可以拿来做现在显示器或者手机的壁纸. 不过视频的upscale我推测和单纯图片的放大还是不太一样, 因此还是趟了点坑.

拿自己录的原始720p视频作为素材, 第一步是拆帧. 因为waifu2x本身只支持图片放大, 所以需要把视频先逐帧拆成图片. 可以用ffmpeg拆, 也可以用adobe premire. 如果视频需要先编辑的话, 建议就直接用pr编辑完导出图片序列了. 纯拆帧用ffmpeg也很快, 命令格式如下ffmpeg -i <video_file_name> <image_name>_%05d.png. %05d是指定图片序列的名字, 如果视频很长的话, 也可以将位数扩长. 当然图片太多的话, 本身用waifu2x也会有性能问题.

接着就是导进waifu2x里面跑了. 因为之前我下载的是带gui的版本, 直接选择图片所在目录, 指定渲染的参数就好了. 作为参考, 我用的放大2倍, 开3级降噪, 对720p图片做放大, 速度大约是1秒1张. 2分多钟60帧的视频, waifu2x渲染完大约用了1小时50分钟, 显卡是gtx1060 6g版本, 应该说还是挺久的.

最后在pr里导入图片序列再合成视频即可. 因为是图片合成, 声音自然是没有的. 将原先视频的声音合并进来就好了, 这一步不管用pr还是ffmpeg都很快. 最后再导出视频, 这一步又会花不少时间. pr的h264实现据说效果不如x264. 但是无损格式导出实在太大了. 暂时没有配置帧服务器, 所以我是先用pr导出码率较高的版本, 再用x264压制一遍上传的版本. 两步各会花费视频时间2~5倍的压制时间. 因为视频不长, 所以还算可以接受.

渲染完的视频可以参考这个链接.waifu2x渲染测试视频

一些坑

waifu2x渲染本身对于图片质量有一定要求, 如果图片很糊的话, 渲染效果会比较差, 因此就对视频源的质量有比较高的要求. 哪怕是本来实际观感还可以的视频, 如果本身码率不足, 拆完的图片有一些细节丢失的话, 对渲染的画面会有比较大的影响. 也就是说, 对于低分辨率低码率视频->低码率图片->waifu2x渲染->高码率高分辨率图片->高码率高分辨率视频, 最终出来的高分辨率视频, 观感不会很好.

实际测试下来, 比较适用的场景是低分辨率高码率视频->高码率图片->waifu2x渲染->高码率高分辨率图片->高分辨率高码率视频->高分辨率低码率视频. 这种路径是是适用的, 最终压制完上传到视频网站的视频可以保持如6k这种方便网络传输的码率. 但是原始视频需要的可能是高一个数量级的码率(比如这里我的测试视频使用了60k的码率).

另外一个有意思的现象是, waifu2x对图片做降噪的时候, 会有明显的画风变化, 产生类似”油画感”. 这种风格对于动画风格的图片, 不得不说还蛮受用的, 在很多场合下效果都不错.

不过这种变化随着图片分辨率的上升会呈下降趋势, 对于720p的图片, 降噪后”油画感”很明显, 升上到900p, 乃至1080p后, “油画感”逐渐消失. 对同一张图片, 原始分辨率是1440p, 逐步下降下720p, 对比比较明显. 参考下图. 第一张是原图, 第二张是经过waifu2x渲染后的. 因为上传博客原因, 尺寸统一缩到了720p, 不过画风变化还是可以直观看出来.

frame_09196

frame_091962

个人猜想是因为随着分辨率上升, waifu2x在渲染时能补充的细节变少, 因此这种画风变化也逐渐变少. 如果比较想要这种滤镜效果, 且原始图片(视频)分辨率较高, 那么还要做一次downscale. 这方面也有一些不错的算法, 不过我暂时也没空折腾这些, 就不展开讲了.

依旧是上次的python 项目, 周末抽时间把流水线建了, 顺便加了一个badge. python项目的流水线相对简单, 对于编译流水线来说, 其实只要跑一下测试就好了.

这里我用的测试框架是pytest, 以azure流水线为例, yaml内容如下

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
trigger:
- master
- dev
- azure

pool:
vmImage: 'ubuntu-latest'
strategy:
matrix:
Python36:
python.version: '3.6'
Python37:
python.version: '3.7'

steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
displayName: 'Use Python $(python.version)'

- script: |
python -m pip install --upgrade pip
cd taskcommander
pip install .
displayName: 'Install dependencies'

- script: |
pip install pytest-azurepipelines
pip install taskcommander/.[test]
pytest --cov=./taskcommander/
displayName: 'pytest'

- script: |
pip install codecov && codecov -t $(CODECOV_TOKEN)
displayName: 'upload code coverage'

azure对于python项目支持多版本测试, 写一套流水线用多个python版本运行, 如上面的yaml中就写了3.6和3.7两个版本, 2.7因为一开始就没想着支持, 3.5因为对于type hint的支持不太一样, 因此这两个版本就去掉了.

步骤是非常简单的三步

  1. pip install安装python包(其实不需要发布的话这步骤也可以去掉, 不过我觉得安装也算是一个测试环节吧)
  2. pip install安装pytest以及测试需要的包, 并运行测试
  3. 将代码覆盖率(code coverage)上传到codecov.

每个步骤都很简单, 就不展开讲了. 唯一需要注意的是上传codecov中用了一个环境变量. 这是因为流水线的配置yaml一般是写到代码库(github)上的, 一般不推荐把诸如用户/密码等敏感信息写到代码库里去, 因此用环境变量的做法更合适一些. 而且azure pipeline支持定义密码类环境变量, 对于此类变量, 在pipeline的输出日志里也会隐藏, 提供更好的保密性.

配置好以后, 每次提交代码到masterdev分支就会自动触发流水线运行, 如果运行报错, 也会有邮件提醒. 另外, 配置了codecov以后, 创建pr时也会自动提醒合并代码会带来的覆盖率变化.

最后是给项目readme加上codecov的badge, 个人感觉算是展示项目一个比较实用的badge. 在readme里加入类似以下内容, 就能正常展示badge了.

1
[![codecov](https://codecov.io/gh/xdsoar/TaskCommander/branch/dev/graph/badge.svg)](https://codecov.io/gh/xdsoar/TaskCommander)

每个项目的badge可以在登录codecov后, 在项目的settings->badge中找到, 不需要自己敲.

GTD, 或者说todo类的产品, 是我一直有频繁使用的一类应用. 不管是哪个平台, 从桌面系统windows\mac到移动端ios\android, 我都有不少尝试. 其中不乏佼佼者, 遗憾的是总是因为跨平台的一些原因, 在这个平台上好用的应用, 换了平台要么不好用了要么就是根本没有.(比如仅限mac系统的omnifocus, 仅限windows系统的mlo).

于是这次筹划干脆自己写一个吧. 初期打算先写一个cli版本的, 有时间了会再研究用诸如electron来写一个跨平台gui版本. 当然移动端依旧是个问题, 这个问题就交给以后去烦恼了.

这个应用的架构会按照这一年里实践了很多次的洋葱架构来写. 关于洋葱架构, 以后有时间会具体来写.

这次主要搞定的是python项目发布的问题. 既然是做成cli应用, 那么能用pip安装自然是最好的.

python的项目结构参考了https://python-packaging.readthedocs.io/en/latest/minimal.html这里的说明. 最小化发布至少需要有一个setup.py文件, 其中包含如发布的名字, 版本号, 依赖等信息. 这里的依赖配置还没研究透, 原来的习惯是写一个requirement.txt文件, 然后在部署的时候pip install, 不过这种做法对于发布成pip包的应用大概不是很友好.

发布的环节与上面网页上描述的略有不同. 因为在使用sdist upload时看到提示建议使用twine来做upload. 因此发布前先pip install twine, 然后使用twine uploa命令发布制品. 发布前需要先去pypi网站上注册账号, 然后才能发布.

我从去年回来玩DNF开始, 就经常录制视频记录自己的游戏经历. 特别是在换了电脑, 录制视频的性能开销基本影响不到游戏体验以后.

途中也更换了好几次录制软件, 因此记录一下工具链更迭的经历.

最初的选择 windows game bar

最开始我选用的是最简单的方案. win10系统自带的游戏录制功能. 这个功能本身还是比较良心的, 特别是在系统更新到1903以后, game bar有了非常丰富的功能. 不过仅针对游戏录制这一点, 不得不说还是有所不足.

首先是录制的码率可选的范围比较有限, 通常需要录完以后再自己用x264之类重新压一遍. 多次压缩对于视频质量也是一点折损. 此外对于音频也没有太多的选项, 反正有声音就录下来了.

另外对于我录DNF来说, 还有一个额外的不足是它只能录制单个游戏窗口. 因此当我使用游戏的外置聊天窗口时, 聊天窗口是无法被录进来的.

这个方案我一直用到今年三四月份. 直到我开始为游戏竞速比赛录制视频时, 才逐渐感觉到工具的不足, 遂开始找其他替换方案.

更强的NVIDIA experience

因为电脑配的显卡是N卡, 因此我很自然的开始用N卡自带的游戏工具来尝试录屏. NVIDIA experience基本延续了windows game bar的体验, 提供的选项非常有限. 不过功能上还是要强一些.

首先他提供了全屏录制的功能, 因此我不用再为聊天窗口无法录制烦恼. 其次是提供了非常丰富的视频码率范围选择, 可以比较容易兼顾画质和文件尺寸.此外还可以为麦克风设置独立的音轨, 可以把游戏声音和语音区分出来.

不过NE的功能虽然更强, 但依然有几个美中不足的点. 其一是虽然麦克风的声音可以被单独分到一个音轨上, 但是语音中听到的声音, 依旧和游戏(或者说整个电脑)的声音混杂在一起. 我有时会把录完的视频上传到视频网站, 但包含语音的话就不太好, 一般来说这时候我希望上传的视频只包含游戏本身的声音. 我在网上搜到过一个方案, 能将语音聊天双方的声音按左右声道放到一个音轨中, 但我尝试后发现还是没办法把声音从游戏声音的那条音轨中去掉.

另外一个点是由于全屏录制的缘故, 所以视频录制的对象是屏幕, 而不是窗口, 当我的游戏画面被别的窗口(比如网页或者聊天窗口)覆盖的时候, 这一情况也会反应在我录制的视频里.

此外因为录制是全屏的关系, 但实际游戏窗口大概只是整个屏幕的3/4, 因此录制完, 如果需要上传到视频网站的话, 我还需要用pr对视频做一定的裁剪, 使得视频的整个编辑流程变的很长.

最终选择 OBS

最后我在上个月开始使用OBS, 也就是我现在使用的方案. OBS是一款有一点历史的软件, 我在很久以前也用过. 也正是因此, 我对当时的OBS录制的视频画质有一点反感.(因为本质上OBS是一款同时面向视频和直播的软件, 相对画质, 会更注重录制时效性和性能).

不过可能是因为与NVIDIA合作的关系, OBS在今年起支持了NVIDIA的编码器NVENC, 使得录制的画质有了很大的提升. 我在实测以后也觉得画质完全没有问题, 甚至相比NVIDIA experience还提供了更低码率的选项, 同时画质也还不错.

此外OBS支持多窗口录制, 因此我终于可以以窗口的形式录制游戏本体和聊天窗口, 并且不用担心游戏被其他窗口覆盖.

多音轨也有了更好的支持, 我可以将麦克风、游戏音效、游戏语音分别设置成不同的音轨.(因为测试NVIDIA experience的关系, 我在使用OBS前已经安装了voice meter banana. 使用虚拟设备将游戏音效和语音分到了两个独立的虚拟设备上).

因为NVENC提供的编码策略已经足够高效了, 因此我已经可以直接将录制好的视频上传到视频网站且不用担心视频被二次压缩. 因此这条工作流甚至还省却了视频编辑的不少事情.

我还是很看好windows game bar未来的发展, 可以将语音聊天, 视频录制, 推流等功能都收纳进来. 不过就目前而言, 对于有复杂录制需求的场景, OBS显然还是最好的选择.