基于SVM的字母验证码识别

摘要

本文研究的问题是包含数字和字母的字符验证码的识别。我们采用的是传统的字符分割识别方法,首先将图像中的字符分割出来,然后再对单字符进行识别。首先通过图像的初步去噪、滤波、形态学操作等一系列预处理过程,我们能够将图像中的噪点去除掉。为了将字符分割开来,我们利用Kmeans聚类算法对图像中的像素点聚成五类,分别代表五个字符,结果表明Kmeans算法的聚类准确度能够达到99.2%。对字符分割完成之后,我们采用支持向量机的算法对字符进行识别,通过调节参数能够使得准确率达到93%左右。

一、问题描述

验证码目前在互联网上非常常见,从学校的教务系统到12306购票系统,充当着防火墙的功能。但是随着OCR技术的发展,验证码暴露出的安全问题越来越严峻。目前对验证码的识别算法研究已经比较成熟,有了许多优秀的验证码识别算法。尤其是近年来深度学习的兴起,一般的验证码已经没有了任何识别难度。抱着学习的态度,我们还是从最基础的传统算法来开始研究。

我们从某验证码识别比赛中获取到验证码数据集,该数据集包括10000个验证码样本,序号0000~9999,每个验证码图片,包含5个英文字母或数字字符。mappings.txt为该数据集的标签,每一行对应一个样例,其中:第一列为样例的序号;第二列为样例图片中的字符内容,英文字符统一转化为大写。验证码样本如图1所示:

图1验证码样本

二、问题分析

传统的图像识别方法一般需要经过以下几个过程:图像预处理、图像分割、字符识别。由于图像中会存在干扰噪点或者干扰线,这对于字符的分割和识别会造成极大的困难。因此图像预处理的任务就是去除掉噪点,只保留有用的字符部分。由于一个样本中都包含了许多字符,要做到直接进行端对端的识别非常困难。而通过字符分割我们就能够够得到字符识别样本库,有了这样的样本之就可以构建将其作为训练集训练分类器从而将字符分割。综合考虑自身能力以及所学的知识,我们决定使用svm算法来作为字符识别的分类器。Svm的好处在于已经有现成的框架可以使用,并且处理大量数据时svm的效果比较好。

三、图像预处理

对于图像的预处理,一般流程为:灰度化—二值化—初步去噪—滤波—形态学处理(二次去噪)—二值化(二次)—字符分割—字符归一化。

3.1灰度化

灰度化即将彩色图像图像转化为灰度图像,这样就能将三通道的彩色图像转化为单通道的图像。图像灰度化处理之后能够使得后续的处理更加方便,灰度化的一般公式为:

灰度化中我们使用的图像工具箱为PIL库中的Image模块,灰度化的python实现代码为:

imgry=img.convert('L')

灰度化后的图像:

图2 灰度化后的图像

3.2 图像二值化

图像二值化是指将灰度图像转化为只包含0-1的黑白,0代表黑色,1代表白色。首先我们需要确定好阈值,将大于阈值的转化为白色,小于阈值的转化为黑色。利用photoshop图像处理工具可以看到阈值变化对于图像二值化效果影响,通过试验我们最终确定128是比较合适的。二值化同样使用Image模块,实现代码如下:

def get_table(threshold=128),def Get_BiImg(img)利用这两个函数即可。

最后得到的二值化的图像图3所示,我们可以看这是图像只有黑白两种颜色,并且主要字符非常突出。

图3 二值化后的图像

3.3初步去噪

从图像中可以看出二值化后的图像干扰噪点大部分成散点分布,而字符的轮廓经二值化处理之后较为明显。因此我们可以采用计算每个像素点周围的黑点个数的方法来作为判断是否为噪点的标准。根据图像的特点,我们有以下经验:如果某个点的位置周围点少于3个则可以认为是噪点。程序实现代码为:

def Get_ClearImg(img)

经过初步去除噪点之后的图像如下:

图4 初步去噪后的图像

从图中4可以看出经过初步去除之后已经去除了大部分的噪点,但是还有仍然存在很多噪点。因此需要进一步处理。

3.4 图像的形态学操作

图像的形态学操作简单来说就是基于形状的一系列图像处理操作,通过将结构元素作用于输入图像来产生输出图像,详细介绍可以在网上查到。图像形态学上的处理主要有:腐蚀(erosion)、膨胀(dilation)、开操作(oening)、闭操作(closing).对于进行过初步处理之后的图像,我们首先去除还有没有删除的孤立的小噪点族。幸运的是skimage库已经给我们准备好了一个函数:

remove_small_objects(img,min_size=15,connectivity=1)

这个函数可以直接帮助我们删除面积小于min_size的图像。我们首先计算图像中的连通区域,通过设定阈值来决定多小的连通区域可以删除掉。通过多次试验,我们发现面积为15时效果较好,既不会误删也不会漏删。最终我们得到的结果如下:

    

图5 形态学操作后的图像

可以看出图像中的大部分噪点已经被去除,但是仍然有些噪点。

在此基础上继续进行形态学出处理,首先进行腐蚀能够去除掉比较窄的线条。如果在进行胀,我们就能恢复到跟原来图像差不多的状态。实际上开操作就是先腐蚀后胀两步操作的整合。经过测试发现边长为3的正方形作为算子,效果最佳。最终的结果如下:

     

图6二次形态学操作后的图像

主要操作代码:

dst=morphology.remove_small_objects(biImg,min_size=15,connectivity=1)#去除小区域面积

dst=dst*1.0#转化为非逻辑型图像

dst1=sm.opening(dst,sm.square(3)) #开运算

四、字符分割

    经过一系列的操作,我们最终去除了大部分的干扰点。但是在操作过程中也有部分字符出现了裂缝。对于字符的分割,主要的方法有连通区域法、积分投影法,还有聚类消除法。由于字符存在不连续性,前两种方法暂时没有想出合适的操作。因为字符断裂开后相差不是很远,并且每张图像上的字符都为5个,所以如果采用Kmeans对数据点进行聚类的话,这些断裂或不联通的字符会聚成一类,这样就实现了字符的分割。这里我们直接用了sklearn库中的Kmeans函数。

最终的字符聚类如图7所示:

    

图7 部分字符聚类结果

经过50张图的测试,除两张图分割错误之外,其余都完全正确。

分类错误的图:

图8 字符定位错误的图像

在出现问题之后,我们认为可能是最初的中心点位置没有选好,但是经过更改质心初始化的方法也不能成功解决。这里还有待改进,但总体来说还是不错,定位准确率能够达到99.2%,所以暂时这样继续下去。

五 字符分割

通过聚类我们已经能够找出字符的位置,只需要找到最小包含字符的矩形就行,也就是找到这个矩形的对角线坐标即可。但是这里存在一个问题就是聚类之后的字符顺序并不是按照图片上的顺序来的,而是随机的,因此还需要对字符子图重新排序,我们以左上角的x坐标作为排序的标准。

排序之后对图像分割之后如下:

   

图9 字符分割的结果

可以看出字符已经被很好的分割开来了,但是由于大小不同对于后面的识别有很大困难。因此还需要将字符尺寸进行归一化,我们选定大小为30*20.结果如下:

    

图10 归一化后的字符

经过标准化之后的字符大小都一样,极大地方便了后续的识别步骤。

六、字符识别

经过预处理之后,将所有图像都进行了分割,得到一系列的大小一暗影的字符图片。经过200张图片的测试,经过预处理之后总的时间为78秒。大概一个小时就能够将所有图像处理完成。

6.1特征提取

我们选取的特征是投影特征,计算在水平方向和垂直方向的黑点个数,然后组成新的特征向量,结果为50维的特征向量。然后经过对文件名的操作,直接读取标签。这样我们就得到所有字符的特征向量和标签。

6.2 单样本字符识别

我们采用支持向量机作为分类器,svm的详细介绍可以参考之前关于支持向量机的算法报告。利用sklearn可以很方便的调动svm算法,并且能够实现多分类。我们选取200张图片作为训练集,得到1000个字符的字符训练集;以1000张验证码作为测试集,得到5000张字符的训练集。下面是在这些数据集上的测试结果,其中参数C表示的是最大间隔,gamma表示的是核函数的σ值。

表1 Svm字符识别算法运行结果

方法

C

gamma

错误率

svm

10

0.00001

90.24%

0.00002

91.92%

0.00005

92.92%

0.0007

91.14%

0.0009

90.52%

0.0001

93.28%

0.0003

92.56%

0.0007

91.14%

0.002

85.78%

0.004

74.56%

0.01

51.10%

0.04

11.20%

0.2

3.84%

6.3 结果分析

从上面的结果我们可以看出svm算法的准确率随参数的变化情况,当gamma为0.0003时准确率为92.56%最高。而随着gamma的增大,准确率会逐渐下降。对于单字符的验证码识别,这样的效果还是很不错的,毕竟字符类型有30多种。但是这样的结果如果用在整个验证码图像上则会使得准确率急剧下降。

七、总结

从最终结果出发,虽然我们对单字符的识别能够达到93%左右,但是这样的结果在实际操作效果是不够好的。通过分析我们不难发现,主要问题在于数据预处理过程的字符分割和特征的选取。虽然字符分割效率很高,但是一旦出现错误那么预测的结果就会完全错误。而由于样本种类较多,但是所选的特征数目较少或者不合适,这样也在很大程度上影响了算法的效果。因此在后面我们继续针对这两个地方进行研究,以期取得更好的结果。

八、代码

###————本模块主要用于预处理图像并把切割的图保存在trainImg中————#####
#verificationCode.py

import itertools
from PIL import Image
from PIL import ImageFilter
from PIL import ImageEnhance
import scipy.ndimage as ndi
from skimage import morphology as sm#形态学操作
import matplotlib.pyplot as plt
from skimage import io,color,transform,filters,segmentation,measure,morphology
from sklearn.cluster import KMeans
import numpy as np
import time
#——————————模式1:0-1二值化————————#
#图像二值化
def get_table(threshold=128):
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
return table
def Get_BiImg(img):
imgry=img.convert('L')
table=get_table()
biImg=imgry.point(table, '1')
return biImg
#初步去除噪声
def Get_ClearImg(img):
##### 去除散点
for j in range(img.height):
for i in range(img.width):
#消除周围黑点小于3的噪点(散点)
x=[i-1,i,i+1]
y=[j-1,j,j+1]
DCTS=itertools.product(x,y)#笛卡尔积
count=0
for p in DCTS:
try:#边界点的想x,y会出现问题,代价是边界点默认为噪点
if img.getpixel(p)==0:
count+=1
except:
continue
if count<=4:#包括自己四个点
img.putpixel((i,j),1)
clearImg=img
return clearImg def get_label(filename='testLabels.txt'):#获取测试集标签
#filename=filename#文件名
fr=open(filename)
labels=[]
for line in fr.readlines():
line=line.strip()
line=line.split(',')
labels.append(line)
return labels
def getIlabel(Labels,i):
cropLabel=[Labels[i][1][j] for j in range(len(Labels[i][1]))]
return cropLabel
def get_feat_label(imgName):#输入如:'0005_3_k.jpg'
img=Image.open(imgName)#读取图片
img=Get_BiImg(img)#转化为2值图片
rowArr=[]#存储特征向量
height=img.height#高度
width=img.width#宽度
for j in range(height):#获取图片的特征
count=0#计算黑点个数
for i in range(width):
if img.getpixel((i,j))==0:
count+=1#如果为黑点则计数加1
rowArr.append(count)
str1=imgName.split('.')#分割路径
label=str1[0][-1]#获取标签
return rowArr,label imgCount=2000
t1=time.time()#开始计时 for i in range(imgCount):
img=Image.open('testData\\{0:0>4.5s}.jpg'.format(str(i+3000)))#读入原数据
#imgry=img.convert('L')#图像灰度化
#imgry.show()
biImg=Get_BiImg(img)#图像二值化
clearImg=Get_ClearImg(biImg)#初步去除噪点
clearImg.save(r'D:\python Code\verificationCode\Img1\\testClearImg\{0:0>4.5s}.jpg'.format(str(i+3000)))
—对字符进行形态学去噪————#
#注意:此处开始使用的是skimage库而不是pIL,因为pIL库不提供形态学操作的工具。
#应当特别注意数据的类型变化
for i in range(imgCount):
img=io.imread('Img1\\testClearImg\\{0:0>4.5s}.jpg'.format(str(i+3000)))
img_gray=color.rgb2gray(img)
biImg=(img_gray<=128)#*1.0#转换成二值图像,*1是为了将逻辑型转化为float型
dst=morphology.remove_small_objects(biImg,min_size=15,connectivity=1)#去除小区域面积
dst=dst*1.0#转化为非逻辑型图像
#io.imshow(dst)
#dst1=sm.erosion(biImg,sm.square(3)) #用边长为3的正方形滤波器进行腐蚀滤波
#dst2=sm.dilation(dst1,sm.square(5)) #用边长为5的正方形滤波器进行膨胀滤波
dst1=sm.opening(dst,sm.square(3)) #开运算
#dst3=sm.closing(dst3,sm.square(2)) #闭运算
dst2=morphology.remove_small_objects(dst1==1,min_size=15,connectivity=1)#去除小区域面积
dst3=dst2*1.0
#dst4=sm.dilation(dst3,sm.rectangle(1,3)) #用边长为5的正方形滤波器进行膨胀滤波
io.imsave(r'D:\python Code\verificationCode\Img1\testMorphologyImg\{0:0>4.5s}.jpg'.format(str(i+3000)),dst3)
#io.imshow(dst3)
#plt.show()
labels=get_label('testLabels.txt')#获取标签
#————基于Kmeans的字符分割————#
for i in range(imgCount):
labelI=getIlabel(labels,i)#获取第i张图的标签
img = io.imread('Img1\\testMorphologyImg\\{0:0>4.5s}.jpg'.format(str(i+3000)))
img=color.rgb2gray(img)#灰度化
biImg=(img>=128)*1.0#转换成二值图像
row, col=img.shape
idy,idx=np.where(biImg==1)
wordIdx=[[idy[i],idx[i]] for i in range(len(idx))]
#initArray=np.array([[np.percentile(idx,100*(i+2)/7),np.percentile(idy,100*(i+2)/7)] for i in range(5)])
label = KMeans(n_clusters = 5,max_iter=300).fit_predict(wordIdx)#获取各点的标签
'''
#————显示结果————#
for i in range(len(label)):
x=idx[i];y=idy[i]
biImg[y][x]=(label[i]+1)*50
io.imshow(biImg)
plt.show()
'''
cropImg=[]#存储字符子图的位置
XX=[]#记录子图的位置
for j in range(5):
cropIdx=np.where(label==j)
minX=min(idx[cropIdx])#左上角X坐标
XX.append(minX)
minY=min(idy[cropIdx])#左上角Y坐标
maxX=max(idx[cropIdx])#右下角X坐标
maxY=max(idy[cropIdx])#右下角Y坐标
Img=biImg[minY:maxY+1,minX:maxX+1]
cropImg.append(transform.resize(Img,(30,20)))#尺寸归一化
#cropImg.append(biImg[minY:maxY+1,minX:maxX+1])#未经尺寸归一化地分割子图
order=np.argsort(XX)#获取顺序
sortCropImg=[cropImg[order[i]] for i in range(5)]#对图进行排序
'''
#————未经归一化的图像显示——————#
fig, axes = plt.subplots(1, 5, figsize=(6,2))#准备绘制多个子图
for i in range(5):
axes[i].imshow(sortCropImg[i])
fig.tight_layout()
plt.show()
'''
for k in range(5):
io.imsave(r'D:\python Code\verificationCode\Img1\testImg\{0:0>4.5s}_{1:0>1.5s}_{2:s}.jpg'.format(str(i+3000),str(k),labelI[k]),sortCropImg[k])
#feat,label=get_feat_label('{0:0>4.5s}_{1:0>1.5s}_{2:s}.jpg'.format(str(i),str(k),labelI[k]))
t2=time.time()
print('所用时间为:%fs'%(t2-t1))
#svm.y
from numpy import*
from sklearn.svm import SVC
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.cluster import KMeans
from skimage import io,color,transform,filters,segmentation,measure,morphology
from sklearn import metrics
import os
from PIL import Image
import time def get_table(threshold=128):
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
return table
def Get_BiImg(img):
imgry=img.convert('L')
table=get_table()
biImg=imgry.point(table, '1')
return biImg
def getFeat(img):#主要用于端对端的验证码识别
featArr=[]#存储特征向量
height=shape(img)[0]#高度
width=shape(img)[1]#宽度
for j in range(height):#获取纵向的特征
count=0#计算黑点个数
for i in range(width):
if img[j][i]==0:
count+=1#如果为黑点则计数加1
featArr.append(count)
for i in range(width):#获取横向的特征\
count=0
for j in range(height):
if img[j][i]==0:
count+=1#如果为黑点则计数加1
featArr.append(count)
return array([featArr])
def get_feat_label(imgName):#输入如:'0005_3_k.jpg'
img=Image.open(imgName)#读取图片
img=Get_BiImg(img)#转化为2值图片
featArr=[]#存储特征向量
height=img.height#高度
width=img.width#宽度
for j in range(height):#获取纵向的特征
count=0#计算黑点个数
for i in range(width):
if img.getpixel((i,j))==0:
count+=1#如果为黑点则计数加1
featArr.append(count)
for i in range(width):#获取横向的特征
count=0#####重新开始计算count的值
for j in range(height):
if img.getpixel((i,j))==0:
count+=1#如果为黑点则计数加1
featArr.append(count)
str1=imgName.split('.')#分割路径
label=str1[0][-1]#获取标签
return featArr,label
def getFeatsLabels(filePath):
featArr=[];labels=[]#存储特征信息和标签
childDir=os.listdir(filePath)#用于存储所有图片的文件名
for allDir in childDir:
img_whole_name=os.path.join('%s\%s'%(filePath,allDir))#图片包含路径的完整名称
rowArr,label=get_feat_label(img_whole_name)
featArr.append(rowArr)#featArr添加一个数据
labels.append(label)#labels添加一个数据
return array(featArr),labels
def get_label(filename='testLabels.txt'):#获取测试集标签,与verication中稍有不同
#filename=filename#文件名
fr=open(filename)
labels=[]
for line in fr.readlines():
line=line.strip()
line=line.split(',')
labels.append(line[1])
return labels
def convertLabel(labels):
newLabel=[]
for label in labels:
newLabel.append(char2intLabel.index(label))
return array(newLabel)
#——————读取数据——————# print('读取数据')
char2intLabel=['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G',
'H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
trainFeatArr,trainLabels=getFeatsLabels('D:\python Code\\verificationCode\Img1\\trainImg')
testFeatArr,testLabels=getFeatsLabels('D:\python Code\\verificationCode\Img1\\testImg') trainLabels=convertLabel(trainLabels)#将数据标签转化为数值型
testLabels=convertLabel(testLabels)
#——————支持向量机算法————#
t1=time.time()
print('开始计时')
#单字符识别
for ga in [0.00002,0.0007,0.001,0.01,0.1]:
for c in [10]:#[0.01,0.07,0.3,0.7,1,2,4,6,10,15,20,30]:
clf =SVC(C=c, kernel='rbf', gamma=ga,
decision_function_shape='ovo')#初始化模型
clf.fit(testFeatArr,testLabels)#训练模型
y_hat = clf.predict(trainFeatArr)#预测结果
#print('decision_function:\n', clf.decision_function(trainFeatArr))
#print('\npredict:\n', clf.predict(trainFeatArr))
accuracy = metrics.accuracy_score(trainLabels,y_hat)
print('accuracy: %.2f%%' % (100 * accuracy))
t2=time.time()
print('时间:%2fs'%(t2-t1))

基于SVM的字母验证码识别的更多相关文章

  1. 基于SVM.NET的验证码识别算法实现

    工作之余,对这个算法做了一些研究,并成功对验证码进行了识别,对普通验证码识别率在90%左右,识别速度相当快,已基于此做过一些自动查询.提交程序(例如投票.发帖等) ,还上过淘宝店,赚过一笔外快,现将相 ...

  2. 基于TensorFlow的简单验证码识别

    TensorFlow 可以用来实现验证码识别的过程,这里识别的验证码是图形验证码,首先用标注好的数据来训练一个模型,然后再用模型来实现这个验证码的识别. 生成验证码 首先生成验证码,这里使用 Pyth ...

  3. python识别验证码——一般的数字加字母验证码识别

    1.验证码的识别是有针对性的,不同的系统.应用的验证码区别有大有小,只要处理好图片,利用好pytesseract,一般的验证码都可以识别 2.我在识别验证码的路上走了很多弯路,重点应该放在怎么把图片处 ...

  4. opencv 视觉项目学习笔记(二): 基于 svm 和 knn 车牌识别

    车牌识别的属于常见的 模式识别 ,其基本流程为下面三个步骤: 1) 分割: 检测并检测图像中感兴趣区域: 2)特征提取: 对字符图像集中的每个部分进行提取: 3)分类: 判断图像快是不是车牌或者 每个 ...

  5. 完整的验证码识别流程基于svm(若是想提升,可优化)

    字符型图片验证码识别完整过程及Python实现 首先很感觉这篇文章的作者,将这篇文章写的这么好.我呢,也是拿来学习,觉得太好,所以忍不住就进行了转载. 因为我个人现在手上也有个验证码识别的项目,只是难 ...

  6. 字符型图片验证码识别完整过程及Python实现

    字符型图片验证码识别完整过程及Python实现 1   摘要 验证码是目前互联网上非常常见也是非常重要的一个事物,充当着很多系统的 防火墙 功能,但是随时OCR技术的发展,验证码暴露出来的安全问题也越 ...

  7. [验证码识别技术]字符验证码杀手--CNN

    字符验证码杀手--CNN 1 abstract 目前随着深度学习,越来越蓬勃的发展,在图像识别和语音识别中也表现出了强大的生产力.对于普通的深度学习爱好者来说,一上来就去跑那边公开的大型数据库,比如I ...

  8. 字符识别Python实现 图片验证码识别

    字符型图片验证码识别完整过程及Python实现 1   摘要 验证码是目前互联网上非常常见也是非常重要的一个事物,充当着很多系统的 防火墙 功能,但是随时OCR技术的发展,验证码暴露出来的安全问题也越 ...

  9. 中国知网(CNKI)验证码识别

    中国知网(CNKI)是最重要的中文学术资源数据库,收录绝大多数中文学术刊物.我们可以检索论文,也可以导出检索结果前6000条论文的题录数据. 在CNKI检索结果翻页10次以上,用户需要手动输入验证码才 ...

随机推荐

  1. 题解-The Number of Good Intervals

    题面 The Number of Good Intervals 给定 \(n\) 和 \(a_i(1\le i\le n)\),\(m\) 和 \(b_j(1\le j\le m)\),求对于每个 \ ...

  2. redis学习之——持久化RDB 和AOF

    RDB: 在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里.rdb 保存的是dump.rdb文件 RDB工作原理: Redis会 ...

  3. C++ 虚函数表与多态 —— 多态的简单用法

    首先看下边的代码,先创建一个父类,然后在来一个继承父类的子类,两个类中都有自己的 play() 方法,在代码的第35-37行,创建一个父类指针,然后将子类地址引用赋值给父类,这时调用 P 指针的 pl ...

  4. Linux下设置定时任务删除归档日志

    1.编辑删除归档日志的脚本----/home/oracle/clearlog.sh #! /bin/bash source ~/profile_orcl#记录归档删除的日志exec >> ...

  5. 记一次HBase的NotServingRegionException问题

    1. 问题 在hbase测试集群上,访问一些hbase表,均报类似下面的错误:region不可用 Wed Oct 28 14:00:56 CST 2020, RpcRetryingCaller{glo ...

  6. 算法(Java实现)—— 分治算法

    分治算法 分治算法的设计模式 基本思想 把复杂问题分解成若干互相独立容易求解的子问题 经典问题 二分搜索 大整数乘法 棋盘覆盖 合并排序 快速排序 线性时间选择 最接近点对问题 循环赛日程表 汉诺塔 ...

  7. Clickhouse的特点

    1.为什么会有Clickhouse? 实时数据分析数据库 俄罗斯的谷歌开发的. 2.Clickhouse的优点. 真正的面向列的 DBMS ClickHouse 是一个 DBMS,而不是一个单一的数据 ...

  8. Bootstrap留言板界面练习

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. python 无损压缩照片,支持批量压缩,支持保留照片信息

    由于云盘空间有限,照片尺寸也是很大,所以写个Python程序压缩一下照片,腾出一些云盘空间 1.批量压缩照片 新建 photo_compress.py 代码如下 1 # -*- coding: utf ...

  10. 记一次真实的webpack优化经历

    前言 公司目前现有的一款产品是使用vue v2.0框架实现的,配套的打包工具为webpack v3.0.整个项目大概有80多个vue文件,也算不上什么大型项目. 只不过每次头疼的就是打包所耗费的时间平 ...