AI 绘画发展的真快啊

AI 绘画的发展速度比我预想的还要快。几个月前,Stable Diffusion 发布的时候,它们训练了一个通用模型,什么类型的图片都能生成,但无论什么类型都是马马虎虎。比如,对于人像的生成,多数都很模糊,甚至走样,只能看个大概。但是这几个月来,针对各种特定目标的模型都被训练出来了。这里当然少不了专门生成帅哥美女的模型,此外还有针对卡通、国画、油画等等各类绘画风格训练的特化模型。除了模型,各种辅助工具也被开发出来了,比如有些工具可以让用户摆放几根线段,代表人体骨架的姿势,然后模型就可以按照这个姿势生成人像。这就实现了对构图的精细控制。精细控制,配合上某明星的订制模型,就可以生成这个明星的任意姿势的照片了。

AI 绘画工具的出现对于我的帮助还是非常大的:我经常希望在自己的文档中添加一些插图,但是却没有绘画的能力。现在可以让 AI 来帮我画了,就算没那么完美也不要紧,有就比没有强。

半年前,AI 绘画勉强可以达到实用水平的时候,从新闻和论坛里看到的绘画从业人员的普遍反应是抵制。随着 AI 能力的加强,从业人员更多的是表示担忧了。我作为非专业的人员,可能体会不到那种危机感,但我还是觉得画师们并不需要太过担心的。在专业领域,那些老板们,无论有什么样的工具,也不可能自己去动手操作的,还是需要有专业人员来操作,差别无非在于一笔一笔得画,还是一条指令一条指令得画。工具毕竟是工具,新的工具不会取代人,只是会改变人们生产的方式。

被改变的可能不止是生产方式,或许还会有审美标准。AI 不但可以生成以假乱真的真人照片,还可以生成并不真实存在,但却是人们心目中的完美人像。当下,对大众审美影响最大的是明星们。将来会有越来越多 AI 生成的虚拟明星们出现。真人明星们就算化妆、整容、开滤镜,也不至于和真人容貌差太多,虚拟明星们长啥样就很难预料了。

我的 Facebook 上经常会收到骗子的好友申请。由于工作相关,我还曾经仔细研究过几个骗子的页面。在社交媒体上有很多知名度极低的小网红们,经常性的发布自己的生活照片视频等。骗子们会把这些内容收集起来,发布在一个虚假的账号下面,把这个账号伪装成某个帅哥美女的账号,然后行骗。估计以后骗子们的手法也要升级了,不需要再去收罗真人的内容,用 AI 生成全套的内容就可以了。

广告

AI 生成的一些图片

这几天在电脑上运行了 Stable Diffusion 玩了玩。这是我正的测试页面:https://qizhen.xyz/genimg

这个模型比 Dall.E 的小很多,所以才能在配置不高的个人电脑上跑。而且,我的电脑也只能勉强生成 512*512 的照片。虽然效果可能不如专业网站用的大模型,但有些作品已经很逼真了。我主要试试了生成真实人像以及和二次元人像之间的转化。Diffusion 模型不太擅长逼真的人像,程序生成的人像使用 GFPGAN 改善过的。在人像中,个人感觉这个模型最擅长生成白人年轻女性的照片。我也尝试了让它生成一些中国人的照片,但感觉都不是那么美,实际上多数生成的中国人照片都看起来比较别扭。很可能是因为它的训练集中就有比较多的白人年轻女性照片,或者也可能是因为我生长在中国,对中国人的面貌更敏感,更容易发觉异常。

先贴两张 AI 生成的仿旧照片,看起来还真挺像真的:

接下来几组图片,都是首先生成了左边的真实照片风格的图片,再根据照片转换成了右边二次元图片:

下面这两组图片是先生成的左边的二次元风格图片,再根据它生成右侧的真实照片风格图片:

可以明显感觉到,生成真实照片要困难很多。可能是因为人脑对所谓“真实”照片更挑剔吧。

下面两组是风景图片,感觉两种风格生成出来的图片区别远没有人像的区别那么大

Scratch 编程语言 2

很早之前,大约是两千零几年的时候,我曾经看别人演示过 LabVIEW 专为儿童教育,以及乐高玩具开发的特别版本。对于少年儿童来说,图形化编程比文本编程要更有吸引力。可惜的是,LabVIEW 起个大早,赶个晚集。现在再提起儿童教育或玩具领域的图形化编程语言,多数人只会想到 Scratch。

Scratch 是 2003 年才诞生的一个新语言。它能够一出现就挤掉 LabVIEW,迅速占领整个儿童教育领域,主要因为具有以下一些优点:

  • 开源。Scratch 是由 MIT 开发的,它从一开始就采用了开源的策略。儿童使用的编程语言,一个重要的功能是控制各种玩具。反过来说,能被玩具厂商广泛采纳的编程语言,会更容易被推广开来。玩具厂商想把自己的产品与 LabVIEW 结合,甚至直接发布一个定制版的 LabVIEW 成本是非常高的。即便是在工业界,LabVIEW 具有统治地位的测试测量领域,LabVIEW 也主要是与 NI 公司自己的硬件结合使用。玩具行业的利润更低,厂商们自然倾向于便宜的软件。更何况 Scratch 还是开源的,可以很方便的就对其进行改造以适应自己的产品。现在,除了最著名的乐高,很多中国厂商的玩具搭配的也是 Scratch 编程语言,比如小米的玩具。
  • 语法简单。Scratch 的语法与 LabVIEW 有着非常大的不同,它隐藏了更多的编程细节,让初学者可以更容易的入手。LabVIEW 虽然也号称容易入门,但相比比起来, Scratch 才真正算得上是“傻瓜”型编程语言。
  • 采用了更主流的技术。Scratch 是采用了 HTML 5 标准,使用 JavaScript 作为开发语言。LabVIEW 也曾经有过网页版,尽管当时已经可以明显看出 HTML 5 是发展趋势,LabVIEW 却采用了 SilverLight 作为开发平台。后来微软彻底抛弃了 SilverLight ,肯定也会对 LabVIEW 的开发推广造成影响的。

下面是一个具体的示例程序:

这段代码中的积木(一种颜色的近似长方形的一个条形块)分成了三堆,这三堆之间是并行的关系。每一堆积木都从一个事件开始。左边这一大堆是主线程,当接受到用户点击绿色旗帜的事件时开始运行,它在运行过程中会发出一些事件,去启动另外两堆积木。这段程序的主要功能是运行一个循环“repeat 20”,在循环内调用“move”功能,让屏幕上的一只小狐狸(绘制在“costume”里面)向前移动一段距离。同时还让小狐狸发出“喵呜”的声音。

从上面这段程序可以看出来,虽然也是图形化编程语言,Scratch 相比与 LabVIEW 还是有一些明显不同的。

  • Scratch 图形化方面没有 LabVIEW 彻底,它借鉴一些文本编程语言的编程方式,同时在编程时也更依赖文本。编写 LabVIEW 的程序更像是绘图,而编写 Scratch 程序更像是搭积木。
  • LabVIEW 中的基本功能模块多以函数和 VI 的形式存在,它们的外观看上去是一个个的正方形方块。LabVIEW 中的结构的外观会更复杂一些,像是尺寸可变的框架。在 Scratch 中,函数和结构都被称作 block(翻译成模块或者积木),它们看上去都是一个个长条。
  • Scratch 中由于每个积木长得都一样(或者十分类似),它只能用来文字区分不同功能的积木。LabVIEW 中推荐给每个子 VI 都绘制一个有意义的图标,这看起来当然是比 Scratch 的代码美观的多。但是,也有很多程序员非常讨厌这个规范,他们宁可把时间用于改进程序的逻辑,而不是美观程度。
  • 对于程序流程的控制,LabVIEW 使用数据线来控制,按照数据在数据线上的流动顺序来控制程序运行顺序。Scratch 没有数据线。凡是挨在一起的积木,它就是按照顺序从上至下执行每一个积木。没有粘连在一起的积木是可以并行执行的。
  • 因为没有数据线,Scratch 只能使用全局变量来传递数据。
  • 积木使用不同颜色表示不同的功能分类,比如浅黄色的用于发送接收事件;深黄色的用于控制程序流程(比如循环结构,条件结构等);紫红色的用于控制声音,蓝色的控制运动等等。
  • 用不同形状表示不同数据类型,比如数值类型数据放在一个两侧是圆弧的长条里;而布尔型数据放在两侧是尖角的长条里。这保证了程序具有一定的数据类型安全,比如某个积木上有一个两侧尖角的长条凹槽,那么在这个凹槽里就只能嵌入布尔型数据(相当于一个函数,具有一个布尔型的输入参数)。

Scheme 编程语言

Scheme 是第一门我真正系统学习过的函数式编程语言。Scheme 语言的标志是一个 Lambda 字符“λ”,一眼就可以看出这门语言的出处。Scheme 是 LISP 语言的两大方言之一,而 LISP 又是人类开发出的第二款高级编程语言(第一个是 FORTRAN)。

因为太古老,Scheme 的编程思路和现在常见的语言差距还是非常大的。我刚开始接触 Scheme 时的困惑不亚于刚接触 LabVIEW。Scheme 的语法定义是比较简单的,比时下流行的编程语言都简单得多,但毕竟也是一门完整编程语言,不可能写一小段就介绍全面,这里就只能介绍一些最基本功能了。

Scheme 直观上最明显的特点是括号多,它的所有数据(比如列表)和程序结构(比如函数、判断语句)等都被包裹在括号内,因此,一段代码里会有数不清的层层括号。在编程思想上的最大特点就是函数式编程。再 Scheme 程序中,一切都是函数。

在 Scheme 中写 Hello, world 的代码如下: (display "Hello, World!")

在这段中 display 是一个函数,用于在屏幕上打印文字,而后面的字符串则是 display 的参数。单这一句,与常见编程语言的用法差距也不算太大。

在 Scheme 语句中,函数名总是要放在参数之前,运算符也是一个函数。所以,如果要计算 “2+3”,写出来的程序是这样的: (+ 2 3) 。 Scheme 的函数很多是可以跟多个参数的,比如 (max 2 6 3) 或者 (+ 6 4 8) 等等。如果是一组数据,比如一个 list,那么就在括号前加个单引号,比如 ‘(a b c d),这就不再是函数调用,而是一个列表了。

函数的定义一般类似如下:(lambda (x) (+ x 2))。lambda 是关键字,后面跟着函数参数,在后面是函数体。在 Scheme 语句中使用关键字 define 给常量命名,使用关键字 let 给变量命名。函数也可以是一种变量,比如下面的语句就给了新定义的函数一个名字“square”:

(define square (lambda (n) (* n n)) )

Scheme 语言还有一特点就是没有循环,所有需要循环的地方都要使用递归来完成。这和早期的 LabVIEW 正相反,早期的 LabVIEW 不支持递归,所有要用到递归的地方都必须转换成循环。比如,在 Scheme 中计算阶乘,只能采用递归的形式:

(define factorial (lambda (n) (if (= n 0) 1 (* n (factorial (- n 1))))))

我在学习 Scheme 的过程中,最大的收获是把递归彻底弄清楚了。递归有时候还是比较容易绕的,比如把一个列表从头开始归并,和从尾开始归并要采用不同的递归策略。以前都没有深入考虑过,学习了 Scheme 才真正系统的研究清楚了。

Lambda Calculus 编程语言

我刚成为程序员的时候,有一次调试一段 C 语言代码。我一层一层的进入到被调用的子函数中去,想看看一个数据到底是怎么产生的。终于在遇到一个库函数的时候,调试器无法再跟踪进去了。C 语言程序通常会调用很多已经编译好的库函数,程序员只知道这些函数的接口,但看不到它们的实现代码。我知道这是处于效率的考虑,但还是忍不住想,一种编程语言,可不可以不调用任何编译好的库,所有功能都以源代码的形式提供给程序员,方便学习啊。后来进而又想,也许很多关键字,运算符都不是必须提前编译好的,有没有编程语言可以以源代码的形式提供这些关键字,运算符呢?

这些问题,当时也只是一想,没有去研究。多年之后,我在帮老婆做编程作业的时候发现,她们在课上居然学到了这样一种编程语言,叫做 Lambda Calculus。

Lambda Calculus 是一类非常精简的编程语言中的代表。这类语言中还包含 SKI,Iota 和 Jot 等,不过 Lambda Calculus 还是最经典的。Lambda Calculus 小到什么程度呢?只需要用几行文字就可以把 Lambda Calculus 的全部语法描述的清清楚楚,所以这里就介绍一下:

  • Lambda Calculus 中用到的全部字符包括:小写英文字母,英文句号,小括号和一个希腊字母 lambda “λ”。
  • 名字 name:由单个英文小写字母构成,格式为 <name>。 比如 x,y,a 等;
  • 函数定义 function:由“λ”字符跟一个变量名,跟一个英文句号,在跟一个函数体构成,结构为 λ<name>.<expression>。比如 λx.x,这个函数写成数学形式是 f(x) = x;
  • 函数调用 application:由函数定义加另一段表达式构成,格式<function><expression>。比如: (λx.x)a,这表示,把 a 作为参数传递给前面那个函数,运算结果就是 a。
  • name, function, application 又被统称为 expression。
  • 括号用于控制计算的优先级。有时代码里也会加入空格方便阅读。

以上就是这个编程语言的全部语法了。当时的要求是给这个语言编写一个编译器,其中最核心的部分只用了十来行代码就实现了,恐怕很难有比这更简单的编程语言了。可以直观的看出,这个编程语言的一些简单运算规则:

  • λx.x 与 λy.y 是完全等价的,或者可以写成 λx.x ≡ λy.y
  • (λx.x)a 可以简化成 a,或者写成 (λx.x)a = a
  • (λx.y)a = y
  • (λx.(λy.x))a = λy.a

大家已经发现了,这个语言,连循环、判断等结构都没有,对了,这些都要自己编程实现;甚至连数字也没有,加减法也没有,这些也通通都要自己定义和实现。下面就介绍一下如何使用 Lambda Calculus 编写一些基本的功能。

  • 实现多参数函数,这个方法有个专用名叫函数柯里化。比如实现函数f(x, y, z) = ((x, y), z) 可以使用如下的代码: λx.λy.λz.xyz
  • 逻辑运算中的“真”被定义为:TRUE ≡ λx.λy.x。这里对于“真”的定义是:输入两个参数,返回第一个。推算一下 TRUE a b ≡ (λx.λy.x) a b = (λy.a) b = a
  • 逻辑运算中的“假”被定义为:FALSE ≡ λx.λy.y。推算一下 FALSE a b ≡ (λx.λy.y) a b = (λy.y) b = b
  • 判断语句 if,假设我们需要当变量 b 为 真时,返回 t;b 为假时返回 f。那么可以定义 IF b t f ≡ λb.b t f 。 推算一下 IF TRUE t f ≡ ((λb.b) (λx.λy.x)) t f = (λx.λy.x) t f = t ; IF FALSE b t f ≡ (λb.b t f) (λx.λy.y) = (λx.λy.y) t f = f
  • 有了以上的基础,逻辑运算的定义就简单多了,比如:AND a b ≡ IF a b FALSE; OR a b ≡ IF a TRUE b; NOT a ≡ IF a FALSE TRUE
  • 定义数字:
    • 0 ≡ λf.λx.x 可发现 0 ≡ FALSE
    • 1 ≡ λf.λx.f x
    • 2 ≡ λf.λx.f (f x)
    • 3 ≡ λf.λx.f (f (f x) x)
  • 为了方便数字运算还要先定义一个辅助的“后继函数”:S = λn.λf.λx.f((n f) x) 。调用这个函数会得到输出参数的下一个数,比如 S4 = 5。我们可以试一下:S 0 ≡ (λn.λf.λx.f((n f) x)) (λf.λx.x) = λf.λx.f(λx.x f) x) = λf.λx.f x ≡ 1
  • 加法就可以定义为: ADD ≡ λa λb.(a S) b 。 拭一下:ADD 2 3 = (λa λb.(a S) b) 2 3 = 2 S 3 ≡ (λf.λx.f (f x)) S 3 = λx. S (S x) 3 = S (S 3) = S 4 = 5.

以上是 Lambda Calculus 一些最基本的功能,作为一个图灵完全的语言,它能做的远不止这些,其它编程语言能做的,它基本也都可以做。但是我们也发现了,如果一个语言什么预先定义都没有,一切都需要开发者自己从头做起,那么在实际应用中就效率太低了。

Lambda Calculus 的发明人是 Alonzo Church,他有个大名鼎鼎的学生,图灵。Lambda Calculus 对于后来编程语言的发展产生了深远的影响。函数式编程就是受此启发而来。如今,Lambda 函数更是成了主流编程语言的标配。

DALL·E 绘图

申请到了 DALL·E 的账号,赶紧玩了几下。DALL·E 是个人工智能绘图程序,可以根据输入的文字绘制图片。它的水平肯定比不上经过训练的画师,但是对于我这种没有绘图能力的人来说,可能会有些帮助,比如,以后制作文档时,也许可以让它帮忙绘制一些插图。

我先画了一幅程序猿自画像:

Photo of a chimpanzee sitting in front of a computer and programming

儿子也画了一幅自己喜欢的图画:

Cartoon of apples, peaches, grapes and many kinds of fruits growing on one tree under a clear sky with the moon shining on the background

优化旧照片

最近测试了一些翻新旧照片的算法,主要是试着把模糊的小照片翻新成清晰的大照片。据说淘宝上有很多人做这个生意,主要是靠手工来修复的。网络少也有不少PS修复旧照片的教程。不过我不会使用PS,所以只能依靠算法了。修复旧照的人工智能的算法也是有一些的,但目前来说,人工智能算法的修复效果在总体上还是比不了人工修复的,但某些情况下表现也还不错。

第1步 选照片

首先找一张老照片:

要想效果好,照片一定要找上图这样的:大头照,而且只有少量折痕和磨损。我也测试了一些质量更差的照片(人脸所占像素太小,有大片磨损等),人工智能对于它们目前还还无能为力。

第2步 去网纹

很多老照片都不是光滑的,像纸上有排列规则的凸点,扫描之后的图片上就会有网纹。上图的旧照片上可以明显看到这些网纹。我找的几个人工智能程序都是解决特定问题的,无法很好的处理这些网纹,所以先要人工去除这些网纹。网上搜了一下,没有专门去除网纹的程序,只有PS相关的教程。PS是收费软件,我也没有。好在有一些免费的图像处理软件可以替代PS。我用了GIMP(GNU Image Manipulation Program)加上FFT插件。FFT是快速傅里叶变换的缩写,用于把时域信号转换为频域信号。

上图经过傅立叶变换,在频域上的图像如下(灰度图部分):

由于原照片里的网纹是规则的(有固定频率),频域图会在一些特定的频段里出现高能量区域,也就是上图用红圈圈出来的部分。我只画了三个圈,但其它那些亮点(除了中心低频区域外),都是网纹造成的,只要把这些亮点全部涂黑,再做反傅立叶变换,新生成的图片就是被去除了网纹的照片了。

第3步 上色

GitHub上最受欢迎的黑白照片上色程序是这个:https://github.com/jantic/DeOldify

我试了一下,效果只能说马马虎虎,上了色的照片看上去还是像旧照片,只是把原来的纯黑白色调中加入一点肉色,一看就不是真的彩色照片。

如果会PS的,肯定还是PS效果好。

第4步 清晰化

我在GitHub上找到了两个照片变清晰的软件,一个是微软的 https://github.com/microsoft/Bringing-Old-Photos-Back-to-Life

一个是腾讯的 https://github.com/TencentARC/GFPGAN

两个软件各有千秋,比如微软的可以修复裂痕,但腾讯的可以放大照片。具体那个效果更好,要针对不同情况测试一下。

这两个软件都是侧重于对人头像进行优化,对其它区域的优化会差一些,所以经过它们优化的照片常常是人脸非常清晰,但衣服还是模糊的。再有它们对于眼镜的处理都不太好。常把眼镜当成皱纹;或者把眼镜上的反光当作眼睛来处理。有时候生成的照片非常诡异吓人。

另外,它们也没法处理太小的人像,或是有缺损的人像。

处理后的效果

重新制作了书籍网页

最近学习完了React,又使用Docusaurus重新为书籍制作了网页:https://lv.qizhen.xyz/

实际上,Docusaurus已经帮我作为绝大部分工作,我学习的React技能基本没有用上,毕竟书籍的格式还是比较简单固定的,不需要非常炫酷的界面。

之前是用Docsify制作的页面。我对于Docsify的界面和功能已经非常满意了,最主要的问题是它制作的网页是客户端渲染的,搜索引擎都不会爬取。墙外的搜索市场已经被Google垄断,恐怕Google是不会有动力去支持搜索客户端渲染页面了。国内的搜索市场竞争还更激烈一点,可以它们居然做的都比Google还差很多,也没有指望。只能自己修改网页了。

Docusaurus使用起来比Docsify麻烦不少,所需技术门槛也稍高。这一是因为Docsify只支持制作文档网页,而Docusaurus还可以制作个人主页和博客,功能复杂了不少,虽然都是我都用不上的。二是因为Docusaurus是制作服务端渲染页面的,多了一个Build步骤。再有Docusaurus比较新,资源相对来说少一些。

但不管怎么样,Docusaurus也是非常令人满意的。现在Google可以爬取我的页面了,国内的搜索引擎都还不行,但这不是网页的问题,最可能是我的域名没有工信部认证,国内引擎不愿意访问。

Docusaurus开发团队非常活跃,应用前景应当超过其它类似工具。非常推荐用于创建静态的个人主页、博客、文档等。

制作电子书网站的一点想法

头一次之作电子书网站,因为没经验,很多问题是做了之后才发现的。等以后有时间了,详细写一写,目前先大致总结一下。

最开始弄的时候,没有借助任何工具,直接就开始搭网页了。当然底层的库用的还是别人写好的,比如最主要的部分,把Markdown格式文档(MD)转换成HTML格式用的是marked.js。上层的代码也并非完全从头开始写, 我也参考了别人的代码。不用工具,自己写的最大好处是让我学习了很多以前不会的技能。以前是没做过网站前端开发的,Javascript也没用过。现在能做个简单网页了。

但是学习过后,发现如果把网页功能完善起来,全自己开发太费时间了。而且继续花时间对我学习的帮助也没有之前那么大了。于是我还是用了工具把网页从新做了一边。最著名的工具大概是 gitbook-cli,一个GitBook发布的旧版本的开源工具。这个工具是在线下把Markdown格式文档转换成HTML格式文档的。更新一个页面的流程大约是这样:找到需要更新的MD文档,修改保存,运行gitbook-cli,生成HTML文档,把生成的HTML文档上传至GitHub。步骤有点繁琐,不是我想要的。而且gitbook-cli有四五年没更新了吧,直接下载后不做点配置都根本运行不起来了。所以放弃了。

后来是用的Docsify重新生成了网站。Docsify还是很简单易用的,不过bug也是很多。有一些bug我直接在本地修改了,但有一些是我没法改的,比如最简单的,它对于HTML tag中图片的路径解析跟别人都不一样,比如:<img src=”images/image192.png”>。它用绝对路径来解析,而其它MD转换工具全部用的相对路径。这个问题我估计Docsify自己也没法改了。不过我现在还有临时解决办法,先将就着用一段时间吧。Docsify生成的网站最大的问题还是没办法被搜索引擎检索,当然也可以说这是搜索引擎不够强大,但是我也没法改变搜索引擎,只能再改进自己的网页了。

Docsify和我最开始写的网页原理完全相同,都是在客户端做渲染,服务器并没有给客户端发送任何HTML格式内容,而现在的搜索引擎只认HTML,它们是不会运行渲染程序的。所以也就没法抓到任何内容。要想被搜索引擎认识,还是得有HTML文件才行。有些网站是可以帮我们做这件事的,比如GitBook。我在GitBook上也生成了一份电子书,https://labview.qizhen.xyz/ 。在GitBook上做电子书网页,基本没有任何可以配置的东西,所有的网页都长一个模样。这也不是设么大问题,但它无法提供留言功能,还是非常不爽的,我就喜欢看留言。GitBook收费版可能有留言功能,不过穷人并没有尝试付费版。

穷人也不是完全没办法,我们开可以利用GitHub Actions这个免费功能来实现HTML文档的自动生成和部署。GitHub Pages默认就集成了一个Action流程,它可以在每次用户更新 MD 文件之后就调用Jekyll这个工具,为更新的MD文档生成HTML文档在部署到服务器上。只不过GitHub Pages默认的工具对生成的网站有一定格式限制,比较适合做博客,不适合发布书籍。其实可以不用它默认的工具,而是利用GitHub Actions调用一个更适合电子书的转换工具,在MD文件更新后做自动转换和部署。近期我还不会尝试这件事,因为React这个东西我才学了一半,业余时间就那么一点,先抓紧时间学完React再去建新网页。至于转换工具,我会去使用 Docusaurus,因为它使用React写的,正好让我学完了可以实践一下。

放在GitHub上的书还没法被检索

我把书的原稿开源放到GeiHub上之后,给它做了个页面方便阅读。做好的网页是一个单页面应用,也就是说书里的所有内容都是放在同一个页面上:https://lv.qizhen.xyz/。别看每个章节都会在地址栏上显示不同的URL,比如“https://lv.qizhen.xyz/#docs/structure_condition”,其实#号后面的都只是同一页面不同的参数而已。浏览器只能展示 HTML 格式的内容,但GitHub的网站上并没有存HTML格式的文档,所有文档使用Markdown(*.md)格式保存的。GitHub的网站也不会把MD格式文件动态渲染成HTML格式再传给读者的浏览器,网站发给浏览器的还是 MD 格式的数据,是浏览器端运行了一段JavaScript程序,才把 MD 格式的数据渲染成了HTML。

GitHub的服务器应该是不支持后台渲染的,做成前台渲染的单页面应用,用户体验会好一些:换页反应比较快,还可以随时改变背景配色等。但是这种做法对搜索引擎非常不友好。

搜索引擎在检索网站时,一是它不会尝试不同的参数,也就是说它只检索 https://lv.qizhen.xyz/ 这一个首页面;二是搜索引擎的爬虫也不会运行JavaScript,所以即便是首页的内容,它也看不到。总之搜索引擎根本看不到这本书的任何内容,也就别想搜索到它了。

我还不知道这个问题怎么解决,不过GitHub上搭建博客还有很多其它方法,有一些是产生静态HTML的,应该没有这个问题。等有时间再试试其它的那些工具吧。

GitHub自带的搜索也几乎没法搜索中文书,它是按词搜索的。它定义的“词“就是两个空格或者符号之间的字符串,这对于代码或者英文文档都非常合适。但是中文的词和词之间是没有空格的,GitHub对于中文只能整句匹配,只有文章里恰好有跟搜索内容一模一样的句子时才会被找到。

最近使用国内网站的麻烦

都是实名认证弄出来的麻烦,看这趋势,以后国内外的网络世界恐怕要彻底分离了。

我前一阵子买了个域名 https://www.qizhen.xyz/ 。为啥是xyz后缀呢,因为便宜,穷人也想有个独立域名,就不能要求太高了。再后来把自己家里电脑做成了服务器,但是如果要让别人看到我家里的服务器就需要一个内网穿透软件了。这个当然首选用国内软件,否则墙内可能还是看不到。我试了一下“花生壳”等几个用户量比较大的软件。软件做的都还不错,要手机认证,这还好,我还有亲戚在国内,虽然麻烦但还是认证通过了。但接下来就无解了,首先是客户端不允许国外IP;再有使用的域名必须是工信部注册的。我还真试了一下通过腾讯云的服务办理工信部注册,但是这个实名认证就严格多了,需要我打开一个微信应用,实时视频,然后发现我的IP在国外,直接就认证失败。

好吧,这些还难不住软件工程师,我还可以自己搭一个FRP服务来实现内网穿透。在腾讯云上买了了一个最便宜的服务器,服务器在国内。然后呢,发现我连不上那个服务器。开始以为我配置有问题,后来才发现,这之间有墙。那台与服务器的IP和我上网的IP之间根本ping不通。我原以为墙只会阻挡一些特定网站的,没想到,国内的运营商可能是为了避免麻烦,大概是一封就封一大段IP的。其它运营商的云服务器不知道会不会好一点。我使用腾讯云是因为可以用微信登陆,即便我的微信账号是美国的。这让我方便了不少。

后来,只好有买了腾讯云在美国的服务器,这个服务器的IP还可以被国内外用户都访问到,算是勉强解决了问题吧。

以上都是政策引起的麻烦,但如果产品做的还行,总还可以找到方法来用。但是产品也做的垃圾,那就彻底没法用了。新浪的产品就是这样。前些天看到一条新闻说近年新浪微博收入大涨,我有点吃惊,心想:现在还有人用微博吗?新浪曾是我每天都登陆的网站,获取新闻的最主要来源,它的博客,微博也都风靡一时,可惜后来越做越差,我的熟人们大多都逐步离开新浪平台了。我曾经试图使用过一阵子新浪博客,但是它的产品体检极差,最主要的一点:经常是我写了一大片技术文章之后,告诉里面有敏感词,不能发。也不说哪些是敏感词,弄的我完全不知道该怎么改。于是就果断告别新浪,使用Workpress做了博客平台。当然也是有代价的,就是国内用户没法访问了。当时幸好没留在新浪博客,因为现在这个产品已经完全不让我用了。登陆它需要手机验证,但是不论我使用国内的还是国外的手机,它都会显示验证出错。可能因为我的IP地址还在国外吧。虽然我本人无法使用了,但是我惊奇的发现,居然有个机器人正在使用的微博账号自动关注别人和发帖。平均每小时就可以关注一个莫名其妙的账号,然后再转发一些内容。我赶紧再去看看以前好友的微博,也都是很久不登陆得了。果然也有一些账号是被机器人接管了,在不知疲倦的转发微博。我现在算是理解新浪微博的活跃度是哪来的了,没想到新浪堕落成这样了。

终于把所有章节都放好了

https://lv.qizhen.xyz/

花了好几天时间,有一天我还请了休假专门来弄它。还依然存在的一些排版问题就只能慢慢修改了。

Markdown 格式在某些方面的功能还是不太够,最主要的问题可能是表格,无表头的表格都不支持。Word文档转换到Markdown,大部分的表格都是直接使用了HTML标签。显示到时没有问题,但如果要编辑就困难了,满眼看去,全是标签,都找不到需要的内容。

还要特别感谢 lhb5883 网友,最初建议我在Github上搭建博客。我这才惊喜的发现原来Github没有被墙(但愿我不是乌鸦嘴),而且Github也确实是最适合放置开源项目的网站。唯一担心的是LabVIEW用户可能很多都不熟悉Github。

这堵墙给我造成了很多麻烦,更糟糕的是,它还越来愈高了。墙里的可能以为它只是为了圈住里面的人,但实际上它是双向的。我访问国内网站也是越来越困难了。比如我已经完全无法使用新浪博客了,最开始我还可以浏览页面,但是不能看评论,现在正文也经常加载不了了。而且如果要登陆,还要每次让我用国内的手机收验证码。目前,国内用户量比较大的网站都必须要使用手机注册和验证了,也就意味着大多数的常用网站我都无法使用了。如果只是注册需要手机,必要时我还可以请国内亲戚帮个忙,但有些网站每次登陆都要手机验证,就真是彻底不让国外用户使用了。

这次把书开源的过程中还是学习了一些建网页的知识的,之前基本没有任何编写网页的经验。不过对于把博客迁移到Github上,我还是有些犹豫。主要是之前博客的内容很多,还包含大量的图片,链接,留言等等。这些要迁移起来工作量恐怕比迁移一本书要大得多,不知要花多少时间。所以我还得再研究研究,有没有什么更简单的做法。

Silverlight程序中显示帧数的方法

接到一个需求:要在Silverlight程序的界面上放置一些控制选项,可以开关Silverlight的EnableFrameRateCounter,EnableRedrawRegions等和显示效率相关的一些设置项。我开始在网上查了一下,这几个与显示性能相关的设置主要被用在装载Silverlight控件的HTML文本中。在Silverlight程序启动之后,这几个属性仍然可以被修改,除EnableGPUAcceleration之外。问题在于,如果EnableGPUAcceleration没有在HTML文件中被设置为True的话,EnableCacheVisualization和EnableFrameRateCounter也无法工作。看来微软给出这几个属性就是为了帮助开发显卡加速的动画的。

EnableFrameRateCounter被设置为真时,Silverlight界面左上角会显示出几个和GPU相关的数据,其中包括动画刷新帧数。但是我搜索了一下,Silverlight似乎没有提供函数,可以在程序中把这个帧数读出来。MSDN中提供了一个间接方法来计算帧数,利用CompositionTarget.Rendering这个事件。Silverlight没刷新一帧,都会抛出一个CompositionTarget.Rendering事件,对这个事件计数,就可以算出每秒帧数了。不过,用这个方法计算出来的帧数,与EnableFrameRateCounter设置显示的帧数居然是不同的。

CompositionTarget.Rendering事件的发生次数与用户设置的MaxFrameRate和系统繁忙程度相关。而EnableFrameRateCounter显示出来的那个帧数还与Silverlight动画是否需要刷新相关。比如对于一个界面不怎么活跃的Silverlight程序,当Silverlight觉得不需要刷新界面时,它就不刷新,显示出来的帧数很可能是个位数,甚至为0。

ArrayList 与 List<T> 不一致的Range行为

今天发现C#中泛型和非泛型集合的某些方法,行为不一致。

比如下面这段程序,运行之后,test中有4个元素,而test1中只剩下两个元素。

System.Collections.Generic.List<string> test = new System.Collections.Generic.List<string>();
test.Add(“1”);
test.Add(“2”);
test.Add(“3”);
test.Add(“4”);
test.GetRange(1, 2).Clear();

System.Collections.ArrayList test1 = new System.Collections.ArrayList();
test1.Add(“1”);
test1.Add(“2”);
test1.Add(“3”);
test1.Add(“4”);
test1.GetRange(1, 2).Clear();