├── .gitignore ├── README.md ├── captcha.proto ├── captcha_pb2.py ├── captcha_pb2_grpc.py ├── main.py └── test ├── README.md ├── images ├── bg0.jpg ├── bg1.jpg ├── bg2.jpg ├── bg3.jpg ├── front0.png ├── front1.png ├── front2.png ├── front3.png ├── slide_bg0.png └── slide_block0.png ├── test_click.py └── test_slide.py /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # poetry 99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 100 | # This is especially recommended for binary packages to ensure reproducibility, and is more 101 | # commonly ignored for libraries. 102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 103 | #poetry.lock 104 | 105 | # pdm 106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 107 | #pdm.lock 108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 109 | # in version control. 110 | # https://pdm.fming.dev/#use-with-ide 111 | .pdm.toml 112 | 113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 114 | __pypackages__/ 115 | 116 | # Celery stuff 117 | celerybeat-schedule 118 | celerybeat.pid 119 | 120 | # SageMath parsed files 121 | *.sage.py 122 | 123 | # Environments 124 | .env 125 | .venv 126 | env/ 127 | venv/ 128 | ENV/ 129 | env.bak/ 130 | venv.bak/ 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | .dmypy.json 145 | dmypy.json 146 | 147 | # Pyre type checker 148 | .pyre/ 149 | 150 | # pytype static type analyzer 151 | .pytype/ 152 | 153 | # Cython debug symbols 154 | cython_debug/ 155 | 156 | # PyCharm 157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 159 | # and can be added to the global gitignore or merged into this file. For a more nuclear 160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 161 | .idea/ 162 | 163 | *.sh 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CaptchaPass 2 | 3 | 项目用于过 点击式图形验证码 和 滑动式验证码 的校验 4 | 5 | ## 声明 6 | 7 | #### 点击式: 8 | 9 | | 背景图 | 目标图 | 10 | | :----:| :----: | 11 | | 背景图 | 目标图 | 12 | 13 | #### 滑动式: 14 | 15 | | 背景图 | 目标图 | 16 | |:------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------:| 17 | | 背景图 | 目标图 | 18 | 19 | 项目非通用解决方案,如果您的问题不满足以下几点,需要做一些简单的定制([有引导](#定制引导)): 20 | #### 点击式: 21 | 1. 目标图像素为 300x84 22 | 2. 目标图为黑色图标的透明背景图 23 | 3. 背景图的目标图形为白色 24 | 25 | ## 介绍 26 | [【看雪】Python OpenCV 过点击式和滑动式图形验证码的校验](https://bbs.kanxue.com/thread-281098.htm) 27 | 28 | [【52破解】Python OpenCV 过点击式和滑动式图形验证码的校验](https://www.52pojie.cn/home.php?mod=space&uid=1920139) 29 | ## 定制引导 30 | 31 | 1.如果目标图像素不是 300x84,则需要修改此处代码: 32 | ```python 33 | # 其中 [9, 9, 75, 75] 为目标图像其中一个图标的 34 | # 左上角坐标: P1=(9,9) 和右下角坐标: P2=(75,75) 35 | # 需要自己计算并修改 36 | rectangle_list = [[9, 9, 75, 75], [109, 9, 175, 75], [209, 9, 275, 75]] 37 | ``` 38 | 39 | 2.如果目标图不是黑色图标的透明背景图,则需要修改此处代码: 40 | ```python 41 | # 此处将透明背景转白色,可定制为你的 42 | white_front = np.ones_like(front) * 255 43 | alpha_channel = front[:, :, 3] 44 | white_front[:, :, 0:3] = cv2.bitwise_and( 45 | white_front[:, :, 0:3], 46 | white_front[:, :, 0:3], 47 | mask=cv2.bitwise_not(alpha_channel) 48 | ) 49 | 50 | # 此处将颜色反转,可定制为你的 51 | strong_contrast_front = cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)) 52 | ``` 53 | 54 | 3.如果背景图的目标图形不是白色,则需要修改此处代码: 55 | ```python 56 | # 此处将图片转为灰度后仅仅保留RGB 250-255的偏白色图片,可定制为你的 57 | gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 58 | _, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY) 59 | ``` 60 | 61 | ## 缺陷 62 | 混淆度不高,或者颜色分明的图像识别都较为容易,如: 63 | 64 | | 背景图 | 目标图 | 65 | |:------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------:| 66 | | 背景图 | 目标图 | 67 | | 背景图 | 目标图 | 68 | | 背景图 | 目标图 | 69 | 70 | 但颜色与待匹配的front图像相似的难以精确匹配,如: 71 | 72 | | 背景图 | 目标图 | 73 | | :----:| :----: | 74 | | 背景图 | 目标图 | 75 | -------------------------------------------------------------------------------- /captcha.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "/captcha"; // 用于生成go语言的包名,可删除 4 | 5 | message Result { 6 | int32 x = 1; 7 | int32 y = 2; 8 | } 9 | 10 | service CaptchaProcessing { 11 | rpc ProcessCaptcha (CaptchaRequest) returns (ResultResponse) {} 12 | } 13 | 14 | message CaptchaRequest { 15 | string bg_path = 1; 16 | string front_path = 2; 17 | } 18 | 19 | message ResultResponse { 20 | repeated Result results = 1; 21 | } 22 | -------------------------------------------------------------------------------- /captcha_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: captcha.proto 4 | # Protobuf Python Version: 4.25.1 5 | """Generated protocol buffer code.""" 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | from google.protobuf.internal import builder as _builder 10 | # @@protoc_insertion_point(imports) 11 | 12 | _sym_db = _symbol_database.Default() 13 | 14 | 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rcaptcha.proto\"\x1e\n\x06Result\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05\"5\n\x0e\x43\x61ptchaRequest\x12\x0f\n\x07\x62g_path\x18\x01 \x01(\t\x12\x12\n\nfront_path\x18\x02 \x01(\t\"*\n\x0eResultResponse\x12\x18\n\x07results\x18\x01 \x03(\x0b\x32\x07.Result2I\n\x11\x43\x61ptchaProcessing\x12\x34\n\x0eProcessCaptcha\x12\x0f.CaptchaRequest\x1a\x0f.ResultResponse\"\x00\x42\nZ\x08/captchab\x06proto3') 18 | 19 | _globals = globals() 20 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 21 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'captcha_pb2', _globals) 22 | if _descriptor._USE_C_DESCRIPTORS == False: 23 | _globals['DESCRIPTOR']._options = None 24 | _globals['DESCRIPTOR']._serialized_options = b'Z\010/captcha' 25 | _globals['_RESULT']._serialized_start=17 26 | _globals['_RESULT']._serialized_end=47 27 | _globals['_CAPTCHAREQUEST']._serialized_start=49 28 | _globals['_CAPTCHAREQUEST']._serialized_end=102 29 | _globals['_RESULTRESPONSE']._serialized_start=104 30 | _globals['_RESULTRESPONSE']._serialized_end=146 31 | _globals['_CAPTCHAPROCESSING']._serialized_start=148 32 | _globals['_CAPTCHAPROCESSING']._serialized_end=221 33 | # @@protoc_insertion_point(module_scope) 34 | -------------------------------------------------------------------------------- /captcha_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import captcha_pb2 as captcha__pb2 6 | 7 | 8 | class CaptchaProcessingStub(object): 9 | """Missing associated documentation comment in .proto file.""" 10 | 11 | def __init__(self, channel): 12 | """Constructor. 13 | 14 | Args: 15 | channel: A grpc.Channel. 16 | """ 17 | self.ProcessCaptcha = channel.unary_unary( 18 | '/CaptchaProcessing/ProcessCaptcha', 19 | request_serializer=captcha__pb2.CaptchaRequest.SerializeToString, 20 | response_deserializer=captcha__pb2.ResultResponse.FromString, 21 | ) 22 | 23 | 24 | class CaptchaProcessingServicer(object): 25 | """Missing associated documentation comment in .proto file.""" 26 | 27 | def ProcessCaptcha(self, request, context): 28 | """Missing associated documentation comment in .proto file.""" 29 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 30 | context.set_details('Method not implemented!') 31 | raise NotImplementedError('Method not implemented!') 32 | 33 | 34 | def add_CaptchaProcessingServicer_to_server(servicer, server): 35 | rpc_method_handlers = { 36 | 'ProcessCaptcha': grpc.unary_unary_rpc_method_handler( 37 | servicer.ProcessCaptcha, 38 | request_deserializer=captcha__pb2.CaptchaRequest.FromString, 39 | response_serializer=captcha__pb2.ResultResponse.SerializeToString, 40 | ), 41 | } 42 | generic_handler = grpc.method_handlers_generic_handler( 43 | 'CaptchaProcessing', rpc_method_handlers) 44 | server.add_generic_rpc_handlers((generic_handler,)) 45 | 46 | 47 | # This class is part of an EXPERIMENTAL API. 48 | class CaptchaProcessing(object): 49 | """Missing associated documentation comment in .proto file.""" 50 | 51 | @staticmethod 52 | def ProcessCaptcha(request, 53 | target, 54 | options=(), 55 | channel_credentials=None, 56 | call_credentials=None, 57 | insecure=False, 58 | compression=None, 59 | wait_for_ready=None, 60 | timeout=None, 61 | metadata=None): 62 | return grpc.experimental.unary_unary(request, target, '/CaptchaProcessing/ProcessCaptcha', 63 | captcha__pb2.CaptchaRequest.SerializeToString, 64 | captcha__pb2.ResultResponse.FromString, 65 | options, channel_credentials, 66 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 67 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from concurrent import futures 3 | 4 | import cv2 5 | import grpc 6 | import numpy as np 7 | 8 | import captcha_pb2 9 | import captcha_pb2_grpc 10 | 11 | logging.basicConfig( 12 | format='%(asctime)s.%(msecs)03d [%(filename)s:%(lineno)d] : %(message)s', 13 | datefmt='%Y-%m-%d %H:%M:%S', 14 | level=logging.INFO 15 | ) 16 | 17 | 18 | class CaptchaProcessingServicer(captcha_pb2_grpc.CaptchaProcessingServicer): 19 | 20 | def ProcessCaptcha(self, request, context): 21 | result = [] 22 | bg_path = request.bg_path 23 | front_path = request.front_path 24 | 25 | """ 26 | 加载图像 27 | 背景为jpg格式的普通图像 像素为 765x396 28 | 目标为png格式的包含透明通道图像 像素为 300x84 29 | """ 30 | bg = cv2.imread(bg_path) 31 | front = cv2.imread(front_path, cv2.IMREAD_UNCHANGED) 32 | 33 | """ 34 | 由于目标图为透明黑色图片,直接加载会导致图像全黑, 35 | 为了避免全黑情况,创建与图像尺寸相同的白色背景图像,再提取图像的透明度通道,将透明部分的像素值设置为白色 36 | 这样加载完后的图像就变成了白底黑色目标的图像 37 | """ 38 | white_front = np.ones_like(front) * 255 39 | alpha_channel = front[:, :, 3] 40 | white_front[:, :, 0:3] = cv2.bitwise_and( 41 | white_front[:, :, 0:3], 42 | white_front[:, :, 0:3], 43 | mask=cv2.bitwise_not(alpha_channel) 44 | ) 45 | 46 | """ 47 | 为了按序点击,需要提取目标区域的矩形方块 48 | 由于目标图像较为规律有序,于是计算出3个目标图像像素坐标,直接按像素截取 49 | """ 50 | rectangle_list = [[9, 9, 75, 75], [109, 9, 175, 75], [209, 9, 275, 75]] 51 | for rectangle in rectangle_list: 52 | cropped_image = white_front[rectangle[1]:rectangle[3], rectangle[0]:rectangle[2]] 53 | 54 | """ 55 | 将背景图片转换为黑白两色的图片,只保留RGB(250-255)的图像 56 | 如此能保留绝大部分目标图像轮廓 57 | 58 | 将目标图像转换为黑色背景白色轮廓 59 | 如此便与背景的颜色逻辑一致 60 | """ 61 | gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 62 | _, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY) 63 | strong_contrast_front = cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)) 64 | 65 | res = cv2.matchTemplate( 66 | strong_contrast_bg, 67 | strong_contrast_front, 68 | cv2.TM_CCOEFF_NORMED 69 | ) 70 | """ 71 | 使用 TM_CCOEFF_NORMED 算法匹配到最佳 72 | 由于此时的 X,Y 坐标为左上角坐标,需要加20进行偏移处理,获取到点击坐标 73 | """ 74 | x, y = cv2.minMaxLoc(res)[3] 75 | result.append(captcha_pb2.Result(x=x + 20, y=y + 20)) 76 | 77 | logging.info(f"Done process: "+str(result).replace('\n', ' ')) 78 | return captcha_pb2.ResultResponse(results=result) 79 | 80 | 81 | def serve(): 82 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 83 | captcha_pb2_grpc.add_CaptchaProcessingServicer_to_server(CaptchaProcessingServicer(), server) 84 | server.add_insecure_port('[::]:50051') 85 | logging.info("Starting server") 86 | server.start() 87 | server.wait_for_termination() 88 | 89 | 90 | if __name__ == '__main__': 91 | serve() 92 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Test func 2 | 3 | 这个文件夹用来执行图形识别测试 4 | 5 | ### 目录介绍 6 | _test_click.py_ 、_test_slide.py_ 两文件是主方法文件 7 | 8 | _images_ 文件夹中存储的是待测试的图像 9 | 10 | 若需要增加图像,则添加到images文件夹中后,再修改main方法中的序号即可 11 | ```python 12 | if __name__ == '__main__': 13 | for i in range(0, 5): # 修改此处 14 | process( 15 | os.path.join(os.getcwd(), f"images/bg{i}.jpg"), 16 | os.path.join(os.getcwd(), f"images/front{i}.png") 17 | ) 18 | ``` 19 | 20 | ### 注 21 | 修改 _process_ 方法后,别忘记同步到 _main.py_ 文件中 22 | ### 缺陷 23 | 混淆度不高,或者颜色分明的图像识别都较为容易例如序号 **0、1、2**,但颜色与待匹配的front图像相似的,难以精确匹配,如序号 **3** 24 | -------------------------------------------------------------------------------- /test/images/bg0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/bg0.jpg -------------------------------------------------------------------------------- /test/images/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/bg1.jpg -------------------------------------------------------------------------------- /test/images/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/bg2.jpg -------------------------------------------------------------------------------- /test/images/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/bg3.jpg -------------------------------------------------------------------------------- /test/images/front0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/front0.png -------------------------------------------------------------------------------- /test/images/front1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/front1.png -------------------------------------------------------------------------------- /test/images/front2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/front2.png -------------------------------------------------------------------------------- /test/images/front3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/front3.png -------------------------------------------------------------------------------- /test/images/slide_bg0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/slide_bg0.png -------------------------------------------------------------------------------- /test/images/slide_block0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThinkerWen/CaptchaPass/c1ea5226ec7ca7276cf58464ac813ab0da683e34/test/images/slide_block0.png -------------------------------------------------------------------------------- /test/test_click.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | import cv2 4 | import numpy as np 5 | 6 | 7 | def show(name): 8 | cv2.imshow('Show', name) 9 | cv2.waitKey(0) 10 | cv2.destroyAllWindows() 11 | 12 | 13 | def process(bg_path: str, front_path: str) -> None: 14 | bg = cv2.imread(bg_path) 15 | front = cv2.imread(front_path, cv2.IMREAD_UNCHANGED) 16 | 17 | white_front = np.ones_like(front) * 255 18 | alpha_channel = front[:, :, 3] 19 | white_front[:, :, 0:3] = cv2.bitwise_and( 20 | white_front[:, :, 0:3], 21 | white_front[:, :, 0:3], 22 | mask=cv2.bitwise_not(alpha_channel) 23 | ) 24 | 25 | rectangle_list = [[9, 9, 75, 75], [109, 9, 175, 75], [209, 9, 275, 75]] 26 | for idx, rectangle in enumerate(rectangle_list, start=1): 27 | cropped_image = white_front[rectangle[1]:rectangle[3], rectangle[0]:rectangle[2]] 28 | 29 | gray_bg = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) 30 | _, strong_contrast_bg = cv2.threshold(gray_bg, 250, 255, cv2.THRESH_BINARY) 31 | 32 | res = cv2.matchTemplate( 33 | strong_contrast_bg, 34 | cv2.bitwise_not(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)), 35 | cv2.TM_CCOEFF_NORMED 36 | ) 37 | min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 38 | 39 | x, y = max_loc 40 | x, y = x + 20, y + 20 41 | cv2.putText(bg, str(idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (7, 249, 151), 2) 42 | show(bg) 43 | 44 | 45 | if __name__ == '__main__': 46 | for i in range(0, 4): 47 | process( 48 | os.path.join(os.getcwd(), f"images/bg{i}.jpg"), 49 | os.path.join(os.getcwd(), f"images/front{i}.png") 50 | ) 51 | -------------------------------------------------------------------------------- /test/test_slide.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import cv2 4 | 5 | 6 | def show(name): 7 | cv2.imshow('Show', name) 8 | cv2.waitKey(0) 9 | cv2.destroyAllWindows() 10 | 11 | 12 | def _tran_canny(image): 13 | image = cv2.GaussianBlur(image, (3, 3), 0) 14 | return cv2.Canny(image, 50, 150) 15 | 16 | 17 | def process(bg_path: str, front_path: str) -> None: 18 | 19 | # flags0是灰度模式 20 | image = cv2.imread(front_path, 0) 21 | template = cv2.imread(bg_path, 0) 22 | template = cv2.resize(template, (340, 191), interpolation=cv2.INTER_CUBIC) 23 | 24 | # 寻找最佳匹配 25 | res = cv2.matchTemplate(_tran_canny(image), _tran_canny(template), cv2.TM_CCOEFF_NORMED) 26 | min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 27 | 28 | x, y = max_loc 29 | w, h = image.shape[::-1] 30 | cv2.rectangle(template, (x, y), (x + w, y + h), (7, 249, 151), 2) 31 | show(template) 32 | # return max_loc[0] 33 | 34 | 35 | if __name__ == '__main__': 36 | for i in range(0, 1): 37 | # 不同环境对distance的像素的计算方式可能不同 38 | # 可能需要进行相应的 +-*/ 操作来求出实际滑动距离 39 | process( 40 | os.path.join(os.getcwd(), f"images/slide_bg{i}.png"), 41 | os.path.join(os.getcwd(), f"images/slide_block{i}.png") 42 | ) --------------------------------------------------------------------------------