难点:
1、发送异步的http请求,包括获取获取token的get请求,和发送图片的post请求
2、post请求需要设置请求头,和请求体,请求体是图片,但是文档上说需要base64加密,还好windows上有一个工具叫certutil.exe 可以把文件加密为base64
3、certutil.exe原本不是用来加密图片的,看名字都知道是加密证书的,但是这里也能用,调用后会生成一个base64的txt文件,我们需要读取该文件把其中的'\',‘+’,'='转为%xx (xx是字符对应的16进制)
4、处理返回结果,结果是一个json字符串,我看网上大多数人是使用三方解析json,我这直接用正则给他匹配出来了。
5、双击窗口事件,读取窗口的文字,无视输入法的问题、
未解决的问题:
1、本来我是想直接从ahk读取粘贴板的图片,我研究了一下ahk的粘贴板没办法实现,需要使用dllcall调用系统的dll来实现数据的读取甚是麻烦(主要是不会,有会的同学可以在评论区告诉我),最后研究了半天还是没有从粘贴板读出图片,最后发现我电脑上之前装了工具叫做snipast,可以实时保存图片,我每次截图后他会保存在用户目录下的Pictures目录,我直接读取该图片就可以了、
2.、百度云那边成功读取数据后,ahk闪烁问题,想用ahk右下角图标闪烁的方式来提示用户请求已经完成,可以查看结果了,但是flash始终不生效。
实现效果:
1、先用snipast截图(必须是snipast)
2、然后ctrl+shift+空格呼出GUI,点击ESC可以隐藏窗口,ctrl+shift+空格可以继续显示出来, 双击可以把窗口文字显示在光标处,ctrl+shift+空格不会再显示了
实现代码
我们来一个功能一个功能的操作
1、请求百度云的OCR
①.这存在一个问题,就是snipast截图后有可能先于按下按键的时候生成,就是图片太小的时候,也可能后于按键生成,图片太大的时候,这个时候我们需要通过snipast保存的图片上自带的时间来判断
②.百度云基本信息需要自行到百度云上面去申请对应的ak和as
#Persistent
return
;检测粘贴板是否变化
OnClipboardChange:
;当包含粘贴板中包含图片的时候才执行ocr操作
if(A_EventInfo!=2)
return
global req
global accessToken
global count:=0
global newPicName:=""
global ocrText
global ocrActualUrl:="https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic?access_token="
global picDir:=userprofile "\Pictures" ;图片保存路径需要设置snipast
;允许捕捉ESC
ESCFlag:=True
GUIFlag:=false
;没有写入之前的png图片个数
Loop, %picDir%\*.png
count:=count+1
if(count>=1)
{
picName:=getPngPath(picDir)
timeNumber := RegExReplace(picName, "[^`\d]","")
FormatTime, nowDateNumber,?%A_Now%,yyyyMMddHHmmss
mSecond:=Abs(timeNumber-nowDateNumber)
;msgBox timeNumber=%timeNumber%
;msgBOx nowDateNumber=%nowDateNumber%
;msgBox mSecond=%mSecond%
if(mSecond==0)
{
newPicName:=picName
;msgBox "save faster than reponse!"
}
else
{
if(mSecond&&mSecond<=1) ;时间先后在1s中的误差就ok
{
;msgBox "save faster than reponse!"
newPicName:=picName
}
else
{
newPicName:=
}
}
}
req := ComObjCreate("Msxml2.XMLHTTP")
;百度云基本信息
apiKey:="xxxxxxxxxxxxxxxxx"
secretKey:="xxxxxxxxxxxxxxxxxxxx"
ocrTokenUrl:="https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials"
;获取token的地址
tokenUrl=%ocrTokenUrl%&client_id=%apiKey%&client_secret=%secretKey%
;发送请求获取accessToken
if !accessToken
{
sendRequest(tokenUrl,"GET","getTokenSuccess","")
}
else
{
dealPngPic()
}
return
;发送异步请求
sendRequest(tokenUrl,Method,funcN,data)
{
;msgBox %data%
req.onreadystatechange := Func(funcN)
req.open(Method,tokenUrl, true)
if data
req.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
req.send(data)
while req.readyState != 4
sleep 100
}
;获取AccessToken
getTokenSuccess() {
if (req.readyState != 4) ; Not done yet.
return
if (req.status == 200) ; 执行成功
{
;MsgBox % "Latest AutoHotkey version: " req.responseText
FoundPos := RegExMatch(req.responseText, "expires_in.:.(.*?).,.*access_token.:.(.*?).,", SubPat) ; 把结果保存SubPat1,和SubPat2
;MsgBox %SubPat2%
;发送post请求
accessToken :=SubPat2
dealPngPic()
}
else
{
;MsgBox 16,, % "Status " req.status
}
;ExitApp ;退出App
}
;获取图片识别的文字
getTextSuccess() {
if (req.readyState != 4) ; Not done yet.
return
if (req.status == 200) ; 执行成功
{
;MsgBox % "Latest AutoHotkey version: " req.responseText
RegExMatch(req.responseText, "words_result_num..(.*?),..words_result.:..(.*?)\]\}", SubPat)
;处理返回结果,有结果才处理
if(SubPat1!=0)
{
;msgBox %subPat2%
words:=SubPat2
StringReplace, words , words, `, `{"words`"`: `",`n, All ;替换words为换行
StringReplace, words , words,`{"words`"`: `",, All ;替换第一个words
StringReplace, words , words, `"},, All ;替换}为空
;msgBox %words%
ocrText:= words
}
}
else
{
;MsgBox 16,, % "Status " req.status
}
;ExitApp ;退出App
}
;处理图片
dealPngPic()
{
if(!newPicName)
{
;msgBox no newPicName!
flag:=ifHasNewPic(picDir)
if(flag==1)
{
;获取新图片的路径
newPicName:=getPngPath(picDir)
}
;msgBox flag=%flag%
}
if(!newPicName)
return
;msgBox newPicName=%newPicName%
;msgBox %newPicName%
;生成base64的文件
base64TxtPath:=Get_Base64(newPicName)
if(base64TxtPath)
{
;删除图片
newPicPath=%picDir%\%newPicName%
deleteObj(newPicPath)
;msgBox base64TxtPath=%base64TxtPath%
;发送post请求并获取txt
sendPostAndGetStr(base64TxtPath)
}
return
}
;判断图片是否写入到了pictures目录下
ifHasNewPic(picDir)
{ ;msgBox count:%count%
cc:=0
loop 8 ;等待3秒钟
{
sleep, 250
count2:=0
Loop, %picDir%\*.png
count2:=count2+1
;msgBox count2: %count2%
if(count2>count)
{
return 1
}
cc:=cc+1
}
return 0
}
;返回最新的一张图片
getPngPath(picDir){
uPicPath:="there is no pic in dir picture!"
Loop, %picDir%\*.png
FileList = %FileList%%A_LoopFileTimeModified%`t%A_LoopFileName%`n
Sort, FileList ;根据日期排序.从小到大
Loop, parse, FileList, `n
{
if A_LoopField = ; 忽略列表末尾的最后一个换行符(空项).
continue
StringSplit, FileItem, A_LoopField, %A_Tab% ;用 tab 作为分隔符切割.
;RunWait, %picDir%\%FileItem2%
FileSetAttrib, -R, %picDir%\%FileItem2%
;uPicPath=%picDir%\%FileItem2%
uPicPath=%FileItem2%
}
return uPicPath
}
;获取64位编码的文件
Get_Base64(imgName)
{
;windows 的base64工具路径
C_basexeTmp:= "C:\Windows\System32\certutil.exe"
;处理文件名
StringReplace, imgNameNoSuffix,imgName, .png
base64Path := picDir "\" imgNameNoSuffix
base64TxtPath:= base64Path ".txt"
if FileExist(base64Path ".png")
{
;如果路径中有空格就用双引号括起来
base64Path := base64Path ".png"
;base64编码保存成txt文件放在图片同一个目录下
TargetTmp := C_basexeTmp " -encode " base64Path " " base64TxtPath
RunWait, %TargetTmp%
return base64TxtPath
}
Else
{
msgBox % imgName ".png not exists!"
}
}
;删除文件
deleteObj(objPath)
{
;msgBox %objPath%
FileDelete, %objPath%
}
;发送高精文字识别请求
sendPostAndGetStr(base64TxtPath)
{
urlWithToken = %ocrActualUrl%%accessToken%
;msgBox urlWithToken=%urlWithToken%
postData:="image=" getBase64Text(base64TxtPath)
sendRequest(urlWithToken,"POST","getTextSuccess",postData)
;删除base64文本
deleteObj(base64TxtPath)
}
;读取base64的文本文件
getBase64Text(base64Txt)
{
if FileExist(base64Txt)
{
P_image := ""
Loop, Read, %base64Txt%
{
if InStr(A_LoopReadLine, "-----") = 0 and StrLen(A_LoopReadLine) > 0
{
tempLine:=A_LoopReadLine
;XtempLine:=RTrim(A_LoopReadLine, OmitChars = " `r`n") ;去掉换行
StringReplace, tempLine , tempLine, +, `%2B, All ;符号 +
StringReplace, tempLine , tempLine, /, `%2F, All ;替换 /
StringReplace, tempLine , tempLine, =, `%3D, All ;替换 =
P_image= %P_image%%tempLine%
}
}
;msgBox %P_image%
return P_image
}
}
2、图形显示出ocr识别的结果
screenWith和Screenheight是屏幕的分辨率,需要你自己手动设置为你自己的分辨率,ahk我没找到可以获取分辨率的操作。
global ESCFlag
global GUIFlag ;是否界面上有图形
^+space::
send #v
;sleep ,200 ;战术性休眠
if(ocrText && !GUIFlag) ;有数据才显示,没数据不显示,界面上有图形不再重新显示
{
GUIFlag:=true
;获取数据行数
lineCount:=getLineCount()
;msgBox lineCount=%lineCount%
height:=14.5*lineCount
windowHeight:=height+18
ScreenWith:=1600
ScreenHeight:=900
x:=ScreenWith-290-4
y:=ScreenHeight-470-height-20
if(y<30)
{
y=30
height:=ScreenHeight-470-y
lineCount:=ceil((height-20)/14.5)
windowHeight:=height
}
MouseGetPos, OutputVarX, OutputVarY, OutputVarWin
Gui, MyGui:New
Gui +AlwaysOnTop
Gui, font,, Verdana ; 首选字体.
Gui, MyGui:+Owner
Gui, Add, edit, R%lineCount% w276 h%height% vMyEdit
GuiControl,, MyEdit ,%ocrText%
Gui, show,W290 H%windowHeight% X%x% Y%y% NoActivate,百度云 OCR
ESCFlag :=true
;监听复制数据
;OnMessage(0x301, "WM_COPY") ; 0x4a 为 复制事件
;WM_NCLBUTTONDBLCLK = 0xA3 ;窗口的回车事件
;WM_LBUTTONDBLCLK ;控件的回车事件
OnMessage(0xA3, "WM_NCLBUTTONDBLCLK") ; 0x203为 左键双击事件
OnMessage(0x10, "WM_CLOSE") ; 0x10 关闭窗口
}
return
;获取返回值有多少行
getLineCount()
{
count:=-1
Loop, Parse, ocrText, `n, `r
{
count:=count+1
if(strLen(A_LoopField)>22)
{
count:=count+round(strLen(A_LoopField)/22)
}
}
count:=count==0?2:count+1
return count
}
return
;退出ocr边框显示
#If ESCFlag
ESC::
Gui MyGui:Destroy
GUIFlag:=false ;图形可以重新显示
ESCFlag:=false
;msgBox %ESCFlag%
send {ESC}
return
;GUI的双击事件
WM_NCLBUTTONDBLCLK(wParam, lParam)
{
GUIFlag:=false ;图形可以重新显示
;msgBox xxxx
Gui MyGui:Destroy
Gui MyGui:Destroy
ocrText:=Trim(ocrText)
ocrText:=Trim(ocrText, OmitChars = " `r`n")
ocrText:=Trim(ocrText, OmitChars = " `t")
ocrText:=Trim(ocrText, OmitChars = " `n")
;msgBox ocrText=%ocrText%
;绕过输入法
ocrText:=ascInput(ocrText)
SendInput %ocrText%
return
}
3、绕过输入法输入字符串
ascInput(string){
u := A_IsUnicode ? 2 : 1 ;Unicode版ahk字符长度是2
length:=StrPut(string,"CP0")
if(A_IsUnicode)
{
VarSetCapacity(address,length),StrPut(string,&address,"CP0")
}
else
address:=string
VarSetCapacity(out,2*length*u)
index =0
Loop
{
index += 1
if (index>length-1)
Break
asc := NumGet(address,index-1,"UChar")
if asc > 126
{
index += 1
asc2 := NumGet(address,index-1,"UChar")
asc := asc*256+asc2
}
Send, % "{ASC " asc "}"
}
}
ascaltinput(string){
u := A_IsUnicode ? 2 : 1 ;Unicode版ahk字符长度是2
length:=StrPut(string,"CP0")
if(A_IsUnicode)
{
VarSetCapacity(address,length),StrPut(string,&address,"CP0")
}
else
address:=string
VarSetCapacity(out,2*length*u)
index =0
Loop
{
index += 1
if (index>length-1)
Break
asc := NumGet(address,index-1,"UChar")
if asc > 126
{
index += 1
asc2 := NumGet(address,index-1,"UChar")
asc := asc*256+asc2
}
StringSplit, var, asc,
Loop % var0
{
str .= "{Numpad" var%A_index% "}"
}
send, {AltDown}%str%{Altup}
str =
}
}
总结:把上面三段代码拼起来就可以实现ocr了!