基本思想:随手记录一下,验证码需要计算之后才能登陆的py脚本开发~

一、下载 paddle 检测和识别模型

https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/models_list.md

检测模型:https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar

识别模型:https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar

python -m pip install paddlepaddle==2.0.1 -i https://mirror.baidu.com/pypi/simple

样例图片:来自百度测试sample的图片

首先先测试一下

from paddleocr import PaddleOCR, draw_ocr
from PIL import Image
img_path = r'F:\tt\source\1.png'
ocr = PaddleOCR(cls_thresh=0,use_gpu=False,det_model_dir=r'F:\tt\ch_ppocr_mobile_v2.0_det_infer', rec_model_dir=r'F:\tt\ch_ppocr_mobile_v2.0_rec_infer') # need to run only once to download and load model into memoryresult = ocr.ocr(img_path, cls=True)
print(result)
for line in result:print(line)
image = Image.open(img_path).convert('RGB')
boxes = [line[0] for line in result]
txts = [line[1][0] for line in result]
scores = [line[1][1] for line in result]
im_show = draw_ocr(image, boxes, txts, scores)
im_show = Image.fromarray(im_show)
im_show.save('result.jpg')

结果

"D:\Program Files\Python36\python.exe" D:/nanodet-main/test.py
Namespace(cls_batch_num=6, cls_image_shape='3, 48, 192', cls_model_dir='C:\\Users\\sxj/.paddleocr/2.1/cls', cls_thresh=0, det=True, det_algorithm='DB', det_db_box_thresh=0.5, det_db_thresh=0.3, det_db_unclip_ratio=1.6, det_east_cover_thresh=0.1, det_east_nms_thresh=0.2, det_east_score_thresh=0.8, det_limit_side_len=960, det_limit_type='max', det_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_det_infer', drop_score=0.5, enable_mkldnn=False, gpu_mem=8000, image_dir='', ir_optim=True, label_list=['0', '180'], lang='ch', max_text_length=25, rec=True, rec_algorithm='CRNN', rec_batch_num=6, rec_char_dict_path='./ppocr/utils/ppocr_keys_v1.txt', rec_char_type='ch', rec_image_shape='3, 32, 320', rec_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_rec_infer', use_angle_cls=False, use_dilation=False, use_gpu=False, use_pdserving=False, use_space_char=True, use_tensorrt=False, use_zero_copy_run=False)
[2021/04/17 15:42:58] root WARNING: Since the angle classifier is not initialized, the angle classifier will not be uesd during the forward process
[2021/04/17 15:42:59] root INFO: dt_boxes num : 36, elapse : 0.9523482322692871
[2021/04/17 15:43:01] root INFO: rec_res num  : 36, elapse : 1.8610682487487793
[[[[704.0, 17.0], [820.0, 17.0], [820.0, 57.0], [704.0, 57.0]], ('PASS', 0.9926876)], [[[158.0, 30.0], [351.0, 26.0], [352.0, 66.0], [159.0, 71.0]], ('登机牌', 0.99803185)], [[[424.0, 27.0], [653.0, 23.0], [654.0, 56.0], [425.0, 61.0]], ('BOARDING', 0.9355928)], [[[678.0, 101.0], [740.0, 100.0], [740.0, 117.0], [678.0, 119.0]], ('座位号', 0.9993448)], [[[490.0, 106.0], [648.0, 102.0], [648.0, 119.0], [491.0, 122.0]], ('序号SERIALNO', 0.9721721)], [[[397.0, 107.0], [455.0, 105.0], [455.0, 122.0], [397.0, 124.0]], ('CLASS', 0.99292123)], [[[340.0, 109.0], [384.0, 107.0], [385.0, 122.0], [341.0, 124.0]], ('舱位', 0.95519966)], [[[749.0, 100.0], [830.0, 97.0], [831.0, 114.0], [750.0, 116.0]], ('SEAT NO', 0.91536474)], [[[215.0, 111.0], [255.0, 110.0], [256.0, 125.0], [216.0, 127.0]], ('日期', 0.99764645)], [[[66.0, 114.0], [189.0, 111.0], [190.0, 128.0], [66.0, 131.0]], ('航班FLIGHT', 0.9937089)], [[[248.0, 110.0], [314.0, 110.0], [314.0, 125.0], [248.0, 125.0]], ('DATE', 0.98909116)], [[[509.0, 131.0], [566.0, 131.0], [566.0, 156.0], [509.0, 156.0]], ('035', 0.9830131)], [[[406.0, 135.0], [430.0, 135.0], [430.0, 157.0], [406.0, 157.0]], ('W', 0.99706143)], [[[84.0, 142.0], [212.0, 139.0], [213.0, 158.0], [84.0, 162.0]], ('MU 2379', 0.9289481)], [[[211.0, 141.0], [324.0, 139.0], [325.0, 156.0], [211.0, 158.0]], ('03DEG', 0.8540861)], [[[489.0, 175.0], [572.0, 173.0], [572.0, 193.0], [489.0, 195.0]], ('登机口', 0.9996843)], [[[568.0, 176.0], [612.0, 176.0], [612.0, 191.0], [568.0, 191.0]], ('GATE', 0.99684405)], [[[345.0, 177.0], [409.0, 177.0], [409.0, 194.0], [345.0, 194.0]], ('始发地', 0.9996207)], [[[400.0, 177.0], [466.0, 173.0], [467.0, 191.0], [401.0, 194.0]], ('FROM', 0.9926705)], [[[68.0, 181.0], [168.0, 178.0], [168.0, 199.0], [69.0, 203.0]], ('目的地TO', 0.98151416)], [[[680.0, 172.0], [806.0, 166.0], [807.0, 186.0], [681.0, 192.0]], ('登机时间BD1', 0.9584261)], [[[98.0, 208.0], [168.0, 208.0], [168.0, 227.0], [98.0, 227.0]], ('福州', 0.8741772)], [[[339.0, 220.0], [474.0, 216.0], [475.0, 235.0], [339.0, 239.0]], ('TAIYUAN', 0.93135375)], [[[507.0, 217.0], [552.0, 214.0], [553.0, 232.0], [508.0, 235.0]], ('G11', 0.84185356)], [[[90.0, 231.0], [200.0, 229.0], [200.0, 247.0], [90.0, 250.0]], ('FUZHOU', 0.98411864)], [[[347.0, 243.0], [481.0, 239.0], [482.0, 254.0], [347.0, 258.0]], ('身份识别IDNO', 0.9800327)], [[[67.0, 253.0], [170.0, 250.0], [170.0, 267.0], [68.0, 270.0]], ('姓名NAME', 0.9965892)], [[[77.0, 279.0], [262.0, 275.0], [262.0, 294.0], [78.0, 299.0]], ('ZHANGQIWET', 0.92218363)], [[[464.0, 299.0], [576.0, 297.0], [577.0, 314.0], [464.0, 316.0]], ('票号TKTNO', 0.9938587)], [[[103.0, 314.0], [208.0, 313.0], [208.0, 334.0], [104.0, 336.0]], ('张祺伟', 0.945729)], [[[70.0, 345.0], [164.0, 343.0], [165.0, 362.0], [71.0, 365.0]], ('票价FARE', 0.99907106)], [[[347.0, 350.0], [659.0, 349.0], [659.0, 366.0], [348.0, 367.0]], ('ETKT7813699238489/1', 0.97859323)], [[[101.0, 459.0], [829.0, 442.0], [829.0, 462.0], [102.0, 478.0]], ('登机口于起飞前10分钟关闭GATESCLOSE10MINUTESBEFOREDEPARTURETIME', 0.9942007)]]
[[[704.0, 17.0], [820.0, 17.0], [820.0, 57.0], [704.0, 57.0]], ('PASS', 0.9926876)]
[[[158.0, 30.0], [351.0, 26.0], [352.0, 66.0], [159.0, 71.0]], ('登机牌', 0.99803185)]
[[[424.0, 27.0], [653.0, 23.0], [654.0, 56.0], [425.0, 61.0]], ('BOARDING', 0.9355928)]
[[[678.0, 101.0], [740.0, 100.0], [740.0, 117.0], [678.0, 119.0]], ('座位号', 0.9993448)]
[[[490.0, 106.0], [648.0, 102.0], [648.0, 119.0], [491.0, 122.0]], ('序号SERIALNO', 0.9721721)]
[[[397.0, 107.0], [455.0, 105.0], [455.0, 122.0], [397.0, 124.0]], ('CLASS', 0.99292123)]
[[[340.0, 109.0], [384.0, 107.0], [385.0, 122.0], [341.0, 124.0]], ('舱位', 0.95519966)]
[[[749.0, 100.0], [830.0, 97.0], [831.0, 114.0], [750.0, 116.0]], ('SEAT NO', 0.91536474)]
[[[215.0, 111.0], [255.0, 110.0], [256.0, 125.0], [216.0, 127.0]], ('日期', 0.99764645)]
[[[66.0, 114.0], [189.0, 111.0], [190.0, 128.0], [66.0, 131.0]], ('航班FLIGHT', 0.9937089)]
[[[248.0, 110.0], [314.0, 110.0], [314.0, 125.0], [248.0, 125.0]], ('DATE', 0.98909116)]
[[[509.0, 131.0], [566.0, 131.0], [566.0, 156.0], [509.0, 156.0]], ('035', 0.9830131)]
[[[406.0, 135.0], [430.0, 135.0], [430.0, 157.0], [406.0, 157.0]], ('W', 0.99706143)]
[[[84.0, 142.0], [212.0, 139.0], [213.0, 158.0], [84.0, 162.0]], ('MU 2379', 0.9289481)]
[[[211.0, 141.0], [324.0, 139.0], [325.0, 156.0], [211.0, 158.0]], ('03DEG', 0.8540861)]
[[[489.0, 175.0], [572.0, 173.0], [572.0, 193.0], [489.0, 195.0]], ('登机口', 0.9996843)]
[[[568.0, 176.0], [612.0, 176.0], [612.0, 191.0], [568.0, 191.0]], ('GATE', 0.99684405)]
[[[345.0, 177.0], [409.0, 177.0], [409.0, 194.0], [345.0, 194.0]], ('始发地', 0.9996207)]
[[[400.0, 177.0], [466.0, 173.0], [467.0, 191.0], [401.0, 194.0]], ('FROM', 0.9926705)]
[[[68.0, 181.0], [168.0, 178.0], [168.0, 199.0], [69.0, 203.0]], ('目的地TO', 0.98151416)]
[[[680.0, 172.0], [806.0, 166.0], [807.0, 186.0], [681.0, 192.0]], ('登机时间BD1', 0.9584261)]
[[[98.0, 208.0], [168.0, 208.0], [168.0, 227.0], [98.0, 227.0]], ('福州', 0.8741772)]
[[[339.0, 220.0], [474.0, 216.0], [475.0, 235.0], [339.0, 239.0]], ('TAIYUAN', 0.93135375)]
[[[507.0, 217.0], [552.0, 214.0], [553.0, 232.0], [508.0, 235.0]], ('G11', 0.84185356)]
[[[90.0, 231.0], [200.0, 229.0], [200.0, 247.0], [90.0, 250.0]], ('FUZHOU', 0.98411864)]
[[[347.0, 243.0], [481.0, 239.0], [482.0, 254.0], [347.0, 258.0]], ('身份识别IDNO', 0.9800327)]
[[[67.0, 253.0], [170.0, 250.0], [170.0, 267.0], [68.0, 270.0]], ('姓名NAME', 0.9965892)]
[[[77.0, 279.0], [262.0, 275.0], [262.0, 294.0], [78.0, 299.0]], ('ZHANGQIWET', 0.92218363)]
[[[464.0, 299.0], [576.0, 297.0], [577.0, 314.0], [464.0, 316.0]], ('票号TKTNO', 0.9938587)]
[[[103.0, 314.0], [208.0, 313.0], [208.0, 334.0], [104.0, 336.0]], ('张祺伟', 0.945729)]
[[[70.0, 345.0], [164.0, 343.0], [165.0, 362.0], [71.0, 365.0]], ('票价FARE', 0.99907106)]
[[[347.0, 350.0], [659.0, 349.0], [659.0, 366.0], [348.0, 367.0]], ('ETKT7813699238489/1', 0.97859323)]
[[[101.0, 459.0], [829.0, 442.0], [829.0, 462.0], [102.0, 478.0]], ('登机口于起飞前10分钟关闭GATESCLOSE10MINUTESBEFOREDEPARTURETIME', 0.9942007)]Process finished with exit code 0

图片

二、但是识别验证码,就是碎了一地的效果,

"D:\Program Files\Python36\python.exe" D:/nanodet-main/test.py
Namespace(cls_batch_num=6, cls_image_shape='3, 48, 192', cls_model_dir='C:\\Users\\sxj/.paddleocr/2.1/cls', cls_thresh=0, det=True, det_algorithm='DB', det_db_box_thresh=0.5, det_db_thresh=0.3, det_db_unclip_ratio=1.6, det_east_cover_thresh=0.1, det_east_nms_thresh=0.2, det_east_score_thresh=0.8, det_limit_side_len=960, det_limit_type='max', det_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_det_infer', drop_score=0.5, enable_mkldnn=False, gpu_mem=8000, image_dir='', ir_optim=True, label_list=['0', '180'], lang='ch', max_text_length=25, rec=True, rec_algorithm='CRNN', rec_batch_num=6, rec_char_dict_path='./ppocr/utils/ppocr_keys_v1.txt', rec_char_type='ch', rec_image_shape='3, 32, 320', rec_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_rec_infer', use_angle_cls=False, use_dilation=False, use_gpu=False, use_pdserving=False, use_space_char=True, use_tensorrt=False, use_zero_copy_run=False)
[2021/04/17 14:57:31] root WARNING: Since the angle classifier is not initialized, the angle classifier will not be uesd during the forward process
[2021/04/17 14:57:31] root INFO: dt_boxes num : 1, elapse : 0.5834400653839111
[2021/04/17 14:57:31] root INFO: rec_res num  : 1, elapse : 0.03889632225036621
[]Process finished with exit code 0

三、如果图稍微截取的大一点,就可以识别,但是识别的存在错误

"D:\Program Files\Python36\python.exe" D:/nanodet-main/test.py
Namespace(cls_batch_num=6, cls_image_shape='3, 48, 192', cls_model_dir='C:\\Users\\sxj/.paddleocr/2.1/cls', cls_thresh=0.9, det=True, det_algorithm='DB', det_db_box_thresh=0.5, det_db_thresh=0.3, det_db_unclip_ratio=1.6, det_east_cover_thresh=0.1, det_east_nms_thresh=0.2, det_east_score_thresh=0.8, det_limit_side_len=960, det_limit_type='max', det_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_det_infer', drop_score=0.5, enable_mkldnn=False, gpu_mem=8000, image_dir='', ir_optim=True, label_list=['0', '180'], lang='ch', max_text_length=25, rec=True, rec_algorithm='CRNN', rec_batch_num=6, rec_char_dict_path='./ppocr/utils/ppocr_keys_v1.txt', rec_char_type='ch', rec_image_shape='3, 32, 320', rec_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_rec_infer', use_angle_cls=False, use_dilation=False, use_gpu=False, use_pdserving=False, use_space_char=True, use_tensorrt=False, use_zero_copy_run=False)
[2021/04/17 15:50:32] root WARNING: Since the angle classifier is not initialized, the angle classifier will not be uesd during the forward process
[2021/04/17 15:50:32] root INFO: dt_boxes num : 1, elapse : 0.6196780204772949
[2021/04/17 15:50:32] root INFO: rec_res num  : 1, elapse : 0.02003645896911621
[[[[340.0, 269.0], [432.0, 272.0], [432.0, 302.0], [339.0, 299.0]], ('2789', 0.8799714)]]
[[[340.0, 269.0], [432.0, 272.0], [432.0, 302.0], [339.0, 299.0]], ('2789', 0.8799714)]Process finished with exit code 0

四、做一下处理,首先参考了代码,进行修改。Python验证码识别 - 老板丶鱼丸粗面 - 博客园

from PIL import Image
from pytesseract import *
from fnmatch import fnmatch
from queue import Queue
import cv2
import os
def clear_border(img,img_name):'''去除边框'''h, w = img.shape[:2]for y in range(0, w):for x in range(0, h):# if y ==0 or y == w -1 or y == w - 2:if y < 4 or y > w -4:img[x, y] = 255# if x == 0 or x == h - 1 or x == h - 2:if x < 4 or x > h - 4:img[x, y] = 255return imgdef interference_line(img, img_name):'''干扰线降噪'''h, w = img.shape[:2]# !!!opencv矩阵点是反的# img[1,2] 1:图片的高度,2:图片的宽度for y in range(1, w - 1):for x in range(1, h - 1):count = 0if img[x, y - 1] > 245:count = count + 1if img[x, y + 1] > 245:count = count + 1if img[x - 1, y] > 245:count = count + 1if img[x + 1, y] > 245:count = count + 1if count > 2:img[x, y] = 255return imgdef interference_point(img,img_name, x = 0, y = 0):"""点降噪9邻域框,以当前点为中心的田字框,黑点个数:param x::param y::return:"""# todo 判断图片的长宽度下限cur_pixel = img[x,y]# 当前像素点的值height,width = img.shape[:2]for y in range(0, width - 1):for x in range(0, height - 1):if y == 0:  # 第一行if x == 0:  # 左上顶点,4邻域# 中心点旁边3个点sum = int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 2 * 245:img[x, y] = 0elif x == height - 1:  # 右上顶点sum = int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y + 1])if sum <= 2 * 245:img[x, y] = 0else:  # 最上非顶点,6邻域sum = int(img[x - 1, y]) \+ int(img[x - 1, y + 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 3 * 245:img[x, y] = 0elif y == width - 1:  # 最下面一行if x == 0:  # 左下顶点# 中心点旁边3个点sum = int(cur_pixel) \+ int(img[x + 1, y]) \+ int(img[x + 1, y - 1]) \+ int(img[x, y - 1])if sum <= 2 * 245:img[x, y] = 0elif x == height - 1:  # 右下顶点sum = int(cur_pixel) \+ int(img[x, y - 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y - 1])if sum <= 2 * 245:img[x, y] = 0else:  # 最下非顶点,6邻域sum = int(cur_pixel) \+ int(img[x - 1, y]) \+ int(img[x + 1, y]) \+ int(img[x, y - 1]) \+ int(img[x - 1, y - 1]) \+ int(img[x + 1, y - 1])if sum <= 3 * 245:img[x, y] = 0else:  # y不在边界if x == 0:  # 左边非顶点sum = int(img[x, y - 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y - 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 3 * 245:img[x, y] = 0elif x == height - 1:  # 右边非顶点sum = int(img[x, y - 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x - 1, y - 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y + 1])if sum <= 3 * 245:img[x, y] = 0else:  # 具备9领域条件的sum = int(img[x - 1, y - 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y + 1]) \+ int(img[x, y - 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y - 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 4 * 245:img[x, y] = 0return imgdef _get_dynamic_binary_image(filedir, img_name):'''自适应阀值二值化'''img_name =os.path.join(filedir,img_name)print('.....' + img_name)im = cv2.imread(img_name)im = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)th1 = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)return th1def _get_static_binary_image(img, threshold = 140):'''手动二值化'''img = Image.open(img)img = img.convert('L')pixdata = img.load()w, h = img.sizefor y in range(h):for x in range(w):if pixdata[x, y] < threshold:pixdata[x, y] = 0else:pixdata[x, y] = 255return imgdef cfs(im,x_fd,y_fd):'''用队列和集合记录遍历过的像素坐标代替单纯递归以解决cfs访问过深问题'''# print('**********')xaxis=[]yaxis=[]visited =set()q = Queue()q.put((x_fd, y_fd))visited.add((x_fd, y_fd))offsets=[(1, 0), (0, 1), (-1, 0), (0, -1)]#四邻域while not q.empty():x,y=q.get()for xoffset,yoffset in offsets:x_neighbor,y_neighbor = x+xoffset,y+yoffsetif (x_neighbor,y_neighbor) in (visited):continue  # 已经访问过了visited.add((x_neighbor, y_neighbor))try:if im[x_neighbor, y_neighbor] == 0:xaxis.append(x_neighbor)yaxis.append(y_neighbor)q.put((x_neighbor,y_neighbor))except IndexError:pass# print(xaxis)if (len(xaxis) == 0 | len(yaxis) == 0):xmax = x_fd + 1xmin = x_fdymax = y_fd + 1ymin = y_fdelse:xmax = max(xaxis)xmin = min(xaxis)ymax = max(yaxis)ymin = min(yaxis)#ymin,ymax=sort(yaxis)return ymax,ymin,xmax,xmindef detectFgPix(im,xmax):'''搜索区块起点'''h,w = im.shape[:2]for y_fd in range(xmax+1,w):for x_fd in range(h):if im[x_fd,y_fd] == 0:return x_fd,y_fddef CFS(im):'''切割字符位置'''zoneL=[]#各区块长度L列表zoneWB=[]#各区块的X轴[起始,终点]列表zoneHB=[]#各区块的Y轴[起始,终点]列表xmax=0#上一区块结束黑点横坐标,这里是初始化for i in range(10):try:x_fd,y_fd = detectFgPix(im,xmax)# print(y_fd,x_fd)xmax,xmin,ymax,ymin=cfs(im,x_fd,y_fd)L = xmax - xminH = ymax - yminzoneL.append(L)zoneWB.append([xmin,xmax])zoneHB.append([ymin,ymax])except TypeError:return zoneL,zoneWB,zoneHBreturn zoneL,zoneWB,zoneHBdef cutting_img(destdir,im,im_position,img,xoffset = 1,yoffset = 1):filename =  os.path.join(destdir,img.split('.')[0])# 识别出的字符个数im_number = len(im_position[1])# 切割字符img = Image.new('RGB', (100, 100), (0, 0, 0))pre_im_start_y=im_position[2][0][0]- yoffsetpre_im_end_Y =im_position[2][0][1]+ yoffsetfor i in range(im_number):im_start_X = im_position[1][i][0] - xoffsetim_end_X = im_position[1][i][1] + xoffsetim_start_Y = min(im_position[2][i][0] - yoffset,pre_im_start_y)im_end_Y = max(im_position[2][i][1] + yoffset,pre_im_end_Y)#print(im_start_Y," ",im_end_Y," ", im_start_X," ",im_end_X)pre_im_start_y=im_position[2][i][0] - yoffsetpre_im_end_Y=im_position[2][i][1] + yoffsetcropped = im[im_start_Y:im_end_Y, im_start_X:im_end_X]cropped = cv2.resize(cropped, (0, 0), fx=2, fy=2, interpolation=cv2.INTER_CUBIC)cropped = cv2.bitwise_not(cropped)#腐蚀图片进行处理,paddle 检测效果更好cv2.imwrite(filename + str(i) + '.jpg', cropped)img1 = Image.open(filename + str(i) + '.jpg')img.paste(img1, (int(35), int(35), int(35) + img1.size[0], int(35) + img1.size[1]))img.save(filename + str(i) + '.jpg')img1.close()return im_numberdef preprocess(desdir):for item in os.listdir(desdir):img = cv2.imread(os.path.join(desdir,item))hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)erode = cv2.erode(hsv, None, iterations=1)dilate = cv2.dilate(erode, None, iterations=1)dilate = cv2.cvtColor(dilate, cv2.COLOR_HSV2RGB)cv2.imwrite(os.path.join(desdir,item), dilate)def main():filedir = r'F:\tt\source'destdir=r'F:\tt\destination'for file in os.listdir(filedir):if fnmatch(file, '*.png') or fnmatch(file, '*.jpg'):img_name = file# 自适应阈值二值化im = _get_dynamic_binary_image(filedir, img_name)# 去除边框im = clear_border(im,img_name)# 对图片进行干扰线降噪im = interference_line(im,img_name)# 对图片进行点降噪im = interference_point(im,img_name)# 切割的位置im_position = CFS(im)maxL = max(im_position[0])minL = min(im_position[0])# 如果有粘连字符,如果一个字符的长度过长就认为是粘连字符,并从中间进行切割if(maxL > minL + minL * 0.7):maxL_index = im_position[0].index(maxL)minL_index = im_position[0].index(minL)# 设置字符的宽度im_position[0][maxL_index] = maxL // 2im_position[0].insert(maxL_index + 1, maxL // 2)# 设置字符X轴[起始,终点]位置im_position[1][maxL_index][1] = im_position[1][maxL_index][0] + maxL // 2im_position[1].insert(maxL_index + 1, [im_position[1][maxL_index][1] + 1, im_position[1][maxL_index][1] + 1 + maxL // 2])# 设置字符的Y轴[起始,终点]位置im_position[2].insert(maxL_index + 1, im_position[2][maxL_index])# 切割字符,要想切得好就得配置参数,通常 1 or 2 就可以cutting_img_num=cutting_img(destdir,im,im_position,img_name,1,1)preprocess(destdir)print('切图:%s' % cutting_img_num)if __name__ == '__main__':main()

处理结果,

               

检测效果仍然很差 运算符检测不出来~~~~mmp,怎么办 怎么办?真的懒得去训练。。。我在思考 paddleOCR如果是蓝底白字是不是效果会变好呢?试试吧~~~

from PIL import Image
import numpy as np
from fnmatch import fnmatch
from queue import Queue
import cv2
import os
from paddleocr import PaddleOCR, draw_ocrdef clear_border(img,img_name):'''去除边框'''h, w = img.shape[:2]for y in range(0, w):for x in range(0, h):# if y ==0 or y == w -1 or y == w - 2:if y < 4 or y > w -4:img[x, y] = 255# if x == 0 or x == h - 1 or x == h - 2:if x < 4 or x > h - 4:img[x, y] = 255return imgdef interference_line(img, img_name):'''干扰线降噪'''h, w = img.shape[:2]# !!!opencv矩阵点是反的# img[1,2] 1:图片的高度,2:图片的宽度for y in range(1, w - 1):for x in range(1, h - 1):count = 0if img[x, y - 1] > 245:count = count + 1if img[x, y + 1] > 245:count = count + 1if img[x - 1, y] > 245:count = count + 1if img[x + 1, y] > 245:count = count + 1if count > 2:img[x, y] = 255return imgdef interference_point(img,img_name, x = 0, y = 0):"""点降噪9邻域框,以当前点为中心的田字框,黑点个数:param x::param y::return:"""# todo 判断图片的长宽度下限cur_pixel = img[x,y]# 当前像素点的值height,width = img.shape[:2]for y in range(0, width - 1):for x in range(0, height - 1):if y == 0:  # 第一行if x == 0:  # 左上顶点,4邻域# 中心点旁边3个点sum = int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 2 * 245:img[x, y] = 0elif x == height - 1:  # 右上顶点sum = int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y + 1])if sum <= 2 * 245:img[x, y] = 0else:  # 最上非顶点,6邻域sum = int(img[x - 1, y]) \+ int(img[x - 1, y + 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 3 * 245:img[x, y] = 0elif y == width - 1:  # 最下面一行if x == 0:  # 左下顶点# 中心点旁边3个点sum = int(cur_pixel) \+ int(img[x + 1, y]) \+ int(img[x + 1, y - 1]) \+ int(img[x, y - 1])if sum <= 2 * 245:img[x, y] = 0elif x == height - 1:  # 右下顶点sum = int(cur_pixel) \+ int(img[x, y - 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y - 1])if sum <= 2 * 245:img[x, y] = 0else:  # 最下非顶点,6邻域sum = int(cur_pixel) \+ int(img[x - 1, y]) \+ int(img[x + 1, y]) \+ int(img[x, y - 1]) \+ int(img[x - 1, y - 1]) \+ int(img[x + 1, y - 1])if sum <= 3 * 245:img[x, y] = 0else:  # y不在边界if x == 0:  # 左边非顶点sum = int(img[x, y - 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y - 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 3 * 245:img[x, y] = 0elif x == height - 1:  # 右边非顶点sum = int(img[x, y - 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x - 1, y - 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y + 1])if sum <= 3 * 245:img[x, y] = 0else:  # 具备9领域条件的sum = int(img[x - 1, y - 1]) \+ int(img[x - 1, y]) \+ int(img[x - 1, y + 1]) \+ int(img[x, y - 1]) \+ int(cur_pixel) \+ int(img[x, y + 1]) \+ int(img[x + 1, y - 1]) \+ int(img[x + 1, y]) \+ int(img[x + 1, y + 1])if sum <= 4 * 245:img[x, y] = 0return imgdef _get_dynamic_binary_image(filedir, img_name):'''自适应阀值二值化'''img_name =os.path.join(filedir,img_name)print('.....' + img_name)im = cv2.imread(img_name)im = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)th1 = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)return th1def _get_static_binary_image(img, threshold = 140):'''手动二值化'''img = Image.open(img)img = img.convert('L')pixdata = img.load()w, h = img.sizefor y in range(h):for x in range(w):if pixdata[x, y] < threshold:pixdata[x, y] = 0else:pixdata[x, y] = 255return imgdef cfs(im,x_fd,y_fd):'''用队列和集合记录遍历过的像素坐标代替单纯递归以解决cfs访问过深问题'''# print('**********')xaxis=[]yaxis=[]visited =set()q = Queue()q.put((x_fd, y_fd))visited.add((x_fd, y_fd))offsets=[(1, 0), (0, 1), (-1, 0), (0, -1)]#四邻域while not q.empty():x,y=q.get()for xoffset,yoffset in offsets:x_neighbor,y_neighbor = x+xoffset,y+yoffsetif (x_neighbor,y_neighbor) in (visited):continue  # 已经访问过了visited.add((x_neighbor, y_neighbor))try:if im[x_neighbor, y_neighbor] == 0:xaxis.append(x_neighbor)yaxis.append(y_neighbor)q.put((x_neighbor,y_neighbor))except IndexError:pass# print(xaxis)if (len(xaxis) == 0 | len(yaxis) == 0):xmax = x_fd + 1xmin = x_fdymax = y_fd + 1ymin = y_fdelse:xmax = max(xaxis)xmin = min(xaxis)ymax = max(yaxis)ymin = min(yaxis)#ymin,ymax=sort(yaxis)return ymax,ymin,xmax,xmindef detectFgPix(im,xmax):'''搜索区块起点'''h,w = im.shape[:2]for y_fd in range(xmax+1,w):for x_fd in range(h):if im[x_fd,y_fd] == 0:return x_fd,y_fddef CFS(im):'''切割字符位置'''zoneL=[]#各区块长度L列表zoneWB=[]#各区块的X轴[起始,终点]列表zoneHB=[]#各区块的Y轴[起始,终点]列表xmax=0#上一区块结束黑点横坐标,这里是初始化for i in range(10):try:x_fd,y_fd = detectFgPix(im,xmax)# print(y_fd,x_fd)xmax,xmin,ymax,ymin=cfs(im,x_fd,y_fd)L = xmax - xminH = ymax - yminzoneL.append(L)zoneWB.append([xmin,xmax])zoneHB.append([ymin,ymax])except TypeError:return zoneL,zoneWB,zoneHBreturn zoneL,zoneWB,zoneHBdef cutting_img(destdir,im,im_position,img,xoffset = 1,yoffset = 1):filename =  os.path.join(destdir,img.split('.')[0])# 识别出的字符个数im_number = len(im_position[1])# 切割字符img = Image.new('RGB', (100, 100), (0, 0, 0))pre_im_start_y=im_position[2][0][0]- yoffsetpre_im_end_Y =im_position[2][0][1]+ yoffsetfor i in range(im_number):im_start_X = im_position[1][i][0] - xoffsetim_end_X = im_position[1][i][1] + xoffsetim_start_Y = min(im_position[2][i][0] - yoffset,pre_im_start_y)im_end_Y = max(im_position[2][i][1] + yoffset,pre_im_end_Y)#print(im_start_Y," ",im_end_Y," ", im_start_X," ",im_end_X)pre_im_start_y=im_position[2][i][0] - yoffsetpre_im_end_Y=im_position[2][i][1] + yoffsetcropped = im[im_start_Y:im_end_Y, im_start_X:im_end_X]cropped = cv2.resize(cropped, (0, 0), fx=2, fy=2, interpolation=cv2.INTER_CUBIC)cropped = cv2.bitwise_not(cropped)#cropped = cv2.medianBlur(cropped, 5)# 中值滤波#腐蚀图片进行处理,paddle 检测效果更好cv2.imwrite(filename + str(i) + '.jpg', cropped)img1 = Image.open(filename + str(i) + '.jpg')img.paste(img1, (int(35), int(35), int(35) + img1.size[0], int(35) + img1.size[1]))img.save(filename + str(i) + '.jpg')img1.close()return im_number#R:5;G:39;B:175
def image2label(dilate):data = np.array(dilate, dtype='int32')# 修改B通道的像素值for i in range(data[:, :, 0].shape[0]):for j in range(data[:, :, 0].shape[1]):if data[:, :, 0][i][j] > 155:data[:, :, 0][i][j] = 255else:data[:, :, 0][i][j] = 175# 修改G通道的像素值for i in range(data[:, :, 1].shape[0]):for j in range(data[:, :, 1].shape[1]):if data[:, :, 1][i][j] > 155:data[:, :, 1][i][j] = 255else:data[:, :, 1][i][j] = 39# 修改R通道的像素值for i in range(data[:, :, 2].shape[0]):for j in range(data[:, :, 2].shape[1]):if data[:, :, 2][i][j] > 155:data[:, :, 2][i][j] = 255else:data[:, :, 2][i][j] = 5return datadef preprocess(desdir):for item in os.listdir(desdir):img = cv2.imread(os.path.join(desdir,item))hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)erode = cv2.erode(hsv, None, iterations=1)#开运算dilate = cv2.dilate(erode, None, iterations=1)#闭运算dilate = cv2.cvtColor(dilate, cv2.COLOR_HSV2RGB)dilate=image2label(dilate)cv2.imwrite(os.path.join(desdir,item), dilate)def main():filedir = r'F:\tt\source'destdir=r'F:\tt\destination'ocr = PaddleOCR(use_angle_cls=True, cls_thresh=0, det_east_score_thresh=0, use_gpu=False,det_model_dir=r'F:\tt\ch_ppocr_mobile_v2.0_det_infer',rec_model_dir=r'F:\tt\ch_ppocr_mobile_v2.0_rec_infer')  # need to run only once to download and load model into memoryfor file in os.listdir(filedir):if fnmatch(file, '*.png') or fnmatch(file, '*.jpg'):img_name = file# 自适应阈值二值化im = _get_dynamic_binary_image(filedir, img_name)# 去除边框im = clear_border(im,img_name)# 对图片进行干扰线降噪im = interference_line(im,img_name)# 对图片进行点降噪im = interference_point(im,img_name)# 切割的位置im_position = CFS(im)maxL = max(im_position[0])minL = min(im_position[0])# 如果有粘连字符,如果一个字符的长度过长就认为是粘连字符,并从中间进行切割if(maxL > minL + minL * 0.7):maxL_index = im_position[0].index(maxL)minL_index = im_position[0].index(minL)# 设置字符的宽度im_position[0][maxL_index] = maxL // 2im_position[0].insert(maxL_index + 1, maxL // 2)# 设置字符X轴[起始,终点]位置im_position[1][maxL_index][1] = im_position[1][maxL_index][0] + maxL // 2im_position[1].insert(maxL_index + 1, [im_position[1][maxL_index][1] + 1, im_position[1][maxL_index][1] + 1 + maxL // 2])# 设置字符的Y轴[起始,终点]位置im_position[2].insert(maxL_index + 1, im_position[2][maxL_index])# 切割字符,要想切得好就得配置参数,通常 1 or 2 就可以cutting_img_num=cutting_img(destdir,im,im_position,img_name,1,1)preprocess(destdir)for item in os.listdir(destdir):filename=os.path.join(destdir,item)result = ocr.ocr(filename, cls=True)for line in result:print(line[-1][0])if __name__ == '__main__':main()

产生的图片为

        

果不其然 可以识别了 符号和数字了 ~~

0

"D:\Program Files\Python36\python.exe" D:/nanodet-main/test.py
Namespace(cls_batch_num=6, cls_image_shape='3, 48, 192', cls_model_dir='C:\\Users\\sxj/.paddleocr/2.1/cls', cls_thresh=0, det=True, det_algorithm='DB', det_db_box_thresh=0.5, det_db_thresh=0.3, det_db_unclip_ratio=1.6, det_east_cover_thresh=0.1, det_east_nms_thresh=0.2, det_east_score_thresh=0, det_limit_side_len=960, det_limit_type='max', det_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_det_infer', drop_score=0.5, enable_mkldnn=False, gpu_mem=8000, image_dir='', ir_optim=True, label_list=['0', '180'], lang='ch', max_text_length=25, rec=True, rec_algorithm='CRNN', rec_batch_num=6, rec_char_dict_path='./ppocr/utils/ppocr_keys_v1.txt', rec_char_type='ch', rec_image_shape='3, 32, 320', rec_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_rec_infer', use_angle_cls=True, use_dilation=False, use_gpu=False, use_pdserving=False, use_space_char=True, use_tensorrt=False, use_zero_copy_run=False)
[2021/04/18 09:40:36] root INFO: dt_boxes num : 1, elapse : 0.4519474506378174
[2021/04/18 09:40:36] root INFO: cls num  : 1, elapse : 0.01795220375061035
[2021/04/18 09:40:36] root INFO: rec_res num  : 1, elapse : 0.006981849670410156
[[[[39.0, 38.0], [71.0, 40.0], [69.0, 79.0], [37.0, 77.0]], ('0', 0.7298239)]]
[[[39.0, 38.0], [71.0, 40.0], [69.0, 79.0], [37.0, 77.0]], ('0', 0.7298239)]Process finished with exit code 0

+

"D:\Program Files\Python36\python.exe" D:/nanodet-main/test.py
Namespace(cls_batch_num=6, cls_image_shape='3, 48, 192', cls_model_dir='C:\\Users\\sxj/.paddleocr/2.1/cls', cls_thresh=0, det=True, det_algorithm='DB', det_db_box_thresh=0.5, det_db_thresh=0.3, det_db_unclip_ratio=1.6, det_east_cover_thresh=0.1, det_east_nms_thresh=0.2, det_east_score_thresh=0, det_limit_side_len=960, det_limit_type='max', det_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_det_infer', drop_score=0.5, enable_mkldnn=False, gpu_mem=8000, image_dir='', ir_optim=True, label_list=['0', '180'], lang='ch', max_text_length=25, rec=True, rec_algorithm='CRNN', rec_batch_num=6, rec_char_dict_path='./ppocr/utils/ppocr_keys_v1.txt', rec_char_type='ch', rec_image_shape='3, 32, 320', rec_model_dir='F:\\tt\\ch_ppocr_mobile_v2.0_rec_infer', use_angle_cls=True, use_dilation=False, use_gpu=False, use_pdserving=False, use_space_char=True, use_tensorrt=False, use_zero_copy_run=False)
[2021/04/18 09:37:24] root INFO: dt_boxes num : 1, elapse : 0.42177367210388184
[2021/04/18 09:37:24] root INFO: cls num  : 1, elapse : 0.015623092651367188
[2021/04/18 09:37:24] root INFO: rec_res num  : 1, elapse : 0.015619516372680664
[[[[37.0, 46.0], [71.0, 47.0], [70.0, 78.0], [36.0, 77.0]], ('十', 0.72421145)]]
[[[37.0, 46.0], [71.0, 47.0], [70.0, 78.0], [36.0, 77.0]], ('十', 0.72421145)]Process finished with exit code 0

但是 仍然存在一些识别错误的问题~~~  心塞, 进步调查原因,难道是没有检测到吗??? 换了服务器版本的模型也不可以。。。看样子无药可救了

           

六、首先先爬虫点数据集吧~,

(1)查看一下自己的google浏览器的版本号

    

然后下载https://sites.google.com/a/chromium.org/chromedriver/downloads ,放入F:\\EXEDIR\\

因为下载google刷新网页驱动需要翻墙下载,请自行下载,let's go 爬验证码图片吧~~

import urllib.request
import time
import os
from selenium import webdriverdef refresh(exedir,url,second):driver = webdriver.Chrome(os.path.join(exedir,"chromedriver.exe"))driver.get(url)time.sleep(second)driver.refresh()driver.close()def getHtml(url):html = urllib.request.urlopen(url).read()return htmldef saveHtml(destdir, file_content,i):# 注意windows文件命名的禁用符,比如 /with open(os.path.join(destdir,".".join([str(i),"jpg"])), "wb") as f:# 写文件用bytes而不是str,所以要转码f.write(file_content)
destdir="F:\\TESTIMG"
exedir="F:\\EXEDIR"
second=1
num=10000
for i in range(num):aurl = "https://******.cn/m/tmri/captcha/math?nocache=1618204715553"html = getHtml(aurl)saveHtml(destdir, html,i)refresh(exedir,aurl,second)
print("下载成功")

下载成功的样子大概这样,有500张吧

直接标注原图吧,不分割单个字符标注在训练了(也可以简单的处理一下)

from fnmatch import fnmatch
import cv2
import osdef _get_dynamic_binary_image(img_name):print('.....' + img_name)im = cv2.imread(img_name)frame = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)height = frame.shape[0]weight = frame.shape[1]print("weight : %s, height : %s" % (weight, height))for row in range(height):  # 遍历高for col in range(weight):  # 遍历宽pv = frame[row, col]# print(pv)if pv > 100:frame[row, col] = 255return frame
def clear_border(img):'''去除边框'''h, w = img.shape[:2]for y in range(0, w):for x in range(0, h):# if y ==0 or y == w -1 or y == w - 2:if y < 4 or y > w -4:img[x, y] = 255# if x == 0 or x == h - 1 or x == h - 2:if x < 4 or x > h - 4:img[x, y] = 255return imgdef main():filedir = r'F:\tt\source'destdir = r'F:\tt\destination'for file in os.listdir(filedir):if fnmatch(file, '*.png') or fnmatch(file, '*.jpg'):img_name = os.path.join(filedir, file)img = _get_dynamic_binary_image(img_name)img_out=clear_border(img)cv2.imwrite(os.path.join(destdir,file),img_out)if __name__ == '__main__':main()

训练自己的paddleOCR数据,这里按照灰度图进行训练吧,先下载源码进行数据标注,标注仍然以切分数字图片和运算符为主,色彩随机设置,以丰富数据集~(注:本项目的某一部分仅需要这样的功能,其它需求暂不考虑)

ubuntu@ubuntu:~$ git clone https://github.com/PaddlePaddle/PaddleOCR
ubuntu@ubuntu:~$ cd PaddleOCR
ubuntu@ubuntu:~$ pip install -r requirements.txt
ubuntu@ubuntu:~PaddleOCR$ cd PPOCRLabel
ubuntu@ubuntu:~PaddleOCR/PPOCRLabel$ python PPOCRLabel.py --lang ch

未完待续

25、使用Baidu的paddle自动进行验证码的识别、并计算验证码的数值相关推荐

  1. 第八部分 验证码的识别(极验验证码)

    前言: 验证码是众多网站采取的反爬措施.验证码的花样也很多,主要有下面这几种类验证码: 图形验证码:数字.英文字母.混淆曲线组合成的验证码. 行为验证码:识别文字,点击与文字相符的图片验证码. 交互式 ...

  2. python识别12306验证码_Python 识别12306图片验证码物品的实现示例

    1.PIL介绍以及图片分割 Python 3 安装:  pip3 install Pillow 1.1 image 模块 Image模块是在Python PIL图像处理中常见的模块,主要是用于对这个图 ...

  3. 验证码的识别(极验验证码)

    本文介绍了几种常见的验证码类型以及它们的识别方法,包括图形验证码.极验滑动验证码.点触验证码和微博宫格验证码等.其中,针对图形验证码的识别方法是使用OCR技术,并且需要安装tesserocr库.我们可 ...

  4. java 验证码图片识别_JavaSE图像验证码简单识别程序详解

    本文为大家分享了JavaSE图像验证码简单识别程序,供大家参考,具体内容如下 首先你应该对图片进行样本采集,然后将样本进行灰度处理,也就是变成黑白两色. 然后你就可以使用该类,对目标文件进行分析.具体 ...

  5. android 验证码图片识别_图片验证码的识别技术

    这里有最简单的一类验证码:他们有固定的背景颜色,相同的字符颜色和字体,字符的坐标位置也是固定的. 对于这类验证码,我们只需要对每个数字进行采样,建立标准库,然后应用的时候一一对照标准库,就可以轻易做到 ...

  6. python实现网站的自动登录(selenium实现,带验证码识别)

    python实现网站自动登录(selenium实现,带验证码识别) 一.前言 这是鄙人写的第一篇博客,旨在总结一下近期所学,本文通过selenium工具实现工作所用网站的自动登录,下图为网站登录界面. ...

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

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

  8. Python网络爬虫开发实战,微博宫格验证码的识别

    本节我们来介绍一下新浪微博宫格验证码的识别,此验证码是一种新型交互式验证码,每个宫格之间会有一条指示连线,指示了我们应该的滑动轨迹,我们需要按照滑动轨迹依次从起始宫格一直滑动到终止宫格才可以完成验证, ...

  9. Class 18 - 1 图形验证码的识别

    一.图形验证码的识别 先将验证码的图片保存到本. 打开开发者工具,找到验证码元素.验证码元素是一张图片,src 属性是 CheckCode.aspx.打开链接 http://my.cnki.net/e ...

最新文章

  1. ASP.NET 5系列教程 (二):Hello World
  2. Flutter retrofit:only “package“ and “asset“ schemes supported
  3. 判断一个IP是否归属于中国
  4. java mousepress_Java线程原语弃用
  5. 2017韩老师计算机网络,2017年计算机等考三级网络技术辅导:计算机网络拓扑结构...
  6. linux ls -l 详解
  7. 操作系统(5) 并发控制(1)线程的互斥
  8. vlookup+match高亮显示行
  9. 清华姚班毕业生开发新特效编程语言,99行代码实现《冰雪奇缘》,网友:大神碉堡!创世的快乐...
  10. vray5.1 for sketchup 安装教程
  11. 蝴蝶蓝暂排第一:第四届橙瓜网络文学奖20年十佳游戏大神
  12. Excel随机 除法 打印版下载
  13. JavaScript【狂神笔记】
  14. 浅谈Java设计之——Java初始化数组(List/Map)时为何要空数组而不是null
  15. HTTP抓包神器---Fiddler
  16. QMI8658 - 姿态传感器学习笔记 - Ⅱ
  17. python 10行代码生成词云图片(基础词云、形状词云)
  18. 【顺序表】13 顺序表ADT模板设计及简单应用:将顺序表中前 m 个元素和后 n 个元素进行互换
  19. bison版本问题导致编译报错:‘parse.error‘ is not used
  20. 微小宝公众号排行榜_8月 | 广东高校团学系统微信公众号影响力排行榜

热门文章

  1. IT行业吸引人的十大原因:高薪的诱惑+成就感
  2. python11.23
  3. Squoosh在线无损图片压缩工具中文版,JPG/webP/PNG/互转
  4. 关于互相帮忙投票的微信群、微信刷投票群、微信投票刷票群的详情介绍
  5. 虚拟麦克风音频输入_塑电竞强音 为专业而声 全新职业级罗技G PRO X游戏耳机麦克风震撼上市...
  6. 使用OC实现单链表:创建、删除、插入、查询、遍历、反转、合并、判断相交、求成环入口...
  7. F2FS nat entry涉及的数据结构(linux 5.18.11)
  8. 快捷给UE4项目改名
  9. 网络游戏架构与微服务架构简单对比
  10. 首次使用Neptune3000海底静力触探CPT记录