百度OCR
详见:
https://github.com/crifan/crifanLibPython/blob/master/crifanLib/crifanBaiduOcr.py
在做安卓和iOS的移动端自动化测试期间,会涉及到从图像中提取文字,用的是百度OCR。
其中有些通用的功能,整理出函数,贴出供参考。
百度OCR初始化
import os
import re
import base64
import requests
import time
import logging
from collections import OrderedDict
from PIL import Image, ImageDraw
class BaiduOCR():
# OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic" # 通用文字识别
# OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/general" # 通用文字识别(含位置信息版)
# OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic" # 通用文字识别(高精度版)
OCR_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate" # 通用文字识别(高精度含位置版)
TOKEN_URL = 'https://aip.baidubce.com/oauth/2.0/token'
RESP_ERR_CODE_QPS_LIMIT_REACHED = 18
RESP_ERR_TEXT_QPS_LIMIT_REACHED = "Open api qps request limit reached"
RESP_ERR_CODE_DAILY_LIMIT_REACHED = 17
RESP_ERR_TEXT_DAILY_LIMIT_REACHED = "Open api daily request limit reached"
API_KEY = 'SOxxxxxxxxxxnu'
SECRET_KEY = 'wlxxxxxxxxxxxxxxxxxxxpL'
def initOcr(self):
self.curToken = self.baiduFetchToken()
def baiduFetchToken(self):
"""Fetch Baidu token for OCR"""
params = {
'grant_type': 'client_credentials',
'client_id': self.API_KEY,
'client_secret': self.SECRET_KEY
}
resp = requests.get(self.TOKEN_URL, params=params)
respJson = resp.json()
respToken = ""
if ('access_token' in respJson.keys() and 'scope' in respJson.keys()):
if not 'brain_all_scope' in respJson['scope'].split(' '):
logging.error('please ensure has check the ability')
else:
respToken = respJson['access_token']
else:
logging.error('please overwrite the correct API_KEY and SECRET_KEY')
# '24.8691f3c6dedd0d0d0b30a9dfec604d52.2592000.1578465979.282335-17921535'
return respToken
百度OCR图片转文字
def baiduImageToWords(self, imageFullPath):
"""Detect text from image using Baidu OCR api"""
# # Note: if using un-paid = free baidu api, need following wait sometime to reduce: qps request limit
# time.sleep(0.15)
respWordsResutJson = ""
# 读取图片二进制数据
imgBinData = readBinDataFromFile(imageFullPath)
encodedImgData = base64.b64encode(imgBinData)
paramDict = {
"access_token": self.curToken
}
headerDict = {
"Content-Type": "application/x-www-form-urlencoded"
}
# 参数含义:http://ai.baidu.com/ai-doc/OCR/vk3h7y58v
dataDict = {
"image": encodedImgData,
"recognize_granularity": "small",
# "vertexes_location": "true",
}
resp = requests.post(self.OCR_URL, params=paramDict, headers=headerDict, data=dataDict)
respJson = resp.json()
logging.debug("baidu OCR: imgage=%s -> respJson=%s", imageFullPath, respJson)
if "error_code" in respJson:
logging.warning("respJson=%s" % respJson)
errorCode = respJson["error_code"]
# {'error_code': 17, 'error_msg': 'Open api daily request limit reached'}
# {'error_code': 18, 'error_msg': 'Open api qps request limit reached'}
# the limit count can found from
# 文字识别 - 免费额度 | 百度AI开放平台
# https://ai.baidu.com/ai-doc/OCR/fk3h7xu7h
# for "通用文字识别(高精度含位置版)" is "50次/天"
if errorCode == self.RESP_ERR_CODE_QPS_LIMIT_REACHED:
# wait sometime and try again
time.sleep(1.0)
resp = requests.post(self.OCR_URL, params=paramDict, headers=headerDict, data=dataDict)
respJson = resp.json()
logging.debug("baidu OCR: for errorCode=%s, do again, imgage=%s -> respJson=%s", errorCode, imageFullPath, respJson)
elif errorCode == self.RESP_ERR_CODE_DAILY_LIMIT_REACHED:
logging.error("Fail to continue using baidu OCR api today !!!")
respJson = None
"""
{
"log_id": 6937531796498618000,
"words_result_num": 32,
"words_result": [
{
"chars": [
...
"""
if "words_result" in respJson:
respWordsResutJson = respJson
return respWordsResutJson
调用:
wordsResultJson = self.baiduImageToWords(imgPath)
respJson = self.baiduImageToWords(screenImgPath)
返回结果举例
安卓游戏 暗黑觉醒 首充豪礼
图片:
返回解析后出json
格式的文字信息:
{
"log_id": 9009770747370640007,
"words_result_num": 12,
"words_result": [
{
"chars": [
{
"char": "首",
"location": { "width": 94, "top": 105, "left": 989, "height": 158 }
},
{
"char": "充",
"location": { "width": 94, "top": 105, "left": 1086, "height": 158 }
},
{
"char": "豪",
"location": { "width": 95, "top": 105, "left": 1183, "height": 158 }
},
{
"char": "礼",
"location": { "width": 77, "top": 105, "left": 1281, "height": 158 }
}
],
"location": { "width": 370, "top": 105, "left": 989, "height": 158 },
"words": "首充豪礼"
},
{
"chars": [
{
"char": "×",
"location": { "width": 30, "top": 161, "left": 1887, "height": 61 }
}
],
"location": { "width": 60, "top": 161, "left": 1887, "height": 61 },
"words": "×"
},
{
"chars": [
{
"char": "充",
"location": { "width": 43, "top": 273, "left": 758, "height": 73 }
},
{
"char": "值",
"location": { "width": 43, "top": 273, "left": 803, "height": 73 }
},
{
"char": "元",
"location": { "width": 67, "top": 273, "left": 912, "height": 73 }
},
{
"char": "可",
"location": { "width": 44, "top": 273, "left": 979, "height": 73 }
},
{
"char": "领",
"location": { "width": 43, "top": 273, "left": 1023, "height": 73 }
},
{
"char": "总",
"location": { "width": 44, "top": 273, "left": 1067, "height": 73 }
},
{
"char": "价",
"location": { "width": 23, "top": 273, "left": 1111, "height": 73 }
},
{
"char": "值",
"location": { "width": 89, "top": 273, "left": 1134, "height": 73 }
},
{
"char": "8",
"location": { "width": 36, "top": 273, "left": 1259, "height": 73 }
},
{
"char": "8",
"location": { "width": 36, "top": 273, "left": 1326, "height": 73 }
},
{
"char": "8",
"location": { "width": 35, "top": 273, "left": 1371, "height": 73 }
},
{
"char": "钻",
"location": { "width": 43, "top": 273, "left": 1444, "height": 73 }
},
{
"char": "豪",
"location": { "width": 44, "top": 273, "left": 1510, "height": 73 }
},
{
"char": "华",
"location": { "width": 43, "top": 273, "left": 1555, "height": 73 }
},
{
"char": "大",
"location": { "width": 43, "top": 273, "left": 1599, "height": 73 }
},
{
"char": "礼",
"location": { "width": 27, "top": 273, "left": 1643, "height": 73 }
}
],
"location": { "width": 911, "top": 273, "left": 758, "height": 73 },
"words": "充值元可领总价值888钻豪华大礼"
},
{
"chars": [
{
"char": "送",
"location": { "width": 65, "top": 369, "left": 832, "height": 107 }
}
],
"location": { "width": 107, "top": 369, "left": 832, "height": 107 },
"words": "送"
},
{
"chars": [
{
"char": "绝",
"location": { "width": 38, "top": 390, "left": 974, "height": 65 }
},
{
"char": "版",
"location": { "width": 38, "top": 390, "left": 1032, "height": 65 }
},
{
"char": "萌",
"location": { "width": 38, "top": 390, "left": 1092, "height": 65 }
},
{
"char": "宠",
"location": { "width": 38, "top": 390, "left": 1150, "height": 65 }
},
{
"char": "、",
"location": { "width": 31, "top": 390, "left": 1184, "height": 65 }
},
{
"char": "专",
"location": { "width": 39, "top": 390, "left": 1230, "height": 65 }
},
{
"char": "属",
"location": { "width": 38, "top": 390, "left": 1289, "height": 65 }
},
{
"char": "神",
"location": { "width": 38, "top": 390, "left": 1368, "height": 65 }
},
{
"char": "兵",
"location": { "width": 39, "top": 390, "left": 1408, "height": 65 }
}
],
"location": { "width": 524, "top": 390, "left": 934, "height": 65 },
"words": "绝版萌宠、专属神兵"
},
{
"chars": [
{
"char": "绝",
"location": { "width": 20, "top": 515, "left": 378, "height": 33 }
}
],
"location": { "width": 33, "top": 515, "left": 378, "height": 33 },
"words": "绝"
},
{
"chars": [
{
"char": "珍",
"location": { "width": 33, "top": 516, "left": 1992, "height": 42 }
}
],
"location": { "width": 33, "top": 516, "left": 1992, "height": 42 },
"words": "珍"
},
{
"chars": [
{
"char": "版",
"location": { "width": 20, "top": 545, "left": 379, "height": 34 }
}
],
"location": { "width": 31, "top": 545, "left": 379, "height": 34 },
"words": "版"
},
{
"chars": [
{
"char": "额",
"location": { "width": 26, "top": 776, "left": 1225, "height": 44 }
},
{
"char": "外",
"location": { "width": 26, "top": 776, "left": 1264, "height": 44 }
},
{
"char": "礼",
"location": { "width": 27, "top": 776, "left": 1291, "height": 44 }
},
{
"char": "包",
"location": { "width": 27, "top": 776, "left": 1317, "height": 44 }
}
],
"location": { "width": 125, "top": 776, "left": 1225, "height": 44 },
"words": "额外礼包"
},
{
"chars": [
{
"char": "首",
"location": { "width": 38, "top": 830, "left": 935, "height": 64 }
},
{
"char": "充",
"location": { "width": 38, "top": 830, "left": 994, "height": 64 }
},
{
"char": "元",
"location": { "width": 38, "top": 830, "left": 1092, "height": 64 }
},
{
"char": "充",
"location": { "width": 38, "top": 830, "left": 1286, "height": 64 }
},
{
"char": "9",
"location": { "width": 31, "top": 830, "left": 1339, "height": 64 }
},
{
"char": "8",
"location": { "width": 31, "top": 830, "left": 1377, "height": 64 }
},
{
"char": "元",
"location": { "width": 38, "top": 830, "left": 1444, "height": 64 }
}
],
"location": { "width": 549, "top": 830, "left": 935, "height": 64 },
"words": "首充元充98元"
},
{
"chars": [
{
"char": "战",
"location": { "width": 42, "top": 970, "left": 373, "height": 69 }
},
{
"char": "斗",
"location": { "width": 42, "top": 970, "left": 437, "height": 69 }
},
{
"char": "1",
"location": { "width": 35, "top": 970, "left": 515, "height": 69 }
},
{
"char": "5",
"location": { "width": 35, "top": 970, "left": 537, "height": 69 }
},
{
"char": "0",
"location": { "width": 34, "top": 970, "left": 580, "height": 69 }
},
{
"char": "0",
"location": { "width": 35, "top": 970, "left": 622, "height": 69 }
},
{
"char": "0",
"location": { "width": 34, "top": 970, "left": 666, "height": 69 }
}
],
"location": { "width": 327, "top": 970, "left": 373, "height": 69 },
"words": "战斗15000"
},
{
"chars": [
{
"char": "战",
"location": { "width": 43, "top": 969, "left": 1648, "height": 73 }
},
{
"char": "斗",
"location": { "width": 43, "top": 969, "left": 1713, "height": 73 }
},
{
"char": "1",
"location": { "width": 36, "top": 969, "left": 1793, "height": 73 }
},
{
"char": "6",
"location": { "width": 36, "top": 969, "left": 1816, "height": 73 }
},
{
"char": "0",
"location": { "width": 35, "top": 969, "left": 1861, "height": 73 }
},
{
"char": "0",
"location": { "width": 36, "top": 969, "left": 1904, "height": 73 }
},
{
"char": "0",
"location": { "width": 29, "top": 969, "left": 1949, "height": 73 }
}
],
"location": { "width": 330, "top": 969, "left": 1648, "height": 73 },
"words": "战斗16000"
}
]
}
其中:
- 首充豪礼
- 都能完整检测出来:已经是效果很不错了
- 当然偶尔也会有失误,比如 偶尔
- 只解析出部分内容:首充豪
- 或个别字错了:首充豪机
- 本身图片上 礼 也的确很像 机
- 作为OCR犯此错误,完全可以理解
- 本身图片上 礼 也的确很像 机
安卓游戏 暗黑觉醒 公告弹框
图片:
返回结果json:
{
"log_id": 2793391773289550472,
"words_result_num": 23,
"words_result": [
{
"chars": [
{
"char": "公",
"location": { "width": 28, "top": 125, "left": 634, "height": 48 }
},
{
"char": "告",
"location": { "width": 29, "top": 125, "left": 691, "height": 48 }
}
],
"location": { "width": 92, "top": 125, "left": 634, "height": 48 },
"words": "公告"
},
{
"chars": [
{
"char": "最",
"location": { "width": 21, "top": 240, "left": 535, "height": 36 }
}
],
"location": { "width": 33, "top": 240, "left": 535, "height": 36 },
"words": "最"
},
{
"chars": [
{
"char": "亲",
"location": { "width": 26, "top": 233, "left": 922, "height": 42 }
},
{
"char": "爱",
"location": { "width": 26, "top": 233, "left": 959, "height": 42 }
},
{
"char": "的",
"location": { "width": 26, "top": 233, "left": 986, "height": 42 }
},
{
"char": "觉",
"location": { "width": 26, "top": 233, "left": 1024, "height": 42 }
},
{
"char": "醒",
"location": { "width": 26, "top": 233, "left": 1063, "height": 42 }
},
{
"char": "勇",
"location": { "width": 26, "top": 233, "left": 1088, "height": 42 }
},
{
"char": "士",
"location": { "width": 25, "top": 233, "left": 1127, "height": 42 }
},
{
"char": ":",
"location": { "width": 21, "top": 233, "left": 1148, "height": 42 }
}
],
"location": { "width": 253, "top": 233, "left": 922, "height": 42 },
"words": "亲爱的觉醒勇士:"
},
{
"chars": [
{
"char": "新",
"location": { "width": 26, "top": 266, "left": 535, "height": 44 }
},
{
"char": "新",
"location": { "width": 27, "top": 266, "left": 588, "height": 44 }
},
{
"char": "服",
"location": { "width": 26, "top": 266, "left": 628, "height": 44 }
},
{
"char": "公",
"location": { "width": 27, "top": 266, "left": 668, "height": 44 }
},
{
"char": "告",
"location": { "width": 26, "top": 266, "left": 709, "height": 44 }
}
],
"location": { "width": 201, "top": 266, "left": 535, "height": 44 },
"words": "新新服公告"
},
{
"chars": [
{
"char": "承",
"location": { "width": 26, "top": 282, "left": 984, "height": 43 }
},
{
"char": "载",
"location": { "width": 26, "top": 282, "left": 1023, "height": 43 }
},
{
"char": "六",
"location": { "width": 26, "top": 282, "left": 1063, "height": 43 }
},
{
"char": "大",
"location": { "width": 26, "top": 282, "left": 1090, "height": 43 }
},
{
"char": "种",
"location": { "width": 26, "top": 282, "left": 1116, "height": 43 }
},
{
"char": "族",
"location": { "width": 26, "top": 282, "left": 1155, "height": 43 }
},
{
"char": "的",
"location": { "width": 26, "top": 282, "left": 1181, "height": 43 }
},
{
"char": "重",
"location": { "width": 26, "top": 282, "left": 1220, "height": 43 }
},
{
"char": "生",
"location": { "width": 26, "top": 282, "left": 1260, "height": 43 }
},
{
"char": "之",
"location": { "width": 26, "top": 282, "left": 1286, "height": 43 }
},
{
"char": "使",
"location": { "width": 27, "top": 282, "left": 1325, "height": 43 }
},
{
"char": "命",
"location": { "width": 26, "top": 282, "left": 1351, "height": 43 }
},
{
"char": ",",
"location": { "width": 21, "top": 282, "left": 1385, "height": 43 }
},
{
"char": "超",
"location": { "width": 26, "top": 282, "left": 1416, "height": 43 }
},
{
"char": "现",
"location": { "width": 26, "top": 282, "left": 1456, "height": 43 }
},
{
"char": "实",
"location": { "width": 26, "top": 282, "left": 1483, "height": 43 }
},
{
"char": "3",
"location": { "width": 22, "top": 282, "left": 1517, "height": 43 }
},
{
"char": "D",
"location": { "width": 21, "top": 282, "left": 1531, "height": 43 }
},
{
"char": "魔",
"location": { "width": 26, "top": 282, "left": 1560, "height": 43 }
},
{
"char": "M",
"location": { "width": 61, "top": 284, "left": 1583, "height": 40 }
},
{
"char": "M",
"location": { "width": 35, "top": 284, "left": 1639, "height": 40 }
},
{
"char": "O",
"location": { "width": 31, "top": 284, "left": 1669, "height": 40 }
},
{
"char": "A",
"location": { "width": 25, "top": 284, "left": 1696, "height": 40 }
},
{
"char": "R",
"location": { "width": 25, "top": 284, "left": 1716, "height": 40 }
},
{
"char": "P",
"location": { "width": 25, "top": 284, "left": 1735, "height": 40 }
},
{
"char": "G",
"location": { "width": 25, "top": 284, "left": 1755, "height": 40 }
},
{
"char": "幻",
"location": { "width": 26, "top": 282, "left": 1588, "height": 43 }
},
{
"char": "手",
"location": { "width": 26, "top": 282, "left": 1784, "height": 43 }
},
{
"char": "游",
"location": { "width": 26, "top": 282, "left": 1823, "height": 43 }
}
],
"location": { "width": 867, "top": 282, "left": 984, "height": 43 },
"words": "承载六大种族的重生之使命,超现实3D魔 MMOARPG幻手游"
},
{
"chars": [
{
"char": "不",
"location": { "width": 37, "top": 337, "left": 923, "height": 40 }
},
{
"char": "负",
"location": { "width": 23, "top": 337, "left": 959, "height": 40 }
},
{
"char": "觉",
"location": { "width": 25, "top": 337, "left": 996, "height": 40 }
},
{
"char": "醒",
"location": { "width": 23, "top": 337, "left": 1021, "height": 40 }
},
{
"char": "勇",
"location": { "width": 23, "top": 337, "left": 1058, "height": 40 }
},
{
"char": "士",
"location": { "width": 25, "top": 337, "left": 1094, "height": 40 }
},
{
"char": "们",
"location": { "width": 25, "top": 337, "left": 1119, "height": 40 }
},
{
"char": "的",
"location": { "width": 25, "top": 337, "left": 1156, "height": 40 }
},
{
"char": "使",
"location": { "width": 25, "top": 337, "left": 1192, "height": 40 }
},
{
"char": "命",
"location": { "width": 25, "top": 337, "left": 1230, "height": 40 }
},
{
"char": "之",
"location": { "width": 25, "top": 337, "left": 1254, "height": 40 }
},
{
"char": "约",
"location": { "width": 25, "top": 337, "left": 1292, "height": 40 }
},
{
"char": ",",
"location": { "width": 20, "top": 337, "left": 1324, "height": 40 }
},
{
"char": "震",
"location": { "width": 25, "top": 337, "left": 1353, "height": 40 }
},
{
"char": "撼",
"location": { "width": 25, "top": 337, "left": 1390, "height": 40 }
},
{
"char": "来",
"location": { "width": 23, "top": 337, "left": 1428, "height": 40 }
},
{
"char": "袭",
"location": { "width": 25, "top": 337, "left": 1452, "height": 40 }
},
{
"char": "。",
"location": { "width": 18, "top": 337, "left": 1485, "height": 40 }
}
],
"location": { "width": 580, "top": 337, "left": 923, "height": 40 },
"words": "不负觉醒勇士们的使命之约,震撼来袭。"
},
{
"chars": [
{
"char": "最",
"location": { "width": 20, "top": 364, "left": 535, "height": 33 }
}
],
"location": { "width": 33, "top": 364, "left": 535, "height": 33 },
"words": "最"
},
{
"chars": [
{
"char": "新",
"location": { "width": 26, "top": 389, "left": 535, "height": 43 }
},
{
"char": "违",
"location": { "width": 26, "top": 389, "left": 588, "height": 43 }
},
{
"char": "规",
"location": { "width": 25, "top": 389, "left": 627, "height": 43 }
},
{
"char": "发",
"location": { "width": 26, "top": 389, "left": 666, "height": 43 }
},
{
"char": "言",
"location": { "width": 26, "top": 389, "left": 704, "height": 43 }
},
{
"char": "处",
"location": { "width": 26, "top": 389, "left": 743, "height": 43 }
},
{
"char": "理",
"location": { "width": 26, "top": 389, "left": 770, "height": 43 }
},
{
"char": "机",
"location": { "width": 26, "top": 389, "left": 808, "height": 43 }
},
{
"char": "制",
"location": { "width": 26, "top": 389, "left": 848, "height": 43 }
}
],
"location": { "width": 343, "top": 389, "left": 535, "height": 43 },
"words": "新违规发言处理机制"
},
{
"chars": [
{
"char": "【",
"location": { "width": 21, "top": 387, "left": 997, "height": 43 }
},
{
"char": "普",
"location": { "width": 25, "top": 387, "left": 1023, "height": 43 }
},
{
"char": "纳",
"location": { "width": 26, "top": 387, "left": 1061, "height": 43 }
},
{
"char": "山",
"location": { "width": 25, "top": 387, "left": 1087, "height": 43 }
},
{
"char": "谷",
"location": { "width": 26, "top": 387, "left": 1126, "height": 43 }
},
{
"char": "1",
"location": { "width": 21, "top": 387, "left": 1148, "height": 43 }
},
{
"char": "2",
"location": { "width": 21, "top": 387, "left": 1160, "height": 43 }
},
{
"char": "0",
"location": { "width": 21, "top": 387, "left": 1187, "height": 43 }
},
{
"char": "服",
"location": { "width": 26, "top": 387, "left": 1204, "height": 43 }
},
{
"char": "】",
"location": { "width": 21, "top": 387, "left": 1238, "height": 43 }
},
{
"char": "将",
"location": { "width": 25, "top": 387, "left": 1269, "height": 43 }
},
{
"char": "于",
"location": { "width": 26, "top": 387, "left": 1308, "height": 43 }
},
{
"char": "0",
"location": { "width": 20, "top": 387, "left": 1329, "height": 43 }
},
{
"char": "7",
"location": { "width": 21, "top": 387, "left": 1355, "height": 43 }
},
{
"char": "月",
"location": { "width": 25, "top": 387, "left": 1385, "height": 43 }
},
{
"char": "0",
"location": { "width": 21, "top": 387, "left": 1406, "height": 43 }
},
{
"char": "8",
"location": { "width": 21, "top": 387, "left": 1420, "height": 43 }
},
{
"char": "日",
"location": { "width": 25, "top": 387, "left": 1451, "height": 43 }
},
{
"char": "0",
"location": { "width": 21, "top": 387, "left": 1471, "height": 43 }
},
{
"char": "0",
"location": { "width": 21, "top": 387, "left": 1497, "height": 43 }
},
{
"char": ":",
"location": { "width": 21, "top": 387, "left": 1510, "height": 43 }
},
{
"char": "1",
"location": { "width": 21, "top": 387, "left": 1523, "height": 43 }
},
{
"char": "5",
"location": { "width": 21, "top": 387, "left": 1535, "height": 43 }
},
{
"char": "震",
"location": { "width": 25, "top": 387, "left": 1567, "height": 43 }
},
{
"char": "撼",
"location": { "width": 26, "top": 387, "left": 1592, "height": 43 }
},
{
"char": "开",
"location": { "width": 25, "top": 387, "left": 1631, "height": 43 }
},
{
"char": "启",
"location": { "width": 26, "top": 387, "left": 1656, "height": 43 }
},
{
"char": "。",
"location": { "width": 21, "top": 387, "left": 1678, "height": 43 }
}
],
"location": { "width": 711, "top": 387, "left": 997, "height": 43 },
"words": "【普纳山谷120服】将于07月08日00:15震撼开启。"
},
{
"chars": [
{
"char": "各",
"location": { "width": 23, "top": 438, "left": 987, "height": 40 }
},
{
"char": "位",
"location": { "width": 25, "top": 438, "left": 1022, "height": 40 }
},
{
"char": "勇",
"location": { "width": 25, "top": 438, "left": 1059, "height": 40 }
},
{
"char": "士",
"location": { "width": 25, "top": 438, "left": 1095, "height": 40 }
},
{
"char": "请",
"location": { "width": 25, "top": 438, "left": 1120, "height": 40 }
},
{
"char": "拿",
"location": { "width": 23, "top": 438, "left": 1157, "height": 40 }
},
{
"char": "起",
"location": { "width": 25, "top": 438, "left": 1181, "height": 40 }
},
{
"char": "手",
"location": { "width": 23, "top": 438, "left": 1230, "height": 40 }
},
{
"char": "中",
"location": { "width": 23, "top": 438, "left": 1255, "height": 40 }
},
{
"char": "武",
"location": { "width": 25, "top": 438, "left": 1291, "height": 40 }
},
{
"char": "器",
"location": { "width": 23, "top": 438, "left": 1316, "height": 40 }
},
{
"char": ",",
"location": { "width": 20, "top": 438, "left": 1348, "height": 40 }
},
{
"char": "与",
"location": { "width": 25, "top": 438, "left": 1389, "height": 40 }
},
{
"char": "我",
"location": { "width": 23, "top": 438, "left": 1414, "height": 40 }
},
{
"char": "们",
"location": { "width": 25, "top": 438, "left": 1449, "height": 40 }
},
{
"char": "一",
"location": { "width": 23, "top": 438, "left": 1487, "height": 40 }
},
{
"char": "同",
"location": { "width": 23, "top": 438, "left": 1524, "height": 40 }
},
{
"char": "踏",
"location": { "width": 25, "top": 438, "left": 1548, "height": 40 }
},
{
"char": "上",
"location": { "width": 23, "top": 438, "left": 1584, "height": 40 }
},
{
"char": "王",
"location": { "width": 25, "top": 438, "left": 1621, "height": 40 }
},
{
"char": "者",
"location": { "width": 25, "top": 438, "left": 1645, "height": 40 }
},
{
"char": "觉",
"location": { "width": 23, "top": 438, "left": 1683, "height": 40 }
},
{
"char": "醒",
"location": { "width": 25, "top": 438, "left": 1718, "height": 40 }
},
{
"char": "之",
"location": { "width": 23, "top": 438, "left": 1756, "height": 40 }
},
{
"char": "路",
"location": { "width": 25, "top": 438, "left": 1780, "height": 40 }
},
{
"char": "!",
"location": { "width": 20, "top": 438, "left": 1813, "height": 40 }
}
],
"location": { "width": 853, "top": 438, "left": 987, "height": 40 },
"words": "各位勇士请拿起手中武器,与我们一同踏上王者觉醒之路!"
},
{
"chars": [
{
"char": "限",
"location": { "width": 21, "top": 486, "left": 535, "height": 35 }
}
],
"location": { "width": 35, "top": 486, "left": 535, "height": 35 },
"words": "限"
},
{
"chars": [
{
"char": "时",
"location": { "width": 26, "top": 512, "left": 534, "height": 42 }
},
{
"char": "新",
"location": { "width": 26, "top": 512, "left": 597, "height": 42 }
},
{
"char": "服",
"location": { "width": 26, "top": 512, "left": 622, "height": 42 }
},
{
"char": "活",
"location": { "width": 25, "top": 512, "left": 661, "height": 42 }
},
{
"char": "动",
"location": { "width": 26, "top": 512, "left": 699, "height": 42 }
}
],
"location": { "width": 202, "top": 512, "left": 534, "height": 42 },
"words": "时新服活动"
},
{
"chars": [
{
"char": "【",
"location": { "width": 22, "top": 538, "left": 927, "height": 43 }
},
{
"char": "游",
"location": { "width": 26, "top": 538, "left": 955, "height": 43 }
},
{
"char": "戏",
"location": { "width": 26, "top": 538, "left": 994, "height": 43 }
},
{
"char": "特",
"location": { "width": 26, "top": 538, "left": 1020, "height": 43 }
},
{
"char": "色",
"location": { "width": 27, "top": 538, "left": 1060, "height": 43 }
},
{
"char": "】",
"location": { "width": 19, "top": 538, "left": 1095, "height": 43 }
}
],
"location": { "width": 187, "top": 538, "left": 927, "height": 43 },
"words": "【游戏特色】"
},
{
"chars": [
{
"char": "火",
"location": { "width": 20, "top": 612, "left": 537, "height": 33 }
}
],
"location": { "width": 33, "top": 612, "left": 537, "height": 33 },
"words": "火"
},
{
"chars": [
{
"char": "1",
"location": { "width": 21, "top": 588, "left": 992, "height": 43 }
},
{
"char": ".",
"location": { "width": 21, "top": 588, "left": 1005, "height": 43 }
},
{
"char": "超",
"location": { "width": 26, "top": 588, "left": 1036, "height": 43 }
},
{
"char": "宏",
"location": { "width": 26, "top": 588, "left": 1076, "height": 43 }
},
{
"char": "伟",
"location": { "width": 26, "top": 588, "left": 1101, "height": 43 }
},
{
"char": "世",
"location": { "width": 26, "top": 588, "left": 1141, "height": 43 }
},
{
"char": "界",
"location": { "width": 26, "top": 588, "left": 1167, "height": 43 }
},
{
"char": "观",
"location": { "width": 26, "top": 588, "left": 1206, "height": 43 }
},
{
"char": "、",
"location": { "width": 21, "top": 588, "left": 1228, "height": 43 }
},
{
"char": "六",
"location": { "width": 26, "top": 588, "left": 1271, "height": 43 }
},
{
"char": "大",
"location": { "width": 26, "top": 588, "left": 1297, "height": 43 }
},
{
"char": "种",
"location": { "width": 26, "top": 588, "left": 1337, "height": 43 }
},
{
"char": "族",
"location": { "width": 26, "top": 588, "left": 1363, "height": 43 }
},
{
"char": "秘",
"location": { "width": 26, "top": 588, "left": 1403, "height": 43 }
},
{
"char": "密",
"location": { "width": 26, "top": 588, "left": 1429, "height": 43 }
},
{
"char": "等",
"location": { "width": 26, "top": 588, "left": 1468, "height": 43 }
},
{
"char": "你",
"location": { "width": 26, "top": 588, "left": 1493, "height": 43 }
},
{
"char": "探",
"location": { "width": 26, "top": 588, "left": 1533, "height": 43 }
},
{
"char": "索",
"location": { "width": 26, "top": 588, "left": 1559, "height": 43 }
},
{
"char": ";",
"location": { "width": 18, "top": 588, "left": 1595, "height": 43 }
}
],
"location": { "width": 642, "top": 588, "left": 971, "height": 43 },
"words": "1.超宏伟世界观、六大种族秘密等你探索;"
},
{
"chars": [
{
"char": "爆",
"location": { "width": 27, "top": 635, "left": 534, "height": 44 }
},
{
"char": "严",
"location": { "width": 27, "top": 635, "left": 588, "height": 44 }
},
{
"char": "禁",
"location": { "width": 26, "top": 635, "left": 628, "height": 44 }
},
{
"char": "代",
"location": { "width": 27, "top": 635, "left": 668, "height": 44 }
},
{
"char": "充",
"location": { "width": 27, "top": 635, "left": 708, "height": 44 }
},
{
"char": "公",
"location": { "width": 26, "top": 635, "left": 735, "height": 44 }
},
{
"char": "告",
"location": { "width": 27, "top": 635, "left": 774, "height": 44 }
}
],
"location": { "width": 273, "top": 635, "left": 534, "height": 44 },
"words": "爆严禁代充公告"
},
{
"chars": [
{
"char": "2",
"location": { "width": 22, "top": 642, "left": 992, "height": 43 }
},
{
"char": ".",
"location": { "width": 21, "top": 642, "left": 1015, "height": 43 }
},
{
"char": "全",
"location": { "width": 26, "top": 642, "left": 1032, "height": 43 }
},
{
"char": "民",
"location": { "width": 26, "top": 642, "left": 1072, "height": 43 }
},
{
"char": "打",
"location": { "width": 26, "top": 642, "left": 1099, "height": 43 }
},
{
"char": "宝",
"location": { "width": 27, "top": 642, "left": 1138, "height": 43 }
},
{
"char": "得",
"location": { "width": 26, "top": 642, "left": 1165, "height": 43 }
},
{
"char": "神",
"location": { "width": 27, "top": 642, "left": 1204, "height": 43 }
},
{
"char": "装",
"location": { "width": 26, "top": 642, "left": 1231, "height": 43 }
},
{
"char": ",",
"location": { "width": 21, "top": 642, "left": 1267, "height": 43 }
},
{
"char": "自",
"location": { "width": 26, "top": 642, "left": 1297, "height": 43 }
},
{
"char": "由",
"location": { "width": 26, "top": 642, "left": 1337, "height": 43 }
},
{
"char": "交",
"location": { "width": 26, "top": 642, "left": 1364, "height": 43 }
},
{
"char": "易",
"location": { "width": 27, "top": 642, "left": 1403, "height": 43 }
},
{
"char": "换",
"location": { "width": 27, "top": 642, "left": 1429, "height": 43 }
},
{
"char": "货",
"location": { "width": 27, "top": 642, "left": 1469, "height": 43 }
},
{
"char": "币",
"location": { "width": 26, "top": 642, "left": 1496, "height": 43 }
},
{
"char": ",",
"location": { "width": 21, "top": 642, "left": 1532, "height": 43 }
},
{
"char": "极",
"location": { "width": 26, "top": 642, "left": 1563, "height": 43 }
},
{
"char": "速",
"location": { "width": 26, "top": 642, "left": 1589, "height": 43 }
},
{
"char": "致",
"location": { "width": 26, "top": 642, "left": 1629, "height": 43 }
},
{
"char": "富",
"location": { "width": 27, "top": 642, "left": 1668, "height": 43 }
},
{
"char": ";",
"location": { "width": 22, "top": 642, "left": 1689, "height": 43 }
}
],
"location": { "width": 719, "top": 642, "left": 992, "height": 43 },
"words": "2.全民打宝得神装,自由交易换货币,极速致富;"
},
{
"chars": [
{
"char": "3",
"location": { "width": 21, "top": 693, "left": 994, "height": 42 }
},
{
"char": ".",
"location": { "width": 21, "top": 693, "left": 1014, "height": 42 }
},
{
"char": "神",
"location": { "width": 25, "top": 693, "left": 1031, "height": 42 }
},
{
"char": "装",
"location": { "width": 25, "top": 693, "left": 1069, "height": 42 }
},
{
"char": "极",
"location": { "width": 26, "top": 693, "left": 1094, "height": 42 }
},
{
"char": "速",
"location": { "width": 25, "top": 693, "left": 1133, "height": 42 }
},
{
"char": "进",
"location": { "width": 26, "top": 693, "left": 1170, "height": 42 }
},
{
"char": "阶",
"location": { "width": 26, "top": 693, "left": 1195, "height": 42 }
},
{
"char": ",",
"location": { "width": 20, "top": 693, "left": 1229, "height": 42 }
},
{
"char": "炼",
"location": { "width": 25, "top": 693, "left": 1259, "height": 42 }
},
{
"char": "化",
"location": { "width": 26, "top": 693, "left": 1296, "height": 42 }
},
{
"char": "玩",
"location": { "width": 25, "top": 693, "left": 1334, "height": 42 }
},
{
"char": "法",
"location": { "width": 25, "top": 693, "left": 1359, "height": 42 }
},
{
"char": "打",
"location": { "width": 26, "top": 693, "left": 1397, "height": 42 }
},
{
"char": "造",
"location": { "width": 25, "top": 693, "left": 1436, "height": 42 }
},
{
"char": "独",
"location": { "width": 25, "top": 693, "left": 1461, "height": 42 }
},
{
"char": "特",
"location": { "width": 26, "top": 693, "left": 1497, "height": 42 }
},
{
"char": "个",
"location": { "width": 25, "top": 693, "left": 1536, "height": 42 }
},
{
"char": "性",
"location": { "width": 25, "top": 693, "left": 1561, "height": 42 }
},
{
"char": "神",
"location": { "width": 26, "top": 693, "left": 1599, "height": 42 }
},
{
"char": "装",
"location": { "width": 26, "top": 693, "left": 1624, "height": 42 }
},
{
"char": ";",
"location": { "width": 21, "top": 693, "left": 1657, "height": 42 }
}
],
"location": { "width": 686, "top": 693, "left": 994, "height": 42 },
"words": "3.神装极速进阶,炼化玩法打造独特个性神装;"
},
{
"chars": [
{
"char": "4",
"location": { "width": 19, "top": 746, "left": 996, "height": 38 }
},
{
"char": ".",
"location": { "width": 19, "top": 746, "left": 1008, "height": 38 }
},
{
"char": "独",
"location": { "width": 22, "top": 746, "left": 1036, "height": 38 }
},
{
"char": "创",
"location": { "width": 23, "top": 746, "left": 1070, "height": 38 }
},
{
"char": "十",
"location": { "width": 23, "top": 746, "left": 1106, "height": 38 }
},
{
"char": "二",
"location": { "width": 23, "top": 746, "left": 1141, "height": 38 }
},
{
"char": "星",
"location": { "width": 23, "top": 746, "left": 1165, "height": 38 }
},
{
"char": "使",
"location": { "width": 23, "top": 746, "left": 1199, "height": 38 }
},
{
"char": "助",
"location": { "width": 23, "top": 746, "left": 1235, "height": 38 }
},
{
"char": "战",
"location": { "width": 23, "top": 746, "left": 1270, "height": 38 }
},
{
"char": "系",
"location": { "width": 23, "top": 746, "left": 1305, "height": 38 }
},
{
"char": "统",
"location": { "width": 23, "top": 746, "left": 1328, "height": 38 }
},
{
"char": ",",
"location": { "width": 19, "top": 746, "left": 1360, "height": 38 }
},
{
"char": "五",
"location": { "width": 22, "top": 746, "left": 1400, "height": 38 }
},
{
"char": "大",
"location": { "width": 23, "top": 746, "left": 1435, "height": 38 }
},
{
"char": "魔",
"location": { "width": 23, "top": 746, "left": 1457, "height": 38 }
},
{
"char": "幻",
"location": { "width": 22, "top": 746, "left": 1494, "height": 38 }
},
{
"char": "星",
"location": { "width": 23, "top": 746, "left": 1529, "height": 38 }
},
{
"char": "使",
"location": { "width": 23, "top": 746, "left": 1564, "height": 38 }
},
{
"char": "技",
"location": { "width": 23, "top": 746, "left": 1599, "height": 38 }
},
{
"char": "能",
"location": { "width": 23, "top": 746, "left": 1623, "height": 38 }
},
{
"char": "逆",
"location": { "width": 23, "top": 746, "left": 1659, "height": 38 }
},
{
"char": "转",
"location": { "width": 23, "top": 746, "left": 1693, "height": 38 }
},
{
"char": "战",
"location": { "width": 23, "top": 746, "left": 1728, "height": 38 }
},
{
"char": "局",
"location": { "width": 22, "top": 746, "left": 1765, "height": 38 }
},
{
"char": ";",
"location": { "width": 15, "top": 746, "left": 1794, "height": 38 }
}
],
"location": { "width": 821, "top": 746, "left": 989, "height": 38 },
"words": "4.独创十二星使助战系统,五大魔幻星使技能逆转战局;"
},
{
"chars": [
{
"char": "5",
"location": { "width": 21, "top": 795, "left": 992, "height": 41 }
},
{
"char": ".",
"location": { "width": 21, "top": 795, "left": 1013, "height": 41 }
},
{
"char": "六",
"location": { "width": 25, "top": 795, "left": 1043, "height": 41 }
},
{
"char": "大",
"location": { "width": 25, "top": 795, "left": 1067, "height": 41 }
},
{
"char": "化",
"location": { "width": 25, "top": 795, "left": 1103, "height": 41 }
},
{
"char": "神",
"location": { "width": 25, "top": 795, "left": 1128, "height": 41 }
},
{
"char": "任",
"location": { "width": 25, "top": 795, "left": 1166, "height": 41 }
},
{
"char": "意",
"location": { "width": 23, "top": 795, "left": 1204, "height": 41 }
},
{
"char": "搭",
"location": { "width": 25, "top": 795, "left": 1228, "height": 41 }
},
{
"char": "配",
"location": { "width": 25, "top": 795, "left": 1264, "height": 41 }
},
{
"char": ",",
"location": { "width": 20, "top": 795, "left": 1297, "height": 41 }
},
{
"char": "助",
"location": { "width": 23, "top": 795, "left": 1327, "height": 41 }
},
{
"char": "力",
"location": { "width": 25, "top": 795, "left": 1364, "height": 41 }
},
{
"char": "培",
"location": { "width": 25, "top": 795, "left": 1400, "height": 41 }
},
{
"char": "养",
"location": { "width": 25, "top": 795, "left": 1438, "height": 41 }
},
{
"char": "个",
"location": { "width": 25, "top": 795, "left": 1463, "height": 41 }
},
{
"char": "性",
"location": { "width": 23, "top": 795, "left": 1501, "height": 41 }
},
{
"char": "化",
"location": { "width": 25, "top": 795, "left": 1525, "height": 41 }
},
{
"char": "职",
"location": { "width": 25, "top": 795, "left": 1563, "height": 41 }
},
{
"char": "业",
"location": { "width": 25, "top": 795, "left": 1599, "height": 41 }
},
{
"char": "属",
"location": { "width": 23, "top": 795, "left": 1624, "height": 41 }
},
{
"char": "性",
"location": { "width": 25, "top": 795, "left": 1661, "height": 41 }
},
{
"char": ";",
"location": { "width": 18, "top": 795, "left": 1694, "height": 41 }
}
],
"location": { "width": 719, "top": 795, "left": 992, "height": 41 },
"words": "5.六大化神任意搭配,助力培养个性化职业属性;"
},
{
"chars": [
{
"char": "6",
"location": { "width": 20, "top": 844, "left": 994, "height": 42 }
},
{
"char": ".",
"location": { "width": 20, "top": 844, "left": 1014, "height": 42 }
},
{
"char": "勇",
"location": { "width": 25, "top": 844, "left": 1030, "height": 42 }
},
{
"char": "者",
"location": { "width": 26, "top": 844, "left": 1068, "height": 42 }
},
{
"char": "转",
"location": { "width": 25, "top": 844, "left": 1106, "height": 42 }
},
{
"char": "职",
"location": { "width": 25, "top": 844, "left": 1131, "height": 42 }
},
{
"char": "突",
"location": { "width": 26, "top": 844, "left": 1168, "height": 42 }
},
{
"char": "破",
"location": { "width": 26, "top": 844, "left": 1193, "height": 42 }
},
{
"char": "属",
"location": { "width": 25, "top": 844, "left": 1232, "height": 42 }
},
{
"char": "性",
"location": { "width": 25, "top": 844, "left": 1269, "height": 42 }
},
{
"char": ",",
"location": { "width": 21, "top": 844, "left": 1291, "height": 42 }
},
{
"char": "职",
"location": { "width": 25, "top": 844, "left": 1333, "height": 42 }
},
{
"char": "业",
"location": { "width": 25, "top": 844, "left": 1371, "height": 42 }
},
{
"char": "时",
"location": { "width": 25, "top": 844, "left": 1396, "height": 42 }
},
{
"char": "装",
"location": { "width": 25, "top": 844, "left": 1433, "height": 42 }
},
{
"char": "免",
"location": { "width": 38, "top": 844, "left": 1459, "height": 42 }
},
{
"char": "费",
"location": { "width": 25, "top": 844, "left": 1496, "height": 42 }
},
{
"char": "领",
"location": { "width": 25, "top": 844, "left": 1534, "height": 42 }
},
{
"char": ";",
"location": { "width": 21, "top": 844, "left": 1555, "height": 42 }
}
],
"location": { "width": 586, "top": 844, "left": 994, "height": 42 },
"words": "6.勇者转职突破属性,职业时装免费领;"
},
{
"chars": [
{
"char": "7",
"location": { "width": 23, "top": 893, "left": 992, "height": 46 }
},
{
"char": ".",
"location": { "width": 22, "top": 893, "left": 1002, "height": 46 }
},
{
"char": "B",
"location": { "width": 23, "top": 893, "left": 1029, "height": 46 }
},
{
"char": "O",
"location": { "width": 22, "top": 893, "left": 1044, "height": 46 }
},
{
"char": "S",
"location": { "width": 23, "top": 893, "left": 1071, "height": 46 }
},
{
"char": "S",
"location": { "width": 23, "top": 893, "left": 1100, "height": 46 }
},
{
"char": "爆",
"location": { "width": 28, "top": 893, "left": 1118, "height": 46 }
},
{
"char": "率",
"location": { "width": 27, "top": 893, "left": 1147, "height": 46 }
},
{
"char": "1",
"location": { "width": 22, "top": 893, "left": 1171, "height": 46 }
},
{
"char": "0",
"location": { "width": 22, "top": 893, "left": 1184, "height": 46 }
},
{
"char": "0",
"location": { "width": 23, "top": 893, "left": 1212, "height": 46 }
},
{
"char": "%",
"location": { "width": 28, "top": 893, "left": 1231, "height": 46 }
},
{
"char": ",",
"location": { "width": 22, "top": 893, "left": 1255, "height": 46 }
},
{
"char": "狂",
"location": { "width": 28, "top": 893, "left": 1287, "height": 46 }
},
{
"char": "爆",
"location": { "width": 27, "top": 893, "left": 1331, "height": 46 }
},
{
"char": "极",
"location": { "width": 28, "top": 893, "left": 1358, "height": 46 }
},
{
"char": "品",
"location": { "width": 27, "top": 893, "left": 1387, "height": 46 }
},
{
"char": "装",
"location": { "width": 28, "top": 893, "left": 1428, "height": 46 }
},
{
"char": "备",
"location": { "width": 27, "top": 893, "left": 1456, "height": 46 }
},
{
"char": ",",
"location": { "width": 22, "top": 893, "left": 1480, "height": 46 }
},
{
"char": "轻",
"location": { "width": 28, "top": 893, "left": 1526, "height": 46 }
},
{
"char": "松",
"location": { "width": 28, "top": 893, "left": 1555, "height": 46 }
},
{
"char": "集",
"location": { "width": 28, "top": 893, "left": 1597, "height": 46 }
},
{
"char": "齐",
"location": { "width": 28, "top": 893, "left": 1625, "height": 46 }
},
{
"char": "全",
"location": { "width": 28, "top": 893, "left": 1653, "height": 46 }
},
{
"char": "套",
"location": { "width": 27, "top": 893, "left": 1696, "height": 46 }
},
{
"char": "神",
"location": { "width": 28, "top": 893, "left": 1724, "height": 46 }
},
{
"char": "装",
"location": { "width": 28, "top": 893, "left": 1752, "height": 46 }
},
{
"char": ";",
"location": { "width": 23, "top": 893, "left": 1775, "height": 46 }
}
],
"location": { "width": 813, "top": 893, "left": 992, "height": 46 },
"words": "7.BOSS爆率100%,狂爆极品装备,轻松集齐全套神装;"
},
{
"chars": [
{
"char": "8",
"location": { "width": 25, "top": 945, "left": 994, "height": 49 }
},
{
"char": ".",
"location": { "width": 23, "top": 945, "left": 1008, "height": 49 }
},
{
"char": "首",
"location": { "width": 44, "top": 945, "left": 1029, "height": 49 }
},
{
"char": "日",
"location": { "width": 29, "top": 945, "left": 1072, "height": 49 }
},
{
"char": "直",
"location": { "width": 29, "top": 945, "left": 1102, "height": 49 }
},
{
"char": "升",
"location": { "width": 29, "top": 945, "left": 1132, "height": 49 }
},
{
"char": "1",
"location": { "width": 23, "top": 945, "left": 1158, "height": 49 }
},
{
"char": "5",
"location": { "width": 25, "top": 945, "left": 1172, "height": 49 }
},
{
"char": "0",
"location": { "width": 25, "top": 945, "left": 1201, "height": 49 }
},
{
"char": "级",
"location": { "width": 29, "top": 945, "left": 1222, "height": 49 }
},
{
"char": ",",
"location": { "width": 25, "top": 945, "left": 1247, "height": 49 }
},
{
"char": "登",
"location": { "width": 30, "top": 945, "left": 1281, "height": 49 }
},
{
"char": "录",
"location": { "width": 29, "top": 945, "left": 1327, "height": 49 }
},
{
"char": "即",
"location": { "width": 29, "top": 945, "left": 1357, "height": 49 }
},
{
"char": "送",
"location": { "width": 29, "top": 945, "left": 1387, "height": 49 }
},
{
"char": ":",
"location": { "width": 23, "top": 945, "left": 1412, "height": 49 }
},
{
"char": "兽",
"location": { "width": 29, "top": 945, "left": 1446, "height": 49 }
},
{
"char": "王",
"location": { "width": 29, "top": 945, "left": 1492, "height": 49 }
},
{
"char": "天",
"location": { "width": 29, "top": 945, "left": 1521, "height": 49 }
},
{
"char": "神",
"location": { "width": 29, "top": 945, "left": 1551, "height": 49 }
},
{
"char": "、",
"location": { "width": 25, "top": 945, "left": 1575, "height": 49 }
},
{
"char": "春",
"location": { "width": 29, "top": 945, "left": 1611, "height": 49 }
},
{
"char": "日",
"location": { "width": 30, "top": 945, "left": 1655, "height": 49 }
},
{
"char": "时",
"location": { "width": 29, "top": 945, "left": 1686, "height": 49 }
},
{
"char": "装",
"location": { "width": 29, "top": 945, "left": 1716, "height": 49 }
},
{
"char": "、",
"location": { "width": 25, "top": 945, "left": 1740, "height": 49 }
},
{
"char": "雪",
"location": { "width": 29, "top": 945, "left": 1789, "height": 49 }
},
{
"char": "豹",
"location": { "width": 28, "top": 945, "left": 1820, "height": 49 }
}
],
"location": { "width": 879, "top": 945, "left": 968, "height": 49 },
"words": "8.首日直升150级,登录即送:兽王天神、春日时装、雪豹"
}
]
}
计算文字的位置
背景:百度OCR返回的文字都是json字典,希望能从对应匹配到的单词,找到对应的位置坐标信息
代码:
def calcWordsLocation(self, wordStr, curWordsResult):
"""Calculate words location from result
Args:
wordStr (str): the words to check
curWordsResult (dict): the baidu OCR result of current words
Returns:
location, a tuple (x, y, width, height)
Raises:
Examples
wordStr="首充"
curWordsResult= {
"chars": [
{
"char": "寻",
"location": {
"width": 15,
"top": 51,
"left": 725,
"height": 24
}
},
...
{
"char": "首",
"location": {
"width": 15,
"top": 51,
"left": 971,
"height": 24
}
},
{
"char": "充",
"location": {
"width": 15,
"top": 51,
"left": 986,
"height": 24
}
}
],
"location": {
"width": 280,
"top": 51,
"left": 725,
"height": 24
},
"words": "寻宝福利大厅商城首充"
}
-> (971, 51, 30, 24)
"""
(x, y, width, height) = (0, 0, 0, 0)
matchedStr = curWordsResult["words"]
# Note: for special, contain multilple words, here only process firt words
foundWords = re.search(wordStr, matchedStr)
if foundWords:
logging.debug("foundWords=%s" % foundWords)
firstMatchedPos = foundWords.start()
lastMatchedPos = foundWords.end() - 1
matchedStrLen = len(matchedStr)
charResultList = curWordsResult["chars"]
charResultListLen = len(charResultList)
firstCharResult = None
lastCharResult = None
if matchedStrLen == charResultListLen:
firstCharResult = charResultList[firstMatchedPos]
lastCharResult = charResultList[lastMatchedPos]
else:
# Special: for 'Loading' matched ' Loading', but charResultList not include first space ' ', but from fisrt='L' to end='g'
# so using find the corresponding char, then got its location
# Note: following method not work for regex str, like '^游戏公告$'
firtToMatchChar = wordStr[0]
lastToMatchChar = wordStr[-1]
for eachCharResult in charResultList:
if firstCharResult and lastCharResult:
break
eachChar = eachCharResult["char"]
if firtToMatchChar == eachChar:
firstCharResult = eachCharResult
elif lastToMatchChar == eachChar:
lastCharResult = eachCharResult
# Note: follow no need check words, to support input ^游戏公告$ to match "游戏公告"
# firstLocation = None
# lastLocation = None
# if firstCharResult["char"] == firtToMatchChar:
# firstLocation = firstCharResult["location"]
# if lastCharResult["char"] == lastToMatchChar:
# lastLocation = lastCharResult["location"]
firstLocation = firstCharResult["location"]
lastLocation = lastCharResult["location"]
# if firstLocation and lastLocation:
# support both horizontal and vertical words
firstLeft = firstLocation["left"]
lastLeft = lastLocation["left"]
minLeft = min(firstLeft, lastLeft)
x = minLeft
firstHorizontalEnd = firstLeft + firstLocation["width"]
lastHorizontalEnd = lastLeft + lastLocation["width"]
maxHorizontalEnd = max(firstHorizontalEnd, lastHorizontalEnd)
width = maxHorizontalEnd - x
lastTop = lastLocation["top"]
minTop = min(firstLocation["top"], lastTop)
y = minTop
lastVerticalEnd = lastTop + lastLocation["height"]
height = lastVerticalEnd - y
return x, y, width, height
调用:
calculatedLocation = self.calcWordsLocation(eachInputWords, eachWordsMatchedResult)
把坐标位置转成中间坐标值
作用:用于后续点击按钮中间坐标值
代码:
def locationToCenterPos(self, wordslocation):
"""Convert location of normal button to center position
Args:
wordslocation (tuple): words location, (x, y, width, height)
Example: (267, 567, 140, 39)
Returns:
tuple, (x, y), the location's center position, normal used later to click it
Example: (337.0, 586.5)
Raises:
"""
x, y, width, height = wordslocation
centerX = x + width/2
centerY = y + height/2
centerPosition = (centerX, centerY)
return centerPosition
调用:
curCenterX, curCenterY = self.locationToCenterPos(eachLocation)
检测文字是否在结果中
代码:
def isWordsInResult(self, respJson, wordsOrWordsList, isMatchMultiple=False):
"""Check words is in result or not
Args:
respJson (dict): Baidu OCR responsed json
wordsOrWordsList (str/list): single input str or str list
isMatchMultiple (bool): for each single str, to match multiple output or only match one output
Returns:
dict, matched result
Raises:
"""
# Note: use OrderedDict instead dict to keep order, for later get first match result to process
orderedMatchedResultDict = OrderedDict()
inputWordsList = wordsOrWordsList
if isinstance(wordsOrWordsList, str):
inputWords = str(wordsOrWordsList)
inputWordsList = [inputWords]
wordsResultList = respJson["words_result"]
for curInputWords in inputWordsList:
curMatchedResultList = []
for eachWordsResult in wordsResultList:
eachWords = eachWordsResult["words"]
foundCurWords = re.search(curInputWords, eachWords)
if foundCurWords:
curMatchedResultList.append(eachWordsResult)
if not isMatchMultiple:
break
orderedMatchedResultDict[curInputWords] = curMatchedResultList
return orderedMatchedResultDict
调用:
matchedResultDict = self.isWordsInResult(wordsResultJson, wordsOrWordsList, isMatchMultiple)
检测当前屏幕中是否包含对应文字
def isWordsInCurScreen(self, wordsOrWordsList, imgPath=None, isMatchMultiple=False, isRespShortInfo=False):
"""Found words in current screen
Args:
wordsOrWordsList (str/list): single input str or str list
imgPath (str): current screen image file path; default=None; if None, will auto get current scrren image
isMatchMultiple (bool): for each single str, to match multiple output or only match one output; default=False
isRespShortInfo (bool): return simple=short=nomarlly bool or list[bool] info or return full info which contain imgPath and full matched result.
Returns:
matched result, type=bool/list[bool]/dict/tuple, depends on diffrent condition
Raises:
"""
retValue = None
if not imgPath:
# do screenshot
imgPath = self.getCurScreenshot()
wordsResultJson = self.baiduImageToWords(imgPath)
isMultipleInput = False
inputWords = None
inputWordsList = []
if isinstance(wordsOrWordsList, list):
isMultipleInput = True
inputWordsList = list(wordsOrWordsList)
elif isinstance(wordsOrWordsList, str):
isMultipleInput = False
inputWords = str(wordsOrWordsList)
inputWordsList = [inputWords]
matchedResultDict = self.isWordsInResult(wordsResultJson, wordsOrWordsList, isMatchMultiple)
# add caclulated location and words
# Note: use OrderedDict instead dict to keep order, for later get first match result to process
processedResultDict = OrderedDict()
for eachInputWords in inputWordsList:
isCurFound = False
# curLocatoinList = []
# curWordsList = []
curResultList = []
curWordsMatchedResultList = matchedResultDict[eachInputWords]
if curWordsMatchedResultList:
isCurFound = True
for curIdx, eachWordsMatchedResult in enumerate(curWordsMatchedResultList):
curMatchedWords = eachWordsMatchedResult["words"]
calculatedLocation = self.calcWordsLocation(eachInputWords, eachWordsMatchedResult)
# curLocatoinList.append(calculatedLocation)
# curWordsList.append(curMatchedWords)
curResult = (curMatchedWords, calculatedLocation)
curResultList.append(curResult)
# processedResultDict[eachInputWords] = (isCurFound, curLocatoinList, curWordsList)
processedResultDict[eachInputWords] = (isCurFound, curResultList)
logging.debug("For %s, matchedResult=%s from imgPath=%s", wordsOrWordsList, processedResultDict, imgPath)
if isMultipleInput:
if isRespShortInfo:
isFoundList = []
for eachInputWords in processedResultDict.keys():
isCurFound, noUse = processedResultDict[eachInputWords]
isFoundList.append(isCurFound)
# Note: no mattter isMatchMultiple, both only return single boolean for each input words
retBoolList = isFoundList
retValue = retBoolList
else:
if isMatchMultiple:
retTuple = processedResultDict, imgPath
retValue = retTuple
else:
# Note: use OrderedDict instead dict to keep order, for later get first match result to process
respResultDict = OrderedDict()
for eachInputWords in processedResultDict.keys():
# isCurFound, curLocatoinList, curWordsList = processedResultDict[eachInputWords]
isCurFound, curResultList = processedResultDict[eachInputWords]
# singleLocation = None
# singleWords = None
singleResult = (None, None)
if isCurFound:
# singleLocation = curLocatoinList[0]
# singleWords = curWordsList[0]
singleResult = curResultList[0]
# respResultDict[eachInputWords] = (isCurFound, singleLocation, singleWords)
respResultDict[eachInputWords] = (isCurFound, singleResult)
retTuple = respResultDict, imgPath
retValue = retTuple
else:
singleInputResult = processedResultDict[inputWords]
# isCurFound, curLocatoinList, curWordsList = singleInputResult
isCurFound, curResultList = singleInputResult
if isRespShortInfo:
# Note: no mattter isMatchMultiple, both only return single boolean for each input words
retBool = isCurFound
retValue = retBool
else:
if isMatchMultiple:
# retTuple = isCurFound, curLocatoinList, curWordsList, imgPath
retTuple = isCurFound, curResultList, imgPath
retValue = retTuple
else:
singleResult = (None, None)
# singleLocation = None
# singleWords = None
if isCurFound:
# singleLocation = curLocatoinList[0]
# singleWords = curWordsList[0]
singleResult = curResultList[0]
# retTuple = isCurFound, singleLocation, singleWords, imgPath
retTuple = isCurFound, singleResult, imgPath
retValue = retTuple
logging.debug("Input: %s, output=%s", wordsOrWordsList, retValue)
return retValue
调用:
allResultDict, _ = self.isWordsInCurScreen(allStrList, imgPath, isMatchMultiple=True)
获取当前屏幕中的文字
def getWordsInCurScreen(self):
"""get words in current screenshot"""
screenImgPath = self.getCurScreenshot()
wordsResultJson = self.baiduImageToWords(screenImgPath)
return wordsResultJson
调用:
curScreenWords = self.getWordsInCurScreen()
检测当前屏幕中是否存在某些信息
def checkExistInScreen(self,
imgPath=None,
mandatoryStrList=[],
mandatoryMinMatchCount=0,
optionalStrList=[],
# optionalMinMatchCount=2,
optionalMinMatchCount=1,
isRespFullInfo=False
):
"""Check whether mandatory and optional str list in current screen or not
Args:
imgPath (str): current screen image file path; default=None; if None, will auto get current scrren image
mandatoryStrList (list): mandatory str, at least match `mandatoryMinMatchCount`, or all must match if `mandatoryMinMatchCount`=0
mandatoryMinMatchCount (int): minimal match count for mandatory list
optionalStrList (list): optional str, some may match
optionalMinMatchCount (int): for `optionalStrList`, the minimal match count, consider to match or not
isRespFullInfo (bool): return full info or not, full info means match location result and imgPath
Returns:
matched result, type=bool/tuple, depends on `isRespFullInfo`
Raises:
"""
if not imgPath:
imgPath = self.getCurScreenshot()
logging.debug("imgPath=%s", imgPath)
isExist = False
# Note: use OrderedDict instead dict to keep order, for later get first match result to process
respMatchLocation = OrderedDict()
isMandatoryMatch = True
isMandatoryShouldMatchAll = (mandatoryMinMatchCount <= 0)
isOptionalMatch = True
allStrList = []
allStrList.extend(mandatoryStrList)
allStrList.extend(optionalStrList)
optionalMatchCount = 0
mandatoryMatchCount = 0
allResultDict, _ = self.isWordsInCurScreen(allStrList, imgPath, isMatchMultiple=True)
for eachStr, (isFoundCur, curResultList) in allResultDict.items():
if eachStr in mandatoryStrList:
if isFoundCur:
mandatoryMatchCount += 1
respMatchLocation[eachStr] = curResultList
else:
if isMandatoryShouldMatchAll:
isMandatoryMatch = False
break
elif eachStr in optionalStrList:
if isFoundCur:
optionalMatchCount += 1
respMatchLocation[eachStr] = curResultList
if mandatoryStrList:
if not isMandatoryShouldMatchAll:
if mandatoryMatchCount >= mandatoryMinMatchCount:
isMandatoryMatch = True
else:
isMandatoryMatch = False
if optionalStrList:
if optionalMatchCount >= optionalMinMatchCount:
isOptionalMatch = True
else:
isOptionalMatch = False
isExist = isMandatoryMatch and isOptionalMatch
logging.debug("isMandatoryMatch=%s, isOptionalMatch=%s -> isExist=%s", isMandatoryMatch, isOptionalMatch, isExist)
if isRespFullInfo:
logging.debug("mandatoryStrList=%s, optionalStrList=%s -> isExist=%s, respMatchLocation=%s, imgPath=%s",
mandatoryStrList, optionalStrList, isExist, respMatchLocation, imgPath)
return (isExist, respMatchLocation, imgPath)
else:
logging.debug("mandatoryStrList=%s, optionalStrList=%s -> isExist=%s",
mandatoryStrList, optionalStrList, isExist)
return isExist
调用:
checkResult = self.checkExistInScreen(
imgPath=imgPath,
optionalStrList=strList,
optionalMinMatchCount=1,
isRespFullInfo=isRespFullInfo,
)
和:
minOptionalMatchCount = 2
mandatoryList = [
# "公告",
"^公告$",
'^强力推荐$', # 王城英雄, 不规则弹框
。。。
]
possibleTitleList = [
"^游戏公告$",
]
optionalList = []
otherOptionalList = [
"新增(内容)?",
"游戏特色",
"(主要)?更新(内容)?",
。。。
"登录即送",
]
optionalList.extend(possibleTitleList)
optionalList.extend(otherOptionalList)
checkResult = self.checkExistInScreen(
imgPath=imgPath,
mandatoryStrList=mandatoryList,
mandatoryMinMatchCount=1,
optionalStrList=optionalList,
optionalMinMatchCount=minOptionalMatchCount,
isRespFullInfo=isRespFullInfo,
)
和:
mandatoryList = [
"^购买$", # 造梦西游:'购买'
"^¥\d+元?", # 剑玲珑, 至尊屠龙
"^\d+元", # 造梦西游:'6元券3元券3’
"^充值$", # 剑玲珑, 至尊屠龙
]
optionalList = [
# common
"元宝",
"月卡",
。。。
# 剑玲珑
"赠",
]
isRealPay, matchResult, imgPath = self.checkExistInScreen(
mandatoryStrList=mandatoryList,
mandatoryMinMatchCount=2,
optionalStrList=optionalList,
optionalMinMatchCount=2,
isRespFullInfo=True,
)
和:
mandatoryList = [
"^((继续)|(结束))$", # 暗黑觉醒:'继续' or '结束'
"^\d秒$", # 暗黑觉醒:'5秒', '1秒'
]
isAutoConverstion, matchResult, imgPath = self.checkExistInScreen(
imgPath=imgPath,
mandatoryStrList=mandatoryList,
mandatoryMinMatchCount=2,
isRespFullInfo=True,
)
和:
mandatoryList = [
"^充值", # 造梦西游: "充值战神榜邮件各但,挑战竞技好友"
"首充$", # 剑玲珑,至尊屠龙
。。。
]
optionalList = [
# common
"组队", # 至尊屠龙, 剑玲珑
"任务", # 至尊屠龙, 剑玲珑
# "商城", # 剑玲珑
"寻宝", # 剑玲珑
"福利大厅", # 剑玲珑
。。。
"背包",
]
isHome, matchResult, imgPath = self.checkExistInScreen(
mandatoryStrList=mandatoryList,
mandatoryMinMatchCount=1,
optionalStrList=optionalList,
isRespFullInfo=True,
)
和:
mandatoryList = [
"立即登录", # 剑玲珑, 至尊屠龙
。。。
"^登录$", # 暗黑觉醒
]
optionalList = [
# common
"一键((注册)|(试玩))",
"忘记密码", # 至尊屠龙, 青云诀
"((用户)|(账号)|(手机))登录", # 剑玲珑, 至尊屠龙,
"点击选服", # 青云诀,暗黑觉醒
。。。
# 造梦西游
"游客登录", #更新:不能用游客登录,否则后续无法弹出支付页面
]
respUserLogin = self.checkExistInScreen(
mandatoryStrList=mandatoryList,
mandatoryMinMatchCount=1,
optionalStrList=optionalList,
isRespFullInfo=isRespFullInfo
)
是否存在任意一个词组
def isExistAnyStr(self, strList, imgPath=None, isRespFullInfo=False):
"""Is any str exist or not
Args:
strList (list): str list to check exist or not
imgPath (str): current screen image file path; default=None; if None, will auto get current scrren image
isRespFullInfo (bool): return full info or not, full info means match location result and imgPath
Returns:
matched result, type=bool/tuple, depends on `isRespFullInfo`
Raises:
"""
if not imgPath:
imgPath = self.getCurScreenshot()
checkResult = self.checkExistInScreen(
imgPath=imgPath,
optionalStrList=strList,
optionalMinMatchCount=1,
isRespFullInfo=isRespFullInfo,
)
if isRespFullInfo:
isExistAny, matchResult, imgPath = checkResult
logging.debug("isExistAny=%s, matchResult=%s, imgPath=%s for %s", isExistAny, matchResult, imgPath, strList)
return (isExistAny, matchResult, imgPath)
else:
isExistAny = checkResult
logging.debug("isExistAny=%s, for %s", isExistAny, strList)
return isExistAny
调用:
isExist, matchResult, imgPath = self.isExistAnyStr(buttonStrList, imgPath=imgPath, isRespFullInfo=True)
和:
mandatoryList = [
# 御剑仙缘
"""^(请)?点击\s?[“”"'][^“”"',]{1,6}[“”"']?""", # '请点击“战骑','请点击“魂兽”','请点击“人物”','请点击“领取”','请点击“仙盟”','点击“+”,放入吞噬','点击“进阶”,提升战',请点击“每日必做”按
]
respResult = self.isExistAnyStr(mandatoryList, imgPath=imgPath, isRespFullInfo=isRespFullInfo)
和:
requireManualOperationList = [
"完成指定操作", # speical:造梦西游 的 '(请完成指定操作)梦口' 的顶部弹框
]
isRequireManual, _, imgPath = self.isExistAnyStr(requireManualOperationList, isRespFullInfo=True)
和:
lanuchStrList = [
"^4399手机游戏$", # 剑玲珑
"^西瓜游戏$", # 青云诀
]
isLaunch, _, imgPath = self.isExistAnyStr(lanuchStrList, isRespFullInfo=True)
和:
loadingStrList = [
# 登录类
"正在登录", # 正在登录
"logging",
。。。
"游戏资源", # 青云诀:本地游戏资源已是最新
。。。
]
isLoadingSth, _, imgPath = self.isExistAnyStr(loadingStrList, isRespFullInfo=True)
和:
gotoPayStrList = [
"^前往充值$", # 剑玲珑
"^立即充值$", # 至尊屠龙
。。。
]
respBoolOrTuple = self.isExistAnyStr(gotoPayStrList, isRespFullInfo=isRespLocation)
判断是否所有的字符都存在
def isExistAllStr(self, strList, imgPath=None, isRespFullInfo=False):
"""Is all str exist or not
Args:
strList (list): str list to check exist or not
imgPath (str): current screen image file path; default=None; if None, will auto get current scrren image
isRespFullInfo (bool): return full info or not, full info means match location result and imgPath
Returns:
matched result, type=bool/tuple, depends on `isRespFullInfo`
Raises:
"""
if not imgPath:
imgPath = self.getCurScreenshot()
checkResult = self.checkExistInScreen(imgPath=imgPath, mandatoryStrList=strList, isRespFullInfo=isRespFullInfo)
if isRespFullInfo:
isExistAll, matchResult, imgPath = checkResult
logging.debug("isExistAll=%s, matchResult=%s, imgPath=%s for %s", isExistAll, matchResult, imgPath, strList)
return (isExistAll, matchResult, imgPath)
else:
isExistAll = checkResult
logging.debug("isExistAll=%s, for %s", isExistAll, strList)
return isExistAll
调用:
realPayStrList = [
"^¥\d+元?", # 剑玲珑, 至尊屠龙
"^充值$", # 剑玲珑, 至尊屠龙
]
return self.isExistAllStr(realPayStrList, isRespFullInfo=isRespLocation)