Просмотр исходного кода

1. 新增验证码类型判断
2. 各验证码处理优化
3. 忽略dev

fangjiasheng 1 год назад
Родитель
Сommit
0917978c59
41 измененных файлов с 9940 добавлено и 159 удалено
  1. 1 0
      .gitignore
  2. 102 0
      captcha_classify/cac_interface.py
  3. 5649 0
      captcha_classify/chinese_simple_5649.txt
  4. 47 0
      captcha_classify/inference_classify.py
  5. 54 0
      captcha_classify/model.py
  6. BIN
      captcha_classify/models/e151-acc0.81-classify.h5
  7. 15 5
      captcha_flask_server.py
  8. 146 0
      captcha_server/captcha_flask_server.py
  9. 1 0
      captcha_server/chinese_characters.txt
  10. 195 0
      captcha_server/predict_model.py
  11. 2 0
      captcha_server/start.sh
  12. 16 2
      chinese_equation_denoise/ced_interface.py
  13. 3 2
      chinese_equation_denoise/inference_equation_denoise.py
  14. 1 0
      chinese_equation_recognize/cer_interface.py
  15. 101 0
      chinese_equation_recognize/cer_interface_torch.py
  16. 36 0
      chinese_equation_recognize/equation_torch.txt
  17. 1 1
      chinese_equation_recognize/inference_equation.py
  18. 134 0
      chinese_equation_recognize/inference_equation_torch.py
  19. 5 1
      chinese_equation_recognize/model.py
  20. 46 0
      chinese_equation_recognize/model_torch.py
  21. BIN
      chinese_equation_recognize/models/equation6_model_acc-0.853.pth
  22. 141 0
      chinese_equation_recognize/pre_process_torch.py
  23. 57 17
      dev/click_captcha/inference_char.py
  24. 221 0
      dev/click_captcha/inference_equation.py
  25. 51 0
      dev/click_captcha/inference_equation_denoise.py
  26. 2 2
      dev/click_captcha/inference_yolo_char.py
  27. 514 3
      dev/click_captcha/loss.py
  28. 557 4
      dev/click_captcha/model.py
  29. 28 0
      dev/click_captcha/model_260.py
  30. 1321 66
      dev/click_captcha/pre_process.py
  31. 84 0
      dev/click_captcha/train_char.py
  32. 81 0
      dev/click_captcha/train_equation.py
  33. 60 0
      dev/click_captcha/train_equation_denoise.py
  34. 1 0
      dev/click_captcha/train_yolo_char.py
  35. 10 9
      dev/click_captcha/train_yolo_char_260.py
  36. 108 19
      interface/captcha_interface.py
  37. 3 1
      interface/start.sh
  38. 3 1
      interface/stop.sh
  39. 4 1
      puzzle_detect/inference_yolo_puzzle.py
  40. 78 22
      puzzle_detect/pzd_interface.py
  41. 61 3
      utils.py

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/dev/

+ 102 - 0
captcha_classify/cac_interface.py

@@ -0,0 +1,102 @@
+import base64
+import json
+import logging
+import os
+import sys
+import time
+import traceback
+from glob import glob
+import cv2
+import numpy as np
+os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
+import tensorflow as tf
+from flask import Flask, request
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
+from captcha_classify.inference_classify import classify
+from captcha_classify.model import cnn_net_tiny
+from utils import pil_resize, np2bytes, request_post, bytes2np, base64_decode, image_to_str, str_to_image
+
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+tf.compat.v1.disable_eager_execution()
+sess = tf.compat.v1.Session(graph=tf.Graph())
+
+package_dir = os.path.abspath(os.path.dirname(__file__))
+model_path = package_dir + "/models/e262-acc0.81-classify.h5"
+image_shape = (128, 128, 1)
+class_num = 3
+
+# 接口配置
+app = Flask(__name__)
+
+
+@app.route('/cac', methods=['POST'])
+def cac():
+    start_time = time.time()
+    logging.info("into cac_interface cac")
+    try:
+        # 接收网络数据
+        if not request.form:
+            logging.info("cac no data!")
+            return json.dumps({"data": "", "success": 0})
+        data = request.form.get("data")
+        logging.info("cac_interface get data time" + str(time.time()-start_time))
+
+        # 加载模型
+        cac_model = globals().get("global_cac_model")
+        if cac_model is None:
+            print("=========== init cac model ===========")
+            cac_model = CacModels().get_model()
+            globals().update({"global_cac_model": cac_model})
+
+        # 数据转换
+        data = base64_decode(data)
+        image_np = bytes2np(data)
+        # 预测
+        result = classify(image_np, cac_model, sess)
+        logging.info('cac result ' + str(result))
+        if result is None:
+            return json.dumps({"data": "", "success": 0})
+        return json.dumps({"data": result, "success": 1})
+    except:
+        traceback.print_exc()
+        return json.dumps({"data": "", "success": 0})
+    finally:
+        logging.info("cac interface finish time " + str(time.time()-start_time))
+
+
+class CacModels:
+    def __init__(self):
+        with sess.as_default():
+            with sess.graph.as_default():
+                self.model = cnn_net_tiny(input_shape=image_shape, output_shape=class_num)
+                self.model.load_weights(model_path)
+
+    def get_model(self):
+        return self.model
+
+
+def test_cac_model(from_remote=True):
+    paths = glob("D:/Project/captcha/data/chinese/1.jpg")
+    for file_path in paths:
+        img_np = cv2.imread(file_path)
+        h, w = img_np.shape[:2]
+        file_bytes = np2bytes(img_np)
+        file_base64 = base64.b64encode(file_bytes)
+
+        if from_remote:
+            file_json = {"data": file_base64}
+            # _url = "http://192.168.2.102:17061/cac"
+            _url = "http://127.0.0.1:17062/cac"
+            result = json.loads(request_post(_url, file_json))
+            if result.get("success"):
+                result = int(result.get("data"))
+                cv2.imshow("img_np", img_np)
+                print("classify result", result)
+                cv2.waitKey(0)
+            else:
+                print("failed!")
+
+
+if __name__ == "__main__":
+    # app.run(host='127.0.0.1', port=17062, debug=False)
+    test_cac_model()

+ 5649 - 0
captcha_classify/chinese_simple_5649.txt

@@ -0,0 +1,5649 @@
+幔
+绂
+笋
+冼
+承
+霰
+翼
+骏
+堂
+扦
+饷
+存
+俗
+邕
+登
+贡
+祉
+鳗
+粑
+轶
+悴
+赳
+眸
+犬
+壤
+磋
+见
+啤
+甩
+尺
+会
+槲
+舸
+踱
+知
+卓
+扮
+昀
+辕
+娴
+喵
+钺
+鼠
+枱
+嗅
+堵
+错
+噎
+厉
+冉
+粹
+葳
+信
+勿
+噜
+澳
+浙
+萘
+迁
+劲
+佑
+亿
+扈
+撑
+锹
+嶂
+咸
+倦
+尴
+戋
+煞
+霆
+延
+雎
+琊
+梓
+铬
+例
+婧
+猜
+汤
+喱
+舒
+亩
+铉
+驻
+疼
+贬
+坯
+遵
+茹
+螃
+穷
+哦
+灶
+仕
+祼
+篑
+鹿
+瑾
+挂
+惜
+杏
+略
+备
+獒
+液
+炼
+珠
+惋
+玻
+棍
+浩
+魁
+跑
+葡
+雷
+纳
+窒
+栩
+逵
+胫
+斛
+肾
+惫
+翁
+挑
+纯
+范
+祎
+润
+惯
+禾
+幢
+漓
+症
+逝
+螈
+回
+艾
+凋
+逭
+沐
+挲
+茫
+砀
+婷
+氧
+羙
+踢
+缘
+扳
+肃
+獗
+嵘
+禹
+肩
+管
+穆
+淖
+碍
+酸
+驴
+摞
+律
+苡
+腑
+歇
+纪
+购
+驯
+觐
+逞
+蹂
+靓
+髌
+抹
+纣
+钯
+酷
+况
+荪
+舵
+李
+砰
+弹
+瘫
+陨
+檀
+胸
+崎
+篁
+邯
+谜
+轩
+歙
+商
+鸾
+昴
+乃
+颍
+孺
+馀
+签
+业
+椰
+崃
+坶
+蹋
+串
+颤
+储
+弄
+潸
+迩
+棽
+峩
+愣
+案
+谊
+渍
+邸
+昭
+唢
+多
+迅
+锏
+埂
+触
+綦
+瞒
+寂
+瞑
+谩
+佼
+椴
+癔
+拧
+钣
+锉
+揍
+届
+碑
+罢
+驰
+待
+止
+勐
+暄
+谄
+咧
+刮
+甯
+簸
+浔
+蔸
+溏
+伍
+禁
+碲
+膜
+请
+衩
+亏
+晳
+浒
+夔
+瀹
+帝
+撩
+抚
+播
+劳
+肓
+榛
+解
+咳
+授
+呗
+蜍
+烦
+爬
+浮
+芪
+灵
+媄
+肆
+众
+蠕
+线
+胍
+岌
+菇
+耄
+蓟
+贻
+剿
+怯
+椋
+牡
+颂
+泱
+糙
+奖
+鲱
+孽
+囡
+努
+盂
+鲢
+拜
+捕
+皑
+崩
+粳
+证
+昱
+臭
+针
+翎
+体
+录
+蜻
+凭
+卖
+琉
+侩
+铕
+蠄
+盔
+卵
+垦
+睾
+鹌
+窗
+圻
+哇
+岗
+徒
+噁
+薄
+展
+敬
+险
+佶
+斤
+乌
+泥
+奢
+聊
+顔
+荨
+怕
+比
+俑
+霜
+掩
+臃
+肖
+処
+滏
+鸬
+碌
+霎
+婴
+蒋
+诚
+屁
+恸
+已
+妊
+泻
+诲
+亓
+衅
+唁
+翦
+桎
+侗
+居
+煌
+悔
+胚
+亍
+酐
+荐
+看
+稞
+爱
+黏
+昶
+祥
+鎏
+碶
+炊
+窟
+铰
+隗
+熹
+颞
+犊
+巴
+蕹
+婿
+煖
+麻
+凝
+蕙
+胧
+曹
+巍
+鹗
+咕
+焯
+挽
+西
+透
+蕲
+钿
+苎
+坟
+秧
+凳
+峭
+悍
+浆
+娣
+婚
+栉
+耆
+偏
+廖
+匹
+谧
+粥
+货
+阴
+牙
+腊
+泓
+楽
+楝
+剩
+衷
+秀
+铞
+肯
+骐
+璧
+久
+贪
+柠
+褰
+掸
+衾
+牲
+顶
+屋
+宵
+覌
+断
+曦
+恺
+溇
+踪
+缬
+闪
+篆
+廓
+洽
+叱
+诸
+鲳
+毕
+葱
+叙
+锛
+谗
+们
+臀
+街
+扶
+埗
+飚
+基
+女
+钱
+踏
+桡
+贮
+祭
+徵
+垃
+呢
+荃
+荧
+啦
+乘
+郏
+陲
+拯
+班
+鉴
+淞
+私
+杩
+酝
+佚
+啰
+莹
+粧
+臊
+噬
+乾
+稼
+耳
+词
+施
+陈
+闱
+煜
+沌
+师
+壹
+殖
+庭
+苋
+杜
+言
+勤
+遴
+纾
+丘
+坠
+逮
+啸
+啄
+激
+昌
+开
+悼
+宝
+楦
+观
+败
+岱
+枕
+姜
+三
+殻
+盲
+凰
+濑
+冬
+淦
+资
+陀
+昵
+据
+坪
+蓉
+虢
+么
+吖
+吠
+谬
+狍
+鞣
+寞
+铭
+蹙
+稚
+祝
+赘
+瞠
+鎻
+无
+贼
+嗲
+千
+掠
+颓
+狗
+蚂
+瓷
+赅
+朕
+骊
+愕
+鸿
+芗
+螟
+适
+哌
+刿
+际
+酬
+芋
+熟
+臆
+诫
+粢
+罔
+栽
+玫
+糟
+哧
+琼
+应
+弋
+漆
+烹
+抖
+碱
+喆
+唳
+氽
+政
+嘛
+镖
+岢
+涿
+涨
+觑
+雕
+隍
+击
+治
+含
+炙
+穵
+嫖
+租
+塍
+挠
+蹈
+钏
+撷
+仡
+异
+衫
+淤
+咂
+做
+林
+砷
+债
+渚
+侨
+淌
+揉
+鹳
+书
+饯
+干
+胎
+蝎
+彩
+旸
+穂
+今
+裁
+裳
+寮
+坑
+踉
+般
+郊
+藩
+翳
+传
+恶
+若
+烩
+疖
+悖
+病
+嘘
+龌
+彰
+倡
+噢
+酊
+遨
+灸
+垩
+奏
+詹
+了
+朵
+狈
+嫒
+骡
+踩
+碚
+冯
+纬
+缈
+佝
+儋
+仰
+倩
+破
+褔
+痪
+篇
+废
+竟
+罹
+巷
+淋
+现
+恩
+鹤
+饿
+筹
+娼
+胯
+狠
+舀
+希
+鲷
+者
+未
+峡
+翻
+迳
+厅
+厌
+毂
+芩
+逻
+蜈
+枇
+秒
+嚎
+瓶
+枰
+龋
+焗
+皴
+桌
+棋
+释
+罱
+唉
+受
+宅
+锭
+畿
+坎
+怎
+拾
+仅
+滩
+茨
+琶
+瑷
+幡
+附
+仪
+藻
+弼
+歉
+柞
+堷
+郅
+曰
+鳅
+琚
+注
+边
+绑
+膈
+珐
+兹
+愉
+蓥
+互
+似
+稀
+嚞
+诛
+腋
+膘
+鲲
+瀵
+秆
+扞
+瓮
+右
+救
+璎
+籤
+髁
+躏
+嘲
+岑
+君
+旨
+坍
+莎
+孃
+汲
+媚
+欧
+选
+渣
+溃
+梵
+屌
+话
+憩
+羯
+扪
+佟
+盅
+掐
+龚
+丫
+莨
+鞍
+埌
+七
+澹
+唛
+絶
+寻
+论
+俨
+疥
+尘
+珉
+步
+宦
+诰
+绲
+龊
+郜
+浈
+庖
+觇
+嗔
+夙
+卧
+麒
+茂
+务
+原
+九
+犇
+薬
+阉
+镌
+酶
+尹
+攫
+目
+泺
+貔
+汀
+莞
+昔
+硂
+铀
+鳝
+警
+敟
+达
+篙
+滟
+重
+罕
+褪
+示
+哝
+亨
+韦
+婕
+蛤
+瑄
+喉
+哆
+铟
+绻
+隐
+中
+赈
+类
+瘠
+暮
+避
+浏
+杭
+窜
+门
+郓
+岷
+阮
+沧
+骋
+逺
+峋
+澡
+焙
+裟
+运
+荡
+幂
+酚
+姬
+洺
+袤
+貊
+架
+姨
+宸
+肛
+捡
+支
+纲
+溥
+尉
+灏
+卅
+距
+荸
+练
+库
+鲮
+猥
+宰
+鳃
+圆
+岂
+南
+沪
+炀
+窕
+尨
+苟
+熬
+仟
+戢
+界
+阙
+雾
+隋
+价
+逍
+佃
+耍
+系
+诤
+孵
+媾
+叽
+攒
+孪
+蜴
+标
+颜
+鞅
+莺
+笫
+裡
+葚
+凇
+凌
+陵
+欠
+络
+贸
+螺
+睐
+尧
+拗
+蹉
+癖
+粱
+萎
+得
+娥
+赖
+溟
+都
+璇
+猬
+嗯
+苒
+镧
+颦
+鸦
+赣
+洪
+确
+参
+坛
+氨
+球
+躺
+仇
+啾
+碳
+族
+闿
+偌
+赠
+扼
+嘀
+菌
+濠
+珩
+学
+咚
+惬
+才
+郛
+咭
+暖
+逢
+恋
+撕
+凛
+绝
+品
+忽
+铛
+鹰
+蘸
+馄
+呓
+昇
+链
+裆
+则
+瘘
+伢
+衖
+撸
+骶
+峯
+蜒
+乡
+全
+刃
+绨
+蛹
+唻
+昏
+暨
+凡
+桓
+婆
+箍
+陋
+封
+枪
+讼
+殃
+辩
+闰
+迦
+尿
+轮
+蝇
+足
+莴
+饲
+又
+馏
+宛
+换
+构
+车
+锌
+兆
+费
+蛇
+本
+落
+蛙
+纵
+琐
+窍
+盼
+洱
+胀
+飬
+择
+謇
+楹
+悲
+公
+揭
+综
+戾
+迷
+泸
+毽
+亟
+剽
+爆
+棠
+吏
+萸
+璩
+纺
+闯
+蚝
+蔷
+舍
+脸
+创
+葛
+羁
+挫
+件
+嫡
+锡
+鱼
+埠
+滑
+议
+托
+旺
+熨
+椎
+衡
+便
+炅
+歪
+诈
+消
+旭
+亲
+武
+拷
+偻
+橱
+趸
+朗
+制
+扇
+因
+檩
+菽
+假
+拖
+鼻
+恒
+诙
+媛
+裨
+萋
+绌
+腕
+或
+笏
+蠓
+难
+貌
+磷
+狮
+珑
+乳
+袁
+金
+闳
+曼
+珂
+苝
+盒
+靡
+删
+艄
+丛
+捞
+醒
+倭
+孤
+溱
+糁
+潜
+冠
+匠
+转
+惹
+锽
+香
+哉
+京
+语
+邂
+醋
+房
+涝
+盗
+周
+蝴
+第
+豉
+亮
+规
+宿
+戌
+茵
+臻
+鳜
+芹
+五
+皲
+瘾
+卷
+赤
+绩
+鳎
+闽
+羚
+竖
+丙
+轰
+匿
+从
+悯
+菜
+祜
+意
+苘
+谁
+虽
+萁
+柄
+吮
+葩
+根
+嗬
+忡
+帽
+如
+峨
+省
+叶
+隅
+蚶
+接
+郑
+咙
+城
+阖
+入
+畔
+枣
+箸
+稍
+咨
+痞
+凯
+亡
+谛
+腌
+阡
+潼
+堰
+缎
+年
+泾
+留
+欣
+摩
+锐
+桐
+掰
+旖
+鲜
+河
+旋
+棱
+虏
+差
+料
+勰
+蛔
+姒
+福
+疮
+瀑
+姓
+礼
+祀
+捶
+丽
+伴
+漕
+诘
+馍
+枢
+讽
+呛
+返
+两
+脏
+油
+搏
+东
+徉
+溯
+砻
+咣
+歧
+蝠
+皂
+瞿
+侄
+卢
+岫
+僳
+腼
+衲
+腻
+祈
+帜
+姊
+泮
+槎
+酞
+氢
+祐
+亘
+忿
+桉
+忻
+柒
+字
+蘖
+橪
+质
+沼
+癌
+汾
+块
+宏
+粒
+昨
+庇
+衮
+鞘
+浑
+槌
+怨
+残
+娓
+什
+馈
+鲟
+椐
+饮
+贤
+俵
+敢
+鬓
+卤
+旗
+董
+超
+泗
+姘
+孩
+佩
+严
+呆
+弥
+烤
+睦
+瓠
+战
+佘
+厄
+茧
+票
+钵
+村
+威
+氖
+吻
+砍
+获
+匮
+阀
+彭
+疆
+象
+拐
+珺
+抿
+燃
+扭
+卫
+驹
+幽
+氚
+吹
+秤
+越
+噗
+哀
+啖
+琨
+递
+盛
+灰
+谥
+融
+鹄
+腩
+罚
+肠
+兼
+赁
+琢
+己
+蓼
+骑
+颛
+柑
+荫
+内
+夷
+执
+珊
+匈
+色
+跚
+斡
+萤
+瘤
+嘱
+胁
+甭
+春
+嶙
+吊
+蕾
+漂
+兄
+応
+霞
+爯
+垄
+濡
+肋
+张
+喘
+鲂
+以
+打
+妗
+韫
+镒
+硚
+茼
+骁
+冲
+鸢
+嘣
+梁
+炬
+沫
+怪
+眙
+罘
+鳞
+柘
+谢
+到
+猎
+髻
+扎
+蹊
+样
+泳
+哟
+削
+妩
+浓
+硒
+锔
+讣
+砾
+脉
+抽
+黟
+渤
+暧
+遁
+咋
+瑶
+殒
+邺
+魅
+匋
+籼
+置
+群
+鞠
+擒
+荼
+勉
+夏
+帚
+曲
+矮
+每
+给
+煦
+位
+尊
+码
+惨
+邻
+娇
+蒲
+癫
+锚
+昊
+懵
+水
+期
+帮
+栗
+市
+绉
+它
+翔
+蚕
+晒
+探
+殿
+头
+郡
+儿
+涞
+琴
+寰
+组
+喙
+厕
+阈
+量
+沿
+挣
+恭
+荭
+烷
+靴
+操
+箭
+涕
+焚
+搴
+剔
+股
+侠
+柔
+持
+蓬
+茗
+藉
+疯
+岐
+胥
+梏
+常
+苛
+款
+庙
+膀
+鹊
+冥
+俞
+纂
+斋
+磕
+宫
+崧
+隔
+拥
+也
+卸
+骇
+六
+曳
+震
+饵
+舫
+竭
+骰
+棒
+韬
+莽
+盈
+阳
+笙
+漾
+尚
+炸
+恍
+汪
+栅
+致
+碟
+沭
+俏
+橡
+堀
+效
+呻
+阆
+剪
+闫
+孛
+吟
+遗
+令
+余
+奕
+稗
+窨
+剃
+挛
+躇
+履
+袈
+培
+弧
+瞎
+芙
+显
+改
+挪
+送
+塔
+雀
+钊
+荆
+筑
+哲
+清
+汽
+岸
+校
+掂
+琬
+袂
+咅
+眷
+集
+娃
+速
+菟
+眼
+癣
+鲤
+荻
+啧
+楸
+加
+刹
+弘
+幺
+暑
+柜
+束
+惊
+邮
+绀
+戚
+恣
+污
+镣
+甘
+即
+碰
+劣
+黠
+卒
+钳
+傣
+鬃
+罪
+母
+瓤
+放
+菡
+吴
+苷
+滔
+筐
+煮
+牍
+绫
+阕
+卯
+安
+否
+娈
+呜
+侏
+愫
+镝
+咪
+聘
+雉
+皋
+瓒
+享
+记
+扒
+佳
+仁
+苕
+壁
+载
+述
+瞧
+媳
+娅
+江
+蓓
+蜡
+歌
+栏
+晏
+怜
+柏
+翌
+钕
+啡
+婵
+楿
+崮
+考
+肼
+喽
+番
+纷
+黄
+缤
+瞻
+砬
+缇
+窥
+娽
+忒
+蛏
+岽
+檫
+浣
+式
+譬
+荀
+司
+唐
+驱
+蛲
+袆
+屿
+壬
+寥
+扩
+诊
+铱
+轨
+锨
+间
+早
+崤
+缕
+咖
+嘻
+癜
+涛
+闻
+濞
+茯
+仑
+菁
+盥
+俩
+悬
+攸
+蕟
+咒
+榕
+琳
+点
+莪
+尼
+珈
+收
+输
+半
+胶
+风
+芊
+遐
+勋
+槽
+邀
+寓
+夸
+荏
+循
+立
+宙
+桅
+特
+巳
+蠹
+雅
+氪
+飕
+脑
+曾
+苇
+紫
+竽
+喇
+栀
+廿
+斟
+怆
+斐
+果
+科
+法
+惕
+崬
+刚
+茶
+爻
+氙
+坻
+研
+哗
+黎
+军
+垫
+高
+齿
+修
+禧
+硬
+吨
+芡
+肴
+厘
+畵
+统
+侣
+垭
+湖
+镭
+粉
+羡
+肥
+璃
+偿
+下
+鼎
+向
+屑
+嵊
+瑰
+瑛
+烜
+起
+味
+乐
+蝈
+枯
+镯
+覆
+艿
+掷
+旷
+恳
+诺
+赎
+鬟
+苗
+蒤
+索
+枫
+辑
+望
+怏
+亦
+毓
+筋
+算
+囔
+挨
+拔
+谕
+拢
+花
+兴
+髪
+舅
+怠
+乔
+膦
+板
+米
+嘴
+厢
+万
+翟
+仆
+矛
+退
+粗
+频
+疸
+绡
+抵
+视
+佗
+蝾
+羰
+烨
+泡
+脾
+虫
+室
+绥
+迮
+溜
+郁
+敛
+杨
+砒
+手
+湟
+涌
+嗷
+耕
+苁
+榫
+跻
+处
+胭
+阊
+队
+郫
+梳
+氯
+峥
+哩
+流
+坦
+权
+槿
+鹧
+抡
+遂
+晾
+徜
+铑
+茁
+戎
+腭
+名
+郦
+铅
+眈
+蚀
+煅
+縤
+坳
+逸
+妨
+挤
+鳟
+缆
+死
+圣
+优
+诶
+秣
+蜜
+诬
+呈
+醌
+佰
+唷
+子
+射
+珞
+忠
+赝
+憔
+辖
+魔
+榔
+逹
+聂
+总
+号
+勾
+荬
+搔
+潞
+此
+踮
+気
+婶
+奄
+营
+主
+坊
+宇
+簧
+兵
+陌
+兢
+帛
+彦
+厂
+绗
+産
+缮
+遢
+箔
+绽
+奓
+环
+蒂
+却
+藿
+拓
+筛
+郎
+掊
+蔬
+薛
+糍
+某
+烧
+耗
+吵
+冽
+御
+厩
+由
+嘢
+笔
+璀
+瑕
+赫
+跷
+伯
+沥
+劈
+燘
+赋
+浍
+不
+光
+钽
+桀
+历
+町
+麓
+嘬
+涪
+愈
+皱
+戬
+畈
+怔
+袒
+惚
+埚
+邳
+肽
+伙
+慧
+芝
+聋
+珣
+枸
+轲
+狼
+熊
+氩
+恨
+壑
+汊
+缐
+镊
+蹄
+进
+讲
+藤
+荤
+俺
+褓
+铎
+抑
+陷
+匪
+瑭
+凶
+船
+呃
+殉
+切
+赌
+滞
+陛
+汹
+山
+楂
+峒
+尻
+燥
+呦
+绵
+役
+士
+纠
+簇
+丧
+挟
+最
+牦
+刺
+乖
+农
+芯
+跖
+呀
+蔺
+页
+财
+弓
+抱
+骷
+责
+惟
+赞
+红
+晃
+梅
+寨
+琏
+控
+芒
+损
+枷
+瘸
+藓
+咀
+蟋
+秦
+枚
+嗒
+宁
+桖
+文
+硷
+鼽
+悉
+哨
+甹
+减
+伟
+吉
+伤
+毫
+羧
+丑
+黍
+绅
+惭
+瓢
+溢
+弦
+浦
+倌
+锯
+缝
+段
+娲
+帷
+生
+八
+疣
+咯
+妳
+泰
+践
+二
+宽
+柱
+涟
+纨
+卜
+躲
+痫
+北
+娱
+莲
+咱
+硫
+温
+噻
+虞
+钬
+孙
+保
+潮
+淼
+遣
+彪
+催
+釉
+荥
+园
+燎
+诀
+牯
+您
+玳
+谪
+稠
+搐
+瘁
+眩
+歓
+锂
+胛
+垛
+饥
+属
+寝
+酮
+陂
+屉
+询
+盆
+辙
+胃
+刻
+耜
+颚
+剡
+缦
+继
+禀
+堤
+圹
+恽
+罗
+锥
+包
+啮
+裹
+戏
+獭
+崽
+跌
+绰
+暗
+飞
+译
+压
+啓
+棵
+嚒
+蹴
+耀
+引
+绊
+宣
+甲
+笺
+簋
+蓿
+题
+臾
+澶
+折
+蒟
+豁
+拈
+瀛
+窦
+戴
+披
+忏
+陟
+愤
+裴
+耘
+快
+圈
+炔
+茅
+黔
+瘊
+泼
+蓦
+薏
+麦
+绒
+晤
+怅
+雨
+宠
+丕
+井
+补
+抓
+仨
+孜
+笑
+尤
+泠
+鲵
+牛
+岔
+渺
+代
+史
+汨
+帏
+嗪
+酰
+椭
+莆
+氛
+嚏
+屯
+芎
+醪
+珍
+谣
+友
+抗
+锅
+巧
+奋
+奉
+雁
+伺
+梗
+虾
+描
+腰
+夆
+幅
+住
+璈
+鹫
+孝
+锶
+躯
+大
+积
+怂
+褥
+扙
+召
+枝
+钡
+镶
+碾
+锘
+镦
+氰
+缂
+蓖
+声
+情
+熵
+荒
+煤
+缓
+蜊
+对
+玷
+固
+蛆
+裙
+乙
+化
+蚊
+圾
+诒
+傩
+鄯
+契
+廷
+孰
+邋
+累
+肱
+粤
+辜
+擘
+摔
+盟
+邓
+鬣
+靛
+版
+丹
+搀
+善
+诞
+稹
+天
+墟
+掇
+楠
+妥
+赡
+反
+惰
+详
+妓
+秭
+停
+捌
+貅
+鸡
+呤
+嗝
+乓
+蜷
+晧
+上
+痧
+搅
+域
+窖
+审
+矾
+蹒
+县
+雌
+终
+睿
+掖
+饪
+杉
+筷
+绮
+梆
+呱
+伦
+跛
+你
+砌
+爵
+丸
+惘
+烛
+儒
+槟
+穴
+邰
+骸
+毁
+妫
+影
+赵
+瑚
+髙
+捅
+丁
+俫
+砥
+铂
+完
+慌
+疡
+肮
+裱
+窝
+缥
+秕
+鼬
+鹑
+岚
+觅
+芥
+毛
+捉
+惮
+划
+之
+霸
+沮
+匍
+棛
+髯
+工
+钒
+泄
+鄂
+叨
+燮
+淝
+罂
+额
+拦
+盱
+外
+监
+乍
+酗
+玄
+馕
+拍
+痼
+氲
+鹦
+明
+血
+告
+冢
+冚
+伪
+艮
+配
+鲑
+疏
+小
+遭
+眺
+喃
+滂
+迎
+嘧
+炯
+嫔
+调
+掴
+愿
+瞌
+鼓
+奎
+剖
+章
+馅
+贫
+沙
+写
+栾
+僮
+跤
+猷
+潺
+煨
+革
+空
+猝
+赐
+呕
+叹
+棕
+竞
+桠
+颇
+燧
+刁
+绾
+蜂
+铺
+蜱
+撅
+茎
+扔
+鸽
+牟
+着
+涑
+摇
+皓
+拟
+囍
+恼
+慰
+抠
+肚
+憎
+过
+婢
+讹
+那
+肄
+正
+堇
+豹
+漱
+绞
+澜
+洲
+钝
+物
+汩
+搜
+肌
+训
+逐
+湛
+剧
+蕈
+桢
+萱
+跆
+单
+助
+襄
+命
+裸
+泞
+舰
+押
+苞
+鹜
+编
+溶
+咛
+徊
+沃
+产
+答
+免
+涔
+憾
+深
+直
+褒
+涯
+像
+峪
+均
+邨
+涎
+儱
+噶
+姝
+妖
+旱
+晞
+嘤
+萼
+纽
+局
+诉
+銮
+攀
+崂
+峁
+匕
+虑
+屏
+牌
+蹇
+恁
+烬
+瘙
+家
+蟀
+吧
+课
+诋
+焱
+俎
+沟
+栋
+虚
+擞
+奥
+谷
+腮
+讷
+攥
+事
+姥
+讫
+极
+袜
+骠
+诱
+狂
+砼
+盎
+绪
+苴
+夭
+娠
+荞
+策
+凿
+导
+甾
+垢
+韩
+靳
+徙
+荷
+动
+级
+鹉
+凑
+嗦
+禺
+怀
+崔
+畹
+晗
+锄
+俪
+眦
+霖
+姣
+铒
+蜇
+篮
+巩
+午
+焊
+栈
+砣
+杲
+蛾
+怦
+邗
+听
+汛
+力
+妒
+痊
+蜛
+踵
+济
+皖
+珏
+姆
+镰
+孖
+泩
+弈
+幌
+底
+劫
+妃
+濂
+锢
+抨
+莳
+牵
+苯
+爪
+敝
+拒
+芬
+棉
+渝
+潋
+摆
+绚
+骆
+蔻
+防
+汁
+璞
+肉
+攘
+妆
+彗
+窠
+嵌
+伸
+邛
+魂
+册
+竺
+闭
+喋
+斑
+瓿
+韭
+溺
+桦
+淫
+泌
+连
+誉
+星
+茆
+厮
+匆
+兰
+醉
+投
+羲
+苓
+抛
+曝
+掌
+毡
+埭
+墒
+典
+喻
+瞰
+杖
+蕊
+啼
+瘪
+婺
+懒
+逑
+网
+媲
+砚
+够
+汶
+湃
+美
+撮
+粘
+韶
+械
+夺
+懊
+桂
+跎
+庥
+狡
+疟
+太
+灭
+软
+猪
+趴
+伎
+盹
+忱
+匙
+脱
+噌
+纹
+散
+洼
+啫
+满
+憨
+同
+骅
+泫
+芾
+郧
+惶
+邝
+撬
+叻
+驷
+贷
+面
+鱿
+滦
+滤
+漫
+畜
+搁
+蹿
+鳐
+结
+骗
+箕
+鼾
+痕
+蚱
+忆
+诧
+埃
+经
+锷
+渗
+拿
+炉
+舟
+庸
+垓
+疤
+镐
+粕
+恃
+巽
+颅
+哥
+神
+丰
+驽
+嘿
+攻
+寅
+胆
+萃
+跺
+脲
+鸥
+墓
+嬴
+潘
+趋
+塌
+斜
+峙
+艺
+樨
+颡
+寤
+亵
+厝
+湫
+谋
+衬
+联
+秉
+璁
+淆
+十
+匀
+滘
+型
+酉
+铄
+窑
+梦
+敲
+矿
+轼
+萜
+婉
+颢
+想
+饭
+砭
+琅
+然
+缠
+蜿
+蔗
+赉
+窃
+允
+紧
+豚
+淇
+焰
+捏
+蕃
+腥
+矗
+徕
+蟆
+叉
+魇
+鸯
+敌
+振
+缩
+榻
+康
+慎
+翠
+赭
+电
+剌
+疾
+笛
+侑
+宴
+醇
+袭
+涂
+痘
+釜
+酡
+滙
+醚
+暴
+糜
+熠
+址
+橇
+戛
+祙
+僻
+害
+饹
+噱
+簌
+净
+很
+呵
+舱
+谌
+府
+日
+飙
+赚
+拳
+仿
+厚
+双
+舜
+躁
+儥
+阔
+嘞
+妇
+筒
+鄙
+变
+摄
+而
+椁
+洞
+瘴
+去
+通
+钉
+秩
+扯
+伶
+瘿
+院
+唰
+阜
+刍
+萄
+痣
+铊
+荟
+颈
+萍
+砧
+必
+苼
+鲫
+蒸
+滨
+晷
+懋
+氟
+翊
+揣
+认
+冷
+括
+糗
+旅
+伏
+罐
+至
+灯
+痈
+浅
+蜚
+逼
+逛
+湮
+拣
+抬
+喧
+横
+菓
+蠡
+唾
+訾
+捲
+勑
+挚
+蚣
+耻
+暹
+耿
+畏
+绢
+骺
+晕
+迹
+贾
+爸
+围
+缚
+奘
+弟
+符
+剑
+喷
+焉
+阅
+殴
+巯
+悄
+撤
+佤
+贯
+使
+兮
+潴
+夋
+铮
+菘
+葆
+洋
+醛
+沽
+豆
+床
+津
+染
+绕
+濛
+逾
+急
+取
+蝙
+觞
+截
+饦
+穿
+盐
+映
+馋
+暂
+钩
+芷
+乱
+拮
+弩
+眯
+怖
+娜
+麾
+噪
+蜗
+淑
+癞
+蕨
+饴
+民
+拇
+份
+稷
+炎
+矣
+镓
+阵
+膏
+壳
+蹦
+挡
+拱
+洁
+镔
+苄
+袋
+哪
+僵
+争
+复
+涮
+诟
+悻
+榜
+喀
+当
+箬
+潭
+庵
+洵
+汕
+羸
+键
+徨
+鱻
+谆
+矸
+布
+陪
+蜥
+尸
+拄
+霓
+皆
+关
+茸
+嬗
+锺
+苣
+梨
+邃
+甙
+铐
+耷
+瑁
+铋
+老
+哑
+漠
+橄
+捣
+犯
+廰
+坭
+咆
+疃
+里
+涣
+瑟
+韧
+崭
+故
+馗
+哞
+侮
+锑
+率
+限
+迂
+麟
+嗤
+痢
+杰
+浊
+玩
+依
+牒
+诵
+嫁
+汝
+增
+娟
+啥
+斯
+驳
+傲
+巫
+炒
+沅
+森
+雏
+卑
+锲
+欢
+煲
+弭
+渲
+扰
+柿
+凉
+桼
+霉
+壴
+党
+钗
+宄
+奂
+伐
+合
+喝
+奶
+痠
+檬
+肿
+丢
+捐
+脐
+胳
+幕
+隹
+刷
+呋
+协
+绯
+蹬
+朴
+景
+理
+叁
+负
+敕
+骛
+泵
+砦
+浴
+朱
+的
+奴
+迢
+搭
+用
+雇
+买
+谀
+伉
+吱
+膺
+榉
+皮
+坂
+宜
+蔽
+缍
+朋
+迄
+眞
+钰
+售
+沏
+崴
+薯
+甸
+漩
+椿
+郴
+恿
+援
+涸
+浸
+蔚
+沦
+様
+塾
+炭
+瞭
+誓
+踺
+傅
+崋
+盖
+愚
+素
+讴
+宾
+嘌
+漉
+层
+擅
+蒌
+吞
+逅
+泔
+糯
+吝
+祖
+寿
+粟
+杈
+拼
+男
+简
+耽
+吃
+锟
+炕
+弗
+烈
+辋
+榄
+铣
+杯
+倘
+菪
+守
+锦
+碴
+酋
+溪
+扁
+怒
+犷
+圜
+葵
+戳
+揖
+咦
+轿
+涠
+郯
+卿
+坤
+怡
+浜
+隙
+鲁
+贴
+莠
+玉
+碓
+垸
+藔
+霹
+伫
+恰
+机
+嗑
+旦
+祚
+糅
+药
+序
+灼
+褀
+夜
+咄
+昝
+毯
+廉
+翘
+称
+牢
+崇
+火
+耶
+坏
+撇
+叭
+辟
+在
+寡
+鄣
+炳
+尝
+离
+坞
+蝗
+绸
+舷
+跪
+普
+精
+醴
+毋
+烟
+脘
+骜
+夕
+螯
+匾
+硼
+堕
+鸪
+麝
+柚
+失
+懿
+霍
+谍
+芳
+川
+雹
+佧
+趾
+食
+慈
+扣
+鏖
+爰
+锁
+荽
+阚
+砖
+方
+洛
+钛
+评
+贝
+唔
+磬
+抻
+镍
+藜
+把
+搂
+丝
+坷
+试
+霾
+感
+龙
+瑙
+煊
+鲆
+浼
+鲩
+唑
+峤
+擦
+娘
+皙
+尬
+晖
+菏
+墉
+渑
+烽
+飒
+捋
+帆
+龟
+首
+忍
+唤
+屐
+榈
+绶
+仃
+陆
+帼
+疗
+勘
+辱
+囟
+忘
+嗉
+慵
+眶
+艇
+派
+烯
+念
+垟
+寺
+骂
+璜
+孑
+蕤
+诩
+轻
+仔
+痔
+祁
+吡
+魄
+赔
+椤
+弱
+蛎
+钎
+灌
+泪
+淬
+胪
+性
+呐
+焘
+曙
+贿
+廊
+馥
+穸
+排
+耧
+突
+炖
+惩
+铁
+萧
+锻
+疱
+娩
+虔
+启
+鸵
+哮
+侥
+萝
+虱
+骧
+桧
+晔
+屹
+场
+预
+鄞
+魏
+个
+糊
+肘
+艶
+猩
+绠
+衍
+泛
+黛
+粿
+笃
+楗
+畲
+敦
+讶
+晶
+冤
+偎
+舆
+鳙
+瘟
+倪
+搪
+唧
+楣
+痉
+矜
+粽
+访
+瀚
+鸣
+寒
+奔
+腾
+枞
+颠
+蒡
+岙
+蕴
+诏
+灞
+世
+贞
+诽
+亭
+唯
+毗
+胡
+腔
+概
+查
+淙
+赢
+所
+列
+笕
+前
+椒
+粮
+沸
+杳
+饶
+帐
+窘
+缙
+沂
+副
+蚜
+硕
+毒
+餐
+儡
+磐
+耱
+蝉
+塬
+顷
+搽
+猿
+旮
+疵
+膊
+纸
+护
+榷
+吁
+腈
+穰
+季
+割
+镉
+猫
+邡
+梯
+迤
+偃
+仲
+渊
+缢
+惠
+狸
+汰
+榭
+祗
+鸠
+圭
+谑
+褂
+樱
+揩
+童
+饰
+卡
+痂
+马
+扛
+朦
+谘
+梢
+楔
+鲍
+樊
+筠
+夯
+壮
+铝
+富
+作
+冈
+漯
+蟹
+焦
+泯
+雍
+缀
+粲
+麸
+逄
+俊
+垠
+读
+氦
+羊
+俱
+嬉
+谔
+犸
+英
+充
+晦
+虬
+锗
+疑
+呼
+绍
+茱
+鹞
+握
+庶
+滴
+鬼
+弛
+痨
+趁
+佛
+嫩
+彝
+翰
+疳
+捎
+峰
+胗
+狩
+雳
+嗖
+翏
+征
+兜
+爽
+缫
+砂
+缜
+喔
+厥
+咎
+玟
+蛊
+缅
+睡
+谟
+髅
+宪
+趟
+画
+鲅
+督
+梧
+晁
+邑
+垮
+胞
+徇
+芽
+烂
+觉
+档
+骓
+聿
+赓
+宗
+夌
+冀
+镑
+磺
+橙
+耦
+氐
+健
+聆
+辨
+褚
+胺
+俭
+鸳
+恙
+妻
+歹
+叛
+铆
+模
+蔡
+迈
+抢
+栖
+毅
+约
+吩
+貂
+蚴
+辞
+诣
+瞪
+添
+霭
+仓
+臂
+荠
+咿
+睽
+臣
+销
+著
+账
+稻
+吒
+楚
+捍
+皇
+绣
+萦
+促
+丐
+嗣
+兔
+愧
+岁
+团
+茴
+巨
+幻
+噼
+悌
+时
+各
+鸨
+找
+岭
+巡
+钴
+音
+郗
+辰
+鲨
+些
+缺
+薪
+傥
+磊
+壕
+圩
+涧
+黑
+漪
+劵
+堆
+辉
+拆
+峄
+沔
+蒻
+蒿
+蹭
+刑
+茌
+擂
+医
+升
+地
+纰
+鬲
+糕
+髓
+悦
+借
+豕
+项
+袪
+乏
+摹
+候
+嘈
+相
+谏
+逆
+状
+豪
+袍
+蔓
+招
+识
+脒
+胴
+糠
+桷
+邪
+叫
+姑
+焖
+彬
+俟
+苫
+铍
+嫚
+赂
+税
+枉
+锋
+近
+蘼
+骼
+硖
+胬
+逃
+蚪
+举
+飓
+垂
+趵
+熙
+沛
+眉
+铿
+草
+裕
+能
+嵩
+问
+勃
+发
+纫
+娑
+苹
+迸
+勺
+斥
+脯
+爷
+崆
+丈
+蚤
+镗
+殚
+踹
+唏
+堪
+昆
+膳
+碉
+容
+唬
+畦
+冰
+辆
+複
+讯
+胱
+走
+框
+皈
+沾
+莘
+计
+讪
+蹶
+碘
+旎
+左
+座
+匐
+挎
+甄
+露
+啉
+涫
+茭
+赦
+钾
+纱
+萭
+缰
+泽
+吭
+蛐
+卟
+湓
+恂
+犹
+哼
+图
+囚
+芘
+洮
+硝
+洙
+占
+降
+吗
+核
+幛
+殆
+邱
+强
+雒
+讨
+吐
+钮
+镀
+庚
+碗
+瘩
+绺
+芫
+篱
+胤
+籽
+槐
+区
+沓
+罡
+菊
+槗
+戟
+榆
+独
+蚁
+究
+鄄
+赊
+篝
+咽
+丼
+奚
+察
+砝
+忙
+搓
+聚
+侬
+后
+梭
+氮
+患
+蝌
+背
+嫣
+息
+谴
+熔
+克
+孕
+迥
+哽
+敏
+琯
+予
+柯
+跳
+宓
+瘢
+栓
+钠
+珲
+熳
+濮
+俐
+广
+隶
+啵
+谱
+芮
+瞩
+筝
+黝
+埋
+先
+热
+桶
+除
+苦
+秋
+港
+苍
+墨
+幄
+溴
+伞
+弃
+莅
+湘
+琪
+静
+蛀
+睢
+客
+醐
+闲
+浐
+薡
+唠
+阄
+颊
+剥
+啊
+喂
+为
+犁
+钙
+亢
+顽
+藏
+顾
+姌
+佣
+衢
+锵
+戮
+钇
+鲸
+钨
+琦
+径
+违
+偕
+颗
+尃
+礴
+与
+楞
+洳
+靥
+碇
+盾
+滁
+雄
+偷
+巢
+兖
+挝
+刊
+辄
+捧
+淮
+酵
+该
+霏
+哄
+慑
+倚
+嘎
+俸
+亥
+官
+钞
+桑
+渭
+邹
+哭
+痒
+撼
+店
+妙
+谙
+樾
+缔
+霄
+捷
+耐
+蟒
+琵
+远
+阶
+亚
+蛋
+拚
+炝
+岿
+娆
+鲈
+节
+闵
+裤
+再
+淀
+蚨
+瑞
+辘
+箐
+橹
+移
+乞
+蔼
+杠
+闺
+耒
+腺
+掏
+谎
+槛
+豊
+矩
+蚓
+活
+棚
+肤
+禽
+朝
+饱
+琛
+臧
+冶
+酿
+并
+殁
+腱
+茬
+始
+鷄
+艰
+葫
+准
+谐
+默
+慨
+嘹
+数
+拨
+哺
+竿
+晰
+洄
+裘
+奈
+钜
+刘
+恹
+疙
+氡
+德
+佞
+益
+碧
+痛
+肇
+吋
+榨
+瘀
+椅
+辣
+惧
+王
+铪
+蹼
+腴
+屈
+戈
+殇
+喰
+甥
+紊
+磨
+谦
+巅
+耑
+俄
+渔
+悚
+微
+辗
+青
+阁
+格
+鹏
+啬
+苪
+苜
+鹭
+铨
+惴
+鸭
+隆
+任
+蝶
+帑
+妍
+州
+帕
+虻
+倍
+炮
+部
+孚
+仝
+续
+畅
+兽
+玮
+楷
+晚
+渥
+沁
+塞
+濉
+醺
+永
+菖
+撒
+熘
+褶
+恐
+要
+自
+傈
+荚
+筏
+椽
+痱
+溧
+柩
+蛴
+倜
+菲
+昂
+愁
+钟
+翡
+滇
+俾
+顿
+覃
+程
+辊
+涡
+鸟
+赴
+舞
+瑅
+企
+觊
+绷
+嗜
+凸
+尖
+兑
+猗
+替
+豌
+龄
+杀
+冮
+建
+甫
+茉
+良
+汗
+篓
+兀
+沉
+拉
+胰
+龈
+裔
+桨
+熏
+叔
+细
+搬
+次
+汞
+摸
+坡
+赃
+呸
+熄
+牺
+溉
+好
+绱
+陇
+瓦
+肟
+悸
+揸
+墙
+惆
+娉
+嗽
+燕
+百
+刀
+抒
+咐
+矫
+跨
+杷
+浚
+密
+骤
+真
+撞
+磅
+孟
+麋
+嗓
+帰
+铈
+涓
+坋
+嫂
+角
+拂
+戊
+镁
+屠
+泊
+浠
+蚌
+狄
+片
+几
+锴
+缭
+冗
+酥
+玑
+鲶
+甚
+国
+脆
+熥
+佯
+出
+习
+哎
+汇
+矞
+困
+忖
+羹
+妹
+绿
+橘
+岳
+啶
+搞
+耵
+垣
+嬷
+验
+装
+湄
+甬
+棺
+娶
+芜
+谰
+职
+螂
+铖
+堃
+脖
+筵
+莫
+鶏
+酒
+尾
+竹
+餮
+哚
+墩
+谯
+嵯
+捂
+遇
+俚
+滕
+来
+谤
+驾
+潢
+玺
+媒
+嗟
+妤
+疹
+整
+蠢
+慢
+蓑
+穗
+婪
+追
+崖
+炽
+渌
+乸
+芸
+阑
+吆
+焕
+寸
+荔
+惑
+躬
+渐
+混
+仙
+樟
+擎
+踝
+辈
+啃
+囿
+猛
+妯
+易
+饼
+吼
+襁
+和
+隼
+靖
+膨
+鲛
+涤
+析
+徐
+铲
+痰
+饽
+饕
+测
+酱
+赏
+缃
+松
+艳
+笆
+孳
+榴
+插
+祢
+思
+云
+坫
+鱬
+秃
+分
+颧
+朽
+担
+炣
+四
+彻
+萨
+箫
+瘦
+溆
+惺
+潍
+垌
+喏
+涩
+羟
+设
+嵇
+弊
+姚
+心
+僧
+雯
+厦
+措
+篡
+人
+啪
+菠
+衣
+姿
+月
+娌
+瞓
+且
+雪
+侃
+狰
+可
+辧
+铜
+供
+祠
+澈
+碎
+沣
+桩
+瞬
+邵
+缄
+舌
+坚
+秽
+斩
+裂
+祺
+币
+瓜
+颖
+刈
+蜘
+苑
+舛
+值
+窄
+邦
+侧
+洗
+驶
+袄
+窿
+匣
+铢
+侈
+黯
+缸
+谨
+唱
+郸
+姗
+我
+阿
+阎
+笈
+嫌
+懦
+髦
+妮
+缨
+碁
+稣
+漏
+恬
+壶
+脓
+敞
+缁
+磁
+铸
+订
+睛
+齐
+玥
+傀
+珀
+非
+澧
+靠
+佬
+嚼
+炫
+楼
+套
+席
+掳
+绐
+馊
+呷
+柳
+酯
+迟
+坝
+柬
+趣
+臼
+笼
+啜
+屎
+父
+蔑
+归
+淄
+澄
+捺
+樽
+圳
+懈
+淡
+陕
+饬
+姻
+昕
+扉
+钓
+驸
+跄
+卦
+骝
+有
+缴
+濒
+毙
+嫦
+于
+诿
+喫
+麂
+许
+海
+汴
+葑
+碣
+殊
+翅
+铧
+功
+鹂
+募
+瞄
+埔
+鍊
+秸
+龛
+葭
+逊
+杂
+髂
+淘
+蹑
+缪
+蜓
+擀
+劾
+晋
+氤
+朐
+谭
+阻
+推
+璐
+池
+燚
+铌
+遍
+检
+掉
+苏
+贺
+纭
+狐
+腿
+饨
+蚧
+恤
+庄
+抉
+鳄
+饸
+潇
+摧
+淳
+砜
+砺
+硪
+淹
+卞
+摊
+螨
+昧
+阂
+玖
+薹
+石
+莒
+身
+猖
+厍
+尔
+籁
+蛭
+飮
+冕
+白
+挞
+铤
+宥
+叩
+悟
+氘
+采
+拌
+利
+别
+磴
+畸
+寐
+咻
+哓
+泷
+涵
+棘
+岛
+袱
+葺
+势
+鹃
+坐
+徳
+撰
+骚
+腓
+响
+另
+榧
+倒
+塘
+鑫
+胖
+隽
+嘟
+枭
+冏
+株
+澎
+垒
+逗
+贲
+镇
+户
+镱
+鲽
+椹
+飘
+昼
+炜
+狱
+蔫
+棣
+轧
+塑
+妞
+剐
+衔
+杞
+敷
+傢
+羽
+临
+稔
+豫
+逯
+欺
+恢
+獴
+桔
+木
+侍
+睨
+将
+禄
+璨
+蛛
+馆
+狞
+褐
+央
+教
+印
+傍
+游
+遮
+軎
+铠
+莱
+倏
+鳕
+领
+鄢
+尽
+凤
+讳
+龢
+殷
+蒽
+嫉
+驮
+舔
+谈
+镂
+侯
+伽
+髮
+蒄
+郝
+服
+坩
+姹
+朔
+钥
+傻
+飨
+嗞
+伊
+境
+悠
+具
+筱
+提
+歼
+酪
+凄
+豇
+态
+涉
+厨
+介
+恻
+踊
+尕
+鲇
+跃
+簪
+蛮
+髋
+崛
+魉
+疫
+博
+献
+睬
+锕
+这
+擢
+垅
+迪
+圃
+诠
+低
+委
+莜
+湾
+辐
+扫
+蛰
+歆
+谅
+轭
+煽
+繁
+灿
+瓯
+纤
+瞳
+韵
+烁
+渡
+带
+唘
+脍
+嚓
+吲
+贩
+庆
+丞
+冻
+奸
+勅
+堑
+沤
+薇
+乒
+掀
+芃
+樵
+瓣
+赶
+肺
+交
+羔
+蚬
+芭
+道
+遥
+勒
+酌
+睑
+鞭
+钧
+钼
+藐
+矶
+婀
+帧
+泉
+俯
+蔟
+邢
+眨
+琰
+彷
+初
+籍
+袅
+犟
+鳌
+办
+蝽
+茄
+妪
+坨
+猴
+蓄
+报
+皎
+掘
+锣
+桥
+桁
+填
+嚷
+成
+撂
+祟
+绔
+元
+只
+奠
+嗳
+啻
+纶
+箩
+没
+鳖
+按
+蒙
+渎
+珙
+驭
+粪
+少
+痴
+肢
+慷
+沱
+哔
+忌
+疌
+眠
+呙
+斧
+虹
+懂
+材
+弯
+绋
+岩
+句
+条
+垡
+唆
+但
+翱
+剁
+技
+较
+聪
+谚
+翩
+帘
+晟
+危
+睫
+颉
+汉
+平
+新
+噤
+羿
+蜃
+粼
+潦
+煸
+涅
+驿
+莼
+箴
+倾
+柴
+襟
+摈
+焼
+浥
+鳍
+滥
+共
+畐
+摘
+伱
+泣
+羌
+峻
+稽
+演
+恕
+谒
+斌
+阱
+庞
+旬
+撵
+邴
+羞
+莉
+篷
+绁
+镳
+蟾
+瞟
+让
+栎
+蜀
+末
+滚
+野
+随
+镛
+扑
+荣
+螳
+眭
+彤
+谡
+寇
+菩
+她
+黜
+瑗
+轴
+幸
+犀
+盏
+霁
+痤
+闾
+烙
+旧
+郢
+贵
+喊
+暇
+憧
+铵
+阗
+曜
+戍
+昽
+赛
+鞋
+券
+诗
+忑
+智
+癸
+烘
+咔
+爹
+錶
+岘
+咬
+壅
+靶
+箱
+庠
+踞
+豺
+夫
+哒
+菀
+屡
+仄
+憋
+蜕
+靑
+嘉
+祯
+讥
+遏
+华
+休
+璋
+庐
+蓝
+踌
+檐
+绳
+宋
+滋
+钻
+憬
+途
+桃
+觎
+乎
+喜
+奇
+拴
+叮
+往
+茜
+胜
+噩
+挥
+础
+簿
+隘
+志
+醍
+造
+殡
+灾
+铡
+绘
+实
+腆
+浃
+鐡
+钦
+痿
+咏
+吓
+度
+骥
+缉
+峦
+遒
+肪
+咩
+斗
+社
+惦
+是
+阐
+长
+婊
+览
+渠
+古
+狭
+郭
+及
+荘
+专
+疲
+绦
+嗨
+睇
+指
+拎
+笠
+励
+批
+须
+杋
+睹
+喹
+凹
+摒
+付
+零
+盘
+胄
+判
+行
+砸
+瑜
+蛳
+瞥
+表
+照
+耙
+敖
+罩
+忧
+狒
+脚
+树
+琥
+蕉
+僚
+捆
+匡
+孬
+口
+窈
+邈
+煎
+欲
+恪
+浪
+稿
+何
+稳
+等
+仞
+腹
+疝
+祸
+庾
+挖
+掣
+土
+郾
+亳
+氓
+鞑
+掬
+匝
+佐
+晓
+帅
+诅
+刨
+镜
+倔
+估
+嵬
+玛
+祛
+腐
+脂
+瓴
+劭
+跋
+徘
+就
+徽
+迫
+端
+旯
+田
+岖
+檗
+猾
+鹅
+貉
+衙
+囊
+妾
+帖
+枼
+畴
+揪
+尓
+暃
+汐
+狙
+诡
+蟠
+獐
+蒜
+罄
+捻
+虐
+函
+妈
+垚
+驼
+路
+袖
+摁
+姐
+叼
+溅
+陶
+弑
+脊
+膻
+育
+禅
+锰
+穹
+藕
+蘑
+种
+氏
+礁
+偈
+孓
+宕
+妲
+辫
+闹
+颁
+倬
+耸
+彼
+茏
+菱
+术
+短
+诃
+卉
+植
+烫
+濯
+杵
+扬
+鄱
+璟
+妄
+渴
+气
+瞅
+铃
+咫
+浇
+湍
+决
+彧
+烃
+馁
+烺
+申
+嵋
+囹
+湿
+甜
+叟
+携
+巾
+障
+蚯
+囱
+闷
+澍
+侵
+夹
+秘
+晴
+祷
+吸
+嗡
+俬
+疚
+轱
+说
+谓
+虎
+仗
+被
+埕
+琮
+拭
+蚩
+辽
+陡
+仍
+绎
+员
+辛
+絮
+矢
+漳
+掺
+骞
+葬
+跟
+陉
+喳
+挺
+拘
+邬
+膛
+瀣
+台
+滢
+翥
+钚
+皿
+骨
+榞
+啕
+吕
+义
+萌
+墅
+晌
+肝
+织
+菅
+辅
+橼
+昙
+辍
+航
+求
+锤
+囤
+闸
+泅
+需
+慕
+吾
+娄
+器
+犍
+伈
+钢
+他
+篪
+棰
+膝
+波
+形
+芦
+既
+蠖
+晨
+孢
+幼
+侦
+定
+偶
+苔
+铔
+糖
+养
+痹
+牧
+一
+艘
+忐
+銭
+哈
+颌
+俘
+误
+顺
+抄
+锆
+锈
+冒
+维
+颔
+唇
+还
+衰
+睁
+淅
+蟑
+署
+银
+栢
+猕
+馨
+篦
+烊
+劝
+盯
+遛
+贱
+隧
+源
+绛
+揽
+蛟
+嚣
+嘶
+枳
+砋
+玲
+珥
+纛
+迭
+莓
+剂
+尐
+係
+戒
+贰
+旁
+孔
+札
+馒
+酣
+更
+勇
+骄
+獠
+站
+枋
+其
+滓
+薰
+衿
+悱
+蹲
+笨
+旌
+竣
+疽
+堡
+舶
+寄
+芍
+洒
+杆
+硅
+颐
+斓
+拽
+饺
+拙

+ 47 - 0
captcha_classify/inference_classify.py

@@ -0,0 +1,47 @@
+import os
+import re
+import sys
+import tensorflow as tf
+import cv2
+import numpy as np
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
+from captcha_classify.model import cnn_net_tiny
+from utils import pil_resize
+
+package_dir = os.path.abspath(os.path.dirname(__file__))
+image_shape = (128, 128, 1)
+class_num = 3
+model_path = package_dir + "/models/e262-acc0.81-classify.h5"
+
+
+def classify(image_np, model=None, sess=None):
+    if sess is None:
+        sess = tf.compat.v1.Session(graph=tf.Graph())
+
+    if model is None:
+        with sess.as_default():
+            with sess.graph.as_default():
+                model = cnn_net_tiny(input_shape=image_shape, output_shape=class_num)
+                model.load_weights(model_path)
+
+    img = image_np
+    X = []
+    img = pil_resize(img, image_shape[0], image_shape[1])
+    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+    img = np.expand_dims(img, axis=-1)
+    img = img / 255.
+    X.append(img)
+    X = np.array(X)
+
+    with sess.as_default():
+        with sess.graph.as_default():
+            pred = model.predict(X)
+
+    cls = int(np.argmax(pred))
+    print("cac result", cls)
+    return cls
+
+
+if __name__ == "__main__":
+    _path = "D:/Project/captcha/data/test/char_4.jpg"
+    print(classify([cv2.imread(_path)]))

+ 54 - 0
captcha_classify/model.py

@@ -0,0 +1,54 @@
+from keras import Input, Model
+from keras.layers import Lambda, Dense, Conv2D, Reshape, GlobalAveragePooling2D, BatchNormalization, \
+    LeakyReLU, MaxPooling2D, Dropout, Flatten
+
+
+def cnn_net_tiny(input_shape, output_shape=6270):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    conv = Conv2D(64, (3, 3))(down2_pool)
+    bn = BatchNormalization()(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+    conv = Conv2D(64, (3, 3))(rl)
+    bn = BatchNormalization()(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+
+    # conv = Conv2D(output_shape, (1, 1), activation='softmax')(rl)
+    # pool = GlobalAveragePooling2D()(conv)
+    # x = Reshape((output_shape,))(pool)
+
+    rl = Flatten()(rl)
+    dense = Dense(16, activation="relu")(rl)
+    drop = Dropout(0.2)(dense)
+    dense = Dense(output_shape, activation="softmax")(drop)
+    drop = Dropout(0.2)(dense)
+    x = Reshape((output_shape,))(drop)
+
+    model = Model(_input, x)
+    model.summary()
+    return model

BIN
captcha_classify/models/e151-acc0.81-classify.h5


+ 15 - 5
captcha_flask_server.py

@@ -16,6 +16,7 @@ coun_dic = {'shuzi':{'total_num':0, 'neg_num':0}, 'suanshu':{'total_num':0, 'neg
 
 app = Flask(__name__)
 
+
 @app.route("/getlog", methods=["POST"])
 def get_acc():
     clear = request.form.get('clear_log', 'no')
@@ -28,22 +29,27 @@ def get_acc():
             return 'clear_log error'
     return jsonify(coun_dic)
 
+
 @app.route("/errorlog", methods=["POST"])
 def save_error():
     """receive not success image and save """
     code_type = request.form.get('code', 'unkown')
     base64pic = request.form.get('base64pic')
     file_obj = request.files.get("pic")
-    data = {'save_success':False}
-    if code_type is None or str(code_type) not in ['shuzi', 'suanshu','yingwen','hanzi']:
-        data = {'errorinfo':'please check you param:code, code must be in shuzi/suanshu/yingwen/hanzi'}
+    data = {'save_success': False}
+    if code_type is None or str(code_type) not in ['shuzi', 'suanshu', 'yingwen', 'hanzi', '1', '2', '3', '4', '5', '6']:
+        data = {'errorinfo':'please check you param:code, code must be in shuzi/suanshu/yingwen/hanzi or number 1-6'}
         return jsonify(data)
+
     if base64pic is not None:
         try:
             src = base64.b64decode(base64pic.split(',')[-1])
             img = Image.open(BytesIO(src))
             time_tr = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
-            img.save('pic/'+str(code_type)+'_'+time_tr+'.jpg')
+            if code_type in ['1', '2', '3', '4', '5', '6']:
+                img.save('pic2/'+str(code_type)+'_'+time_tr+'.jpg')
+            else:
+                img.save('pic/'+str(code_type)+'_'+time_tr+'.jpg')
             data['save_success'] = True
             coun_dic[code_type]['neg_num'] += 1
             return jsonify(data)
@@ -53,7 +59,10 @@ def save_error():
         try:
             img = Image.open(file_obj)
             time_tr = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
-            img.save('pic/'+str(code_type)+'_'+time_tr+'.jpg')
+            if code_type in ['1', '2', '3', '4', '5', '6']:
+                img.save('pic2/'+str(code_type)+'_'+time_tr+'.jpg')
+            else:
+                img.save('pic/'+str(code_type)+'_'+time_tr+'.jpg')
             data['save_success'] = True
             coun_dic[code_type]['neg_num'] += 1
             return jsonify(data)
@@ -62,6 +71,7 @@ def save_error():
     else:
         return 'please check you parameter '
 
+
 @app.route("/upload", methods=["POST"])
 def upload():
     start_time = time.time()

+ 146 - 0
captcha_server/captcha_flask_server.py

@@ -0,0 +1,146 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# @Author  : bidikeji
+# @Time    : 2019/11/21 0021 15:55
+
+from predict_model import *
+from flask import request, Flask, jsonify
+from PIL import Image
+from io import BytesIO
+import base64
+import time
+import logging
+
+coun_dic = {'shuzi':{'total_num':0, 'neg_num':0}, 'suanshu':{'total_num':0, 'neg_num':0}
+               ,'yingwen':{'total_num':0, 'neg_num':0},'hanzi':{'total_num':0, 'neg_num':0}}
+
+app = Flask(__name__)
+
+
+@app.route("/getlog", methods=["POST"])
+def get_acc():
+    clear = request.form.get('clear_log', 'no')
+    if clear == 'yes':
+        try:
+            with open('upload_num_log.txt', 'a', encoding='utf=8') as f:
+                f.write(str(coun_dic))
+                f.write('\n')
+        except:
+            return 'clear_log error'
+    return jsonify(coun_dic)
+
+
+@app.route("/errorlog", methods=["POST"])
+def save_error():
+    """receive not success image and save """
+    code_type = request.form.get('code', 'unkown')
+    base64pic = request.form.get('base64pic')
+    file_obj = request.files.get("pic")
+    data = {'save_success': False}
+    if code_type is None or str(code_type) not in ['shuzi', 'suanshu', 'yingwen', 'hanzi'] \
+            or code_type not in [1, 2, 3, 4, 5, 6]:
+        data = {'errorinfo':'please check you param:code, code must be in shuzi/suanshu/yingwen/hanzi or number 1-6'}
+        return jsonify(data)
+
+    if base64pic is not None:
+        try:
+            src = base64.b64decode(base64pic.split(',')[-1])
+            img = Image.open(BytesIO(src))
+            time_tr = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
+            if code_type in [1, 2, 3, 4, 5, 6]:
+                img.save('pic2/'+str(code_type)+'_'+time_tr+'.jpg')
+            else:
+                img.save('pic/'+str(code_type)+'_'+time_tr+'.jpg')
+            data['save_success'] = True
+            coun_dic[code_type]['neg_num'] += 1
+            return jsonify(data)
+        except:
+            return jsonify(data)
+    if file_obj is not None:
+        try:
+            img = Image.open(file_obj)
+            time_tr = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
+            if code_type in [1, 2, 3, 4, 5, 6]:
+                img.save('pic2/'+str(code_type)+'_'+time_tr+'.jpg')
+            else:
+                img.save('pic/'+str(code_type)+'_'+time_tr+'.jpg')
+            data['save_success'] = True
+            coun_dic[code_type]['neg_num'] += 1
+            return jsonify(data)
+        except:
+            return jsonify(data)
+    else:
+        return 'please check you parameter '
+
+
+@app.route("/upload", methods=["POST"])
+def upload():
+    start_time = time.time()
+    """receive image and predict """
+    code_type = request.form.get('code')
+    base64pic = request.form.get('base64pic')
+    file_obj = request.files.get("pic")
+    data = {'success':False}
+    if code_type is None or str(code_type) not in ['shuzi', 'suanshu','yingwen','hanzi']:
+        data = {'errorinfo':'please check you param:code, code must be in shuzi/suanshu/yingwen/hanzi'}
+        return jsonify(data)
+    if base64pic is not None:
+        try:
+            src = base64.b64decode(base64pic.split(',')[-1])
+            img = Image.open(BytesIO(src))
+            if img.mode != "RGB":
+                img = img.convert("RGB")
+            if code_type == 'shuzi':
+                pre = predict_digit(img)
+            elif code_type == 'suanshu':
+                pre = predict_arith(img)
+                # pre = str(eval(pre))
+            elif code_type == 'hanzi':
+                pre = predict_chinese(img)
+            elif code_type == 'yingwen':
+                pre = predict_english(img)
+            data['predict'] = pre
+            data['success'] = True
+            coun_dic[code_type]['total_num'] +=1
+            app.logger.info("success ,use time:%.4f" %(time.time() - start_time))
+            return jsonify(data)
+        except:
+            app.logger.info("except error,use time:%.4f" %(time.time() - start_time))
+            return jsonify(data)
+    if file_obj is not None:
+        try:
+            img = Image.open(file_obj)
+            if img.mode != "RGB":
+                img = img.convert("RGB")
+            if code_type == 'shuzi':
+                pre = predict_digit(img)
+            elif code_type == 'suanshu':
+                pre = predict_arith(img)
+                # pre = str(eval(pre))
+            elif code_type == 'hanzi':
+                pre = predict_chinese(img)
+            elif code_type == 'yingwen':
+                pre = predict_english(img)
+            data['success'] = True
+            data['predict'] = pre
+            coun_dic[code_type]['total_num'] += 1
+            app.logger.info("success ,use time:%.4f" %(time.time() - start_time))
+            # print('graph_node_num',len(tf.get_default_graph().as_graph_def().node))
+            return jsonify(data)
+        except:
+            app.logger.info("except error, use time:%.4f" %(time.time() - start_time))
+            return jsonify(data)
+
+    return 'please check you post '
+
+
+if __name__ == '__main__':
+    handler = logging.FileHandler('flask.log', encoding='UTF-8')
+    app.logger.setLevel("INFO")
+    logging_format = logging.Formatter(
+        '%(asctime)s - %(levelname)s - %(filename)s -%(lineno)s - %(message)s'
+    )
+    handler.setFormatter(logging_format)
+    app.logger.addHandler(handler)
+    app.run("0.0.0.0", port=17052, debug=False) # 2.177 本地IP
+

+ 1 - 0
captcha_server/chinese_characters.txt

@@ -0,0 +1 @@
+特啦同爱手使清弟时还睡近线却文政完么展便工在不办入强路安起理斥队何听农验它哦呢座口刚叔沙信內笑装往用更站社亲房跑啊章屋马直众立前都其造该瓜片论水夜习白感离下神送常当只认干发到思席研画领极指吗讲九越仗过受这已乐穿原才包丛满岸第千限今色种头被要活或转紧没后向气咯者跟想你吧学如问忙界解坚科一确非经张快顶由争目雪仔拿分小照般表牛命上北各他部之新孩令黑爬无级友睛脸关忽给动嘴南事写应光将情八晚成紫步所呀些飞息似车开边敢匆阵伯志禾四着够底处道衣说难七音伟仪儿通术面形停胜尤二谁题您赶热万深历导帮反收行传少生度侯员位斗会流五岁村因决但轿歌菜地找古再作物力总她样字围性准苦和怎人咱六候冲叶空加付提望外而熟共坐战连读吃点把是让心打于丘那间河整劳老建倒数治女本十接每高竺世身单山土城个敌明类士己失乎觉合门区轻究子定中我雨记体从至必甩半任告就得册民草姑产什报现青算钱太比大压见师国运石取们出怕像句唱知话家旧主眼自野去变化哪重花急火然哥并星别乡多书法月阶回相早意系以天很破的件业带跳仙机了林印声先代旁风渐长进许名晴块阳船也放几实年团此果最军史际脚革又公树呼婆切饭群两做全里平有庄等改未拉来根亮叫次百刻响海结三落走场识教且为义内条慢方寻真观看东日利答兴服枪掉量住好对可背细

+ 195 - 0
captcha_server/predict_model.py

@@ -0,0 +1,195 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+# @Author  : bidikeji
+# @Time    : 2019/11/25 0025 9:54 
+import os
+os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
+os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
+
+from tensorflow.keras import models
+import tensorflow.keras.backend as K
+import tensorflow as tf
+from PIL import Image
+import numpy as np
+import string
+import re
+
+global graph
+total_num = 0
+neg_num = 0
+graph = tf.get_default_graph()
+
+digit_characters = string.digits
+digit_base_model = models.load_model('gru_digit_base_model.h5')
+# arith_characters = '0123456789+*-%'
+# arith_characters = '0123456789+?-×=' #2021/11/17新增几种算术验证码
+arith_characters = '0123456789+?-×/='  #2022/6/21 新增除法,新增两种验证码,两个算术符验证码
+arith_base_model = models.load_model('gru_arith_base_model.h5')  #2021/11/17新增几种算术验证码
+# chinese_characters = '四生乐句付仗斥令仔乎白仙甩他瓜们用丘仪失丛代印册匆禾'
+with open('chinese_characters.txt', encoding='utf-8') as f:
+    chinese_characters = f.read().strip()  # 20200728 更新到524个中文
+chinese_base_model = models.load_model('gru_chinese_base_model.h5') # 20191219 新增 20200728 更新到524个中文
+# english_characters = string.digits + string.ascii_uppercase + string.ascii_lowercase
+english_characters = string.ascii_lowercase + string.digits  # 20200728 更新为全部小写多种验证码
+english_base_model = models.load_model('gru_english_base_model.h5') # 20200518 新增  20200728 更新为全部小写多种验证码
+
+digit_input = digit_base_model.output
+digit_input_length = tf.keras.Input(batch_shape=[None], dtype='int32')
+digit_decode = K.ctc_decode(y_pred=digit_input, input_length=digit_input_length * K.shape(digit_input)[1])
+digit_decode = K.function([digit_base_model.input, digit_input_length], [digit_decode[0][0]])
+
+arith_input = arith_base_model.output
+arith_input_length = tf.keras.Input(batch_shape=[None], dtype='int32')
+arith_decode = K.ctc_decode(y_pred=arith_input, input_length=arith_input_length * K.shape(arith_input)[1])
+arith_decode = K.function([arith_base_model.input, arith_input_length], [arith_decode[0][0]])
+
+chinese_input = chinese_base_model.output
+chinese_input_length = tf.keras.Input(batch_shape=[None], dtype='int32')
+chinese_decode = K.ctc_decode(y_pred=chinese_input, input_length=chinese_input_length * K.shape(chinese_input)[1])
+chinese_decode = K.function([chinese_base_model.input, chinese_input_length], [chinese_decode[0][0]])
+
+english_input = english_base_model.output
+english_input_length = tf.keras.Input(batch_shape=[None], dtype='int32')
+english_decode = K.ctc_decode(y_pred=english_input, input_length=english_input_length * K.shape(english_input)[1])
+english_decode = K.function([english_base_model.input, english_input_length], [english_decode[0][0]])
+
+# def decode_arith(arith = '2×?=12'):
+#     arith = arith.replace('×', '*')
+#     items = re.split('=', arith)
+#     if len(items)==2:
+#         if items[-1] in ['?', '']:
+#             return eval(items[0])
+#         l = re.split('-|\+|\*', items[0])
+#         signs = re.findall('-|\+|\*', items[0])
+#         if len(l)==2 and len(signs)==1:
+#             if l[1] == '?':
+#                 if signs[0] == '+':
+#                     return eval('%s-%s'%(items[-1], l[0]))
+#                 elif signs[0] == '-':
+#                     return eval('%s-%s'%(l[0],items[-1]))
+#                 elif signs[0] == '*':
+#                     return int(eval('%s/%s'%(items[-1], l[0])))
+#             elif l[0] == '?':
+#                 if signs[0] == '+':
+#                     return eval('%s-%s'%(items[-1], l[1]))
+#                 elif signs[0] == '-':
+#                     return eval('%s+%s'%(l[1],items[-1]))
+#                 elif signs[0] == '*':
+#                     return int(eval('%s/%s'%(items[-1], l[1])))
+#     return ''
+
+
+def decode_arith(arith='2×?=12'):
+    try:
+        arith = arith.replace('×', '*')
+        if re.search('^(\d+|\?)([\+\-\*/](\d+|\?))+=(\d+|\?)?$', arith) and len(re.findall('\?', arith)) <= 1:
+            if arith[-1] == '?':
+                answer = str(int(eval(arith[:-2])))
+            elif arith[-1] == '=':
+                answer = str(int(eval(arith[:-1])))
+            elif re.search('^(\d+|\?)[\+\-\*/](\d+|\?)=\d+$', arith):
+                a, sign, b, _, quest = re.split('(\+|\-|\*|×|/|=)', arith)
+                if a == '?':
+                    if sign == "+":
+                        sign = '-'
+                    elif sign == '-':
+                        sign = '+'
+                    elif sign == "*":
+                        sign = '/'
+                    elif sign == '/':
+                        sign = '*'
+                    a, quest = quest, a
+                elif b == '?':
+                    if sign == "+":
+                        sign = '-'
+                        b, quest = quest, b
+                        a, b = b, a
+                    elif sign == '-':
+                        b, quest = quest, b
+                    elif sign == "*":
+                        sign = '/'
+                        b, quest = quest, b
+                        a, b = b, a
+                    elif sign == '/':
+                        b, quest = quest, b
+                else:
+                    print('公式出错:', arith)
+                answer = str(int(eval('%s%s%s' % (a, sign, b))))
+            else:
+                print('公式出错:', arith)
+        else:
+            answer = ''
+        return answer
+    except:
+        answer = ''
+        return answer
+
+def predict_digit(img):
+    img_arr = np.array(img.resize((100, 50), Image.BILINEAR)) / 255.0
+    X_test = np.array([img_arr])
+    with graph.as_default():
+        out_pre = digit_decode([X_test, np.ones(X_test.shape[0])])[0]
+        # y_pred = digit_base_model.predict(X_test)
+        # out_pre = K.get_value(K.ctc_decode(y_pred, input_length=np.ones(y_pred.shape[0]) * y_pred.shape[1])[0][0])[:, :6]
+    out = ''.join([digit_characters[x] for x in out_pre[0]])
+    return out
+
+def predict_arith(img):
+    # img_arr = np.array(img.resize((100, 50), Image.BILINEAR)) / 255.0
+    img_arr = np.array(img.resize((200, 64), Image.BILINEAR)) / 255.0  #20211117更换图片尺寸 20220621 由100,32 改为200 64
+    X_test = np.array([img_arr])
+    with graph.as_default():
+        out_pre = arith_decode([X_test, np.ones(X_test.shape[0])])[0]
+    out = ''.join([arith_characters[x] for x in out_pre[0]])
+    try:
+        out = decode_arith(out)
+    except:
+        out = ""
+    return out
+
+def predict_chinese(img):
+    # img_arr = np.array(img.resize((100, 50), Image.BILINEAR)) / 255.0
+    img_arr = np.array(img.resize((120, 40), Image.BILINEAR)) / 255.0 # 更新两种中文验证码
+    X_test = np.array([img_arr])
+    with graph.as_default():
+        out_pre = chinese_decode([X_test, np.ones(X_test.shape[0])])[0]
+    out = ''.join([chinese_characters[x] for x in out_pre[0]])
+    return out
+
+def predict_english(img):
+    img_arr = np.array(img.resize((200, 70), Image.BILINEAR)) / 255.0  #BILINEAR  NEAREST
+    X_test = np.array([img_arr])
+    with graph.as_default():
+        out_pre = english_decode([X_test, np.ones(X_test.shape[0])])[0]
+    out = ''.join([english_characters[x] for x in out_pre[0]])
+    return out
+
+if __name__ == "__main__":
+    import glob
+    import time
+    import sys
+    import shutil
+    neg = []
+    files = glob.glob(r'E:\linuxPro\captcha_pro\FileInfo0526\标注样本\shensexiansandian\*.jpg')[-3000:]
+    t1 = time.time()
+    for i in range(len(files)):
+        file = files[i].split('\\')[-1]
+        label = files[i].split('\\')[-1].split('_')[0]
+        img = Image.open(files[i])
+        if img.mode != "RGB":
+            img = img.convert("RGB")
+        pre = predict_english(img)
+        if label!=pre:
+            print(file,label, pre)
+            neg.append(file)
+        elif len(label) == 4:
+            if os.path.exists(files[i]):
+                try:
+                    shutil.copy(files[i], 'english_imgs/' + file)
+                except IOError as e:
+                    print('Unable to copy file %s' % e)
+                except:
+                    print('Unexcepted error', sys.exc_info)
+    print(len(neg), time.time()-t1)
+
+

+ 2 - 0
captcha_server/start.sh

@@ -0,0 +1,2 @@
+#!/bin/bash
+nohup python captcha_flask_server.py >> captcha.log &

+ 16 - 2
chinese_equation_denoise/ced_interface.py

@@ -95,5 +95,19 @@ def test_ced_model(from_remote=True):
 
 
 if __name__ == "__main__":
-    app.run(host='127.0.0.1', port=17060, debug=False)
-    # test_ced_model()
+    # app.run(host='127.0.0.1', port=17060, debug=False)
+    test_ced_model()
+
+    # with open(r'C:\Users\Administrator\Downloads\新建文本文档+(3).txt', 'r') as f:
+    #     _b = f.read()
+    # # b_str = str(_b)
+    # # print(len(b_str))
+    # data = base64_decode(_b)
+    # with open(r'C:\Users\Administrator\Downloads\11.jpg', 'wb') as f:
+    #     f.write(data)
+    # image_np = bytes2np(data)
+    # print(image_np.shape)
+    # cv2.imwrite(r'C:\Users\Administrator\Downloads\11.jpg', image_np)
+    # cv2.imshow('img', image_np)
+    # cv2.waitKey(0)
+

+ 3 - 2
chinese_equation_denoise/inference_equation_denoise.py

@@ -11,7 +11,7 @@ from utils import pil_resize
 
 package_dir = os.path.abspath(os.path.dirname(__file__))
 image_shape = (32, 192, 1)
-model_path = package_dir + "/models/e153-loss53.97-denoise.h5"
+model_path = package_dir + "/models/denoise_loss_53.97.h5"
 
 
 def denoise(image_np, model=None, sess=None):
@@ -37,6 +37,7 @@ def denoise(image_np, model=None, sess=None):
             pred = model.predict(X)
     pred = np.uint8(pred[0]*255.)
 
+    # cv2.imshow("origin", image_np)
     # cv2.imshow("pred", pred)
     # cv2.waitKey(0)
     return pred
@@ -44,7 +45,7 @@ def denoise(image_np, model=None, sess=None):
 
 if __name__ == "__main__":
     # _path = "../data/test/char_9.jpg"
-    _paths = glob("../data/equation/*")
+    _paths = glob(r"D:\Project\captcha\data\equation\*")
     # _paths = glob("../data/test/FileInfo1021/*")
     for _path in _paths:
         denoise(cv2.imread(_path))

+ 1 - 0
chinese_equation_recognize/cer_interface.py

@@ -75,6 +75,7 @@ class CerModels:
 
 def test_cer_model(from_remote=True):
     paths = glob("D:/Project/captcha/data/test/FileInfo1021/1d419189-5116-11ed-851c-b4b5b67760ae_7.jpg")
+    paths = glob(r'C:\Users\Administrator\Downloads\default.jfif')
     for file_path in paths:
         img_np = cv2.imread(file_path)
         h, w = img_np.shape[:2]

+ 101 - 0
chinese_equation_recognize/cer_interface_torch.py

@@ -0,0 +1,101 @@
+import base64
+import json
+import logging
+import os
+import sys
+import time
+import traceback
+from glob import glob
+import cv2
+
+# 只导入torch,protobuf会报错。需先导入TensorFlow再导入torch
+import tensorflow
+import torch
+from flask import Flask, request
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
+from chinese_equation_recognize.inference_equation_torch import recognize
+from model_torch import crnn_ctc_equation_torch6
+from utils import pil_resize, np2bytes, request_post, bytes2np, base64_decode, image_to_str, str_to_image
+
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+
+package_dir = os.path.abspath(os.path.dirname(__file__))
+model_path = package_dir + "/models/equation6_model_acc-0.853.pth"
+image_shape = (32, 192, 3)
+
+# 接口配置
+app = Flask(__name__)
+
+
+@app.route('/cer', methods=['POST'])
+def cer():
+    start_time = time.time()
+    logging.info("into cer_interface cer")
+    try:
+        # 接收网络数据
+        if not request.form:
+            logging.info("cer no data!")
+            return json.dumps({"data": "", "success": 0})
+        data = request.form.get("data")
+        logging.info("cer_interface get data time" + str(time.time()-start_time))
+
+        # 加载模型
+        cer_model = globals().get("global_cer_model")
+        if cer_model is None:
+            print("=========== init cer model ===========")
+            cer_model = CerModels().get_model()
+            globals().update({"global_cer_model": cer_model})
+
+        # 数据转换
+        data = base64_decode(data)
+        image_np = bytes2np(data)
+        # 预测
+        result = recognize(image_np, cer_model)
+        if result is None:
+            return json.dumps({"data": "", "success": 0})
+        return json.dumps({"data": result, "success": 1})
+    except:
+        traceback.print_exc()
+        return json.dumps({"data": "", "success": 0})
+    finally:
+        logging.info("cer interface finish time " + str(time.time()-start_time))
+
+
+class CerModels:
+    def __init__(self):
+        device = torch.device("cpu")
+        class_num = 35 + 1
+        self.model = crnn_ctc_equation_torch6(class_num)
+        self.model.load_state_dict(torch.load(model_path, map_location=torch.device(device)))
+        self.model.eval()
+
+    def get_model(self):
+        return self.model
+
+
+def test_cer_model(from_remote=True):
+    paths = glob("D:/Project/captcha/data/test/FileInfo1021/1d419189-5116-11ed-851c-b4b5b67760ae_7.jpg")
+    paths = glob(r'C:\Users\Administrator\Downloads\default.jfif')
+    for file_path in paths:
+        img_np = cv2.imread(file_path)
+        h, w = img_np.shape[:2]
+        file_bytes = np2bytes(img_np)
+        file_base64 = base64.b64encode(file_bytes)
+
+        if from_remote:
+            file_json = {"data": file_base64}
+            # _url = "http://192.168.2.102:17061/cer"
+            _url = "http://127.0.0.1:17061/cer"
+            result = json.loads(request_post(_url, file_json))
+            if result.get("success"):
+                result = int(result.get("data"))
+                cv2.imshow("img_np", img_np)
+                print("equation result", result)
+                cv2.waitKey(0)
+            else:
+                print("failed!")
+
+
+if __name__ == "__main__":
+    # app.run(host='127.0.0.1', port=17061, debug=False)
+    test_cer_model()

+ 36 - 0
chinese_equation_recognize/equation_torch.txt

@@ -0,0 +1,36 @@
+@
+1
+2
+3
+4
+5
+6
+7
+8
+9
+0
+一
+二
+三
+四
+五
+六
+七
+八
+九
+零
+加
+减
+乘
+除
++
+-
+*
+/
+=
+?
+上
+去
+以

+ 1 - 1
chinese_equation_recognize/inference_equation.py

@@ -13,7 +13,7 @@ package_dir = os.path.abspath(os.path.dirname(__file__))
 image_shape = (32, 192, 1)
 model_path = package_dir + "/models/e55-loss0.14-equation.h5"
 
-with open(package_dir + "/equation.txt", 'r') as f:
+with open(package_dir + "/equation.txt", 'r', encoding='utf-8') as f:
     char_list = f.readlines()
 char_str = "".join(char_list)
 char_str = re.sub("\n", "", char_str)

+ 134 - 0
chinese_equation_recognize/inference_equation_torch.py

@@ -0,0 +1,134 @@
+import os
+import random
+import re
+import sys
+from glob import glob
+import cv2
+import numpy as np
+import torch
+from torch.utils.data import DataLoader
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
+from model_torch import crnn_ctc_equation_torch6
+from pre_process_torch import EquationDataset, py_ctc_decode
+
+
+package_dir = os.path.abspath(os.path.dirname(__file__))
+model_path = package_dir + "/models/equation6_model_acc-0.853.pth"
+
+random.seed(42)
+device = torch.device("cpu")
+image_shape = (32, 192, 3)
+project_root = os.path.dirname(os.path.abspath(__file__)) + "/../"
+class_num = 35 + 1
+batch_size = 1
+input_len = 12
+label_len = 8
+
+with open(package_dir + "/equation_torch.txt", 'r', encoding='utf-8') as f:
+    char_list = f.readlines()
+char_str = "".join(char_list)
+char_str = re.sub("\n", "", char_str)
+
+
+def recognize(image_np, model=None):
+    if model is None:
+        model = crnn_ctc_equation_torch6(class_num)
+        model.load_state_dict(torch.load(model_path, map_location=torch.device(device)))
+        model.eval()
+
+    # print('type(image_np)', type(image_np))
+    dataset = EquationDataset([image_np], image_shape, input_len, label_len, channel=image_shape[-1], mode=1)
+    data_loader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
+
+    calculate_result = None
+    with torch.no_grad():
+        for data, targets, _, _ in data_loader:
+            data = data.to(device)
+            data = data.to(torch.float32)
+            outputs = model(data)
+            result_list = py_ctc_decode(outputs)[0]
+
+            for result in result_list:
+                cal = calculate(result)
+                if cal:
+                    calculate_result = cal
+                    break
+
+    print("cer result", result, calculate_result)
+    return calculate_result
+
+
+def calculate(_list):
+    char_dict = {
+        "1": 1,
+        "2": 2,
+        "3": 3,
+        "4": 4,
+        "5": 5,
+        "6": 6,
+        "7": 7,
+        "8": 8,
+        "9": 9,
+        "0": 0,
+        "一": 1,
+        "二": 2,
+        "三": 3,
+        "四": 4,
+        "五": 5,
+        "六": 6,
+        "七": 7,
+        "八": 8,
+        "九": 9,
+        "零": 0,
+        "加": "加",
+        "减": "减",
+        "乘": "乘",
+        "除": "除",
+        "+": "加",
+        "-": "减",
+        "*": "乘",
+        "×": "乘",
+        "/": "除",
+        "÷": "除",
+        "=": "",
+        "?": "",
+        "上": "",
+        "去": "",
+        "以": "",
+    }
+    equation_str = ""
+    for c in _list:
+        equation_str += str(char_dict.get(c))
+    op = re.findall("加|减|乘|除", equation_str)
+    op = list(set(op))
+    if len(op) != 1:
+        return None
+    nums = re.split("加|减|乘|除", equation_str)
+    if len(nums) != 2:
+        return None
+    try:
+        num1 = int(nums[0])
+        num2 = int(nums[1])
+    except:
+        print("非数字!")
+        return None
+
+    op = op[0]
+    if op == "加":
+        result = num1 + num2
+    elif op == '减':
+        result = num1 - num2
+    elif op == '乘':
+        result = num1 * num2
+    elif op == '除':
+        result = int(num1 / max(num2, 1))
+    return result
+
+
+if __name__ == "__main__":
+    # _path = "../data/test/char_9.jpg"
+    # _path = "../data/equation/38376_减_1_问_加_4_除.jpg"
+    _paths = glob("./*.jpg")
+    for _path in _paths:
+        recognize(cv2.imread(_path))

+ 5 - 1
chinese_equation_recognize/model.py

@@ -339,4 +339,8 @@ class Vgg19:
         return tf.constant(self.data_dict[name][1], name="biases")
 
     def get_fc_weight(self, name):
-        return tf.constant(self.data_dict[name][0], name="weights")
+        return tf.constant(self.data_dict[name][0], name="weights")
+
+
+if __name__ == '__main__':
+    crnn_ctc_equation_loss()

+ 46 - 0
chinese_equation_recognize/model_torch.py

@@ -0,0 +1,46 @@
+import torch.nn as nn
+from torchvision.models import resnet18, resnet34, resnet50, resnet101
+
+
+class crnn_ctc_equation_torch6(nn.Module):
+    def __init__(self, class_num):
+        super(crnn_ctc_equation_torch6, self).__init__()
+        self.class_num = class_num
+
+        # 18 - 512
+        # 50 - 2048
+        # 101 - 2048
+
+        resnet = resnet50(pretrained=False)
+        modules = list(resnet.children())[:-3]
+        self.resnet = nn.Sequential(*modules)
+        self.fc1 = nn.Linear(2048, 256)
+        self.fc2 = nn.Linear(256, self.class_num)
+        self.gru1 = nn.GRU(input_size=256, hidden_size=256)
+        self.dropout = nn.Dropout(0.5)
+        self.relu = nn.LeakyReLU()
+
+        self.log_softmax = nn.LogSoftmax(2)
+
+    def forward(self, x):
+        x = x.permute(0, 3, 1, 2).contiguous()
+        x = self.resnet(x)
+        # print('x0', x.shape)
+        x = x.permute(0, 3, 1, 2).contiguous()
+        # print('x1', x.shape)
+        x = x.view(x.shape[0], x.shape[1], -1)
+        # print('x2', x.shape)
+        x = self.fc1(x)
+        x = self.relu(x)
+        x = self.dropout(x)
+        # print('x3', x.shape)
+        output, _ = self.gru1(x)
+        # print('x4', x.shape)
+        x = self.fc2(output)
+        # x = self.relu(x)
+        x = self.dropout(x)
+        # print('x5', x.shape)
+        # x = x.permute(1, 0, 2)
+
+        x = self.log_softmax(x)
+        return x

BIN
chinese_equation_recognize/models/equation6_model_acc-0.853.pth


+ 141 - 0
chinese_equation_recognize/pre_process_torch.py

@@ -0,0 +1,141 @@
+import os
+import re
+import cv2
+import numpy as np
+from PIL import Image
+import torch.nn.functional as F
+from torch.utils.data import Dataset
+from pyctcdecode import build_ctcdecoder
+
+
+equation_char_dict_path = os.path.dirname(os.path.abspath(__file__)) + "/equation_torch.txt"
+with open(equation_char_dict_path, "r", encoding='utf-8') as f:
+    map_list = f.readlines()
+map_str = "".join(map_list)
+map_str = re.sub("\n", "", map_str)
+
+decoder = build_ctcdecoder([x for x in map_str])
+
+
+class EquationDataset(Dataset):
+    def __init__(self, paths, image_shape, input_len, label_len, mode=0, channel=1):
+        self.image_shape = image_shape
+        self.batch_size = image_shape[0]
+        self.label_len = label_len
+        self.input_len = input_len
+        self.mode = mode
+        self.channel = channel
+
+        with open(equation_char_dict_path, "r", encoding='utf-8') as f:
+            map_list = f.readlines()
+        map_str = "".join(map_list)
+        self.map_str = re.sub("\n", "", map_str)
+
+        self.char_map_dict = {
+            "星": '*',
+            "斜": "/",
+            "问": "?",
+            'x': '×',
+            '?': '?'
+        }
+
+        self.data, self.targets, self.data_len, self.targets_len, self.texts = self.gen_data(paths)
+
+    def gen_data(self, paths):
+        data_x = []
+        data_y = []
+        data_x_len = []
+        data_y_len = []
+        text_list = []
+
+        inference_flag = 0
+        if paths and type(paths[0]) == np.ndarray:
+            inference_flag = 1
+
+        for p in paths:
+            if not inference_flag:
+                path = p.split(os.sep)[-1]
+                char_index_list = []
+                char_len = self.label_len
+                if self.mode == 0:
+                    chars = path.split(".")[0].split('_')[1:]
+                    text_list.append(chars)
+                    char_len = 0
+                    for c in chars:
+                        if c in self.char_map_dict.keys():
+                            c = self.char_map_dict.get(c)
+                        if not c:
+                            continue
+                        char_index_list.append(self.map_str.index(c))
+                        char_len += 1
+
+                char_index_list.extend([0] * (self.label_len - len(char_index_list)))
+                label = np.array(char_index_list)
+
+                img1 = cv2.imread(p)
+            else:
+                label = []
+                char_len = 0
+                img1 = p
+
+            if img1 is None:
+                img_pil = Image.open(p)
+                img1 = pil2np(img_pil)
+            img1 = pil_resize(img1, self.image_shape[0], self.image_shape[1])
+
+            # cv2.imshow("gen_char", img1)
+            # cv2.waitKey(0)
+
+            if self.channel == 1:
+                img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
+                img1 = np.expand_dims(img1, axis=-1)
+            img1 = img1 / 255.
+
+            data_x.append(img1)
+            data_y.append(label)
+            data_x_len.append(self.input_len)
+            data_y_len.append(char_len)
+        return data_x, data_y, data_x_len, data_y_len, text_list
+
+    def __len__(self):
+        return len(self.data)
+
+    def __getitem__(self, idx):
+        x = self.data[idx]
+        y = self.targets[idx]
+        x_len = self.data_len[idx]
+        y_len = self.targets_len[idx]
+        # print(self.texts[idx], x_len, y_len)
+        return x, y, x_len, y_len
+
+
+def py_ctc_decode(logits):
+    # labels = [
+    #     " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
+    #     "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+    # ]
+    batch_result_list = []
+    logits = F.pad(logits, (0, 1), value=-1000)
+    # print('logits.shape', logits.shape)
+    for i in range(logits.shape[0]):
+        # prepare decoder and decode logits via shallow fusion
+        sub_logits = logits[i, :, :].detach().numpy()
+        # start_time = time.time()
+        text = decoder.decode_beams(sub_logits, beam_width=10)
+        # print('logits.numpy() cost', time.time()-start_time)
+        text = [x[0] for x in text]
+        text = [re.sub('@', '', x) for x in text]
+        batch_result_list.append(text)
+    return batch_result_list
+
+
+def pil_resize(image_np, height, width):
+    image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB))
+    image_pil = image_pil.resize((int(width), int(height)), Image.BICUBIC)
+    image_np = cv2.cvtColor(np.asarray(image_pil), cv2.COLOR_RGB2BGR)
+    return image_np
+
+
+def pil2np(image_pil):
+    image_np = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
+    return image_np

+ 57 - 17
dev/click_captcha/inference_char.py

@@ -1,36 +1,76 @@
 import os
+import re
+from glob import glob
 
 import cv2
 import numpy as np
-from click_captcha.model import mobile_net, cnn_net
+from click_captcha.model import mobile_net, cnn_net, cnn_net_tiny, cnn_net_small
 from click_captcha.utils import pil_resize
 
-image_shape = (40, 40, 3)
-weights_path = "./models/char_f1_0.93.h5"
+image_shape = (40, 40, 1)
+weights_path = "./models/e01-acc0.83-char.h5"
 project_dir = os.path.dirname(os.path.abspath(__file__)) + "/../"
 
 
 def recognize(image_path):
-    model = cnn_net(input_shape=image_shape)
+    model = cnn_net_small(input_shape=image_shape)
     model.load_weights(weights_path)
 
-    img = cv2.imread(image_path)
-    img = pil_resize(img, image_shape[0], image_shape[1])
-    cv2.imshow("img", img)
-    cv2.waitKey(0)
-    img = img / 255.
-    X = np.expand_dims(img, 0)
+    paths = glob("../data/test/char_*.jpg")
 
+    X = []
+    for image_path in paths:
+        print(image_path)
+        img = cv2.imread(image_path)
+        img = pil_resize(img, image_shape[0], image_shape[1])
+        # cv2.imshow("img", img)
+        # cv2.waitKey(0)
+        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+        img = np.expand_dims(img, axis=-1)
+        img = img / 255.
+        # X = np.expand_dims(img, 0)
+        X.append(img)
+
+    X = np.array(X)
+    print("X.shape", X.shape)
     pred = model.predict(X)
-    index = int(np.argmax(pred))
-    with open(project_dir + "data/chinese_5710.txt") as f:
-        char_str = f.read()
-    char = char_str[index]
-    print("recognize chinese", char)
-    return char
+    print("pred.shape", pred.shape)
+
+    with open(project_dir + "data/chinese_6270.txt", 'r') as f:
+        char_list = f.readlines()
+    char_str = "".join(char_list)
+    char_str = re.sub("\n", "", char_str)
+    for p in pred:
+        index = int(np.argmax(p))
+        print(char_str[index], p[index])
+
+
+
+    # index_list = []
+    # prob_list = []
+    # for i in range(5):
+    #     index = int(np.argmax(pred))
+    #     index_list.append(index)
+    #     prob_list.append(np.max(pred))
+    #     pred = np.delete(pred, index)
+    #
+    # index = index_list[0]
+    # print("index_list", index_list)
+    # print("index", index)
+    # with open(project_dir + "data/chinese_6270.txt", 'r') as f:
+    #     char_list = f.readlines()
+    # char_str = "".join(char_list)
+    # char_str = re.sub("\n", "", char_str)
+    # char = char_str[index]
+    # print("recognize chinese", char, prob_list[0])
+    #
+    # for i in range(1, len(index_list)):
+    #     print("possible chinese", i, char_str[index_list[i]], prob_list[i])
+    return
 
 
 if __name__ == "__main__":
-    _path = "../data/test/char_6.jpg"
+    # _path = "../data/test/char_9.jpg"
+    _path = "../data/click/80_70_2.jpg"
     # _path = "../data/click/2019_73_1.jpg"
     recognize(_path)

+ 221 - 0
dev/click_captcha/inference_equation.py

@@ -0,0 +1,221 @@
+import os
+import re
+from glob import glob
+
+import cv2
+import numpy as np
+
+from click_captcha.inference_equation_denoise import denoise
+from click_captcha.model import crnn_ctc_equation, ctc_decode, crnn_ctc_equation_large, crnn_ctc_equation_less, \
+    crnn_ctc_equation_loss
+from click_captcha.pre_process import eight_neighbour, connected_component, add_contrast
+from click_captcha.utils import pil_resize
+
+image_shape = (32, 192, 1)
+weights_path = "./models/e55-loss0.14-equation.h5"
+project_dir = os.path.dirname(os.path.abspath(__file__)) + "/../"
+model = crnn_ctc_equation_loss(input_shape=image_shape, class_num=35+2, is_train=False)
+model.load_weights(weights_path)
+
+
+def recognize(image_path):
+    X = []
+    img = cv2.imread(image_path)
+
+    img = pil_resize(img, image_shape[0], image_shape[1])
+    # cv2.imshow("img", img)
+    img = denoise(img)
+    # cv2.imshow("denoise", img)
+    img = add_contrast(img)
+    if img.shape[2] == 3:
+        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+        img = np.expand_dims(img, axis=-1)
+    # _, img = cv2.threshold(img, 110, 255, cv2.THRESH_BINARY)
+
+    # cv2.imshow("contrast", img)
+    # gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+    # gray = eight_neighbour(gray, 4)
+    # cv2.imshow("eight_neighbour", gray)
+    # img2 = connected_component(gray)
+    # cv2.imshow("connected_component", img2)
+
+    # img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+    # img = np.expand_dims(img, axis=-1)
+    img = img / 255.
+    X.append(img)
+
+    X = np.array(X)
+    pred = ctc_decode(X, model)
+    print(image_path)
+    pred = pred[0][0]
+
+    with open(project_dir + "data/equation.txt", 'r') as f:
+        char_list = f.readlines()
+    char_str = "".join(char_list)
+    char_str = re.sub("\n", "", char_str)
+    result_list = []
+    for index in pred:
+        index = int(index-1)
+        # index = int(index)
+        if index < 0:
+            continue
+        # print(char_str[index])
+        result_list.append(char_str[index])
+
+    print(result_list)
+    label = image_path.split("_")[-1].split(".")[0]
+    pred = calculate(result_list)
+    print("计算结果:", pred, label)
+    # cv2.waitKey(0)
+    if pred is None:
+        return 0
+    if int(label) == int(pred):
+        return 1
+    else:
+        return 0
+
+
+def calculate1(_list):
+    char_dict = {
+        "1": 1,
+        "2": 2,
+        "3": 3,
+        "4": 4,
+        "5": 5,
+        "6": 6,
+        "7": 7,
+        "8": 8,
+        "9": 9,
+        "0": 0,
+        "一": 1,
+        "二": 2,
+        "三": 3,
+        "四": 4,
+        "五": 5,
+        "六": 6,
+        "七": 7,
+        "八": 8,
+        "九": 9,
+        "零": 0,
+        "加": "加",
+        "减": "减",
+        "乘": "乘",
+        "除": "除",
+        "+": "加",
+        "-": "减",
+        "*": "乘",
+        "×": "乘",
+        "/": "除",
+        "÷": "除",
+        "=": "",
+        "?": "",
+        "上": "",
+        "去": "",
+        "以": "",
+    }
+    equation_str = ""
+    for c in _list:
+        equation_str += str(char_dict.get(c))
+    op = re.findall("加|减|乘|除", equation_str)
+    op = list(set(op))
+    if len(op) != 1:
+        return None
+    nums = re.split("加|减|乘|除", equation_str)
+    if len(nums) != 2:
+        return None
+    try:
+        num1 = int(nums[0])
+        num2 = int(nums[1])
+    except:
+        print("非数字!")
+        return None
+
+    op = op[0]
+    if op == "加":
+        result = num1 + num2
+    elif op == '减':
+        result = num1 - num2
+    elif op == '乘':
+        result = num1 * num2
+    elif op == '除':
+        result = int(num1 / max(num2, 1))
+    return result
+
+
+def calculate(_list):
+    char_dict = {
+        "1": 1,
+        "2": 2,
+        "3": 3,
+        "4": 4,
+        "5": 5,
+        "6": 6,
+        "7": 7,
+        "8": 8,
+        "9": 9,
+        "0": 0,
+        "一": 1,
+        "二": 2,
+        "三": 3,
+        "四": 4,
+        "五": 5,
+        "六": 6,
+        "七": 7,
+        "八": 8,
+        "九": 9,
+        "零": 0,
+        "加": "加",
+        "减": "减",
+        "乘": "乘",
+        "除": "除",
+        "+": "加",
+        "-": "减",
+        "*": "乘",
+        "×": "乘",
+        "/": "除",
+        "÷": "除",
+        "=": "",
+        "?": "",
+        "上": "",
+        "去": "",
+        "以": "",
+    }
+    equation_str = ""
+    for c in _list:
+        equation_str += str(char_dict.get(c))
+    op = re.findall("加|减|乘|除", equation_str)
+    op = list(set(op))
+    if len(op) != 1:
+        return None
+    nums = re.split("加|减|乘|除", equation_str)
+    if len(nums) != 2:
+        return None
+    try:
+        num1 = int(nums[0])
+        num2 = int(nums[1])
+    except:
+        print("非数字!")
+        return None
+
+    op = op[0]
+    if op == "加":
+        result = num1 + num2
+    elif op == '减':
+        result = num1 - num2
+    elif op == '乘':
+        result = num1 * num2
+    elif op == '除':
+        result = int(num1 / max(num2, 1))
+    return result
+
+
+if __name__ == "__main__":
+    # _path = "../data/test/char_9.jpg"
+    # _path = "../data/equation/38376_减_1_问_加_4_除.jpg"
+    _paths = glob("../data/test/FileInfo1021/*")
+    right_num = 0
+    for _path in _paths:
+        r = recognize(_path)
+        if r:
+            right_num += 1
+    print("准确率:", right_num / len(_paths), right_num, len(_paths))

+ 51 - 0
dev/click_captcha/inference_equation_denoise.py

@@ -0,0 +1,51 @@
+import os
+import re
+from glob import glob
+import cv2
+import numpy as np
+from click_captcha.model import u_net_denoise
+from click_captcha.pre_process import add_contrast
+from click_captcha.utils import pil_resize
+
+image_shape = (32, 192, 1)
+weights_path = "./models/e153-loss53.97-denoise.h5"
+project_dir = os.path.dirname(os.path.abspath(__file__)) + "/../"
+model = u_net_denoise(input_shape=image_shape, class_num=image_shape[2])
+model.load_weights(weights_path)
+
+
+def denoise(image_np):
+    X = []
+    # img = cv2.imread(image_path)
+    img = pil_resize(image_np, image_shape[0], image_shape[1])
+    # cv2.imshow("img", img)
+
+    # img = add_contrast(img)
+    # cv2.imshow("contrast", img)
+    # cv2.waitKey(0)
+    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+    img = np.expand_dims(img, axis=-1)
+    img = img / 255.
+    X.append(img)
+
+    X = np.array(X)
+    pred = model.predict(X)
+    # print(pred.shape)
+
+    pred = np.uint8(pred[0]*255.)
+
+    # pred = cv2.adaptiveThreshold(pred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,
+    #                              11, 20)
+    #
+    # cv2.imshow("pred", pred)
+    # cv2.waitKey(0)
+
+    return pred
+
+
+if __name__ == "__main__":
+    # _path = "../data/test/char_9.jpg"
+    _paths = glob("../data/equation/*")
+    # _paths = glob("../data/test/FileInfo1021/*")
+    for _path in _paths:
+        denoise(cv2.imread(_path))

+ 2 - 2
dev/click_captcha/inference_yolo_char.py

@@ -71,7 +71,7 @@ def get_tiny_inference_model(anchors, num_classes, weights_path='models/tiny_yol
 
 if __name__ == '__main__':
     _dir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
-    model_path = _dir + "/models/char_yolo_loss_39.90.h5"
+    model_path = _dir + "/models/e14-loss17.08-char.h5"
     anchors = get_anchors(_dir + "/yolo_data/my_anchors.txt")
     class_names = get_classes(_dir + "/yolo_data/my_classes.txt")
     colors = get_colors(len(class_names))
@@ -79,6 +79,6 @@ if __name__ == '__main__':
     yolo_model = get_tiny_inference_model(anchors, len(class_names), weights_path=model_path)
     yolo_model.load_weights(model_path)
 
-    image_path = "../data/test/phrase_5.jpg"
+    image_path = "D:/Project/captcha/data/test/yolo_20.jpg"
     # image_path = "../data/detect/1.jpg"
     detect_char(image_path, yolo_model)

+ 514 - 3
dev/click_captcha/loss.py

@@ -2,6 +2,10 @@ import os
 import sys
 import tensorflow as tf
 import keras.backend as K
+from keras import Input
+import numpy as np
+np.set_printoptions(threshold=np.inf)
+from keras.engine.base_layer import Layer
 from tensorflow.python.ops.control_flow_ops import while_loop
 sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
@@ -45,9 +49,12 @@ def l2_loss():
     return mse
 
 
-def l2_focal_loss(threshold=0.2):
+def l2_focal_loss(threshold=0.2, ratio=1000, reverse=False):
     def mse(y_true, y_pred):
-        y_minus = tf.where(tf.abs(y_pred-y_true) <= threshold, tf.abs(y_pred-y_true), 1000*tf.abs(y_pred-y_true))
+        if reverse:
+            y_minus = tf.where(tf.abs(y_pred-y_true) <= threshold, 1/ratio*tf.abs(y_pred-y_true), 0.1*tf.abs(y_pred-y_true))
+        else:
+            y_minus = tf.where(tf.abs(y_pred-y_true) <= threshold, tf.abs(y_pred-y_true), ratio*tf.abs(y_pred-y_true))
         return tf.reduce_mean(tf.square(y_minus))
     return mse
 
@@ -134,4 +141,508 @@ def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False):
         loss += xy_loss * 10 + wh_loss * 10 + confidence_loss
         # if print_loss:
         #     loss = tf.Print(loss, [loss, xy_loss, wh_loss, confidence_loss, class_loss, K.sum(ignore_mask)], message='loss: ')
-    return loss
+    return loss
+
+
+def ctc_lambda_func(args):
+    """
+        定义ctc损失函数
+        参数:y_pred:预测值,labels:标签,input_length:lstm tiemstep,label_length:标签长度
+    """
+    y_pred, labels, input_length, label_length = args
+    # return K.ctc_batch_cost(labels, y_pred, input_length, label_length)
+    return my_ctc_batch_cost(labels, y_pred, input_length, label_length, mode=0)
+
+
+def my_ctc_batch_cost(y_true, y_pred, input_length, label_length, mode=0):
+    """Runs CTC loss algorithm on each batch element.
+
+    Args:
+        y_true: tensor `(samples, max_string_length)`
+            containing the truth labels.
+        y_pred: tensor `(samples, time_steps, num_categories)`
+            containing the prediction, or output of the softmax.
+        input_length: tensor `(samples, 1)` containing the sequence length for
+            each batch item in `y_pred`.
+        label_length: tensor `(samples, 1)` containing the sequence length for
+            each batch item in `y_true`.
+
+    Returns:
+        Tensor with shape (samples,1) containing the
+            CTC loss of each element.
+    """
+    input_length = tf.cast(
+        tf.squeeze(input_length, axis=-1), tf.int32)
+    label_length = tf.cast(
+        tf.squeeze(label_length, axis=-1), tf.int32)
+
+    sparse_labels = tf.cast(
+        K.ctc_label_dense_to_sparse(y_true, label_length), tf.int32)
+
+    y_pred = tf.math.log(tf.compat.v1.transpose(y_pred, perm=[1, 0, 2]) + K.epsilon())
+
+    loss = tf.compat.v1.nn.ctc_loss(inputs=y_pred,
+                                    labels=sparse_labels,
+                                    sequence_length=input_length,
+                                    preprocess_collapse_repeated=False,
+                                    ctc_merge_repeated=True)
+    loss = tf.expand_dims(loss, 1)
+
+    if mode == 1:
+        loss = focal_ctc(sparse_labels, y_pred, input_length, loss)
+
+    # if mode == 2:
+    #     loss = loss + ctc_decode_mse_loss((y_pred, y_true, input_length, label_length))
+    # print("loss1", loss.shape)
+    return loss
+
+
+# @tf.function
+def ctc_decode_mse_loss(args):
+    num_classes = 35+2
+    time_step = 11
+
+    # y_pred [32, 21, 37]
+    y_pred, labels, input_length, label_length = args
+    # print("y_pred", y_pred.shape)
+
+    # y_pred [37, 32, 21]
+    # y_pred = tf.compat.v1.transpose(y_pred, perm=[2, 0, 1])
+    # y_max [32, 21]
+    y_max = tf.argmax(y_pred, axis=-1, name='raw_prediction')
+
+    # 判断是否为预测的字符
+    is_char = tf.greater(y_max, 0)
+    # 错位比较法,找到重复字符
+    char_rep = tf.equal(y_max[:, :-1], y_max[:, 1:])
+    tail = tf.greater(y_max[:, :1], num_classes - 1)
+    char_rep = tf.concat([char_rep, tail], axis=1)
+    # 去掉重复字符之后的字符位置,重复字符取其 最后一次 出现的位置
+    # [32, 21]
+    char_no_rep = tf.math.logical_and(is_char, tf.math.logical_not(char_rep))
+    # char_no_rep = tf.expand_dims(char_no_rep, axis=-1)
+    # char_no_rep = tf.concat([char_no_rep]*37, axis=-1)
+
+    # [32, 21, 37]
+    # y_pred = tf.compat.v1.transpose(y_pred, perm=[1, 2, 0])
+    # y_pred_no_rep [32*?, 37]
+    # y_pred_no_rep = tf.boolean_mask(y_pred, char_no_rep)
+    # y_pred_no_rep [32, ?, 37]
+    # y_pred_no_rep = tf.compat.v1.transpose(y_pred_no_rep, perm=[1, 0, 2])
+
+    # time_step = tf.cast(K.shape(y_pred_no_rep)[0]/K.shape(y_pred)[0], tf.int32)
+    # y_pred_no_rep [32, 21, 37]
+    # y_pred_no_rep = tf.reshape(y_pred_no_rep, (K.shape(y_pred)[0], time_step, K.shape(y_pred_no_rep)[-1]))
+
+    # 填充两个张量的时间步维度到同一大小
+    # y_pred_no_rep = tf.concat([y_pred_no_rep, tf.zeros((K.shape(labels)[0], K.shape(labels)[1], K.shape(y_pred)[2]-K.shape(labels)[2]))],
+    #                    axis=2)
+    # [32, 37, 21]
+    labels = tf.cast(labels, tf.int32)
+    labels = tf.one_hot(labels, depth=num_classes, axis=1, dtype=tf.float32)
+    labels = tf.concat([labels, tf.zeros((K.shape(labels)[0], K.shape(labels)[1], K.shape(y_pred)[2]-K.shape(labels)[2]))],
+                       axis=2)
+
+    # [32, 21, 37]
+    labels = tf.compat.v1.transpose(labels, perm=[0, 2, 1])
+
+    new_label = tf.zeros((1, time_step, num_classes), dtype=tf.float32)
+    # tf.autograph.experimental.set_loop_options(
+    #     shape_invariants=[(new_label, tf.TensorShape([None, None, 37]))]
+    # )
+
+    @tf.function
+    def body(_i, _label):
+        # print("_i", _i)
+        sample = char_no_rep[_i, :]
+        if sample[0]:
+            new_sample = labels[_i:_i+1, 0:1, :]
+            new_sample = tf.cast(new_sample, tf.float32)
+        else:
+            new_sample = tf.zeros((1, 1, 37), dtype=tf.float32)
+        for j in range(1, 11):
+            step = char_no_rep[_i, j]
+            k = 0
+            if step and k < K.shape(labels)[1]:
+                new_sample = tf.concat([new_sample, labels[_i:_i+1, k:k+1, :]], axis=1)
+                k += 1
+            else:
+                new_sample = tf.concat([new_sample, tf.zeros((1, 1, 37), dtype=tf.float32)], axis=1)
+        if _i == 0:
+            _label = new_sample
+        else:
+            _label = tf.concat([_label, new_sample], axis=0)
+        _i = tf.add(_i, 1)
+        return _i, _label
+
+    def cond(_i, _label):
+        return tf.less(_i, K.shape(labels)[0])
+
+    i = tf.constant(1, dtype=tf.int32)
+    # time_step_tensor = tf.constant(time_step, dtype=tf.int32)
+    # num_classes_tensor = tf.constant(num_classes, dtype=tf.int32)
+    _, new_label = tf.while_loop(cond, body, [i, new_label],
+                                 shape_invariants=[i.get_shape(), tf.TensorShape([None, None, 37]),])
+    # print("new_label", new_label.shape)
+    # for i in range(32):
+    #     sample = char_no_rep[i, :]
+    #     if sample[0]:
+    #         new_sample = labels[i:i+1, 0:1, :]
+    #         new_sample = tf.cast(new_sample, tf.float32)
+    #     else:
+    #         new_sample = tf.zeros((1, 1, 37), dtype=tf.float32)
+    #     for j in range(1, 21):
+    #         step = char_no_rep[i, j]
+    #         k = 0
+    #         if step and k < K.shape(labels)[1]:
+    #             new_sample = tf.concat([new_sample, labels[i:i+1, k:k+1, :]], axis=1)
+    #             k += 1
+    #         else:
+    #             new_sample = tf.concat([new_sample, tf.zeros((1, 1, 37), dtype=tf.float32)], axis=1)
+    #     # if i == 0:
+    #     #     new_label = new_sample
+    #     # else:
+    #     new_label = tf.concat([new_label, new_sample], axis=0)
+
+
+    # def cond(_i, _j):
+    #     return tf.less(_i, K.shape(char_no_rep)[-1])
+    #
+    # def body(_i, _j):
+    #     def func1(j):
+    #         tf.add(j, 1)
+    #         return tf.cast(labels[:, j-1], tf.int32)
+    #
+    #     def func2():
+    #         return tf.zeros((K.shape(labels)[0], K.shape(labels)[0]-31), dtype=tf.int32)
+    #
+    #     cond_func = tf.cond(char_no_rep[:, _i], lambda: func1(_j), func2)
+    #     return cond_func
+    #
+    # i = K.constant(1, tf.int32)
+    # j = K.constant(1, tf.int32)
+    # y_pred_no_rep, _ = tf.while_loop(cond, body, [i, j])
+
+    # pred_sum = tf.reduce_sum(y_pred)
+    # label_sum = tf.reduce_sum(raw_labels)
+    # labels [32, 37, 21]
+    # y_pred [32, 37,   ]
+    # new_label = tf.reshape(new_label, (None, 777))
+    loss = tf.reduce_mean(tf.abs((new_label-y_pred)), axis=-1)
+    loss = tf.reduce_mean(loss, axis=-1)
+    loss = tf.expand_dims(loss, -1)
+    # loss = tf.reduce_mean(loss, axis=-1)
+    # print("loss2", loss.shape)
+    # loss.set_shape(None, 1)
+    # print("loss22", loss.shape)
+    return loss
+
+
+def ctc_decode_mse_loss2(args):
+    batch_size = 32
+    num_classes = 35+2
+    time_step = 21
+    label_len = 8
+    blank_index = num_classes-1
+
+    # [32, 21, 37]
+    y_pred, labels, input_length, label_length = args
+    # [32, 21]
+    y_max = tf.argmax(y_pred, axis=-1, name='raw_prediction', output_type=tf.int32)
+
+    # [32, 8]
+    labels = tf.cast(labels, tf.int32)
+    # [batch, step]
+    # new_label = tf.zeros((batch_size, time_step), dtype=tf.int32)
+    new_label = tf.fill((batch_size, time_step), blank_index)
+
+    @tf.function
+    def body(_i, _label):
+
+        # new_sample = tf.zeros((1, time_step), dtype=tf.int32)
+        new_sample = tf.fill((1, time_step), blank_index)
+        for j in range(0, label_len):
+            # if tf.greater(0, y_max[_i, j]):
+            find_flag = False
+            for k in range(0, time_step):
+                # 循环y_pred,找对应labels,会漏掉
+                # if k < K.shape(labels)[1] and tf.equal(y_max[_i, j], labels[_i, k]):
+                #     # tf.print("equal", y_max[_i, j], labels[_i, k])
+                #     if j == 0:
+                #         new_sample = tf.concat([labels[_i:_i+1, k:k+1], new_sample[:, j+1:]], axis=-1)
+                #     elif j >= time_step-1:
+                #         new_sample = tf.concat([new_sample[:, :j], labels[_i:_i+1, k:k+1]], axis=-1)
+                #     else:
+                #         new_sample = tf.concat([new_sample[:, :j], labels[_i:_i+1, k:k+1], new_sample[:, j+1:]], axis=-1)
+
+                # 循环labels,找对应y_pred,漏掉的找个0位置覆盖
+                # tf.print("labels", labels[_i], last_k, j, labels[_i].shape, new_sample.shape)
+                if tf.equal(y_max[_i, k], labels[_i, j]) and tf.not_equal(y_max[_i, k], blank_index):
+                    find_flag = True
+                    if k == 0:
+                        new_sample = tf.concat([labels[_i:_i+1, j:j+1], new_sample[:, k+1:]], axis=-1)
+                    elif k >= time_step-1:
+                        new_sample = tf.concat([new_sample[:, :k], labels[_i:_i+1, j:j+1]], axis=-1)
+                    else:
+                        new_sample = tf.concat([new_sample[:, :k], labels[_i:_i+1, j:j+1], new_sample[:, k+1:]], axis=-1)
+                    # tf.print("new_sample", new_sample, last_k, j, K.shape(labels[_i]), K.shape(new_sample))
+            if not find_flag and tf.not_equal(labels[_i, j], blank_index):
+                find_flag2 = False
+                for k in range(0, time_step):
+                    if not find_flag2  and tf.equal(new_sample[0, k], blank_index):
+                        find_flag2 = True
+                        if k == 0:
+                            new_sample = tf.concat([labels[_i:_i+1, j:j+1], new_sample[:, k+1:]], axis=-1)
+                        elif k >= time_step-1:
+                            new_sample = tf.concat([new_sample[:, :k], labels[_i:_i+1, j:j+1]], axis=-1)
+                        else:
+                            new_sample = tf.concat([new_sample[:, :k], labels[_i:_i+1, j:j+1], new_sample[:, k+1:]], axis=-1)
+                    # tf.print("new_sample", new_sample, labels[_i, j], find_flag, find_flag2, summarize=100)
+            # tf.print("new_sample", new_sample, summarize=100)
+        tf.print("y_max[_i]", y_max[_i], summarize=100)
+        tf.print("new_samele", new_sample, summarize=100)
+        tf.print("labels[_i]", labels[_i], summarize=100)
+        tf.print("loss", tf.reduce_mean(tf.abs((y_max[_i]-new_sample)), axis=-1))
+
+        if _i == 0:
+            _label = tf.concat([new_sample[:, :], _label[_i+1:, :]], axis=0)
+        elif _i >= time_step-1:
+            _label = tf.concat([_label[:_i, :], new_sample[:, :]], axis=0)
+        else:
+            _label = tf.concat([_label[:_i, :], new_sample[:, :], _label[_i+1:, :]], axis=0)
+        _i = tf.add(_i, 1)
+        return _i, _label
+
+    def cond(_i, _label):
+        return tf.less(_i, K.shape(labels)[0])
+
+    i = tf.constant(1, dtype=tf.int32)
+    _, new_label = tf.while_loop(cond, body, [i, new_label],
+                                 shape_invariants=[i.get_shape(), tf.TensorShape([None, None])])
+    new_label = tf.one_hot(new_label, depth=num_classes, axis=1, dtype=tf.float32)
+    new_label = tf.compat.v1.transpose(new_label, perm=[0, 2, 1])
+
+    # print("y_pred", y_pred.shape)
+    # print("new_label", new_label.shape)
+
+    loss = tf.reduce_mean(tf.abs((new_label-y_pred)), axis=-1)
+    loss = tf.reduce_mean(loss*1, axis=-1)
+    loss = tf.expand_dims(loss, -1)
+    return loss
+
+
+class CtcDecodeMseLoss(Layer):
+    def __init__(self, **kwargs):
+        super(CtcDecodeMseLoss, self).__init__(**kwargs)
+
+    def build(self, input_shape):
+        # Create a trainable weight variable for this layer.
+        super(CtcDecodeMseLoss, self).build(input_shape)  # Be sure to call this somewhere!
+
+    def call(self, inputs):
+        # y_pred [32, 21, 37]
+        y_pred, labels, input_length, label_length = inputs
+
+        # y_max [32, 21]
+        y_max = tf.argmax(y_pred, axis=-1, name='raw_prediction')
+        num_classes = 35+2
+        # 判断是否为预测的字符
+        is_char = tf.greater(y_max, 0)
+        # 错位比较法,找到重复字符
+        char_rep = tf.equal(y_max[:, :-1], y_max[:, 1:])
+        tail = tf.greater(y_max[:, :1], num_classes - 1)
+        char_rep = tf.concat([char_rep, tail], axis=1)
+        # 去掉重复字符之后的字符位置,重复字符取其 最后一次 出现的位置
+        # [32, 21]
+        char_no_rep = tf.math.logical_and(is_char, tf.math.logical_not(char_rep))
+
+        # [32, 37, 21]
+        labels = tf.cast(labels, tf.int32)
+        labels = tf.one_hot(labels, depth=37, axis=1, dtype=tf.float32)
+        labels = tf.concat([labels, tf.zeros((K.shape(labels)[0], K.shape(labels)[1], K.shape(y_pred)[2]-K.shape(labels)[2]))],
+                           axis=2)
+
+        # [32, 21, 37]
+        labels = tf.compat.v1.transpose(labels, perm=[0, 2, 1])
+
+        for i in range(32):
+            sample = char_no_rep[i, :]
+            if sample[0]:
+                new_sample = labels[i:i+1, 0:1, :]
+                new_sample = tf.cast(new_sample, tf.float32)
+            else:
+                new_sample = tf.zeros((1, 1, 37), dtype=tf.float32)
+            for j in range(1, 21):
+                step = char_no_rep[i, j]
+                k = 0
+                if step and k < K.shape(labels)[1]:
+                    new_sample = tf.concat([new_sample, labels[i:i+1, k:k+1, :]], axis=1)
+                    k += 1
+                else:
+                    new_sample = tf.concat([new_sample, tf.zeros((1, 1, 37), dtype=tf.float32)], axis=1)
+            if i == 0:
+                new_label = new_sample
+            else:
+                new_label = tf.concat([new_label, new_sample], axis=0)
+
+        loss = tf.reduce_mean(tf.abs((new_label-y_pred)*100))
+        # loss = tf.expand_dims(loss, 1)
+        print("loss2", loss.shape)
+        return loss
+
+    def compute_output_shape(self, input_shape):
+        return (K.shape(input_shape)[0], 1)
+
+
+def focal_ctc(targets, logits, seq_len, ctc_loss, alpha=0.8, gamma=2.0):
+    # FOCAL LOSS
+    # This function computes Focal Loss
+    # Inputs: alpha, gamma, targets, logits, seq_len
+    # Default Values: alpha=0.5 and gamma=2.0
+    # Output: loss
+
+    # ctc_loss = tf.compat.v1.nn.ctc_loss(labels=targets, inputs=logits, sequence_length=seq_len, time_major=True)
+    p = tf.exp(-ctc_loss)
+    # ((alpha)*((1-p)**gamma)*(ctc_loss))
+    focal_ctc_loss = tf.multiply(tf.multiply(alpha, tf.pow((1-p), gamma)), ctc_loss)
+    loss = tf.reduce_mean(focal_ctc_loss)
+
+    return loss
+
+
+def ctc_center_loss(labels, features, _lambda=0.0005):
+    def center_loss(labels, features, alpha=0.6, num_classes=240):
+        """
+        获取center loss及更新样本的center
+        :param labels: Tensor,表征样本label,非one-hot编码,shape应为(batch_size,).
+        :param features: Tensor,表征样本特征,最后一个fc层的输出,shape应该为(batch_size, num_classes).
+        :param alpha: 0-1之间的数字,控制样本类别中心的学习率,细节参考原文.
+        :param num_classes: 整数,表明总共有多少个类别,网络分类输出有多少个神经元这里就取多少.
+        :return: Tensor, center-loss, shape因为(batch_size,)
+        """
+        # 获取特征的维数,例如256维
+        len_features = features.get_shape()[1]
+        # 建立一个Variable,shape为[num_classes, len_features],用于存储整个网络的样本中心,
+        # 设置trainable=False是因为样本中心不是由梯度进行更新的
+        centers = tf.compat.v1.get_variable('centers', [num_classes, len_features], dtype=tf.float32,
+                                  initializer=tf.constant_initializer(0), trainable=False)
+        # 将label展开为一维的,如果labels已经是一维的,则该动作其实无必要
+        labels = tf.reshape(labels, [-1])
+
+        # 根据样本label,获取mini-batch中每一个样本对应的中心值
+        centers_batch = tf.gather(centers, labels)
+
+        # 当前mini-batch的特征值与它们对应的中心值之间的差
+        diff = centers_batch - features
+
+        # 获取mini-batch中同一类别样本出现的次数,了解原理请参考原文公式(4)
+        unique_label, unique_idx, unique_count = tf.unique_with_counts(labels)
+        appear_times = tf.gather(unique_count, unique_idx)
+        appear_times = tf.reshape(appear_times, [-1, 1])
+
+        diff = diff / tf.cast((1 + appear_times), tf.float32)
+        diff = alpha * diff
+
+        # 更新centers
+        centers_update_op = tf.compat.v1.scatter_sub(centers, labels, diff)
+
+        # 这里使用tf.control_dependencies更新centers
+        with tf.control_dependencies([centers_update_op]):
+            # 计算center-loss
+            c_loss = tf.nn.l2_loss(features - centers_batch)
+        return c_loss
+
+    def get_slice(pos):
+        feature_one_char = features[pos[1], pos[0], :]
+        return feature_one_char
+
+    num_classes = 35+2
+    # 判断是否为预测的字符
+    raw_pred = tf.argmax(features, axis=2, name='raw_prediction')
+    is_char = tf.greater(raw_pred, 0)
+    # 错位比较法,找到重复字符
+    char_rep = tf.equal(raw_pred[:, :-1], raw_pred[:, 1:])
+    tail = tf.greater(raw_pred[:, :1], num_classes - 1)
+    char_rep = tf.concat([char_rep, tail], axis=1)
+    # 去掉重复字符之后的字符位置,重复字符取其 最后一次 出现的位置
+    char_no_rep = tf.math.logical_and(is_char, tf.math.logical_not(char_rep))
+    char_pos = tf.boolean_mask(features, char_no_rep)
+
+    features = tf.map_fn(get_slice, char_pos, dtype=tf.float32)
+
+    labels = K.cast(labels, dtype=tf.float32)
+    # softmax loss
+    s_loss = K.categorical_crossentropy(labels, K.softmax(features, axis=-1))
+    # center loss
+    c_loss = center_loss(K.argmax(labels, axis=-1), features)
+    return s_loss + _lambda * c_loss
+
+
+def ctc_center_accuracy(y_true, y_pred):
+    """
+    重写categorical_accuracy函数,以适应去掉softmax层的模型
+    :param y_true: 等同于labels,
+    :param y_pred: 等同于features。
+    :return: 准确率
+    """
+    # 计算y_pred的softmax值
+    sm_y_pred = K.softmax(y_pred, axis=-1)
+    # 返回准确率
+    return K.cast(K.equal(K.argmax(y_true, axis=-1), K.argmax(sm_y_pred, axis=-1)), K.floatx())
+
+
+def ctc_accuracy(y_true, y_pred):
+    # 使用CTC decoder
+    decoded = K.ctc_decode(y_pred, input_length=21, greedy=False, beam_width=6)
+
+    # 计算编辑距离
+    distance = tf.edit_distance(tf.cast(decoded[0], tf.int32), y_true)
+    # 计算label error rate (accuracy)
+    label_error_rate = tf.reduce_mean(distance, name='label_error_rate')
+    return label_error_rate
+
+
+def perceptual_loss(gamma=2., alpha=.25):
+    from click_captcha.model import Vgg19
+    def perceptual_loss_fixed(y_true, y_pred):
+        if globals().get("vgg") is None:
+            vgg = Vgg19("./vgg19.npy")
+            globals().update({"vgg": vgg})
+            print("init vgg19 success!")
+        else:
+            vgg = globals().get("vgg")
+
+        # mask_1 = tf.where(y_true[:, :, :, 0] >= 0.75, 1, 0)
+        # mask_2 = tf.where(y_true[:, :, :, 1] >= 0.75, 1, 0)
+        # mask_3 = tf.where(y_true[:, :, :, 2] >= 0.75, 1, 0)
+        # mask_white = tf.expand_dims(mask_1 * mask_2 * mask_3, -1)
+        # mask_white = tf.concat([mask_white, mask_white, mask_white], -1)
+        # y_true_mask = tf.where(mask_white == 1, 1., y_true)
+        # y_pred_mask = tf.where(mask_white == 1, 1., y_pred)
+
+        # print("y_pred.shape", y_pred.shape)
+        y_pred = tf.concat([y_pred, y_pred, y_pred], -1)
+        y_true = tf.concat([y_true, y_true, y_true], -1)
+
+        vgg.build(y_true)
+        vgg_true_1 = vgg.conv1_1
+        vgg_true_2 = vgg.conv2_1
+        vgg_true_3 = vgg.conv3_1
+        vgg_true_4 = vgg.conv4_1
+        vgg_true_5 = vgg.conv5_1
+
+        vgg.build(y_pred)
+        vgg_pred_1 = vgg.conv1_1
+        vgg_pred_2 = vgg.conv2_1
+        vgg_pred_3 = vgg.conv3_1
+        vgg_pred_4 = vgg.conv4_1
+        vgg_pred_5 = vgg.conv5_1
+
+        loss_0 = l2_focal_loss(threshold=0.2, ratio=1000, reverse=True)(y_true, y_pred)
+        loss_1 = l2_focal_loss(threshold=0.2, ratio=1000, reverse=True)(vgg_true_1, vgg_pred_1)
+        loss_2 = l2_focal_loss(threshold=0.2, ratio=1000, reverse=True)(vgg_true_2, vgg_pred_2)
+        loss_3 = l2_focal_loss(threshold=0.2, ratio=1000, reverse=True)(vgg_true_3, vgg_pred_3)
+        loss_4 = l2_focal_loss(threshold=0.2, ratio=1000, reverse=True)(vgg_true_4, vgg_pred_4)
+        loss_5 = l2_focal_loss(threshold=0.2, ratio=1000, reverse=True)(vgg_true_5, vgg_pred_5)
+        return (loss_0+loss_1+loss_2+loss_3+loss_4+loss_5) / 6
+    return perceptual_loss_fixed

+ 557 - 4
dev/click_captcha/model.py

@@ -9,10 +9,11 @@ from keras.engine.base_layer import Layer
 from keras.layers import Lambda, Dense, Conv2D, Reshape, GlobalAveragePooling2D, BatchNormalization, Activation, Add, \
     Multiply, DepthwiseConv2D, LeakyReLU, MaxPooling2D, UpSampling2D, Concatenate, Dropout, concatenate, Embedding, \
     LSTM, \
-    Bidirectional, CuDNNLSTM, Conv1D, MaxPooling1D, GlobalMaxPooling1D
+    Bidirectional, CuDNNLSTM, Conv1D, MaxPooling1D, GlobalMaxPooling1D, GlobalMaxPooling2D, GRU
 import keras.backend as K
 from keras.regularizers import l2
 
+from click_captcha.loss import ctc_lambda_func, ctc_decode_mse_loss, CtcDecodeMseLoss, ctc_decode_mse_loss2
 from click_captcha.utils import compose
 
 
@@ -45,7 +46,7 @@ def yolo_net(input_shape, anchors, num_classes, load_pretrained=True,
     return model
 
 
-def mobile_net(input_shape, output_shape=2496):
+def mobile_net(input_shape, output_shape=5710):
     model = MobileNetV3Small(input_shape, output_shape).build()
     model.summary()
     return model
@@ -96,6 +97,154 @@ def cnn_net(input_shape, output_shape=5710):
     return model
 
 
+def cnn_net_small(input_shape, output_shape=6270):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    conv = Conv2D(128, (3, 3))(down2_pool)
+    bn = BatchNormalization()(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+    conv = Conv2D(128, (3, 3))(rl)
+    bn = BatchNormalization()(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+
+    conv = Conv2D(output_shape, (1, 1), activation='softmax')(rl)
+    pool = GlobalAveragePooling2D()(conv)
+    x = Reshape((output_shape,))(pool)
+
+    model = Model(_input, x)
+    model.summary()
+    return model
+
+
+def cnn_net_tiny(input_shape, output_shape=6270):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    conv = Conv2D(64, (3, 3))(down2_pool)
+    bn = BatchNormalization()(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+    conv = Conv2D(64, (3, 3))(rl)
+    bn = BatchNormalization()(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+
+    conv = Conv2D(output_shape, (1, 1), activation='softmax')(rl)
+    pool = GlobalAveragePooling2D()(conv)
+    x = Reshape((output_shape,))(pool)
+
+
+    #
+    # dense = Dense(16, activation="relu")(rl)
+    # drop = Dropout(0.2)(dense)
+    # dense = Dense(output_shape, activation="softmax")(drop)
+    # drop = Dropout(0.2)(dense)
+    # x = Reshape((output_shape,))(drop)
+
+    model = Model(_input, x)
+    model.summary()
+    return model
+
+
+def cnn_net_tiny_dropout(input_shape, output_shape=6270):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = Dropout(0.2)(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = Dropout(0.2)(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = Dropout(0.2)(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = Dropout(0.2)(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = Dropout(0.2)(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = Dropout(0.2)(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    conv = Conv2D(64, (3, 3))(down2_pool)
+    bn = Dropout(0.2)(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+    conv = Conv2D(64, (3, 3))(rl)
+    bn = Dropout(0.2)(conv)
+    rl = LeakyReLU(alpha=0.1)(bn)
+
+    conv = Conv2D(output_shape, (1, 1), activation='softmax')(rl)
+    pool = GlobalAveragePooling2D()(conv)
+    x = Reshape((output_shape,))(pool)
+
+
+    #
+    # dense = Dense(16, activation="relu")(rl)
+    # drop = Dropout(0.2)(dense)
+    # dense = Dense(output_shape, activation="softmax")(drop)
+    # drop = Dropout(0.2)(dense)
+    # x = Reshape((output_shape,))(drop)
+
+    model = Model(_input, x)
+    model.summary()
+    return model
+
+
 def cnn_net_drag(input_shape, output_shape=260):
     _input = Input(input_shape)
 
@@ -272,6 +421,324 @@ def siamese_net(input_shape, output_shape=2):
     return model
 
 
+def crnn_ctc_equation(input_shape=(32, 192, 3), class_num=32, is_train=True):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(16, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(16, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    down3 = Conv2D(64, (4, 4), use_bias=use_bias)(down2_pool)
+    down3 = BatchNormalization()(down3)
+    down3 = LeakyReLU(alpha=0.1)(down3)
+
+    sq = Lambda(lambda x: K.squeeze(x, axis=1))(down3)
+
+    x = Bidirectional(GRU(32, return_sequences=True))(sq)
+    x = Bidirectional(GRU(32, return_sequences=True))(x)
+
+    x = Dense(class_num, activation='softmax')(x)
+
+    if not is_train:
+        model = Model(inputs=_input, outputs=x)
+
+    else:
+        labels = Input(name='the_labels', shape=[None], dtype='float32')
+        input_length = Input(name='input_length', shape=[1], dtype='int64')
+        label_length = Input(name='label_length', shape=[1], dtype='int64')
+        loss_out = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([x, labels, input_length, label_length])
+        model = Model(inputs=[_input, labels, input_length, label_length], outputs=loss_out)
+        model.summary()
+    return model
+
+
+def crnn_ctc_equation_large(input_shape=(32, 192, 3), class_num=32, is_train=True):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    down3 = Conv2D(128, (4, 4), use_bias=use_bias)(down2_pool)
+    down3 = BatchNormalization()(down3)
+    down3 = LeakyReLU(alpha=0.1)(down3)
+
+    sq = Lambda(lambda x: K.squeeze(x, axis=1))(down3)
+
+    x = Bidirectional(GRU(64, return_sequences=True))(sq)
+    x = Bidirectional(GRU(64, return_sequences=True))(x)
+
+    x = Dense(64, activation="relu")(x)
+    # x = Dropout(rate=0.2)(x)
+    # x = Dense(class_num, activation='softmax')(x)
+
+    if not is_train:
+        model = Model(inputs=_input, outputs=x)
+
+    else:
+        labels = Input(name='the_labels', shape=[None], dtype='float32')
+        input_length = Input(name='input_length', shape=[1], dtype='int64')
+        label_length = Input(name='label_length', shape=[1], dtype='int64')
+        loss_out = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([x, labels, input_length, label_length])
+        model = Model(inputs=[_input, labels, input_length, label_length], outputs=loss_out)
+        model.summary()
+    return model
+
+
+def crnn_ctc_equation_loss(input_shape=(32, 192, 3), class_num=32, is_train=True):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    down3 = Conv2D(128, (4, 4), use_bias=use_bias)(down2_pool)
+    down3 = BatchNormalization()(down3)
+    down3 = LeakyReLU(alpha=0.1)(down3)
+
+    sq = Lambda(lambda x: K.squeeze(x, axis=1))(down3)
+
+    x = Bidirectional(GRU(64, return_sequences=True))(sq)
+    x = Bidirectional(GRU(64, return_sequences=True))(x)
+
+    x = Dense(64, activation="relu")(x)
+    x = Dropout(rate=0.2)(x)
+    x = Dense(class_num, activation='softmax')(x)
+
+    if not is_train:
+        model = Model(inputs=_input, outputs=x)
+
+    else:
+        labels = Input(name='the_labels', shape=[None], dtype='float32')
+        input_length = Input(name='input_length', shape=[1], dtype='int64')
+        label_length = Input(name='label_length', shape=[1], dtype='int64')
+        loss_out_1 = Lambda(ctc_lambda_func, output_shape=(1,),)([x, labels, input_length, label_length])
+        loss_out_2 = Lambda(ctc_decode_mse_loss2, output_shape=(1, ))([x, labels, input_length, label_length])
+        # loss_out_2 = CtcDecodeMseLoss(name='ctc')([x, labels, input_length, label_length])
+        loss_out = Add(name='ctc')([loss_out_1, loss_out_2])
+        model = Model(inputs=[_input, labels, input_length, label_length], outputs=loss_out)
+        model.summary(130)
+    return model
+
+
+def crnn_ctc_equation_less(input_shape=(32, 192, 3), class_num=32, is_train=True):
+    _input = Input(input_shape)
+
+    use_bias = False
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(_input)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down0)
+    down0 = BatchNormalization()(down0)
+    down0 = LeakyReLU(alpha=0.1)(down0)
+    down0_pool = MaxPooling2D((2, 2), strides=(2, 2))(down0)
+
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down0_pool)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    down3 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down2_pool)
+    down3 = BatchNormalization()(down3)
+    down3 = LeakyReLU(alpha=0.1)(down3)
+    down3_pool = MaxPooling2D((2, 2), strides=(2, 2))(down3)
+
+    down4 = Conv2D(128, (3, 3), padding='same', use_bias=use_bias)(down3_pool)
+    down4 = BatchNormalization()(down4)
+    down4 = LeakyReLU(alpha=0.1)(down4)
+    down4_pool = MaxPooling2D((2, 2), strides=(1, 1))(down4)
+
+    sq = Lambda(lambda x: K.squeeze(x, axis=1))(down4_pool)
+
+    x = Bidirectional(GRU(64, return_sequences=True))(sq)
+    x = Bidirectional(GRU(64, return_sequences=True))(x)
+
+    x = Dense(64, activation="relu")(x)
+    x = Dropout(rate=0.3)(x)
+    x = Dense(class_num, activation='softmax')(x)
+
+    if not is_train:
+        model = Model(inputs=_input, outputs=x)
+
+    else:
+        labels = Input(name='the_labels', shape=[None], dtype='float32')
+        input_length = Input(name='input_length', shape=[1], dtype='int64')
+        label_length = Input(name='label_length', shape=[1], dtype='int64')
+        loss_out_1 = Lambda(ctc_lambda_func, output_shape=(1,), )([x, labels, input_length, label_length])
+        loss_out_2 = Lambda(ctc_decode_mse_loss2, output_shape=(1, ))([x, labels, input_length, label_length])
+        loss_out = Add(name='ctc')([loss_out_1, loss_out_2])
+        model = Model(inputs=[_input, labels, input_length, label_length], outputs=loss_out)
+        model.summary()
+    return model
+
+
+def u_net_denoise(input_shape=(32, 192, 3), class_num=3):
+    inputs = Input(shape=input_shape)
+    use_bias = False
+
+    # 128
+    down1 = Conv2D(16, (3, 3), padding='same', use_bias=use_bias)(inputs)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1 = Conv2D(16, (1, 1), padding='same', use_bias=use_bias)(down1)
+    down1 = BatchNormalization()(down1)
+    down1 = LeakyReLU(alpha=0.1)(down1)
+    down1_pool = MaxPooling2D((2, 2), strides=(2, 2))(down1)
+
+    # 64
+    down2 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(down1_pool)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2 = Conv2D(32, (1, 1), padding='same', use_bias=use_bias)(down2)
+    down2 = BatchNormalization()(down2)
+    down2 = LeakyReLU(alpha=0.1)(down2)
+    down2_pool = MaxPooling2D((2, 2), strides=(2, 2))(down2)
+
+    # 32
+    down3 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down2_pool)
+    down3 = BatchNormalization()(down3)
+    down3 = LeakyReLU(alpha=0.1)(down3)
+    down3 = Conv2D(64, (1, 1), padding='same', use_bias=use_bias)(down3)
+    down3 = BatchNormalization()(down3)
+    down3 = LeakyReLU(alpha=0.1)(down3)
+    down3_pool = MaxPooling2D((2, 2), strides=(2, 2))(down3)
+
+    # 16
+    center = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(down3_pool)
+    center = BatchNormalization()(center)
+    center = LeakyReLU(alpha=0.1)(center)
+    center = Conv2D(64, (1, 1), padding='same', use_bias=use_bias)(center)
+    center = BatchNormalization()(center)
+    center = LeakyReLU(alpha=0.1)(center)
+
+    # 32
+    up3 = UpSampling2D((2, 2))(center)
+    up3 = concatenate([down3, up3], axis=3)
+    up3 = Conv2D(64, (3, 3), padding='same', use_bias=use_bias)(up3)
+    up3 = BatchNormalization()(up3)
+    up3 = LeakyReLU(alpha=0.1)(up3)
+    up3 = Conv2D(64, (1, 1), padding='same', use_bias=use_bias)(up3)
+    up3 = BatchNormalization()(up3)
+    up3 = LeakyReLU(alpha=0.1)(up3)
+
+    # 64
+    up2 = UpSampling2D((2, 2))(up3)
+    up2 = concatenate([down2, up2], axis=3)
+    up2 = Conv2D(32, (3, 3), padding='same', use_bias=use_bias)(up2)
+    up2 = BatchNormalization()(up2)
+    up2 = LeakyReLU(alpha=0.1)(up2)
+    up2 = Conv2D(32, (1, 1), padding='same', use_bias=use_bias)(up2)
+    up2 = BatchNormalization()(up2)
+    up2 = LeakyReLU(alpha=0.1)(up2)
+
+    # 128
+    up1 = UpSampling2D((2, 2))(up2)
+    up1 = K.concatenate([down1, up1], axis=3)
+    up1 = Conv2D(16, (3, 3), padding='same', use_bias=use_bias)(up1)
+    up1 = BatchNormalization()(up1)
+    up1 = LeakyReLU(alpha=0.1)(up1)
+    up1 = Conv2D(16, (1, 1), padding='same', use_bias=use_bias)(up1)
+    up1 = BatchNormalization()(up1)
+    up1 = LeakyReLU(alpha=0.1)(up1)
+
+    classify = Conv2D(class_num, (1, 1), activation='sigmoid')(up1)
+    # classify = Dense(cls_num, activation="softmax")(up1)
+    model = Model(inputs=inputs, outputs=classify)
+    # model.summary()
+    return model
+
+
+def ctc_decode(image, model):
+    x = model.output
+    input_length = Input(batch_shape=[None], dtype='int32')
+    ctc_decode = K.ctc_decode(x, input_length=input_length * K.shape(x)[1], greedy=False, beam_width=100)
+    decode = K.function([model.input, input_length], [ctc_decode[0][0]])
+    out = decode([image, np.ones(image.shape[0])])
+    # print(len(out))
+    # print(len(out[0]))
+    # print(len(out[0][0]))
+    # print(out[0][0])
+    # print(len(out[0][0][0]))
+    # print(out[0][0][0])
+    # print(out[0][0][0][0].shape)
+    # print(out[0][0][0][0])
+    return out
+
+
 class Vgg16:
     def __init__(self, vgg16_npy_path="./vgg16.npy"):
         if vgg16_npy_path is None:
@@ -393,6 +860,93 @@ class Vgg16:
         return tf.constant(self.data_dict[name][0], name="weights")
 
 
+class Vgg19:
+    def __init__(self, vgg19_npy_path=None):
+        if vgg19_npy_path is None:
+            print("there is no vgg_16_npy!")
+            raise
+
+        self.data_dict = np.load(vgg19_npy_path, encoding='latin1', allow_pickle=True).item()
+
+    def build(self, bgr):
+        """
+        load variable from npy to build the VGG
+        :param rgb: rgb image [batch, height, width, 3] values scaled [0, 1]
+        """
+        bgr = bgr * 255.0
+        # bgr = bgr - np.array(VGG_MEAN).reshape((1, 1, 1, 3))
+
+        self.conv1_1 = self.conv_layer(bgr, "conv1_1")
+        self.conv1_2 = self.conv_layer(self.conv1_1, "conv1_2")
+        self.pool1 = self.max_pool(self.conv1_2, 'pool1')
+
+        self.conv2_1 = self.conv_layer(self.pool1, "conv2_1")
+        self.conv2_2 = self.conv_layer(self.conv2_1, "conv2_2")
+        self.pool2 = self.max_pool(self.conv2_2, 'pool2')
+
+        self.conv3_1 = self.conv_layer(self.pool2, "conv3_1")
+        self.conv3_2 = self.conv_layer(self.conv3_1, "conv3_2")
+        self.conv3_3 = self.conv_layer(self.conv3_2, "conv3_3")
+        self.conv3_4 = self.conv_layer(self.conv3_3, "conv3_4")
+        self.pool3 = self.max_pool(self.conv3_4, 'pool3')
+
+        self.conv4_1 = self.conv_layer(self.pool3, "conv4_1")
+        self.conv4_2 = self.conv_layer(self.conv4_1, "conv4_2")
+        self.conv4_3 = self.conv_layer(self.conv4_2, "conv4_3")
+        self.conv4_4 = self.conv_layer(self.conv4_3, "conv4_4")
+        self.pool4 = self.max_pool(self.conv4_4, 'pool4')
+
+        self.conv5_1 = self.conv_layer(self.pool4, "conv5_1")
+        self.conv5_2 = self.conv_layer(self.conv5_1, "conv5_2")
+        self.conv5_3 = self.conv_layer(self.conv5_2, "conv5_3")
+        self.conv5_4 = self.conv_layer(self.conv5_3, "conv5_4")
+        self.pool5 = self.max_pool(self.conv5_4, 'pool5')
+
+    def avg_pool(self, bottom, name):
+        return tf.nn.avg_pool(bottom, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name=name)
+
+    def max_pool(self, bottom, name):
+        return tf.nn.max_pool(bottom, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name=name)
+
+    def conv_layer(self, bottom, name):
+        with tf.compat.v1.variable_scope(name):
+            filt = self.get_conv_filter(name)
+
+            conv = tf.nn.conv2d(bottom, filt, [1, 1, 1, 1], padding='SAME')
+
+            conv_biases = self.get_bias(name)
+            bias = tf.nn.bias_add(conv, conv_biases)
+
+            relu = tf.nn.relu(bias)
+            return relu
+
+    def fc_layer(self, bottom, name):
+        with tf.compat.v1.variable_scope(name):
+            shape = bottom.get_shape().as_list()
+            dim = 1
+            for d in shape[1:]:
+                dim *= d
+            x = tf.reshape(bottom, [-1, dim])
+
+            weights = self.get_fc_weight(name)
+            biases = self.get_bias(name)
+
+            # Fully connected layer. Note that the '+' operation automatically
+            # broadcasts the biases.
+            fc = tf.nn.bias_add(tf.matmul(x, weights), biases)
+
+            return fc
+
+    def get_conv_filter(self, name):
+        return tf.constant(self.data_dict[name][0], name="filter")
+
+    def get_bias(self, name):
+        return tf.constant(self.data_dict[name][1], name="biases")
+
+    def get_fc_weight(self, name):
+        return tf.constant(self.data_dict[name][0], name="weights")
+
+
 def my_conv(inputs, output_shape=100):
     x = Conv2D(64, (3, 3), padding='same')(inputs)
     x = BatchNormalization()(x)
@@ -767,5 +1321,4 @@ def DarknetConv2D_BN_Leaky(*args, **kwargs):
 
 
 if __name__ == "__main__":
-    text_cnn_phrase((3, 5792))
-    print(math.log(5792, 2))
+    crnn_ctc_equation_less()

+ 28 - 0
dev/click_captcha/model_260.py

@@ -47,6 +47,34 @@ def yolo_net(input_shape, anchors, num_classes, load_pretrained=True,
     return model
 
 
+def yolo_net_char(input_shape, anchors, num_classes, load_pretrained=True,
+             weights_path='models/tiny_yolo_weights.h5'):
+    """create the training model, for Tiny YOLOv3"""
+    # get a new session
+    # ops.reset_default_graph()
+    K.clear_session()
+    image_input = Input(shape=(None, None, 3))
+    h, w = input_shape
+    num_anchors = len(anchors)
+
+    y_true = [Input(shape=(h//{0: 32, 1: 16}[l], w//{0: 32, 1: 16}[l],
+                           num_anchors//2, num_classes+5)) for l in range(2)]
+
+    model_body = tiny_yolo_body(image_input, num_anchors//2, num_classes)
+    print('Create Tiny YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes))
+
+    if load_pretrained:
+        model_body.load_weights(weights_path)
+        print('Load weights {}.'.format(weights_path))
+
+    model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',
+                        arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': .5})(
+        [*model_body.output, *y_true])
+    model = Model([model_body.input, *y_true], model_loss)
+    model.summary(120)
+    return model
+
+
 @wraps(Conv2D)
 def DarknetConv2D(*args, **kwargs):
     """Wrapper to set Darknet parameters for Convolution2D."""

Разница между файлами не показана из-за своего большого размера
+ 1321 - 66
dev/click_captcha/pre_process.py


+ 84 - 0
dev/click_captcha/train_char.py

@@ -0,0 +1,84 @@
+import os
+import random
+import sys
+from glob import glob
+os.environ["CUDA_VISIBLE_DEVICES"] = "0"
+import tensorflow as tf
+tf.compat.v1.disable_eager_execution()
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
+from keras.callbacks import ReduceLROnPlateau, ModelCheckpoint
+from keras.losses import BinaryCrossentropy, mse, CategoricalCrossentropy, MSE
+from keras.optimizer_v2.adam import Adam
+from click_captcha.metrics import precision, recall, f1
+from click_captcha.loss import focal_loss, contrastive_loss, l2_focal_loss, l1_focal_loss
+from click_captcha.model import siamese_net, mobile_net, cnn_net, cnn_net_small, cnn_net_tiny, cnn_net_tiny_dropout
+from click_captcha.pre_process import gen_char
+
+PRETRAINED = False
+random.seed(42)
+image_shape = (40, 40, 1)
+project_root = os.path.dirname(os.path.abspath(__file__)) + "/../"
+class_num = 5649
+data_path = 'click_simple'
+
+
+if __name__ == "__main__":
+    model = cnn_net_small(input_shape=image_shape, output_shape=class_num)
+
+    if PRETRAINED:
+        _path = "./models/e130-acc0.87-char.h5"
+        model.load_weights(_path, skip_mismatch=True, by_name=True)
+        print("read pretrained model", _path)
+    else:
+        print("no pretrained")
+
+    # with open(project_root + "data/click/map.txt", "r") as f:
+    #     paths = f.readlines()
+    # print("len(paths)", len(paths))
+    paths = glob("../data/" + data_path + "/*.jpg")
+
+    # data path split into train,test
+    random.shuffle(paths)
+    # paths = paths[:100000]
+    trainP = paths[:int(len(paths)*0.9)]
+    testP = paths[int(len(paths)*0.9):]
+    print('total:', len(paths), 'train:', len(trainP), 'test:', len(testP))
+
+    # batch num
+    batch_size = 32
+    steps_per_epoch = max(1, len(trainP) // batch_size)
+    validation_steps = max(1, len(testP) // batch_size)
+
+    # 模型权重存放位置
+    filepath = 'models/e{epoch:02d}-acc{val_acc:.2f}-char.h5'
+    check_pointer = ModelCheckpoint(filepath=filepath, monitor='val_acc', verbose=0,
+                                    save_weights_only=True, save_best_only=True,
+                                    mode="max", save_freq='epoch')
+    rlu = ReduceLROnPlateau(monitor='val_acc', factor=0.5, patience=10,
+                            verbose=1, mode='max', cooldown=0, min_lr=0)
+    model.compile(optimizer=Adam(lr=0.0003), loss=CategoricalCrossentropy(),
+                  metrics=['acc', f1])
+
+    # data loader
+    train_loader = gen_char(trainP, batch_size=batch_size, shape=image_shape, cls_num=class_num, data_path=data_path)
+    test_loader = gen_char(testP, batch_size=batch_size, shape=image_shape, cls_num=class_num, data_path=data_path)
+    # train
+    model.fit(train_loader,
+              steps_per_epoch=steps_per_epoch,
+              callbacks=[check_pointer, rlu],
+              validation_data=test_loader,
+              validation_steps=validation_steps,
+              epochs=1000,
+              max_queue_size=1000,
+              use_multiprocessing=True,
+              workers=10)
+    # model.fit_generator(train_loader,
+    #                     steps_per_epoch=steps_per_epoch,
+    #                     callbacks=[check_pointer, rlu],
+    #                     validation_data=test_loader,
+    #                     validation_steps=validation_steps,
+    #                     epochs=1000,
+    #                     max_queue_size=1000,
+    #                     use_multiprocessing=True,
+    #                     workers=8)

+ 81 - 0
dev/click_captcha/train_equation.py

@@ -0,0 +1,81 @@
+import os
+import random
+import sys
+from glob import glob
+os.environ["CUDA_VISIBLE_DEVICES"] = "0"
+import tensorflow as tf
+import keras.backend as K
+# tf.compat.v1.disable_eager_execution()
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
+from keras.callbacks import ReduceLROnPlateau, ModelCheckpoint
+from keras.losses import BinaryCrossentropy, mse, CategoricalCrossentropy, MSE
+from keras.optimizer_v2.adam import Adam
+from click_captcha.metrics import precision, recall, f1
+from click_captcha.loss import focal_loss, contrastive_loss, l2_focal_loss, l1_focal_loss, ctc_accuracy
+from click_captcha.model import crnn_ctc_equation, crnn_ctc_equation_large, crnn_ctc_equation_less, \
+    crnn_ctc_equation_loss
+from click_captcha.pre_process import gen_equation, gen_equation2
+
+PRETRAINED = True
+random.seed(42)
+image_shape = (32, 192, 1)
+project_root = os.path.dirname(os.path.abspath(__file__)) + "/../"
+class_num = 35 + 2
+data_path = 'equation2'
+
+
+if __name__ == "__main__":
+    model = crnn_ctc_equation_loss(input_shape=image_shape, class_num=class_num)
+
+    if PRETRAINED:
+        _path = "./models/e83-loss0.06-equation.h5"
+        model.load_weights(_path, skip_mismatch=True, by_name=True)
+        print("read pretrained model", _path)
+    else:
+        print("no pretrained")
+
+    # with open(project_root + "data/click/map.txt", "r") as f:
+    #     paths = f.readlines()
+    # print("len(paths)", len(paths))
+    paths = glob("../data/" + data_path + "/*.jpg")
+
+    # data path split into train,test
+    random.shuffle(paths)
+    # paths = paths[:100000]
+    trainP = paths[:int(len(paths)*0.9)]
+    testP = paths[int(len(paths)*0.9):]
+    print('total:', len(paths), 'train:', len(trainP), 'test:', len(testP))
+
+    # batch num
+    batch_size = 32
+    steps_per_epoch = max(1, len(trainP) // batch_size)
+    validation_steps = max(1, len(testP) // batch_size)
+
+    # 模型权重存放位置
+    filepath = 'models/e{epoch:02d}-loss{val_loss:.2f}-equation.h5'
+    check_pointer = ModelCheckpoint(filepath=filepath, monitor='val_loss', verbose=0,
+                                    save_weights_only=True, save_best_only=True,
+                                    mode="min", save_freq='epoch')
+    rlu = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10,
+                            verbose=1, mode='min', cooldown=0, min_lr=0)
+    model.compile(optimizer=Adam(lr=0.0003), loss={'ctc': lambda y_true, y_pred: y_pred})
+
+    # 使用ctc center loss 所需
+    # sess = K.get_session()
+    # sess.run(tf.compat.v1.global_variables_initializer())
+
+    # data loader
+    train_loader = gen_equation2(trainP, batch_size=batch_size, shape=image_shape, cls_num=class_num, data_path=data_path)
+    test_loader = gen_equation2(testP, batch_size=batch_size, shape=image_shape, cls_num=class_num, data_path=data_path)
+    # train
+    steps_per_epoch = 500
+    validation_steps = int(steps_per_epoch * 0.1)
+    model.fit_generator(train_loader,
+                        steps_per_epoch=steps_per_epoch,
+                        callbacks=[check_pointer, rlu],
+                        validation_data=test_loader,
+                        validation_steps=validation_steps,
+                        epochs=1000,
+                        max_queue_size=100,
+                        use_multiprocessing=False)

+ 60 - 0
dev/click_captcha/train_equation_denoise.py

@@ -0,0 +1,60 @@
+import os
+import random
+import sys
+from glob import glob
+os.environ["CUDA_VISIBLE_DEVICES"] = "1"
+import tensorflow as tf
+# tf.compat.v1.disable_eager_execution()
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
+from keras.callbacks import ReduceLROnPlateau, ModelCheckpoint
+from keras.losses import BinaryCrossentropy, mse, CategoricalCrossentropy, MSE
+from keras.optimizer_v2.adam import Adam
+from click_captcha.metrics import precision, recall, f1
+from click_captcha.loss import focal_loss, contrastive_loss, l2_focal_loss, l1_focal_loss, perceptual_loss
+from click_captcha.model import u_net_denoise
+from click_captcha.pre_process import gen_equation, gen_equation2, gen_equation_denoise
+
+PRETRAINED = False
+random.seed(42)
+image_shape = (32, 192, 1)
+project_root = os.path.dirname(os.path.abspath(__file__)) + "/../"
+
+
+if __name__ == "__main__":
+    model = u_net_denoise(input_shape=image_shape, class_num=image_shape[2])
+
+    if PRETRAINED:
+        _path = "./models/e130-acc0.87-char.h5"
+        model.load_weights(_path, skip_mismatch=True, by_name=True)
+        print("read pretrained model", _path)
+    else:
+        print("no pretrained")
+
+    # batch num
+    batch_size = 32
+
+    # 模型权重存放位置
+    filepath = 'models/e{epoch:02d}-loss{val_loss:.2f}-denoise.h5'
+    check_pointer = ModelCheckpoint(filepath=filepath, monitor='val_loss', verbose=0,
+                                    save_weights_only=True, save_best_only=True,
+                                    mode="min", save_freq='epoch')
+    rlu = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10,
+                            verbose=1, mode='min', cooldown=0, min_lr=0)
+    model.compile(optimizer=Adam(lr=0.0003), loss=perceptual_loss(),
+                  metrics=['acc', precision, recall, f1])
+
+    # data loader
+    train_loader = gen_equation_denoise(None, batch_size=batch_size, shape=image_shape)
+    test_loader = gen_equation_denoise(None, batch_size=batch_size, shape=image_shape)
+    # train
+    steps_per_epoch = 1000
+    validation_steps = int(steps_per_epoch * 0.1)
+    model.fit_generator(train_loader,
+                        steps_per_epoch=steps_per_epoch,
+                        callbacks=[check_pointer, rlu],
+                        validation_data=test_loader,
+                        validation_steps=validation_steps,
+                        epochs=1000,
+                        max_queue_size=100,
+                        use_multiprocessing=False)

+ 1 - 0
dev/click_captcha/train_yolo_char.py

@@ -24,6 +24,7 @@ from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
 from glob import glob
 from click_captcha.pre_process import gen_yolo_char
 from keras.metrics import mse
+# from keras.losses import categorical_crossentropy as CategoricalCrossentropy
 
 
 PRETRAINED = False

+ 10 - 9
dev/click_captcha/train_yolo_char_260.py

@@ -12,7 +12,7 @@ import sys
 sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 from click_captcha.utils import get_classes, get_anchors
-from click_captcha.model_260 import yolo_net
+from click_captcha.model_260 import yolo_net, yolo_net_char
 
 os.environ["CUDA_VISIBLE_DEVICES"] = "1"
 # train need keras low version
@@ -33,7 +33,7 @@ random.seed(42)
 
 if __name__ == '__main__':
     annotation_path = '../data/detect/map.txt'
-    weight_path = 'models/e20-loss116.38.h5'
+    weight_path = 'models/e16-loss16.27-char.h5'
     log_dir = 'yolo_data/logs/000/'
     classes_path = 'yolo_data/my_classes.txt'
     anchors_path = 'yolo_data/my_anchors.txt'
@@ -46,9 +46,9 @@ if __name__ == '__main__':
 
     # default setting
     is_tiny_version = len(anchors) == 6
-    model = yolo_net(input_shape, anchors, num_classes,
-                     load_pretrained=PRETRAINED,
-                     weights_path=weight_path)
+    model = yolo_net_char(input_shape, anchors, num_classes,
+                          load_pretrained=PRETRAINED,
+                          weights_path=weight_path)
 
     val_split = 0.1
     with open(annotation_path) as f:
@@ -61,8 +61,8 @@ if __name__ == '__main__':
 
     file_path = 'models/e{epoch:02d}-loss{val_loss:.2f}-char.h5'
     checkpoint = ModelCheckpoint(file_path, monitor='val_loss',
-                                 save_weights_only=True, save_best_only=True, period=2)
-    reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, verbose=1)
+                                 save_weights_only=True, save_best_only=True, period=1)
+    reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=5, verbose=1)
     # model.compile(optimizer=Adam(lr=0.003), loss={'yolo_loss': lambda y_true, y_pred: y_pred},
     #               metrics=['acc', mse])
 
@@ -88,11 +88,12 @@ if __name__ == '__main__':
         model.layers[i].trainable = True
     print('Unfreeze all of the layers.')
 
-    model.compile(optimizer=Adam(lr=0.003), loss={'yolo_loss': lambda y_true, y_pred: y_pred},
+    model.compile(optimizer=Adam(lr=0.0003), loss={'yolo_loss': lambda y_true, y_pred: y_pred},
                   metrics=[mse])
     model.fit_generator(train_loader,
                         steps_per_epoch=steps_per_epoch,
                         validation_data=test_loader,
                         validation_steps=max(1, num_val//batch_size),
                         epochs=500,
-                        callbacks=[checkpoint, reduce_lr])
+                        callbacks=[checkpoint, reduce_lr],
+                        max_queue_size=50)

+ 108 - 19
interface/captcha_interface.py

@@ -14,6 +14,7 @@ os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
 sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
 from flask import Flask, request
 from utils import request_post, bytes2np, np2bytes, base64_decode, image_to_str, str_to_image
+from puzzle_detect.pzd_interface import get_puzzle_tip_location
 
 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
@@ -25,16 +26,18 @@ cho_url = "http://127.0.0.1:17058/cho"
 pzd_url = "http://127.0.0.1:17059/pzd"
 ced_url = "http://127.0.0.1:17060/ced"
 cer_url = "http://127.0.0.1:17061/cer"
+cac_url = "http://127.0.0.1:17062/cac"
 captcha_url = "http://127.0.0.1:17054/captcha"
 
 
 #     验证码类型:
-#     1:  拖拽还原拼图
+#     1:  拖拽还原拼图(多个拼图干扰)
 #     2:  拖拽还原图片
 #     3:  点击中文(带提示文字图片)
 #     4:  点击中文(按语序)
 #     5:  点击中文(带提示文字)
 #     6:  计算算式(带中文)
+#     7:  验证码类型分类
 
 
 # 接口配置
@@ -82,12 +85,14 @@ def captcha():
 def get_captcha_result(base64_list, code):
     """
     验证码类型:
-    1:  拖拽还原拼图
+    1:  拖拽还原拼图(多个拼图干扰)
     2:  拖拽还原图片
     3:  点击中文(带提示文字图片)
     4:  点击中文(按语序)
     5:  点击中文(带提示文字)
     6:  计算算式(带中文)
+    7:  验证码类型分类
+    8:  识别图中中文
 
     :param base64_list: base64格式图片列表
     :param code: 验证码类型
@@ -98,7 +103,18 @@ def get_captcha_result(base64_list, code):
         if result.get("success"):
             predict = result.get("data")
             logging.info("code " + str(code) + "  pzd  " + "predict " + str(predict))
-            return predict
+            if len(predict) <= 1:
+                return predict
+            else:
+                h_list = get_puzzle_tip_location(base64_list[1])
+                min_len = 10000
+                new_predict = None
+                for box in predict:
+                    if abs(box[1]-h_list[0]) + abs(box[3]-h_list[1]) < min_len:
+                        new_predict = [box]
+                        min_len = abs(box[1]-h_list[0]) + abs(box[3]-h_list[1])
+                logging.info("code " + str(code) + "  pzd  " + "match predict " + str(new_predict))
+                return new_predict
 
     elif code == 2:
         result = json.loads(request_post(bdr_url, prepare_data(base64_list[0], "bdr")))
@@ -229,15 +245,15 @@ def get_captcha_result(base64_list, code):
 
     elif code == 6:
         # denoise
-        result = json.loads(request_post(ced_url, prepare_data(base64_list[0], "ced")))
-        if result.get("success"):
-            image_np = str_to_image(result.get("data"))
-        else:
-            return None
-        logging.info("code " + str(code) + "  ced  " + "predict success")
+        # result = json.loads(request_post(ced_url, prepare_data(base64_list[0], "ced")))
+        # if result.get("success"):
+        #     image_np = str_to_image(result.get("data"))
+        # else:
+        #     return None
+        # logging.info("code " + str(code) + "  ced  " + "predict success")
 
         # recognize
-        result = json.loads(request_post(cer_url, prepare_data([image_np], "cer")))
+        result = json.loads(request_post(cer_url, prepare_data(base64_list[0], "cer")))
         if result.get("success"):
             equation_result = result.get("data")
         else:
@@ -245,11 +261,40 @@ def get_captcha_result(base64_list, code):
         logging.info("code " + str(code) + "  cer  " + "predict " + str(equation_result))
         return equation_result
 
+    elif code == 7:
+        result = json.loads(request_post(cac_url, prepare_data(base64_list[0], "cac")))
+        if result.get("success"):
+            predict = result.get("data")
+            logging.info("code " + str(code) + "  cac  " + "predict " + str(predict))
+            return predict
+
+    elif code == 8:
+        # detect
+        result = json.loads(request_post(chd_url, prepare_data(base64_list[0], "chd")))
+        if result.get("success"):
+            box_list = result.get("data")
+        else:
+            return None
+        logging.info("code " + str(code) + "  chd  " + "predict " + str(box_list))
+        if len(box_list) == 0:
+            return None
+
+        # recognize
+        result = json.loads(request_post(chr_url, prepare_data([base64_list[0], box_list], "chr")))
+        if result.get("success"):
+            char_list = result.get("data")
+        else:
+            return None
+        logging.info("code " + str(code) + "  chr  " + "predict " + str(char_list))
+        if len(char_list) != len(box_list):
+            return None
+        return char_list
+
     return None
 
 
 def prepare_data(data, _type):
-    if _type in ['pzd', 'bdr', 'chd', 'ced']:
+    if _type in ['pzd', 'bdr', 'chd', 'ced', 'cac', 'cer']:
         return {"data": data}
 
     elif _type in ['chd2']:
@@ -272,28 +317,54 @@ def prepare_data(data, _type):
     elif _type in ['cho']:
         return {"data": json.dumps(data)}
 
-    elif _type in ['cer']:
-        data = image_to_str(data[0])
-        return {"data": json.dumps(data)}
+    # elif _type in ['cer']:
+    #     data = image_to_str(data)
+    #     return {"data": json.dumps(data)}
 
     else:
         raise
 
 
 def test_interface(from_remote=True):
-    # captcha_url = "http://192.168.2.103:17054/captcha"
-    captcha_url = "http://127.0.0.1:17054/captcha"
+    captcha_url = "http://192.168.2.103:17054/captcha"
+    # captcha_url = "http://127.0.0.1:17054/captcha"
+
     # paths = glob("D:/Project/captcha/data/test/yolo_15.jpg")
     # paths = glob("D:/Project/captcha/data/test/FileInfo1021/19584571-511d-11ed-93ac-b4b5b67760ae_3.jpg")
-    paths = glob("yolo_15.jpg")
+    # paths = glob("yolo_15.jpg")
+    # paths = glob(r'C:\Users\Administrator\Desktop\test_capture\puzzle8.png')
+    # paths = glob("D:/Project/captcha/data/equation1/227_1.jpg")
+    paths = glob(r'C:\Users\Administrator\Downloads\1.jpg')
+
     for file_path in paths:
         img_np = cv2.imread(file_path)
         file_bytes = np2bytes(img_np)
         file_base64 = base64.b64encode(file_bytes)
-        code = 1
+
+        # img_np = None
+        # with open(file_path, 'r') as f:
+        #     file_base64 = f.read()
+
+        code = 6
 
         if from_remote:
-            if code in [1, 4]:
+            if code in [1]:
+                file_base64_2 = cv2.imread(r"C:\Users\Administrator\Desktop\test_capture\puzzle7.png")
+                file_base64_2 = np2bytes(file_base64_2)
+                file_base64_2 = base64.b64encode(file_base64_2)
+                file_json = {"base64pic": file_base64, 'base64pic2': file_base64_2, "code": code}
+                result = json.loads(request_post(captcha_url, file_json))
+                print("result", result)
+                if result.get("success"):
+                    out_boxes = result.get("predict")
+                    print("out_boxes", out_boxes)
+                    for box in out_boxes:
+                        cv2.rectangle(img_np, (box[0], box[1]), (box[2], box[3]), (0, 0, 255))
+                    cv2.imshow("img_np", img_np)
+                    cv2.waitKey(0)
+                else:
+                    print("failed!")
+            elif code in [4]:
                 file_json = {"base64pic": file_base64, "code": code}
                 result = json.loads(request_post(captcha_url, file_json))
                 print("result", result)
@@ -357,6 +428,24 @@ def test_interface(from_remote=True):
                     equation_result = result.get("predict")
                     print(equation_result)
 
+            elif code in [7]:
+                file_json = {"base64pic": file_base64, "code": code}
+                result = json.loads(request_post(captcha_url, file_json))
+                print("result", result)
+                if result.get("success"):
+                    classify_result = result.get("predict")
+                    print(classify_result)
+
+            elif code in [8]:
+                file_json = {"base64pic": file_base64, "code": code}
+                result = json.loads(request_post(captcha_url, file_json))
+                print("result", result)
+                if result.get("success"):
+                    chars = result.get("predict")
+                    print("chars", chars)
+                else:
+                    print("failed!")
+
 
 if __name__ == "__main__":
     # app.run(host='127.0.0.1', port=17054, debug=False)

+ 3 - 1
interface/start.sh

@@ -1,7 +1,9 @@
 nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17054 --chdir /data/python/fangjiasheng/VerificationCode/interface/ captcha_interface:app &
+nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17055 --chdir /data/python/fangjiasheng/VerificationCode/border_recognize/ bdr_interface:app &
 nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17056 --chdir /data/python/fangjiasheng/VerificationCode/chinese_detect/ chd_interface:app &
 nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17057 --chdir /data/python/fangjiasheng/VerificationCode/chinese_recognize/ chr_interface:app &
 nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17058 --chdir /data/python/fangjiasheng/VerificationCode/chinese_order/ cho_interface:app &
 nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17059 --chdir /data/python/fangjiasheng/VerificationCode/puzzle_detect/ pzd_interface:app &
 nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17060 --chdir /data/python/fangjiasheng/VerificationCode/chinese_equation_denoise/ ced_interface:app &
-nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17061 --chdir /data/python/fangjiasheng/VerificationCode/chinese_equation_recognize/ cer_interface:app &
+nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17061 --chdir /data/python/fangjiasheng/VerificationCode/chinese_equation_recognize/ cer_interface_torch:app &
+nohup /home/user/anaconda3/envs/tf2/bin/gunicorn -w 2 -t 300 -b 0.0.0.0:17062 --chdir /data/python/fangjiasheng/VerificationCode/captcha_classify/ cac_interface:app &

+ 3 - 1
interface/stop.sh

@@ -1,7 +1,9 @@
 kill -9 $(lsof -i:17054|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
+kill -9 $(lsof -i:17055|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
 kill -9 $(lsof -i:17056|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
 kill -9 $(lsof -i:17057|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
 kill -9 $(lsof -i:17058|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
 kill -9 $(lsof -i:17059|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
 kill -9 $(lsof -i:17060|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
-kill -9 $(lsof -i:17061|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
+kill -9 $(lsof -i:17061|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')
+kill -9 $(lsof -i:17062|sed -n '2,$p'|awk '{print $2}'|tr '\n' ' ')

+ 4 - 1
puzzle_detect/inference_yolo_puzzle.py

@@ -80,6 +80,8 @@ def detect(image_np, model=None, sess=None, draw=False):
             temp_boxes.append([(left, top), (right, bottom)])
         out_boxes = temp_boxes
     out_boxes = [[int(x[0][0]), int(x[0][1]), int(x[1][0]), int(x[1][1])] for x in out_boxes]
+    if draw:
+        print('out_boxes', out_boxes)
     return image_np, out_boxes, out_classes
 
 
@@ -107,4 +109,5 @@ def get_tiny_inference_model(anchors, num_classes, weights_path='models/tiny_yol
 
 if __name__ == '__main__':
     image_path = "D:/Project/captcha/data/test/yolo_12.jpg"
-    detect(cv2.imread(image_path))
+    image_path = "C:\\Users\\Administrator\\Downloads\\1.jpg"
+    detect(cv2.imread(image_path), draw=True)

+ 78 - 22
puzzle_detect/pzd_interface.py

@@ -6,12 +6,13 @@ import sys
 import time
 import traceback
 from glob import glob
+import numpy as np
 import cv2
 os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
 sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
 import tensorflow as tf
 from flask import Flask, request
-from utils import np2bytes, request_post, bytes2np, get_anchors, get_classes, get_colors, base64_decode
+from utils import np2bytes, request_post, bytes2np, get_anchors, get_classes, get_colors, base64_decode, rgba_to_rgb
 from puzzle_detect.inference_yolo_puzzle import get_tiny_inference_model, detect
 
 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
@@ -50,9 +51,26 @@ def pzd():
         # 数据转换
         # print("base64_data", data)
         data = base64_decode(data)
-        image_np = bytes2np(data)
+        image_np = bytes2np(data, unchanged=True)
+
+        # 处理透明通道
+        rgba_flag = 0
+        position_list = []
+        if image_np.shape[2] == 4:
+            # 将透明部分置为黑色,转为3通道输出
+            rgba_flag = 1
+            image_np, position_list = rgba_to_rgb(image_np, return_position=True)
+
         # 预测
         _, out_boxes, out_classes = detect(image_np, pzd_model, sess)
+
+        if len(out_boxes) == 0 and rgba_flag and position_list:
+            logging.info('pzd 直接输出透明位置1')
+            out_boxes = [position_list[0]]
+        if len(out_boxes) >= 1 and rgba_flag and len(position_list) == 1:
+            logging.info('pzd 直接输出透明位置2')
+            out_boxes = [position_list[0]]
+
         return json.dumps({"data": out_boxes, "success": 1})
     except:
         traceback.print_exc()
@@ -72,29 +90,67 @@ class PzdModels:
         return self.model
 
 
-def test_pzd_model(from_remote=True):
-    paths = glob("D:/Project/captcha/data/test/yolo_12.jpg")
+def get_puzzle_tip_location(image_base64):
+    """
+    获取提示拼图的位置高度
+
+    :param image_base64:
+    :return:
+    """
+    image_bytes = base64_decode(image_base64)
+    image_np = bytes2np(image_bytes)
+
+    # image_np = cv2.imread(r'C:\Users\Administrator\Desktop\test_capture\puzzle2.jpg')
+    # print(image_np)
+    # image_np = np.mean(image_np, axis=0)
+    hs = np.where(image_np > 20)[0]
+    h1 = hs[0]
+    h2 = hs[-1]
+    # cv2.line(image_np, (0, h1), (image_np.shape[1], h1), (0, 0, 255))
+    # cv2.line(image_np, (0, h2), (image_np.shape[1], h2), (0, 0, 255))
+    # print('hs', hs)
+    # cv2.imshow('img', image_np)
+    # cv2.waitKey(0)
+    return [h1, h2]
+
+
+def test_pzd_model(from_remote=1, read_base64_file=0):
+    paths = glob('puzzle4.png')
     for file_path in paths:
-        img_np = cv2.imread(file_path)
-        file_bytes = np2bytes(img_np)
-        file_base64 = base64.b64encode(file_bytes)
+
+        if not read_base64_file:
+            img_np = cv2.imread(file_path)
+            file_bytes = np2bytes(img_np)
+            file_base64 = base64.b64encode(file_bytes)
+        else:
+            with open('base64.txt', 'r') as f:
+                file_base64 = f.read()
+            file_bytes = base64_decode(file_base64)
+            img_np = bytes2np(file_bytes, unchanged=True)
+            img_np = rgba_to_rgb(img_np)
+            # cv2.imshow('img', img_np)
+            # cv2.waitKey(0)
 
         if from_remote:
-            file_json = {"data": file_base64}
-            # _url = "http://192.168.2.102:17000/ocr"
-            _url = "http://127.0.0.1:17000/pzd"
-            result = json.loads(request_post(_url, file_json))
-            if result.get("success"):
-                out_boxes = result.get("data")
-                print("out_boxes", out_boxes)
-                for box in out_boxes:
-                    cv2.rectangle(img_np, (box[0], box[1]), (box[2], box[3]), (0, 0, 255))
-                cv2.imshow("img_np", img_np)
-                cv2.waitKey(0)
-            else:
-                print("failed!")
+            _url = "http://192.168.2.103:17059/pzd"
+        else:
+            _url = "http://127.0.0.1:17059/pzd"
+        file_json = {"data": file_base64}
+        result = json.loads(request_post(_url, file_json))
+        if result.get("success"):
+            out_boxes = result.get("data")
+            print("out_boxes", out_boxes)
+            for box in out_boxes:
+                cv2.rectangle(img_np, (box[0], box[1]), (box[2], box[3]), (0, 0, 255))
+            cv2.imshow("img_np", img_np)
+            cv2.waitKey(0)
+        else:
+            print("failed!")
 
 
 if __name__ == "__main__":
-    app.run(host='127.0.0.1', port=17059, debug=False)
-    # test_pzd_model()
+    # app.run(host='127.0.0.1', port=17059, debug=False)
+
+    test_pzd_model()
+
+    # get_puzzle_tip_location(None)

+ 61 - 3
utils.py

@@ -1,8 +1,10 @@
 import base64
 import colorsys
+import copy
 import re
 import socket
 import traceback
+import urllib
 from enum import Enum
 from functools import reduce
 
@@ -262,10 +264,15 @@ def np2bytes(image_np):
     return img_bytes
 
 
-def bytes2np(_b):
+def bytes2np(_b, unchanged=False):
     try:
         # 二进制数据流转np.ndarray [np.uint8: 8位像素]
-        image_np = cv2.imdecode(np.frombuffer(_b, np.uint8), cv2.IMREAD_COLOR)
+        if unchanged:
+            # unchanged能读取透明通道
+            image_np = cv2.imdecode(np.frombuffer(_b, np.uint8), cv2.IMREAD_UNCHANGED)
+        else:
+            image_np = cv2.imdecode(np.frombuffer(_b, np.uint8), cv2.IMREAD_COLOR)
+
         # 将rgb转为bgr
         # image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
         return image_np
@@ -279,9 +286,20 @@ def bytes2np(_b):
 
 
 def base64_decode(b64, is_test=False):
+    # b64 = str(b64)
+    # missing_padding = 4 - len(b64) % 4
+    # if missing_padding:
+    #     print('base64 missing_padding', missing_padding)
+    #     b64 += '=' * missing_padding
+    #     print('len(b64)', len(b64))
+
+    if '%' in str(b64):
+        b64 = urllib.parse.unquote(str(b64))
+        print(b64)
+
     if type(b64) == bytes:
         return base64.b64decode(b64)
-    # b64 = re.sub(r"\r\n", "\n", b64)
+    b64 = re.sub(r"\r\n", "\n", b64)
     missing_padding = 4 - len(b64) % 4
     if missing_padding:
         b64 += '=' * missing_padding
@@ -368,3 +386,43 @@ def str_to_image(_str):
     b64 = _str.encode("utf-8")
     image_np = bytes2np(base64_decode(b64))
     return image_np
+
+
+def rgba_to_rgb(image_np, return_position=False):
+    # 提取 RGB 通道和透明通道
+    rgb_channels = image_np[:, :, :3]
+    alpha_channel = image_np[:, :, 3]
+
+    # 找到透明通道小于250的部分
+    transparent_pixels = alpha_channel < 250
+
+    # 将透明通道小于250的部分的RGB值置为黑色
+    rgb_channels[transparent_pixels] = [100, 100, 100]
+
+    image_np = rgb_channels
+
+    # 创建图像的副本,并将所有像素值置为白色
+    # white_background = np.ones_like(image_np) * 255
+    white_background = copy.copy(image_np)
+
+    # 在图像副本上找到黑色部分的轮廓
+    contours, hierarchy = cv2.findContours(transparent_pixels.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
+
+    # 输出黑色部分的位置(边界框坐标)
+    position_list = []
+    for contour in contours:
+        x, y, w, h = cv2.boundingRect(contour)
+        position_list.append([int(x), int(y), int(x+w), int(y+h)])
+
+    # 将黑色部分的轮廓描绘为白色
+    cv2.drawContours(white_background, contours, -1, (255, 255, 255), thickness=1)
+
+    # 合并原始图像和处理后的图像
+    # image_np = cv2.addWeighted(image_np, 0, white_background, 1, 0)
+
+    image_np = white_background
+
+    if return_position:
+        return image_np, position_list
+
+    return image_np

Некоторые файлы не были показаны из-за большого количества измененных файлов