在上一篇文章【图工具的优化——实现文本居中】中,我们已经实现了对插入字体的左中右对齐显示,那因为上期文章混进去了不少语法讲解,所以后面的内容就顺延到这啦,哈哈哈。
我比较长怎么办啊?
我们的斗图小工具,现在面临这一个苦恼,这些文本他坏,一会长一会短的,一旦有个很长很长的,直接就捅到里面去了,根本显示不全啊,这咋办呢?
我稍微想了下,这个也简单,我可以不断的减小字号,直到我们的空白区域可以放得下:
while (CONST_IMG_WIDTH <= textLen + 2*off_set[0]) and fontSize >= 1:
fontSize -= 1
imageFont = ImageFont.truetype('./resources/msyh.ttc', fontSize)
textLen = draw.textsize(text, imageFont)[0]
print("当前字号{},文本宽度{}".format(fontSize, textLen))
看看效果吧:
python emofigther.py 长的就会变细变细了就能塞下了嘛
效果其实还是挺好的,就是实现的方式有点太low了,而且不停的加载字体,看着就觉得开销很大,那有没有更优雅的办法呢?
来做点小数据分析吧
下面我们来研究一下,字体的字号大小跟其经过PIL绘制之后的大小有什么关系,接下来我们主要会用到Numpy、matplotlib跟scipy几个库。
先来准备点数据样本,通过draw.textSize函数,绘制单个字并获取其大小:
# 准备分析数据
font_num = []
text_size = []
for i in range(1, 31):
imageFont = ImageFont.truetype('./resources/msyh.ttc', i)
text_size.append(draw.textsize("字", font=imageFont))
font_num.append(i)
借助matplotlib的pyplot模块,我们可以绘制各种图像,先让我们以字号为x轴,字体宽度为y轴,画出样本的散点图
import matplotlib.pyplot as plt
#....
# scatter画出散点图,以字号为x轴,字体宽度为y轴
# 在分析前,先绘制散点图,对大致的函数形状进行分析
plt.scatter(list(map(lambda x: x[0], text_size)), font_num, color="b", label=u"字体宽度")
运行之后,会弹出这样一个窗口
好的,从这个图片上分析,我们的字号与宽度是一个完美的正相关,用函数来表示,就是
$$ y=kx+b $$
那问题来了,我们如何取得k和b两个常数的值呢,那个说k=1,b=0的同学你坐下!我们要严谨,看出来了也不要说出来嘛,额,不对,就算是看出来了,但我们还是要以严谨的方式去证明他的!为了求出k和b两个常数的最优解,我们需要用到scipy.optimize模块的leastsq函数,这个函数实现了“最小二乘法”算法,通过不断的尝试不同的常数,求出与期望结果误差最小的最优解,那下面就简单介绍一下怎么用leastsq对函数进行拟合:
首先,我们要定义一个函数形状(一元一次、一元二次、多元多次)
def func_shape(p, x):
"""定义函数形状,哈哈哈,就是 y = kx+b 直线!
Args:
p: 常数
x: 自变量
Returns:
函数运算求得的因变量
"""
k,b = p
return k*x + b
然后定义一个误差计算函数
def func_err(p, x, y):
"""定义误差函数
Args:
p: 常数
x: 自变量
y: 验证因变量
Returns:
返回函数运算结果与验证因变量之间的误差值
"""
return func_shape(p, x) - y
使用leastsq函数进行求解,获取最优常量k、b
from scipy.optimize import leastsq
r = leastsq(func_err, p0, args=(_font_size_np[:,0], _font_num_np))
# 计算结果中的r[0]为一个元组,为求得的k和b
k, b = r[0]
# 最后我们得出结论,拟合结果为y = x
print("k=",k,"b=",b, "r=", r)
把拟合曲线也画在图标上:
# 画出拟合线,以字号为X轴,函数运算结果为Y轴
plt.plot(X,func_shape((k, b), X),color="orange",label=u"字体宽度拟合",linewidth=2)
可以看到拟合曲线完美的经过了每一个数据点,这基本就可以认定我们的拟合曲线基本上就是 y=x了,
当然,我们的样本量现在是非常少的,也非常的规整,其实更多情况下,数据可能是这样分布的:
这样是不是就能体现出拟合的意义了呢?
让我们把研究成果用在我们的代码上:
# 方法2:通过简单的数据分析,我们研究出字体宽度 = 字体字号这一函数
def char_len(text_size):
return text_size
# 减小字号,直到 字数*单位宽度 适应空白区域宽度
while char_len(fontSize) * len(text) > (CONST_IMG_WIDTH - 2*off_set[0]):
fontSize -= 1
学霸们,动起来!
如果有小伙伴们看到这个章节,对本章节描述的数据分析过程非常感兴趣,而且觉得自己的数学功底非常扎实(特别是离散数学、概率、统计这方面的)你们请离开本系列文章——因为你们已经了解到了在科学计算领域,Python也是一把不错的兵刃,而你们,被选中的魔法少女(大雾)们,可以去深入了解以下几个库,然后投入到轰轰烈烈的数据分析事业中去吧!
- Numpy —— 为Python提供了多维数组的扩展,同时也提供了丰富的集合运算、矩阵运算、向量运算,可以说是Python科学计算的基石
- matplotlib —— 可产生出版物质量的图表的2D绘图库,数据可视化是数据分析不可或缺的手段之一
- pandas —— 数据分析库,包括数据框架(dataframes)等结构
- Scipy —— 高级科学计算库,提供了大量的科学计算工具及算法,例如本文用到的leastsq最小二乘法求解多项式算法(妈妈再也不用担心我要重复造轮子了!)
这些库的相关资料都非常的好找,而小弟又才疏学浅,就不再对它们在作过多展开了!
因为作者数学水平太差了,我们下期换个方向玩
按照惯例,放上此次的源码:
GitHub
其中的char_analysis.py即为本文所属的函数拟合例子
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。