由于要测试一些代码,其运行结果会受到多核并行的影响,所以希望能够调整使用的 CPU 数量。网络上之前看到的方法是在内核的启动参数上添加一个 maxcpus,但是如果这样的话每切换一次都要重启一次,是在太麻烦了。想想 Linux 应该是很强大的,所以可以动态修改 CPU 数量才对。
无意中看到 Linux 代码的 Documentation 文件夹下有个文件叫做 cpu-hotplug.txt,于是就看了一下,发现可以在 /sys/devices/system/cpu 看到代表各 CPU 的文件夹按照 cpuX 的命名方式,如 cpu0、cpu1、cpu2 等。这些文件夹里面有一个 online 文件,如果其值为0则禁用该 CPU,如果为1则启用该 CPU。注意,这里需要 root 权限哦。
因为我只要在单核和多核之间切换,所以我写了两个脚本放在 /usr/local/sbin 里面:
singlecore
1
2
3
4
5
6
7
8
9
10
11
12
| #!/bin/bash
cpus_dir="/sys/devices/system/cpu"
for cpu in $(ls "$cpus_dir" | grep 'cpu[0-9]\+')
do
cpu_online="$cpus_dir/$cpu/online"
if [[ -e "$cpu_online" && $(cat $cpu_online) = 1 ]]
then
echo 0 > "$cpu_online"
fi
done |
multicore
1
2
3
4
5
6
7
8
9
10
11
12
| #!/bin/bash
cpus_dir="/sys/devices/system/cpu"
for cpu in $(ls "$cpus_dir" | grep 'cpu[0-9]\+')
do
cpu_online="$cpus_dir/$cpu/online"
if [[ -e "$cpu_online" && $(cat $cpu_online) = 0 ]]
then
echo 1 > "$cpu_online"
fi
done |
之后需要切换的时候,只要运行 sudo singlecore 或者 sudo multicore 就可以了~
顺便说一句,我当时在想,如果我禁用了所有的 CPU 会怎么样呢?结果发现 cpu0 是没有 online 文件的,也就是 Linux 至少保证一个 CPU 处于可用状态。
专业课学习操作系统,满心欢喜地以为可以是 Linux 代码导读,结果选用了一本八十年代的教材,介绍 UNIX v6 的。于是自己从图书馆借来了内核开发的入门读物《Linux 内核设计与实现》。既然是介绍内核的书,自然少不了代码,但是书中又不可能将每个提到的代码的相关信息全部写出来,这时就得自己去查看代码。查看代码的话,虽然 Linux 的代码可以很容易地得到,但那来看终归有些麻烦,各种调用需要查找半天。于是想起了很有名的一个网站 LXR:http://lxr.linux.no,Linux 交叉引用。这个网站好是好,就是网络原因速度太慢,于是就想自己装一个。
先说一句,下面的安装环境都是64位 Gentoo。
最开始找到了 LXR Cross Referencer 这个项目,一看最后更新时间还挺新,看起来是一直都还在维护的。然后在 Gentoo 的网站上查到 LXR 是可以直接用 portage 安装的,于是安装,安装相关的包,最后放弃了。出于什么原因我也不记得了,最后一个原因肯定是不好看,肯定达不到上面那个网站的效果,所以就放弃了,到那个网站上去下载 LXR 分出来的版本 LXRng。(话说这个 ng 不会是表示 next generation 吧?)
Read the rest of this entry »
如果你也用 Gmail,他可以让你轻易拥有无限多的子邮箱,发给这些邮箱也就等于发给你的邮箱。这有什么意义呢?很简单,你可以在每个地方注册的时候都使用不同的邮箱,这样当你收到垃圾邮件广告邮件,你就可以知道是从哪里来的了~再结合 Gmail 强大的过滤器,你也可以针对性的进行屏蔽~
Gmail 事实上支持两种方式来扩张你的邮箱,第一种是在你的用户名后面加上“+”和任意字符串,比如我的邮箱 quanxunzhen@gmail.com(我这邮箱无所谓了已经嗯),如果我想要识别不同的网站,比如豆瓣,我可以直接在豆瓣的注册邮箱里填写 quanxunzhen+douban@gmail.com,所有验证邮件都可以发给我,我如果不想申请一个新邮箱却想注册个豆瓣小号,也可以再写 quanxunzhen+douban2@gmail.com。
当然,支持上面的方式固然是最好的,但遗憾的是很多网站并不支持带加号的邮箱,那就要稍稍麻烦点了。
根据 @fanzeyi 的说法,Gmail 的邮箱是自动忽略点,也就是“.”的,也就是说对于我上面的邮箱,如果写为 quan.xun.zhen@gmail.com,或者 ..quanxunzhen..@gmail.com,我照样可以收到。而且这样写法的邮箱通常不会被屏蔽,因为点毕竟是邮箱必备的部分。
但是这样加点有一个问题,就是我们无法像第一种方式那样很容易的区分来源,因此我们不得不进行某种编码。
顺序编码
第一种编码的思想大体上是按照传统的数据库中记录的自增 ID,转为二进制表示,具体的说就是,比如我这个邮箱有11位,那么我们可以以中间有打点或者没有打点作为二进制位中的0和1,比如就可以如下表示:
1
2
3
4
5
| 0 quanxunzhen
1 quanxunzhen.
2 quanxunzhe.n
3 quanxunzhe.n.
29 quanxun.z.h.en. |
这样……
但是这样总编码数也就 2^len(email)+1 种,对于我邮箱这么长,也仅仅4096种,总归有点少。(其实也不少了吧喂……)
于是我们考虑一下扩张的方法。
其实扩张很简单,因为任意两个字母之间可以插入的点是任意多的,我们可以利用这一点按照一定的协议来编码,比如我们规定第一个字符之前如果有 n 个点,则之后每个字母之间最多可以有 n+1 个点,这样当第一个字符前有 n 个点时,最大可以编码 (n+2)^len(email) 种,如果规定这里每次数字都是从上一次结束的数字开始,编码就不会重复了,而且可编码的范围大大增加,却又没有太多的增加点的数量。对于我的邮箱来说,前面一个点也没有时可以有 2048 种,有一个点时可以有 177147+2048 种,有两个点时可以有 4194304+177147+2048 种,我相信是用不完的了……
事实上基于此还可以延伸出各种各样的编码方式~
字符编码
不过上面的方法总归不那么直观,而且如果想追踪来源,还要建表,表的同步还是个问题,所以这个问题还是挺大的……于是我有了第二种想法,可以把注册地的名字嵌入到里面~
最基本的想法是根据字母序,比如在 Arch 论坛注册我们可以把 Arch 变成数字串 0 17 2 7,插入进去变成:
1
| qu.................a..n.......xunzhen |
不过我们可以明显的感觉到很蛋疼……Arch 还算好的,如果是新浪(Sina -> 18 8 13 0)或者淫淫网(Renren -> 17 4 13 17 4 13)就更蛋疼了……
但是我们感觉到不是这样的,我们完全不需要那么多字母其实也可以认出他们来不是么?比如淫淫网我们可以用 rr,或者原名校内的 xn,新浪就 sn 什么的,这样需要的字母就少了,我们就可以利用变换进制的优势了~26对于二进制位需要5位,对于3进制恰好只要3位,当然算起来如果你的邮箱够长肯定还是用二进制可以增加的更短一些,对于我来说就是如此,比如上面的就可以这样变化了:
1
2
3
| rr -> 17 17 -> 10001 10001 -> q.uanx.u.nzhe.n
xn -> 23 13 -> 10111 01101 -> q.ua.n.x.un.z.he.n
sn -> 18 13 -> 10010 01101 -> q.uan.xun.z.he.n |
如果三进制的话,我的 upsuper 也可以容下了:
1
2
3
| rr -> 17 17 -> 122 122 -> u.p..s..u.p..e..r
xn -> 23 13 -> 212 111 -> u..p.s..u.p.e.r
sn -> 18 13 -> 200 111 -> u..psu.p.e.r |
看过去好多了~
我们还可以发挥其它的想象力,比如找寻标识中的字母频率然后构建一个霍夫曼编码树什么的~
我能想到的暂时只有这么多了,各位还有什么更有意思的想法么?
上周六参加了好久没有参加的的 SHLUG 月聚,恰逢 TualatriX 也带团来上海。自由讨论的时候,我看到 TualatriX 的终端十分色彩斑斓,便询问,他给我们展示了他的 bash 的两个特色功能:1、当上一条命令返回结果不为0时显示返回值并高亮显示提示符;2、自动检测git分支。他说这个在他的博客上都可以找到,今天想起来去找了一下,发现了这篇:史上最强的PS1 | I’m TualatriX,感觉满强大的。
不过,说实话,我觉的这个还不够完美,原因有二:一是我发觉高亮显示的时候那个配色相当不怎么样,二是我本来就讨厌提示符太长,这样一下就更长了……于是我就想起 ghosTM 的 zsh 里面有一些信息是放在右边的,我想把返回值也扔右边去,并且是右边上移一行。此外,由于很少使用 git,所以检测 git 分支的功能也就不需要了~
先放一个最终效果图:

然后直接写出了我的新的 PS1:
1
| PS1='`a=$?;if [ $a -ne 0 ]; then a=" "$a; echo -ne "\[\e[s\e[1A\e[$((COLUMNS-2))G\e[31m\e[1;41m${a:(-3)}\e[u\]\[\e[0m\e[7m\e[2m\]"; fi`\[\e[1;32m\]\u@\h:\[\e[0m\e[1;34m\]\W\[\e[1;34m\]\$ \[\e[0m\]' |
非常复杂唉……让我自己再看一次都头晕……
Read the rest of this entry »
前几天因为某些原因,我把饭否上所有的好友和关注者全部清空了。当然,如果没有程序的帮忙,估计还不等我删完我也就后悔了。
我没有那么狠心的把饭否的消息给清空,因为消息是不可恢复的(而且也太多了),但好友和关注者是可以的。做事情都给自己留后路显然是我一贯的风格,不然的话,我大概早从我家阳台跳下去了……
Read the rest of this entry »
我曾经很早以前就在想,能不能将U盘作为登入我系统的验证机制。当时的想法是,这样比较有趣~不过后来发现另外一个重要的用途就是,防止在众目睽睽之下输入密码……
这个问题提出后,邮件列表里很快就有人告诉我,Linux 已经有一个现成的机制了,这就是 pam_usb。不过我在网上搜了半晌都没搜到相关的中文资料,前几天弄成了,就写出来供大家参考~
其实来说,是很简单的。首先,当然,要准备一个U盘~(废话),然后安装 pam_usb。在 Ubuntu 下的话,源里就有,可以输入命令
1
| sudo apt-get install pamusb-tools |
直接安装。当然如果你连终端也懒得打开,可以直接点击这里:安装 pamusb-tools。当然,在后面的步骤中你终归还是要打开终端的,所以还是先开了吧~这个东西目前暂时还没什么图形界面的样子(当然做一个相信也不难)。
另外,Fedora 源里有 pam_usb 包,Arch Linux 似乎在 AUR 里有,在 Gentoo 中似乎是被默认屏蔽的,可以通过下面指令安装:
1
2
| echo "sys-auth/pam_usb" >> /etc/portage/package.keywords
emerge -av ">=sys-auth/pam_usb-0.4.1" |
其他的发行版也可以直接从他们的项目主页下载源码包编译安装~
安装好了以后,首先插入你作为钥匙的U盘,然后在终端中运行
1
| sudo pamusb-conf --add-device MyUSBDevice |
其中的 MyUSBDevice 可以任意修改,只是一个标识符而已。接下来根据提示操作即可。如果你的电脑此时连接着超过一个U盘、移动硬盘,或者某个U盘、移动硬盘包含超过一个分区(就像我给U盘分了2个区),就会提示选择安装到哪里。设置完确认保存到配置文件即可。
下面添加认证用户,下面的命令是添加我为认证用户的:
1
| sudo pamusb-conf --add-user upsuper |
原教程里面写的是添加 root 我认为是没有必要的,添加 sudoer 应该是已经足够了的。这条命令几乎不问什么问题,直接就完成了……这样以后在使用这把钥匙的时候就可以不需要输入相应用户的密码了。
最后最重要的一步,是编辑认证系统的配置文件。打开 /etc/pam.d/common-auth (对于 Gentoo 来说是 /etc/pam.d/system-auth),将下面这行插入到所有条目的前面:
1
| auth sufficient pam_usb.so |
现在你的 USB 钥匙已经可以用了!现在,另外再打开一个终端,随便 sudo 点什么,然后你应该不会再看到输入密码的画面,取而代之的是下面的东西:
1
2
3
4
5
| * pam_usb v0.4.2
* Authentication request for user "upsuper" (sudo)
* Device "MyUSBDevice" is connected (good).
* Performing one time pad verification...
* Access granted. |
然后运行成功了!不仅 sudo 可以验证,包括 gksu 和登入框等等都已经可以使用这把钥匙直接略过不需要输入密码了。
现在你已经成功的制作了一个属于自己的 USB 钥匙!
Read the rest of this entry »
上一篇日志里面我做了一个从 VeryCD 为 Rhythmbox 提取专辑封面的程序,后来想到豆瓣其实也有这个东西,于是又做了一个豆瓣的。然后想,既然这样我干脆把原来的封面艺人插件 fork 出来,自己开一个新项目,专门做封面艺人插件的 China Mod~
现在已经开好了,就在 Google Code 上:http://code.google.com/p/rhythmbox-artdisplay-cn/
其实说 Mod 也不算,就是加了几个扩展进去……主要是考虑到如果不 fork 似乎很单独安装,所以就 fork 出来了……那个插件看过去也很长时间没有更新了呢?
第一个版本0.1可以点击这里下载:rhythmbox-artdisplay-cn-0.1.tar.bz2,暂时还没有做任何安装包,deb 包和 arch 的包应该会随后奉上~
安装很简单,常规的 make 和 make install 就可以了~
如果各位有什么改进意见,欢迎提出~
我们知道 Rhythmbox 里面有一个插件叫做“封面艺人”,可以通过互联网下载对应专辑的封面图片。可是,那个插件使用的似乎是美国的 Amazon,中文歌曲基本找不到对应封面,怎么办呢……
想到我的很多专辑都是从 VeryCD 上下载的,那些下载页面通常都会有专辑封面图片,于是我就写了个封面艺人的扩展,让其可以从 VeryCD 上搜索专辑,并下载相应专辑封面。这个扩展可以在这里下载:verycd-cover.tar.bz2 (5KB)
下载解压后,运行其中的 compile.py,然后把除了 compile.py 以外的四个文件拷贝到 Rhythmbox 的封面艺人插件的目录就可以了,在 Ubuntu 下,那个目录是 /usr/lib/rhythmbox/plugins/artdisplay。
Read the rest of this entry »
当小白鼠绝对是一件得耐得住折腾的事情……当然,这看起来和RC不RC似乎并没有太大的关系,作为在普罗大众之前开始接触桌面Linux的一员,绝对可谓是小白鼠了。
说 Ubuntu 10.10 的安装界面确实是做的不错了,而且还能自动识别需要的私有驱动。不过有一点让我比较无语的是,在我还没有联网的时候,他找到我的无线网卡的私有驱动,但是下载安装不了……所以我不得不接上网线安装。安装过程比较顺利,除了似乎选择硬盘分区的时候十分卡以外,其它的地方还是很满意的,包括一边复制文件一边询问一些详细配置、一边复制文件一边给出 Ubuntu 的特性介绍这样。另外,正如 Canonical 公司之前承诺的那样,中文成为了 Ubuntu 10.10 的官方语言,安装完成后,全部中文界面,并且自动安装好了 ibus-pinyin。不过,Firefox 却意外的成为异类,仍然是英文(先在不知道什么时候已经突然变成中文了)。此外还有一点就是,很希望 ibus-sunpinyin 能尽快成为默认的,因为确实比 ibus-pinyin 好很多的感觉。
一安装完问题就来了,没法启动!后来几经周折,发现原因是安装的时候莫名其妙的把我的安装U盘识别为 sda 而硬盘识别为 sdb,并且安装程序还把这个写进了 fstab 里面。理论上说 Ubuntu 应该自动写入 fstab 的都是 UUID 才对,这次却写了 /dev/sdXY 进去,着实让人感到奇怪。通过我的修复盘手动修改了 fstab 以后终于成功进入了,后面就改成了使用 UUID 了,保证不会出问题……我去网上一搜,没有发现有人遇到同样的问题,我就到 Launchpad 报告了 bug:installer identified hard drive as sdb led to the new system unbootablity。话说我的英文这么差,各位看到有什么语法错误之类的,那也是很正常的了……提交 bug 也算是我对开源的一点贡献了吧……
然后安装了 nVidia 的私有驱动 256.53,成功开启了各种 Compiz 特效,非常赞,而且效果相当不错。不过问题也随之而来:开机画面惨不忍睹……这是个很纠结的问题,你说那么漂亮的一个开机画面,怎么就被个驱动整成那模样……接着就在 Ubuntu 中文论坛上找到了解决方案。这个方案比较复杂,我就直接给链接好了:Ubuntu 10.04 安装Nvidia显卡后开机花屏完美解决(图解),可以看到这是 10.04 时代遗留下来的问题……不过这个方案解决还算完美,至少开机画面是漂亮了不少的。不过这个事情有一个副作用,就是切入切出 tty 变得格外缓慢。于是在这栋楼的47楼发现了解决方法,这个就简单了。因为那个作者没有写出详细的做法,我就简单说一下。在解决的第一步肯定要用到 hwinfo –framebuffer 这个命令,先在仍然用这个命令,察看你选择的那个分辨率的编号,比如我选择的模式是“Mode 0×0318: 1024×768 (+4096), 24 bits”,那么我需要的编号就是 0×0318 = 792,就是十六进制和十进制的转换咯。然后修改 /etc/default/grub,把刚才改过的
1
| GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nomodeset video=uvesafb:mode_option=1024x768-24,mtrr=3,scroll=ywrap" |
修改为
1
| GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nomodeset video=uvesafb:mtrr=3,scroll=ywrap vga=792" |
然后打开 /etc/initramfs-tools/module,同样去掉 mode_option=…. 的那个部分保存。然后还是
1
2
| sudo update-initramfs -u
sudo update-grub |
就可以了。然后再重启以后,切换 tty 慢的问题也算是初步解决了。
接下来遇到的问题就更囧了,gVim 和 OpenOffice.org 里面各种花屏,字体混乱。这个问题倒是很多人遇到过了,在 Launchpad 这个 bug 已经被先后3次提交到了不同的软件包,包括 nvidia 驱动包、openoffice.org 包和 vim 包……看了一遍里面的各种提示,发现有两种方法可以解决这个问题:第一种是使用 metacity 替换 compiz,具体的做法就是 Alt-F2,输入“metacity –replace”,替换速度还是挺快的,替换后就解决问题了,缺点是切换过去以后各种桌面效果就没有了。当然,想要切换回 compiz 感受桌面效果的时候,再按 Alt-F2,输入“compiz –replace”就可以了。第二种是调整字体配置,也就是在 系统/首选项/外观/字体 里,把下面的次像素平滑修改为上面的最佳形状。说实话,最佳形状看过去没有次像素平滑漂亮,不过问题是可以解决的。后来在那个 bug 反馈中有人提出,在选择最佳形状后点击“细节”,在微调内选择“轻微”可以改善显示效果,并且不会导致 gVim 和 OpenOffice.org 花屏了,我试了一下确实如此,不过总觉得是不是可能还有问题……现在暂时使用这个了……
嗷……现在暂时遇到了这些问题,很纠结的呢……所以说当小白鼠还真是一件得耐得住折腾的事情呢……
继上一篇文章之后,我又下大力气对这个程序做了许多修改,在精确度和速度方面似乎都有些许提高。在此推出第二季~
使用真正的12px宋体
在上一次的程序中使用的 PIL 似乎是因为不支持宋体 ttc 文件中对于小字体下优化的点阵形式,才在选择小于 19px 的字号时不能正确渲染汉字。考虑到这一点,我就想到把 ttc 文件里面 12px 的点阵字体单独提取出来使用,毕竟贴吧上面显示 ID 都是用这个字号显示的。
使用 FontForge 提取出来了 simsun-12.bdf 文件,就是宋体 12px 下的点阵。参考 PIL 的手册,发现 PIL 不能直接使用 .bdf 文件,需要使用一个叫做 pilfont 的脚本转换成专有的 .pil 文件才行。我想转换就转换呗。simsun-12.bdf 一个 2.4MB 的文件,转换完就剩不到 100KB,我就觉得肯定有问题,用 PIL 导入,发现还是不能渲染中文。后来知道,这个 .pil 文件根本不支持非拉丁字母的字符,它的储存空间限定了 256 个字符……
无奈了,这意味着 PIL 完全无法支持中文点阵了……
当然,办法总归是有的,那就是——抛弃 PIL!为什么我能有这样的想法呢,因为看到 .bdf 文件是 UNIX 标准的。UNIX 标准意味着什么呢?记不记得 UNIX 有一个非常好的传统叫做,尽量使用纯文本。是的,这虽然让有些文件会变得太大,不过同时也让这些东西更容易被其他程序读取,而 .bdf 恰好即使这么一种文件。
这样读取 .bdf 点阵字体文件的程序自己写不就好了,什么额外的库都不需要……当然,纠错性极弱就是了~
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
| line_count = 0
def read_split(f):
global line_count
line_count += 1
line = f.readline()
if not line:
return False
return line[:-1].split()
with open('simsun-12.bdf', 'r') as f:
chars = {}
line = ['']
try:
# 获得字符总数
while line[0] != 'CHARS':
line = read_split(f)
# 读取所有字符
for i in range(int(line[1])):
assert read_split(f)[0] == 'STARTCHAR'
line = read_split(f)
# 编码
assert line[0] == 'ENCODING'
char = unichr(int(line[1]))
line = read_split(f)
# 绘制参数
assert line[0] == 'BBX'
width, height, x1, y1 = [int(x) for x in line[1:]]
x0, y0 = (1 + x1, 12 - y1 - height)
# 准备绘制文字
base_image = [[0 for x in range(14)] for y in range(14)]
# 读取并绘制文字
line = read_split(f)
assert line[0] == 'BITMAP'
for y in range(y0, y0 + height):
line = read_split(f)[0]
bits = int(line, 16)
bits >>= len(line) * 4 - width
for x in range(x0 + width - 1, x0 - 1, -1):
base_image[y][x] = bits & 1
bits >>= 1
chars[char] = base_image
# 结束这个文字
line = read_split(f)
assert line[0] == 'ENDCHAR'
line = read_split(f)
assert line[0] == 'ENDFONT'
except AssertionError:
print 'line', line_count
raise |
这样所有的字符就被读入,并变成一个单色像素二位数组了。
当然,这个性能很低,在我的机器上转换读取文件的2W+字符大概需要 18s,这可能也是为什么 PIL 要选择进行转换。事实上,使用纯文本储存一直以来都给 UNIX 风格的这一类软件带来一定性能缺陷。不过其实,这很值得,因为方便。
不过这个时间确实是太长了,更何况我们到目前为止还什么都没处理。怎么办呢?两个想法:一、优化代码;二、保存处理的数据。
第一种,基本上是没什么希望了,而且即使能优化,估计效果也不会太好,可能省个几秒封顶了。第二个显然不是个坏想法。
Python 在数据的持续化方面还是有很多现成的东西的,比如 pickle 什么的。不过那个速度太慢,而且是纯文本!好吧,偶尔我也会不喜欢纯文本,因为在这里意义不大……因此选择了 marshal。marshal 也是一个用于数据持续化的库,不过仅能对 Python 的内部类型进行。我会看中它最重要的原因就是它的应用范围极其有限,只能持续化内部类型。如果一个 Python 标准库,它有很明显的限制,却没有标明不推荐或在新版中被剔除,说明它必然有一个其他库不可及的优势。对于 marshal,我猜它的优势就是效率。
使用 marshal 就很简单了……
62
63
| import marshal
marshal.dump(chars, open('base_image', 'wb')) |
这样后面的处理不需要不断重复这个低效的步骤了~
处理文字图像
原来是用 PIL 处理文字图像,现在抛弃 PIL 了,就得自己写了……不过这样也很好,自由发挥的空间很大了~
我猜用的是 Matrix67 大牛的那种在附近留阴影的方法,不过似乎我写的不够好就是了,怎么测试效果都不大理想。除此之外,纯 Python 实现的算法效率和 PIL 这种包装还是没得比,很简单的算法却慢的不得了……
下面是目前的处理代码:
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/bin/python
# - * - coding: utf8 - * -
import marshal
import copy
base_chars = marshal.load(open('base_image', 'rb'))
chars = {}
for char, image in base_chars.iteritems():
new_image = []
for y in range(14):
new_row = []
for x in range(14):
if image[y][x]:
value = 81
else:
value = 0
if y > 0 and image[y-1][x]:
value += 4
if y < 13 and image[y+1][x]:
value += 4
if x > 0 and image[y][x-1]:
value += 4
if x < 13 and image[y][x+1]:
value += 4
new_image.append(value)
chars[char] = new_image
marshal.dump(chars, open('advanced_data', 'wb')) |
处理效果不是很理想就是了,耗时大概也是 30s+。
寻找相似字符
其实这个部分就是一样的了……直接贴代码好了……
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
| #!/usr/bin/python
# - * - coding: utf8 - * -
import marshal
chars = marshal.load(open('advanced_data', 'rb'))
def image_diff(image1, image2):
ret = 0
for v1, v2 in zip(image1, image2):
ret += (v1 - v2) ** 2
return ret
remember_number = 5
try:
searched = marshal.load(open('searched_chars', 'rb'))
except IOError:
searched = {}
while True:
input_string = unicode(raw_input('ID: '), 'utf8')
if not input_string:
break
for char in input_string:
if char not in chars:
continue
if char not in searched:
diff_data = []
image = chars[char]
for c, v in chars.iteritems():
if c == char:
continue
diff_data.append((image_diff(v, image), c))
diff_data.sort()
searched[char] = diff_data[:remember_number]
print char,
for item in searched[char]:
print u'({0}, {1})'.format(*item),
print
print
marshal.dump(searched, open('searched_chars', 'wb')) |
和上次不同的是查找过的字会被保存下来,效率可以高一些……
继续改进
现在的主要问题就是如何提高相似度的识别精度了……目前的想法是通过逐像素比对测试两个字的相似度,最多加一些模糊化什么的处理。doggy 提出一个想法是计算连通区域面积的比例,我个人认为不大可行……我的想法是识别文字的笔画,把文字的骨架弄出来,然后对比什么的,可能效果更好吧?
不知道各位还有没有其他什么想法?
下载
最后是完整版的目前的程序以及已经生成的数据,各位有兴趣可以直接拿来试用了……find-similar-character.tar.bz2 (1.7MB)
参考资料
Comments