盲文、ASCII、字符画

盲文、ASCII、字符画

这篇文章主要是分享一下解决问题思路主要是觉得挺有意思的,所以复盘了一下,想要答案的话请直接调至文末

新年,QQ收到了这样一条消息,里面除了饱含真挚美好的祝愿以外还在最后附上了一个字符画👇

咱说,字符画不稀奇,毕竟这就是几百年前玩烂了的东西

但咱新年实在是无聊,要不自己写个玩玩

简单搜了一下编码敲个代码看看

盲文字符Braille Patterns

U+2800 - U+28FF

for i, j in enumerate(range(0x2800, 0x2800 + 2 ** 8)):
	if i % 20 == 0: print('')    
	print(chr(j), end='')
print()
# ouput ⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿

行,既然能打印出来,那咱就有戏

首先要解决的问题就是要怎么将每个盲文对应的位置信息提取出来,因为这样我们才能反过来通过图像的位置信息找到对应的盲文字符,如果可以生成图片就好了,就可以通过简单的图像处理把位置信息提取出来,但是字符串似乎生成不出来图片,自己一个一个做有很麻烦(能偷懒绝对不用笨方法)

看了下编码从0x2800到0x28FF,盲文一共8个位置,刚好这是一个全排列的问题,而且数量正好能和ASCII码对应。首先,分析一下这个是什么规律,因为一般这种情况是不会乱排的

我们一般想到的数全排列的方法是下面这种(反正我是)

3个点 : 1 + 3 + 3 + 1 = 8种 = 2^3

这规律对不上啊。。

换一种思路,因为我们要拿去转换的图像矩阵为(4, 2)所以我们把盲文的信息也用矩阵表示出来,以⠪为例(我不知道到时候能不能正常显示,不能显示你们照下面的图看)

[[0, 1], 
 [1, 0], 
 [0, 1], 
 [0, 0]]

出于职业习惯把它拉直...

np.reshape(arr, -1)得到[0, 1, 1, 0, 0, 1, 0, 0]

拉了几个看看也没找到啥规律...

期间甚至产生了整个神经网络的想法(就像是大学生学会了微积分做小学奥数图形面积体直接建系开始微积分一样),不过过拟合成这样和全等有啥区别。。。还不如直接写个字典。

然后我发现一丝曙光

我超,这不就是我心心念念的图片吗,有这个我直接就有位置信息做个字典不就解决了

正当我打算写个爬虫查看网页源代码的时候

我超不是图片合计这么大是CSS弄出来的啊!

(其实网页已经给了提示,我现在写文章的时候才看到)

横竖半天没看出来什么规律,寻思上网搜搜有没有关于这方面的介绍(简单找了下好像没看到)。遗憾的是,虽然是玩烂了的东西,网上全是一些不忍直视的外行教程,有些甚至还用到了奇奇怪怪的盲文库,咱就是说整个这东西不至于还要专门用个第三方库吧。

那行吧,既然没有找到资料,咱也不屑去当脚本小子,还是得靠自己

从网站上找到的背景资料

盲文又称点字、盲字、凸字,是盲人使用的文字,由法国人路易·布莱叶发明,透过盲文板、盲文机、盲文打印机等在纸张上制作出不同组合的凸点而组成。盲文的基本单位是长方形的盲符,有位置固定的六个点,每个点可以凸出或不凸出,形成64种可能。六个点的分布是左右两行,上中下三层。如图所示,左行自上而下称为1、2、3点,右行自上而下称为4、5、6点。在电脑的使用范畴内,盲人可以配合盲文显示机将萤幕上的文字即时转化成盲文;而为了能表达ASCII的所有符号,故有增至八点的盲文产生。

实在不行,又看会之前的输出琢磨了一会

惊了,这不二进制吗

我们这里以⠕为例

这是我以为的排列顺序(之一,横着排和之前的拉直是一个意思)

实际上的排列顺序

所以转换成二进制应该是(10101000)因为是从第一位到第八位进位,所以计算的时候应该反过来(00010101 = 2 ^ 4  + 2 ^ 2 + 1)= 21 换算成16进制就是0x15

现在加上初始的offset 0x2800就得到⠪这个符号的编码0x2815了!!

下面是实现代码(主要部分)

def pic2tile(pic, tile_size=(5, 3)):
    # 将图片分成(5, 3)的tile
    img_size = pic.shape
    tile_list = np.split(pic, img_size[0]//tile_size[0], axis=0)
    for i, sub_list in enumerate(tile_list):
        tile_list[i] = np.split(sub_list, img_size[1]//tile_size[1], axis=1)
    return tile_list


def tile2braille(tile):
    # 将tile分割,转换成16进制code
    part_1 = tile[:3, 0]
    part_2 = tile[:3, 1]
    part_3 = tile[3, :2]
    new_array = reduce(lambda a, b: np.append(a, b), (part_1, part_2, part_3))
    binary_str = ''
    for i in new_array:
        binary_str = str(i) + binary_str
    code = 0x2800 + int(binary_str, base=2)
    return chr(code)

ps: tile分成(5, 3)是考虑到有字间距和行间距所以各留了一格留白,可以一定程度上防止结果图像变形

一些补充

GBK(国标)编码是没有盲文的,所以编码要换成utf-8with

with open(f'output/{file_name.split(".")[0]}.txt', 'w', encoding='utf-8') as file:
	file.write(result)
  • 在网页上一个字符宽高比大概是1.68,QQ聊天的话是2.28,微信是1.54(拿截图工具测像素测出来的)。所以在意的话可以适当调整图像缩放比例
  • 微信聊天框(在我手机上)一行是能显示20多个字符的,所以我这里将图片宽度缩放为60px,这样结果出来就是20个字符
  • 由于我是用于生成聊天的字符画,对于这种小图不要用边缘检测!!用二值化将深色的勾线分离出来就好了 ret, img = cv2.threshold(img, 80, 255, cv2.THRESH_BINARY_INV)(这里的threshold是手动调整的,之后如果闲的话可能会更新一下这里)