这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战
本篇内容改编自我的微信公众号,所述工作是我初学编程时的一个趣味小项目,虽然没什么含金量,但可以算得上是我作为一个初学者的一次勇敢尝试和有趣探索,在此分享给大家,欢迎不吝赐教。
背景介绍
从某APP中截取了我的背单词曲线之后,我敏锐地发现了蕴藏在其中的数学规律。
每六个月达到一次峰值,峰值的高度不断减小。为了在图上画一条线来拟合这个折线,我打开 MATLAB 一通操作猛如虎,画出了一个正弦函数受到一个衰减指数函数的调制的图像。但问题来了,我不知道怎么把这两个图贴在一起,需要把 MATLAB 绘制的曲线从白色背景中抠出来。
联想到曾经我用 Excel 抠二维码的憨批操作(当时不用PS),我觉得还是有必要进化一下我的抠图技能。
纯色背景抠图
从原理上讲,从这么一个简单背景中把图案抠出来,无非就是对每一个像素点检查它的颜色,如果是背景色就把透明度变成0,否则保留。因此,这是非常容易实现的。
from PIL import Image
im = Image.open('SaoMaYouJingXi.jpg') #读取图片
im = im.convert('RGBA') #转化成RGBA(RBG+透明度)格式
pix = im.load()width = im.size[0]
height = im.size[1]
for x in range(width):
for y in range(height):
r, g, b, a = pix[x, y]
if (r>100)&(g>100): #如果是白色
a = 0 #透明度改成0
im.putpixel((x,y),(r,g,b,a)) #把四个参数还给这个像素
im.save("SaoMaYouJingXi.PNG") #保存
# 早期写的东西甚至完全没有代码规范意识
小试牛刀:
不愧是精确到像素的抠图,放大了看也这么干净利索。
变色背景抠图
但,从纯色简单背景中抠图就太没意思了。我又联想到曾经用Excel从照片里抠手绘的班徽:
当时也是费了很大功夫,抠出来的效果在浅色背景下还可以,但是放在深色背景中就非常不堪入目了。于是,我打算想办法把班徽重新抠一下。
第一次尝试
既然不是简单的纯黑或纯白背景,没办法直接判断颜色的RGB值大概在什么范围,那么我们首先要让程序去识别并记住背景有哪些颜色,然后再把这些颜色的像素抠掉。
所以我们要分两大步:
- 手动从背景中采样,截图保存,导入程序,供其识别、记录;
- 把整个图片导入,处理,导出。
这张班徽的照片里,我在四条边附近分别截取了一张背景样本,后来为了优化效果,又在没抠掉的地方补了一张。
接下来就是让计算机记住这些出现过的颜色。
我们知道,一个像素的颜色可以用R(0-255)
、G(0-255)
、B(0-255)
三个变量来表示,一张图中的颜色总数不超过256^3
。
鉴于我们现在只关心某一种颜色有没有出现过,不妨创建一个256x256x256
的三维哈希表。
可以想象一个大的正方体,里边有256^3
个小格子。初始状态下每个小格子里都是0
,当计算机看到出现某一种颜色(m,n,p)
,就把第m行
、第n列
、第p层
的那个小格子里的0
变成1
。等计算机遍历完了所有的像素,这些格子里该填1
的都填上了1
。
这里,我们为了减小计算机的工作量,可以把相邻的3
个R/G/B
值简化成一个(如R=0,1,2→R'=0
、R=3,4,5→R'=1
),把这个256x256x256
的正方体压缩一下,长宽高各变为原来的1/3
,列表大小缩小到原来的1/27
。
matrix=[]
for i in range(85):
matrix.append([])
for j in range(85):
matrix[i].append([])
for k in range(85):
matrix[i][j].append(0)
然后我们不动这个大盒子,让计算机再去检查第二个样本……等到所有的样本都看完了,盒子就填得差不多了。这时候,背景中出现过的颜色大部分已经被标记了。
我们把这个检查的过程定义为learn
:
def learn(str):
im = Image.open(str)
pix = im.load()
width = im.size[0]
height = im.size[1]
for x in range(width):
for y in range(height):
r, g, b = pix[x, y]
matrix[r][g][b]=1
在本例中,让程序learn
完五张采样得到的背景图,再执行抠图的主程序,保存图片。
接下来我们看下效果:
失望,非常失望。无论我再怎么给它查漏补缺,补充新的背景来learn
,抠出来还是会有大量残留。
不过没关系,我们可以再模糊一点。宁可错抠一千,不放过一个。
第二次尝试
接下来,我们增设一个模糊度dim
,意思是在抠图的判断条件中,只要这个点的RGB
坐标出现在被标记坐标的附近,三个坐标分量都在某被标记点对应坐标分量±dim
的范围内,即该点的RGB
坐标在以某被标记点为中心、棱长为2*dim
的正方体内,我们就把它抠掉。
具体操作不再赘述。随着我不断增大dim
的值,残留的区域确实越来越少,但是结果总是不尽人意。
第三次尝试
于是,我不再满足于小立方体级别的模糊,决定大手一挥,激进一点。
这次,我打算计算出背景中所有像素点的R、G、B
各自的最大值和最小值,然后把所有R、G、B
都在最大值之下、最小值之上(包含最值)的像素全部抠掉。
为了判断方案的可行性,我把图片信息按颜色展开,在坐标系中呈现出来。
这里,X、Y、Z轴分别是压缩之后的RGB值,范围是[0,85]
中的整数。
每个点的颜色就是它的坐标对应的RGB值所对应的颜色。
在三维坐标系中,引入透明度,可以刻画第四个维度的信息。我们选取该坐标处像素的数量(也就是在原图中有多少个点同为这个颜色)作为透明度的自变量。
为了控制因变量,也就是透明度的范围在[0,1]
,我们需要知道最多有多少个像素是同一个颜色,把这个最大值作为分母。
同时,为了避免这个最大值过大,导致大部分点的透明度都非常高(透明度越接近0,记为越高),我们换一种映射,把通过比值求出来的相对透明度开四次根号。
最终,我们画出来的图像是这样的:
越不透明的点表示对应颜色的像素越多。
下面我们回到抠图。
分析一下刚刚费了不少力气画的这个图,我们发现,三种主色还是相对来说界限分明的。如果你不信,我可以随便找一张图给你展开一下:
我们来看一下背景样本中所有颜色的R、G、B的最大值和最小值。
这个结果其实非常符合预期,因为从刚才那个坐标系中可以看到,跟背景相近的颜色都集中在R、G、B都非常大的地方。
我们把R、G、B都在对应区间里的点抠掉,然后看一下效果:
AMAZING!
简直完美。尽管我事实上并不认可这个原理,毕竟本质上这属于莽夫行为。但是不得不说,这个效果确实是非常不错的。
对比一下Excel抠图、learn抠图和莽夫抠图:
简直是一部愈演愈烈的革命乐章!
总结
至此,我已经完成了我最初的目标,并且积累了一套极其简陋但却好像还挺像回事儿的抠图方案。
在我看来,代码抠图的魅力不仅在于它精确到像素的精准打击,也在于它可以让你随心所欲定制需求的可塑性,还在于它只需要你更改一两个参数、点一下编译运行,就能自动输出成果的便捷。
此外,我还顺便做了一些同样没什么用的小功能,比如马赛克:
不论是抠图也好,打码也好,逻辑上不难理解,代码上也不难实现。在日常生活中,有时候手边就是缺乏处理简单任务的专业工具,比如 PhotoShop 。这时候与其安装 Ps ,倒不如自己写一个程序,根据自己的需求编码实现功能,看似麻烦,实则不失为一条捷径。
项目代码:Yuezih-Playground/Ez Image Process (github.com)
(早期工作,年久失修。没有demo,摆烂开源。)
今天的文章简陋无比的 Python 抠图方案,好像还挺像回事儿?分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/17676.html