├── .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 | )
--------------------------------------------------------------------------------