All posts by BlueCocoa

我們的敵人是遠比病毒可怕的愚昧和壞

我最早聽聞有武漢肺炎病毒「2019-nCoV」的時候,大約是在 2019 年 12 月初,當時在推特上已見到有不少人討論。然而那個時候國內還是摀得嚴嚴實實,到後來還有“闢謠”,甚至還逮捕了 8 個“散佈謠言者”。

同時,當有人在某網站上提問“武漢肺炎”時,該網站直接刪除掉用戶提問,而理由則是狗屁不通的“違反社區管理規定”

今天再回過頭來看這些,真的不禁讓人想問責武漢市的 CDC 和警方,為什麼沒有在第一時間通報疫情,為什麼民間自發傳遞信息第一時間想到的是“謠言”、“抓人”、“刪帖”?

不僅官方渠道失職,民間渠道被噤聲,甚至武漢市還舉辦“萬家宴席”!

四萬餘家庭的話,即便按每家去 2 人來計算,也有將近 8、9 萬人。這要是在“瘟疫公司”遊戲裡發生的話,玩家大概玩的是「白痴」難度,這種規模的活動傳播起病毒來真的不能再棒了!

官方自以為聰明的瞞報、謊報,權力不受約束的警方,沒有衛生健康常識的民眾,他們為這次病毒的傳播提供了絕佳的溫床。哪怕十多年前發生過「SARS」,這個國家從官方到民間不但未見得有總結多少經驗,反而相比以前更差了 —— 當有人在微博上紀錄下自己家人的不幸感染了這次的冠狀病毒時,被網絡暴力指責“製造恐慌”,被迫道歉。

在寫這篇 post 的時候,丁香園上的數據為「确诊 443 例 疑似 151 例 治愈 25 例 死亡 9 例(其中有 99 例确诊暂未明确地区 )」

世界數據如下

本以為現在 PRC 中國迫於國際壓力公布之後,疫情情況的公布會有所改善,然而

「我建議你自己查」

愚昧和壞遠比病毒可怕。

我们可以有无任何监管或审核的自由 App Store / Market 吗?

其实很早以前就考虑过这个问题了,我们能否构建一个没有任何监管/审核的自由 Store / Market?(「政治审核」类无论何时我都认为有多远就该滚多远)

其实听起来真的很棒,开发者做出来了好的应用,马上就可以 deliver 到用户那里去,没有任何的限制,也不需要等待。但是我们无法回避的一个问题则是,现实里并没有那么美好 —— 哪怕现在就把中国从地图上抹掉,做二次打包植入恶意代码盈利的也大有人在。

写的软件质量糟糕其实都是小问题,大问题是用户数据的安全。毕竟恶意代码的目的包括但不限于 blackmail、未经授权的转账、窃取用户隐私数据,无论是 iOS 还是 Android,都不可能是没有任何漏洞的。难道我们要把一切都简化为一句

“安装此软件造成的一切后果均由用户自行承担”

我相信我认识的和不认识的绝大多数做技术的人,只要想的话,写一个完全没有任何审核的自由 Store / Market 绝不是难事。无监管或审核的自由 Store / Market,对于开发者来说必然是非常快捷、便利的,但因此带来的对安全的考量就转移到了最终用户身上 ——

我们假设,开发者 A 做了一个很有特色的 X 软件,或者本身就是做了一个 X 软件的美化版 / 增强版之类的,并发布在了某自由 Store / Market 上;随后开发者 B 下载、植入恶意代码、二次打包后也发布了上来(可能还改个名之类的)。这个时候普通人应该如何分辨?

  1. 靠评论或者下载量?可是评论、下载量必然也是有黑产可以代刷❌
  2. 靠开发者可信度认证?但那不就是有一定程度的监管了吗❌
  3. 靠开发者 GPG 公钥签名?拜托,普通人连使用协议都很少有去看的,更别说对于他们而言过于专业的 GPG 签名验证❌
  4. 靠实名制?有了这个根本就不可能叫自由 Store / Market 了吧❌
  5. 靠口碑 / 懂这些的朋友?听起来真的很原始,但也许还算可行🤔
  6. 靠自己?以后普通人都自己去应用的官网下载,可那样的话,自由 Store / Market 又有什么存在的意义呢🤔

如果说自由 Store / Market 只是给一小部分人的话,那么现在 iOS 上可以在 Jailbreak 后自行安装,部分 Android 手机 root 后随意安装,部分 Android 本来也就没限制。

对于我们做技术相关的人来说,自己区分是否包含恶意代码还是可行的。然而,程序员以及技术爱好者不过是所有用户中的一部分,剩下很多人完全不懂的,哪天中招下载了包含恶意代码的软件,由此带来的物质 and / or 精神上的损失又该怎么办呢?

还是中国人最擅长骗中国人的钱。

到了迪拜之后的第二天,早上一上车,导游就说在阿联酋这边,有他们国旗的地方都不能拍照,因为一部分是政府机关,一部分是国营企业所以才挂国旗。一开始我也没有细听细想,现在回想起来,这便是骗局的伏笔了。

Continue reading

记得很久之前就看到有人说很多中餐(尤其是重庆、湖南、四川这一带)都是靠海椒的辣味掩盖食物本身的味道,于是今天再次体验了一下这一点。

在坐 Air China 去迪拜的时候,晚餐选了鱼肉饭,果然跟上次坐 Qatar 航空去多哈的时候提供的餐食完全没法比——

Air China 上的晚餐就是很普通的锡箔纸包装的,揭开之后也不过零星的有 3、4 块小小的鱼肉。

入口之后,辣,dominate 了味蕾的感受,只留下了我的视觉和舌头的触觉告诉我这大概是鱼肉。咽下之后能够回想起来的也就是海椒的辣带来的刺激。

辣,只不过是众多味道中的一种,但是却被很多中国人吹上天,从“无辣不欢”到“吃嘛,不辣”。以及出了国就是带/买上“老干妈”,然后还顺带嘲讽别的地方的食物——“不就是那样吗”。可是他们却不曾想过,他们自己喜欢的食物按照他们自己的标准来评价的话,也不过是“不就是辣吗”。

现在吃到中餐里辣的食物的时候,我觉得“辣”已经取代了很多食材本身的味道,食材几乎只剩下了口感。就像我今天在 Air China 上吃的那一盒鱼肉饭一样,就像我平时在大陆吃到的那些以辣为主的菜一样。

而将“辣”使用到极致的便是火锅。说实话,偶尔吃一次牛油火锅也还是会觉得很好吃,但是在追求舌尖上的刺激的同时,火锅对味蕾的伤害也不可忽视。很多人说吃得辣是训练出来的,在某种程度上并没错,毕竟味蕾都被辣椒素干掉了,自然也就觉得别的菜都吃不惯或者吃不来了。

Arduino 的小板子 PORTENTA H7

这两天 Arduino 出了一个新的小型嵌入式开发板,Portenta H7 https://store.arduino.cc/usa/portenta-h7

性能上的话,CPU 是一个 Cortex [email protected] + Cortex [email protected] ,两个核心用 RPC 机制通信,可以跑 MicroPython / JavaScript,还有 TensorFlow Lite

个人觉得亮点 1) 是有双频 Wi-Fi(虽然最大速率只有 65Mbps),可以当成 AP / STA,这样一来的话,2.4GHz 和 5GHz 的 Wi-Fi 抓包和嗅探应该是可以了。

亮点 2) 的话就是终于可以直接用 Type C 了,并且还支持 DisplayPort。

最后亮点 3) 就是 Portenta 系列会有 2 个 80 pin 的高密度连接口,扩展性似乎比以前我用过的 Arduino 系的板子的好不少。

不过 $99 的基础款只有 2MB 内存,16MB 的 NOR Flash 存储空间。官网上倒是说最大可以配置 64 MB 的内存和 128 MB 的 QSPI Flash,不过并没有给价格。

就算是这样,99 美元也蛮贵的了,完全可以买一个 Raspberry Pi 4 Model B 4GB 版了,当然,tradeoff 就是 Raspberry Pi 相对而言没有这么小。这个怎么选就看使用场景了

略有一点点种草,要是什么时候有闲钱和时间倒是想试试看这个

从零开始的 Rust 学习笔记(18) —— Rust Script Runner

Rust 并不能像 Python 那样有全局的 Package(当然,现在就算是写 Python,也很少有谁一上来就往全局环境里安装 Package 了),于是 Rust 要想单独运行一个引用了第三方库的 Rust script 时,就必须用 Cargo 创建一个 project。

绝大多数时候这个倒也是能接受啦,但是有时真的只是想在一边测试一个小的 function 或者验证一下自己的想法。如果直接在 working-in-progress 的 project 里写的话,就可能

  1. 不得不配合已有的部分做一些 error handling
  2. 或者手工测试到该条代码路径上
  3. 又或者写上相应的 unit test

显然只是想快速验证一下的话,上面三种方式都有不便之处。如果单独再用 cargo new 一个 project 的话,也不是不行,但是懒(

在用 Code Runner(对你来说也许是 VSCode 之类的)的时候,直接新建一个 Rust file 开始写会相对方便。假如我们的 Rust script 叫 example.rs,那么要引入第三方 crate 的话,比如引用 regex,我们可以用这样的语法,

// cargo-deps: regex="1"

如果要控制 crate 的 feature 之类的,则可以写

// cargo-deps: opencv = {version = "0.28", default-features = false, features = ["opencv-41", "contrib"]}

虽然并不能实现 Python 那样的全局 package,但是我们可以用代码扫描 exmaple.rs 里面所有的 // cargo-deps: {:dependency},然后自动生成一个 example 目录和相应的 Cargo.toml 文件,接着将 example.rs 文件复制到 example/src/main.rs, 最后自动调用 cargo run

Continue reading 从零开始的 Rust 学习笔记(18) —— Rust Script Runner

It's time to 2020

上一次寫這個還是在 2017 年?!It's time to 2017

不過正如 17 年那篇的我說的 —— 与其总是怀念过去,不过着眼于未来。

我其實還蠻想專門寫一篇 post 來說明這個觀點的。總是有不少人在懷舊,可是懷舊的人有沒有想過,他們用來懷舊的今天,很快就會成為他們明天懷舊的內容。

想想看未來的事情,2020 年想做些什麼呢?想要自己的生活變成什麼樣子呢?沒有什麼不敢想的,要不然來年這個時候就又該感懷了。

17 年說了要學習 Golang,結果因為各種機緣巧合,這幾年來都沒時間自己學新的東西,是時候為自己著想了;今年 9 月底開坑的 「從零開始的 Rust 學習筆記」基本上就是為了自己而寫,17 年後半到 19 年後半寫的絕大部分技術類的,都是為了別人。

現在回想起來,同樣是凌晨 3 點半,在 LA 的我只感覺傷心難過;而三個月後,坐凌晨末班火車從劍橋趕往倫敦的時候心裡卻滿是高興。

凌晨幾乎空無一人的 ThamesLink

無論如何,我做不到自欺欺人,何苦讓自己強顏歡笑。

最後借用內田彩「キックとパンチどっちがいい?」的幾句歌詞

最高の幸せを
最棒的幸福
私は手に入れてみせるの!
我会得到给你看 !

Now, it's time to 2020!

从零开始的 Rust 学习笔记(17) —— 做一个提问箱 Boxy ?

于是我自己的提问箱就是 https://ask.cocoa.me.uk

源代码在这里 https://github.com/BlueCocoa/boxy ?(给大家表演如何用一个项目气死你写前端和 Rust 的朋友(x

回答是人工的,并不是人工智能?(不过感觉加上人工智能的话似乎也蛮好玩诶!

然后因为是模仿 peing 或者 sarahah 那样的匿名的提问箱,所以这个截图里就差不多是后台了~既然说是匿名的话,那就是真匿名~除了记录了提问时间,IP 只用来做了速度限制,没扔数据库。Question 在 MongoDB 里的模型则是

{ 
    "_id" : ObjectId("5e0a0ca0001cd1ea00876f2e"), 
    "question" : "What's this?", 
    "question_time" : NumberLong(1577716896), 
    "answer" : "It's a question box!", 
    "answer_time" : NumberLong(1577716906), 
    "id" : NumberLong(0) 
}

箱子的 owner 登录之后,直接点击答案的部分就进入编辑 / 回答模式,然后也可以删除提问什么的~

代码里倒是 Web 和纯 API 方式都实现了~然后回答之后自动生成 Twitter 卡片什么的还没有做_(:3」∠)_

再从技术层面上来说的话,这个项目用 Rust 上的 Hyper 作为 HTTP 服务器,然后数据库使用了 MongoDB,考虑到是作为单一用户的提问箱,因此用户名和密码是需要写在 boxy.json 里的~当然放进数据库里的是密码加盐后再 HMAC + SHA512 过的

stored_password = HMACSHA512(password + password_salt, password_salt)
{
    "_id" : ObjectId("5e09a250004b7da70096e3dc"),
    "user" : "cocoa",
    "password" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Web 界面和后端做了分离,我自己写了一个简单的~因此就直接手写了,没有用到现在前端流行的 Vue 或者 Angular 之类的框架。访问后端的 API 也是直接用的 jQuery 的 AJAX 模块。

登录之后会返回一个 token,包含了有效期,默认是 365 天,保存在了浏览器的 localStorage 里。登出的时候会自动清掉,但不是关闭页面 / 浏览器自动清除,所以在公用电脑上要记得登出~

那么下面是目前有的一些 API~ 以 http://localhost:5534 为例子!

Continue reading 从零开始的 Rust 学习笔记(17) —— 做一个提问箱 Boxy ?

从零开始的 Rust 学习笔记(16) —— K-means 模版

重构一下前两天用 Rust 写的 Colorline 中 K-means 聚类算法的部分~因为之前 kmeans 放在了 dominant_color.rs 下,显然 kmeans 这个算法不应该属于 dominant_color;同时,之前的 kmeans 算法只能用在这里,考虑到以后代码复用的话,当然是要写成模版啦╮(╯▽╰)╭

kmeans 独立出来之后,让 kmeans 可以接受任意 impl 了 KmeansComputable trait 的类。其实很久以前用 C++ 也写了一个比较通用的 K-means 模版,但是当时并没有考虑 trait 这样的,而是直接用了两个回调函数(不过写完这个 Rust 版本的之后似乎突然有点思路了)

Rust 这个写起来思路很清晰,首先就是 kmeans 函数应该接受:

  1. 一组待聚类的数据 array
  2. 要求聚类的的类数 k
  3. 收敛条件 min_diff —— k 个类每次迭代后各类中心点移动距离的上界

其中,array 应该是 KmeansComputable 的。

那么 KmeansComputable 这个 trait 的设计的话,第一点显然是要可以给出该类任意两个 instance 之间的距离;第二点则是可以在给出一组该类的后计算其中心点。也就是

pub trait KmeansComputable {
    fn distance(&self, other: &Self) -> f64;
    fn compute_center(cluster: &Vec<Self>) -> Self where Self: Sized;
}

于是 kmeans 函数如下~(高亮的部分则是用到 KmeansComputable trait 里要求实现的函数的地方)

Continue reading 从零开始的 Rust 学习笔记(16) —— K-means 模版

从零开始的 Rust 学习笔记(15) —— Colorline

在圣诞节的时候一个人回顾了一下 LoveLive μ's 3rd Live,然后就做了这个~从视频里提取每个时间点的主要色彩并生成一张大的图。

假如视频时长为 01:43:25,每隔 1 秒计算一次其画面的主要色彩,并且在画出高度为 120 像素,宽度为 1 像素的 colorline 的话,就可以组成下面这样图的啦~

那么理论上长度就是 103 * 60 + 25 = 6205 秒,也就是 6205 像素。但是实际上需要注意的是 FPS 的获取,视频时长的计算和如何选择帧。因为视频的 FPS 可能并不是一个整数,而是类似 29.97 这样的浮点数,可是在 OpenCV 里并不支持按秒读取。于是只好先获取视频的 FPS 与 FRAME_COUNT。

但是如果直接 as i32 的话,原本 29.97 的 FPS 就会变成 29,那么显然对视频时长的计算就会出错,6205 * 29.97 / 29 = 6412 秒,足足多了 207 秒!(那么问题来了)同时也会导致生存的图片在时间轴上不够精确。因此在代码上需要注意一下~

那比如在 3135 像素附近,开始出现了很多蓝色的条~粗略算一下也就是视频的 52:15 附近,这个显然就是「賢い、かわいい —— エリーチカ!」

(下面两张截图是在对应的区段里随便找的~并不是严格对应到起点的)

然后到了 3310 像素附近是大段连续紫色出现的起点~也就是视频里 55:10 的地方~「希パワーたーっぷり注入 はーいっプシュ!

那么实现起来的话,也并不算是很复杂~项目的源代码在 GitHub 上~https://github.com/BlueCocoa/colorline

1. 整体思路

首先需要视频文件的路径,然后用户要求每隔多少秒计算一次画面的主要色彩。接下来打开文件之后,计算视频以秒为单位的长度以及按照要求的话,需要产生多少根 Colorline。

随后启动一个抽取视频每一帧的线程,Video Extraction Thread。因为视频并不能真正的随机访问某一帧(可参考视频编码原理, P 帧、I 帧的概念等),故将会顺序遍历一次所有的 frame。

接下来到了应该被抽取出来计算主要彩色的帧的时候,为了利用好 CPU 资源,肯定会放到别的线程上去计算,不会放在 Video Extraction Thread 里做 。但是每到一个都另起一个线程的话,显然一会儿线程的数量可能就爆炸了~

那么我们就用一个线程池来做。这个线程池里的每一个 worker 接收的参数是一个需要计算的画面,以及这个画面对应的 Colorline 的 index。一个 worker 一次只负责计算某一帧的主要色彩,然后将计算结果与对应的 Colorline 的 index用 channel 送回。

为什么不直接画在 Mat 上?理论上这些线程访问的内存资源都不会有冲突,但是 Rust 里 OpenCV bindings 的 Mat 是不能在线程之间共享的(要么就一路 unsafe 走起,但那样似乎不如直接用 C++ 写了)。

所以对于线程池 worker threads 的计算结果,我们需要另外 spawn 一个 Worker-Thread Gather Thread,它负责收集所有 worker 计算的结果。如何判断收集完呢?我们已经有了视频长度与用户希望的间隔时间,那么提前就可以计算出一共会从 channel 中收到多少 message。

最后,用户可以 poll 我们的计算结果。如果已经计算完成,但是还没生成最后的图,那么就生成好 Mat 然后返回;如果还没好的话,就告知 InProgress;要是中间什么环节出错的话,就返回 Err

好了,基本的想法确定下来了,下面就可以开始写大坑了(*^3^)

Continue reading 从零开始的 Rust 学习笔记(15) —— Colorline