Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

我写了一个程序来清洁手写笔记的扫描图,并同时减少文件大小。

示例输入输出:

左边: 300 DPI, 7.2MB PNG / 790KB JPG. 右边: 300DPI, 121KB PNG.(注解1)

免责声明:此次描述的过程基本上是Office Lens应用已经实现了的,或许还有很多其他工具也可以做同样的事。我并没有提出一个完全新的发明,我只是对现有的工具重新实现。

如果你没时间,可以直接查看github库,或者跳到结果一节,那里你可以与3D色簇程序交互(本翻译稿已将交互过程显示为gif动图,请查看原文链接参与交互)。

动机

我的有些课并没有指定教材。这些课上,我习惯每周指定一位“学生记录员”分享他们的课堂笔记,这样就可以留下些文字记录以备学生们检查自己对材料的理解。这些笔记会以PDF格式上传到课程网站上。

在学校,我们有一台可以扫描成PDF文件的“智能”影印机,但扫描输出文档有点,嗯,不美观。下面是扫描一页手写作业的输出:

影印机似乎是随机的选择是要二值化每个符号(如上图的x)还是转换符号为笨拙糟糕的JPG图像(如上图中的平方根符号)。不用说,我们可以做得更好。

概述

我们从一份可爱的学生笔记扫描开始,如下图:

这张原始的PNG图以300DPI的分辨率扫描,大小大约是7.2MB,以85的质量级转换为JPG图像后大小大概790KB(注解2)。因为扫描的PDF格式仅仅是PNG或JPG的封装容器,所以我们不会期望图片转换成PDF后文件大小会减小。况且800KB每页也很大,出于加载时间考虑,大约100KB每页(注解3)才是最合适的。

虽然这个学生做的笔记很整洁,但扫描图像看起来还是有点乱(你不能怪她)。有很多反面的字迹或隐或现,相比于背景是单一颜色的情况,反面字迹不仅让观看者分神,也不利于JPG和PNG编码器压缩。

下面是我的noteshrink.py程序输出的样子:

这是一个比较而言很小的PNG文件,大概只有121KB。输出图像不仅更小,也更干净。

流程与彩色图像基础

下面是生成上面紧致、干净图像的处理步骤:

1、确定原始扫描图像的背景色。

2、通过阈限一个不同于背景色的值来分离出图像前景。

3、在前景中选择一个小数量的“呈现色",然后把原图转换为一个索引色彩PNG图像。

在深入介绍上面每个步骤之前,复习一下彩色图像的数字存储会很有帮助。因为人类眼睛有三种类型的色彩敏感细胞,我们可以通过组合不同强度的红绿蓝三种色光(注解4)来重现每种颜色。结果就是每种颜色都对应RGB色彩空间中的一个三维点,如下所示(注解5):

尽管真正的向量空间允许无限个连续不同的像素光强,但为了数字存储我们需要离散化颜色值。典型情况是为红绿蓝三色各分配8位存储空间。无论如何,把图像中的颜色对应到三维空间中的一个点是一种有力的分析方法,在深入讨论上面的三个步骤时你会清楚的看到这一点。

确定背景色

因为一页纸的大部分面积都是没有笔迹或横线的,我们可以假定纸张颜色是扫描图像中最常出现的那一种颜色。如果扫描仪总是将空白的地方用同一个RGB三元值呈现,那问题就简单了。遗憾的是实际情况并非如此。扫描得到的颜色值会受到灰尘点,玻璃上的污迹,纸张本身各处颜色差异,光感器噪声等因素的影响。所以实际情况是,“纸张颜色”可能会是上千个不同的RGB值。

原始扫描图像是2081X2531,总共是5267011个像素。虽然我们可以考察所有的像素点,但是只考察输入图像中有代表性的样本会更快。noteshrink.py程序默认情况下只取样5%的像素(对于300DPI的扫描完全没问题)。现在,我们看看在原始扫描下随机取出的

10000个像素样本:

虽然上图和原始扫描图像一点也不像——上面看不到一个字——但两张图的色彩分布情况大体相当。两张图大体上都是灰白色的,有一些红的蓝色和灰色像素。下图是同样的10000个像素,以亮度(像素的R、G、B值相加得到)排序得到的:

离远点看上图,底下的80-90%的地方似乎都是同一颜色,然而,仔细点看还是嫩发现颜色差异的。事实上,上图中出现频率最高的的颜色,RGB值是(240,240,242),仅在10000个样本中出现226次,还不到总像素的3%。

因为众数的比重在总的样本数中只占很小比例,我们应该对其描述图像中颜色分布的可靠性提出质疑。如果在找众数前先减小色彩深度的话,我们可以更方便的找到页面背景色。下图是将8比特每通道消减至4比特每通道后的图:

现在频率最高的颜色RGB值是(224,224,224),在总的样本像素中出现3623次(36%)。本质上来说,我们通过减小色彩深度,让相近颜色变成了同一颜色,也使得找到数据中的峰值点更容易了(注解6)。

这里在可靠性和准确性之间做了一个平衡:浅的色彩深度更有利于找到颜色分布中值,较深的色彩深度更准确。最终,我用6比特每通道来确定背景色,这个值似乎很好的平衡了以上两点。

分离前景色

一旦我们找出了背景色,就可以根据每个像素颜色近似程度对图像二值化。一种自然的计算两个颜色相似程度的方法就是依据颜色的RGB空间计算它们的欧几里得距离;然而,这个简单的方法却不能很好的分开下面几种颜色:

下表是上面各种颜色和它们到背景色的欧几里得距离:

可以看到,暗灰色,也就是应该归为背景色的背面笔迹的颜色,欧几里得距离比应该归为前景的粉色还要大。任何可以将粉色归为前景的阈值都会把背面笔迹也归为前景。

我们可以通过将色彩从RGB空间映射到HSV空间来解决这个问题。HSV空间将RGB空间的立方体形状扭曲成圆柱体形状,如下剖面图所示(注解7) :

HSV圆柱以从内到外,从下到上的多彩圆环为特点,色调H指圆环上的角度,圆柱的中心线从底下的黑色到顶部的白色,中间是逐渐过度的灰色,整个线具有饱和度S为0。而整个圆柱体的圆弧外表面不论色调多少,饱和度都是1。最后,亮度V指一个颜色总的明亮程度,从圆柱底的黑到圆柱顶的白。

现在再来考虑前面RGB空间中不好区分的几种颜色,这次考察它们在HSV空间中的V值和S值:

可以看到,白色黑色和灰色在明亮度V上有很大差别,但是具有相似的低饱和度S,至少现对于红色和粉色时是这样。通过HSV提供的信息,我们可以通过下面规则成功的区分像素是否为前景:

a、明亮度V比背景色大0.3以上的,或者

b、饱和度S比背景色大0.2以上的颜色

前一个条件区分出黑色笔迹,后一个区分出红色和粉色笔迹。两个条件都有效的排除了灰色的背面笔迹,不同的图像可能需要设置不同的S/V的阈值,到结果一节可以看到详细介绍。

选择一组呈现色

一旦我们分离出了前景,我们就只剩下一些对应着纸上笔迹的颜色。让我们可视化这些颜色。但是,不是将这些颜色看成一组像素,而是把它看成是在RGB彩色空间中的一些3D点。结果的散点图看起来有点“厚重”,有几条色带:

上图是由three.js生成的可交互3D图(本译稿呈现为动图)。

现在的任务变成了将原始24位每像素的图像转换成一个具有小数量(这里是8个)代表色的索引色彩图像。这样做有两个效果:一、呈现一个颜色现在只要3比特(因为8等于2的三次方),减少了文件大小;二、使得结果图像颜色更一致,因为相似颜色很可能由结果图像中的同一颜色呈现。

为了完成这个任务,我们使用一种利用了上图“笨重”属性的数据驱动方法。选择对应着上图颜色簇中心的颜色,就可以得到一组较精确的代表色。专业点说,就是我们要使用簇分析法解决一个色彩量化问题(这个问题本身是向量量化问题的一个特例)。

我选择解决这个问题的算法是k平均算法。总的来说就是找到一组中心点,使得周围点到最近中心点的平均距离最小。下图就是你在上图中选择7个簇(也即7个中心点)时得到的(注解8):

这里本来也是gif图, 但是图片太大, 微信不让上传, 可以在下面链接地址查看网站文章原图: http://python.freelycode.com/contribution/detail/454

在这个图示中,有黑色外壳的点代表前景色的取样点,有颜色的线把这些点连到最近的中心点。当整个原图转换为索引色彩图像时,每个前景点都会被离它最近的中心点代替。最后,外圆指示了到中心点的最远距离。

哨声与铃声

除了能设置亮度V和饱和度S的阈值,noteshrink.py程序还有几个其他的特性。默认情况下,程序会通过重新设置颜色的最大最小强度值为0到255,来增加最后调色板各色的艳丽度和对比度。如果没有这个调整,上面图像的8色调色板看起来就是这样:

调整后的调色板更鲜艳:

程序还有一个选项可以在隔离前景后,强制背景色变成白色。为了进一步减小PNG图像的大小,noteshrink.py程序可以自动调用像optipng,pngcrush和pngquant这样的PNG优化工具。

程序最后使用ImageMagick的转换工具将几张图像组合到一个PDF文件中去,就像这个PDF文件一样。还有,noteshrink.py自动的根据输入文件名排序(以数字为关键字,而非像shell中的globbing操作一样使用字母表排序)。当你傻傻的扫描程序(注解9)输出的文件名像scan 9.png 和 scan 10.png这样时,依据数字大小排序则更好。

结果

下面是几个程序输出的对比图,第一个(pdf文件在此)使用默认的阈值设置看起来很好:

下面是色簇的可视化结果:

下个例子(PDF文件在这)要求将饱和度阈值降低到0.045,因为下面的蓝灰线很淡。

这是色簇图:

最后,一个扫描自工程图纸的例子(pdf文件在这),这里,我把亮度V阈值设为0.05,因为背景色和线条的对比度很低:

色簇图如下:

所有4个PDF文件总共788KB,大概130KB每页。

结论和进一步工作

我很高兴能为我的课程网站写一个处理手写笔记的实用程序。另外,我也很享受本文的写作,特别是写作时我被鼓励去完善维基百科中颜色量化里的2D可视化部分。而且也终于学习了three.js(非常有趣,我还会再用这个的)。

如果我哪天重访本项目,我会试试其他的量化方案。本周我想到的一个就是使用频谱簇化处理一集色彩样本的最近相邻图。我以为这是一个全新的想法看,但是结果已经有一篇2012年的论文提出了这个想法。恩,好吧

你也可以尝试用最大期望算法来构造一个高斯混合模型来描述颜色分布——不太确定以前是否有人做过。其他有趣的想法还有试试“知觉一致”的色彩空间,比如L*a*b*来形成簇。或者尝试对给定图像自动确定最佳簇数量。

另一方面,我还有很多播客项目要做,所以我就先暂时搁置这个项目,并要求你来查看一下noteshrink.py的github库。

----------------------------------------------------------------

注解1、这里使用的手写笔录得到了我学生Ursula Monaghan和John Larkin的慷慨允许。

注解2、为了加载速度,本图片其实已经降到了150DPI。

注解3、我们的影印机在限制PDF文件大小上做的很好,这种类型的文件大概会少个50-75KB每页。

注解4、这就是加法三原色:红绿蓝。你的初级艺术老师可能告诉你说三元色是红黄蓝,这不正确。确实有减法三原色,

他们是青色,黄色和洋红色。加法三原色和光的组合有关(你显示器发的光),减法三原色和墨水及染料的组合有关。

注解5、图像由Wikimedia用户Maklaan提供,许可协议是CC BY-SA 3.0。

注解6、查看维基百科文章histogram article了解为什么增加“带宽”有作用。

注解7、图像由Wikimedia用户SharkD提供,许可协议是CC BY-SA 3.0。

注解8、为什么k=7而不是8?因为在最终图像里,我们只要8种颜色,而背景色已经占了一种。

注解9、没错,我看着你呢,Mac OS的Image Capture。

英文原文:https://mzucker.github.io/2016/09/20/noteshrink.html

译者:yuezy3

python笔记手写照片_用Python对手写笔记进行压缩与增强相关推荐

  1. python练手经典100例-【Python精华】100个Python练手小程序

    100个Python练手小程序,学习python的很好的资料,覆盖了python中的每一部分,可以边学习边练习,更容易掌握python. [程序1] 题目:有1.2.3.4个数字,能组成多少个互不相同 ...

  2. python写金字塔_金字塔python开发手册

    python API 基本方法 编写策略过程中所需要使用的基本函数.其中init和handle_bar是必须实现的两个方法,其余是可选择实现的方法. init (必须实现) init(context) ...

  3. 用python写搜索引擎_用python做一个搜索引擎(Pylucene)的实例代码

    1.什么是搜索引擎? 搜索引擎是"对网络信息资源进行搜集整理并提供信息查询服务的系统,包括信息搜集.信息整理和用户查询三部分".如图1是搜索引擎的一般结构,信息搜集模块从网络采集信 ...

  4. 为什么用python写爬虫_老猿为什么写Python爬虫教程

    对于"爬虫", 或许你只是听说过,或许已经有所了解.无论怎样,你可能有过这样的困惑: + 学了爬虫不知道怎么挣钱? + 技术不知道如何进阶? + 遇到问题不知道找谁交流? 十多年前 ...

  5. python写表格_使用Python对Excel进行读写操作

    学习Python的过程中,我们会遇到Excel的读写问题.这时,我们可以使用xlwt模块将数据写入Excel表格中,使用xlrd模块从Excel中读取数据.下面我们介绍如何实现使用Python对Exc ...

  6. python函数赋值给对象_【Python核心编程笔记】一、Python中一切皆对象

    Python中一切皆对象本章节首先对比静态语言以及动态语言,然后介绍 python 中最底层也是面向对象最重要的几个概念-object.type和class之间的关系,以此来引出在python如何做到 ...

  7. 老男孩python全栈开发视频教程_老男孩Python全栈开发(92天全)视频教程 自学笔记08...

    day8课程内容: 文件操作 f=open('小重山','r',encoding='utf8')   #以读的方式打开文件 data=f.read() print(data) f.close()  # ...

  8. python做审计底稿视频_最新Python教学视频,每天自学俩小时,让你offer拿到手软...

    2020最新Python零基础到精通资料教材,干货分享,新基础Python教材,看这里,这里有你想要的所有资源哦,最强笔记,教你怎么入门提升!让你对自己更加有信心,重点是资料都是免费的,免费!!! 如 ...

  9. python怎么做软件程序_看 Python 超级程序员使用什么开发工具

    Python超级程序员使用的开发工具 我以个人的身份采访了几个顶尖的Python程序员,问了他们以下5个简单的问题: 当前你的主要开发任务是什么? 你在项目中使用的电脑是怎样的? 你使用什么IDE开发 ...

最新文章

  1. 流控制传输协议 SCTP
  2. 羊皮卷的故事-第十章-羊皮卷之三
  3. 服务器2003添加共享文档权限,Windows2003使用命令行设置共享权限与安全权限心得...
  4. codeforces 1038a(找最长的前k个字母出现相同次数的字符串)水题
  5. [ARC072C]Alice in linear land(dp,贪心)
  6. Apache-SimpleEmail 简单应用
  7. 前端学习(3172):react-hello-react之实现底部功能
  8. python学习笔记(五)字符串函数二
  9. c语言学习-使用指针求一个字符串的长度
  10. 一对一直播app大热,使用源码或自主开发一对一APP需要了解哪些技术?
  11. c语言程序设计输出函数,输出函数C语言程序设计.pdf
  12. java商城系统设计——秒杀
  13. 管家婆设计页面用什么做的_管家婆软件主要是用来做什么的?
  14. 为什么要在MD5加密的密码中加“盐”
  15. 操作系统软件测试,什么是操作系统_app软件测试全栈系列精品课程_功能测试视频-51CTO学堂...
  16. CR渲染器全景图如何渲染颜色通道_Corona渲染器如何处理材质溢色?【渲云渲染】...
  17. 【uniapp | 微信小程序】注册和开发环境搭建
  18. 赵旭计算机论文,上海交通大学赵旭教授团队在计算机视觉顶级期刊上发表最新研究成果...
  19. 墨尘 - UE4 入门教程笔记 —— 二
  20. 随时随地开展绘图工作,还得靠CAD在线!

热门文章

  1. “0x00000014”内存。该内存不能为“Written”(或“Read”)的解决办法。
  2. pyspark:FPgrowth
  3. 面试时,如何进行自我介绍
  4. 大浪淘金——下半年经济与资本市场展望(姜超6月25日于海通深圳策略会)
  5. 第01节 Go语言简介
  6. 朴素贝叶斯案例之text classification
  7. cad缩小_CAD图纸输出或打印后的尺寸为什么比实际小几毫米?【AutoCAD教程】
  8. 快速过熊掌号2.0新手任务了解熊掌号!
  9. Proteus教程——LED 应急灯电路
  10. STM32 库函数 延时函数计算