首先呢?问题是很简单的,如图,用过的人都知道,可是要让计算机识别出具体的位置就不好整了。
![](https://img.haomeiwen.com/i2536873/af1b38623ae287de.jpg)
这是一部血泪史
自己先硬搞
一,我把图片分成了两部分。
一部分是右上角的字,一部分是底下的图。
![](https://img.haomeiwen.com/i2536873/bd60c9a892120e40.jpg)
![](https://img.haomeiwen.com/i2536873/2e74b156b7ed4408.jpg)
这个实现很简单,所幸每个验证码大小是确定的,直接用python的PIL库直接裁就好了,就算不是确定的,因为色彩很分明,用色彩的分布也好分开。
def firstcuts(img):
gray=img.convert('L')
boxtar=(第一部分的做坐标)
boxsou=(第二部分的做坐标)
sou=img.crop(boxsou)
tar=img.crop(boxtar)
return tar,sou
二,从简单的开始对第一部分的这几个字进行切分
这个是典型的类似普通文字验证码的切割,我们不用什么水来灌,因为每个字之间没有粘连在一起,所以我用了最暴力的方法:投影
具体说来就是
直接二位的图形投影到一位的X轴上,因为边界分明,选好阈值就能很好的切割
def cuttar(img,threshold=0): #二值化垂直投影
tar=img.convert('1')
data=np.array(tar)
cond=data==255
data[cond]=1
h,w = data.shape
ver_list = []
for x in range(w):
black=0
for y in range(h):
if data[y][x]==0:
black=black+1
ver_list.append(black)
l,r=threshold,threshold
flag=False
cuts=[]
for i ,count in enumerate(ver_list):
if flag is False and count > threshold:
l=i
flag=True
if flag and count==threshold:
r=i-1
flag=False
if l!=r and r-l>10:
cuts.append((l,r))
return cuts
这个代码一看就会懂,这是效果:
![](https://img.haomeiwen.com/i2536873/230ad014e4511afe.jpg)
三,然后开始搞下面这个难搞的图片,我们想让计算机识别出字的位置,就要去掉一些干扰。
因为设计验证码的人很聪明,一般区分图片的方法都是通过图片的色彩空间,具体点说类似与色彩的直方图之类的,我试着画了几个不同的验证码的,发现色彩很平均。
当然聪明反被聪明误,因为色彩分配的很平均,但是要显示字啊,字很靠近黑或者白,这样,我们先将图片转成灰度的,然后通过选取合适的阈值来去掉中间的部分,只保留靠近黑白两边的颜色
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
def fliter(img,fliterhow=3):
#去除中间大多数的无用值
img=img.convert('L')
(w,h)=img.size
x=w/4
y=h/4
img=img.resize((x,y))
data=np.array(img)
#计算均值及要过滤掉的范围
datamean=data.mean()
datamin=data.min()
datamax=data.max()
fliternum=(datamax-datamin)/fliterhow
cond=(data>datamean-fliternum)&(data<datamean+fliternum)
data[cond]=-1
return data
def pltfliter(img,flited):
#画出过滤效果
plt.figure("fliter")
plt.subplot(1,2,1),plt.title('sou')
plt.imshow(img,cmap='gray')
plt.subplot(1,2,2),plt.title('flited')
plt.imshow(data,cmap='gray')
plt.show()
在上面的函数中通过fliterhow参数来决定我们去掉中间的范围是多少。
效果如下:
![](https://img.haomeiwen.com/i2536873/fd33c5327282dd2b.jpg)
然后我能想的招是先试试聚类
先试验了kmeans算法
import numpy as np
def loadDataSet(imgarray):
imgdata=[]
threshold=5
[rows, cols] = imgarray.shape
for i in range(rows - 1):
for j in range(cols-1):
if (imgarray[i][j]>(0+threshold))&(imgarray[i][j]<(255-threshold)):
imgdata.append([i,j])
'''
piexnum=imgarray[i][j]
if(255-piexnum)>piexnum:
imgdata.append([i,j,2])
else:
imgdata.append([i,j,1])
'''
return np.array(imgdata)
def distEclud(vecA,vecB):
return np.sqrt(sum(np.power(vecA-vecB,2))).all()
def randCent(data,k):
n=np.shape(data)[1]
centroids=np.mat(np.zeros((k,n)))
#构建簇质点
for j in range(n):
minJ=np.min(data[:,j])
rangeJ=np.max(data[:,j]-minJ)
centroids[:,j]=minJ+rangeJ*np.random.rand(k,1)
return centroids.astype(int)
def KMeans(dataSet,k,distMeas=distEclud,createCent=randCent):
datanum=np.shape(dataSet)[0]
print("一共有%s 行数据"%datanum)
if datanum==0:
return None,None
clusterAssment=np.mat(np.zeros((datanum,2)))
centroids=createCent(dataSet,k)
print("init centroid")
print centroids
clusterChanged=True
times=0
while clusterChanged:
times=times+1
print("第%s次迭代"%times)
if times>1:
break
clusterChanged=False
for itemindex in range(datanum):
minDist=float("inf")
minIndex=-1
for centroidindex in range(k):
distJI=distMeas(centroids[centroidindex,:],dataSet[itemindex,:])
if distJI<minDist:
minDist=distJI
minIndex=centroidindex
if clusterAssment[itemindex,0] !=minIndex:
clusterChanged=True
clusterAssment[itemindex,:]=minIndex,minDist**2
print("once assment is")
print(clusterAssment)
for cent in range(k):
thiscentcond=clusterAssment[:,0].A==cent
thiscentindex=np.nonzero(thiscentcond)[0]
if len(thiscentindex)==0:
continue
else:
ptsInClust=dataSet[thiscentindex]
newcentitem=np.mean(ptsInClust,axis=0)
centroids[cent,:]=newcentitem
print("the %s new cent item is"%cent)
print(newcentitem)
return centroids,clusterAssment
第一个loadDataSet函是将刚刚过滤来的第二部分的图像数组当参数传入,得到kmeans需要的数据集。
第二个distEclud用来测量两个点之间的距离
第三个randCent用来生成k个随机 质心
第四个 KMeans
试了下效果不好,又在kmeans基础上试了二分的kmeans
def biKmeans(dataSet,k,distMeas=distEclud):
datanum=np.shape(dataSet)[0]
clusterAssment=np.mat(np.zeros((datanum,2)))
centroid0=np.mean(dataSet,axis=0).tolist()[0]
centList=[centroid0]
for j in range(datanum):
clusterAssment[j,1]=distMeas(np.mat(centroid0),dataSet[j,:])**2
while(len(centList)<k):
lowestSSE=np.inf
for centindex in range(len(centList)):
ptsInCurrCluster=dataSet[np.nonzero(clusterAssment[:,0].A==centindex)[0],:]
centroidMat,splitClustAss=KMeans(ptsInCurrCluster,2,distMeas)
sseSplit=sum(splitClustAss[:,1])
sseNotSplit=sum(clusterAssment[np.nonzero(clusterAssment[:,0].A!=centindex)[0],1])
print("ssesplit and not:")
print(sseSplit,sseNotSplit)
if(sseSplit+sseNotSplit)<lowestSSE:
bestCentToSplit=centindex
bestNewCents=centroidMat
bestClustAss=splitClustAss.copy()
lowestSSE=sseSplit+sseNotSplit
bestClustAss[np.nonzero(bestClustAss[:,0].A==1)[0],0]=len(centList)
bestClustAss[np.nonzero(bestClustAss[:,0].A==0)[0],0]=bestCentToSplit
print 'the bestcenttosplit is ',bestCentToSplit
print 'the len o fbestClustass is ',len(bestClustAss)
centList[bestCentToSplit]=bestNewCents[0,:]
centList.append(bestNewCents[1,:])
clusterAssment[np.nonzero(clusterAssment[:,0].A==bestCentToSplit)[0],:]=bestClustAss
return centList,clusterAssment
然后就出现了一个棘手的问题,等的时间过长
原因是640x480的图片一共点数有个30多万个点,虽然通过上一部过滤了大部分,可是还有个4万多个点,这迭代几次就要命了,没办法,只能先借助PIL库压缩下图片。
用代码画出来,测试下聚类的效果:
from PIL import Image ,ImageOps
import matplotlib.pyplot as plt
import numpy as np
import cut
import filtration
from cluster import *
def get_cutedcaptcha_sou(img):
captchalist,sou=cut.get_cuted_code(img)
return captchalist,sou
img=Image.open('测试图片')
captchalist,sou=get_cutedcaptcha_sou(img)
imgarray=filtration.fliter(sou,3.1)
dataset=loadDataSet(imgarray)
print("shape")
print(np.shape(dataset))
cen,ass=KMeans(dataset,30)
print("cen is")
print(cen)
plt.figure("fliter")
plt.subplot(1,2,1),plt.title('sou')
plt.imshow(sou,cmap='gray')
plt.subplot(1,2,2),plt.title('flited')
plt.imshow(imgarray,cmap='gray')
for item in cen:
x=item[0,0]
y=item[0,1]
plt.scatter(x,y, color='r')
plt.show()
![](https://img.haomeiwen.com/i2536873/9d6a499ea6ea8ef0.jpg)
四,我想的当然不是一步到位,想法是先用聚类搞出个十几个可能的位置,然后给个字的宽带,拿这个区域和第一部分被切出来的那几个字做比较,做比较的方法使用k邻近就好
但是我突然想到了,百度有OCR的接口
试试百度的
首先生成access_token
import ssl
import json
def getToken():
ak="XXX"
sk="XXX"
# client_id 为官网获取的AK, client_secret 为官网获取的SK
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id='+ak+'&client_secret='+sk
request = urllib2.Request(host)
request.add_header('Content-Type', 'application/json; charset=UTF-8')
response = urllib2.urlopen(request)
content = response.read()
if (content):
token=json.loads(content)
return token['access_token']
将图片变成base64,然后url编码,然后请求百度的ocr带返回位置的高精度api
import requests
import sys, urllib, urllib2, json
import base64
import StringIO
import baidutoken
from cStringIO import StringIO
def getPost(img):
url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate'
access_token={'access_token':baidutoken.getToken()}
headers = {'Content-Type':'application/x-www-form-urlencoded'}
r=requests.post(url, headers=headers,params=access_token,data=getImageData(img))
print(r.text)
def getImageData(img):
data = {}
data['language_type'] = "CHN_ENG"
data['probability']="true"
data['image'] = image_to_base64(img)
decoded_data = urllib.urlencode(data)
return decoded_data
def image_to_base64(img):
output_buffer = StringIO()
img.save(output_buffer, format='JPEG')
binary_data = output_buffer.getvalue()
base64_data = base64.b64encode(binary_data)
return base64_data
结果是屁都没识别出来,识别的文字数量是0
网友评论