# SliderSolver




一个用于识别滑块验证码缺口位置的 Python 包。通过图像处理技术,自动识别缺口在背景图中的位置,并返回缺口左边缘到背景图左边缘的距离。
## 功能特点
- 🎯 精准定位:使用 OpenCV 模板匹配算法精确识别缺口位置
- 🖼️ 边缘检测:基于 Canny 边缘检测提高匹配准确率
- 📊 可视化结果:自动在背景图上标记识别位置
- 🚀 简单易用:仅需两行代码即可完成识别
## 安装
### 从源码安装
```bash
git clone https://github.com/SkyAerope/SliderSolver.git
cd SliderSolver
pip install -e .
```
### 使用 pip 安装
```bash
pip install slider-solver-cv
```
## 依赖
- Python >= 3.9
- opencv-python >= 4.5.0
- numpy >= 1.19.0
- pillow >= 8.0.0
## 使用方法
### 基本用法
```python
from slider_solver import SliderSolver
# 创建求解器实例
solver = SliderSolver(
bg_img_path='path/to/background.png', # 背景图路径
front_img_path='path/to/slider.png' # 缺口图路径
)
# 计算缺口位置
distance = solver.detect_distance()
print(f'缺口位置: {distance}px')
```
### 带可视化的完整示例
```python
from slider_solver import SliderSolver
import os
def solve_slider_captcha():
# 图片路径
bg_img = 'images/background.png'
slider_img = 'images/slider.png'
# 检查文件是否存在
if not os.path.exists(bg_img) or not os.path.exists(slider_img):
print("错误:图片文件不存在")
return
# 创建求解器
solver = SliderSolver(bg_img, slider_img)
# 计算位置
distance = solver.detect_distance()
print(f'✅ 识别成功!缺口位置在 x = {distance}px')
# 可选:绘制标记线并保存结果图片
result_path = solver.draw_line(distance, bg_img)
print(f'📁 结果图片已保存到: {result_path}')
return distance
if __name__ == '__main__':
solve_slider_captcha()
```
### 自定义保存路径
```python
from slider_solver import SliderSolver
solver = SliderSolver('background.png', 'slider.png')
distance = solver.detect_distance()
# 指定自定义的保存路径
result_path = solver.draw_line(
x=distance,
bg_img_path='background.png',
target_path='output/marked_result.png' # 可选,不指定则自动生成
)
print(f'结果保存到: {result_path}')
```
## API 说明
### SliderSolver 类
#### `__init__(bg_img_path, front_img_path)`
初始化求解器。
**参数:**
- `bg_img_path` (str): 背景图片的文件路径
- `front_img_path` (str): 缺口图片的文件路径
#### `detect_distance()`
检测缺口在背景图中的 x 坐标位置。
**返回值:**
- `int`: 缺口距离左边界的像素距离
**示例:**
```python
solver = SliderSolver('bg.png', 'slider.png')
distance = solver.detect_distance() # 返回如: 120
```
#### `draw_line(x, bg_img_path, target_path=None)`
在背景图上绘制红色竖线标记位置。
**参数:**
- `x` (int): 竖线的 x 坐标
- `bg_img_path` (str): 背景图片路径
- `target_path` (str, 可选): 结果图片保存路径,不指定则自动生成(添加 `_result` 后缀)
**返回值:**
- `str`: 保存的结果图片路径
**示例:**
```python
result_path = solver.draw_line(120, 'bg.png') # 自动保存为 bg_result.png
# 或指定路径
result_path = solver.draw_line(120, 'bg.png', 'output/marked.png')
```
## 工作原理
1. **读取图片**:加载背景图和缺口图
2. **预处理**:去除缺口图的透明边界
3. **灰度转换**:将图片转换为灰度图
4. **边缘检测**:使用 Canny 算法检测边缘
5. **模板匹配**:使用 TM_CCOEFF_NORMED 方法进行匹配
6. **返回结果**:返回匹配到的 x 坐标
7. **可选可视化**:调用 `draw_line()` 方法绘制标记线
## 项目结构
```
SliderSolver/
├── src/
│ └── slider_solver/
│ ├── __init__.py # 包初始化文件
│ └── solver.py # 核心实现
├── tests/ # 测试目录
│ ├── __init__.py # 测试包初始化
│ ├── test_solver.py # 单元测试
│ └── test_images/ # 测试图片
│ ├── bg1.png # 测试背景图1
│ ├── bg2.png # 测试背景图2
│ ├── t1.png # 测试滑块图1
│ └── t2.png # 测试滑块图2
├── setup.py # 安装配置(传统方式)
├── pyproject.toml # 现代 Python 包配置
├── requirements.txt # 依赖列表
├── README.md # 使用文档
├── LICENSE # 许可证
└── .gitignore # Git 忽略规则
```
## 开发与测试
### 安装开发依赖
```bash
# 安装所有依赖(包括测试工具)
pip install -r requirements.txt
```
### 运行测试
`tests/test_images`中含有示例图片,可以用于测试目的
```bash
# 运行所有测试
pytest tests/ -v
# 运行特定测试
pytest tests/test_solver.py::TestSliderSolver::test_detect_distance_case1 -v
# 显示详细输出
pytest tests/ -v -s
```
### 测试覆盖的功能
- ✅ 基本的距离检测功能
- ✅ 多组图片组合测试
- ✅ 默认路径的标记线绘制
- ✅ 自定义路径的标记线绘制
- ✅ 无效路径的错误处理
- ✅ 初始化参数验证
## 许可证
本项目采用 Apache-2.0 许可证。详见 [LICENSE](LICENSE) 文件。
## 贡献
欢迎提交 Issue 和 Pull Request!
## 作者
- GitHub: [@SkyAerope](https://github.com/SkyAerope)
Raw data
{
"_id": null,
"home_page": "https://github.com/SkyAerope/SliderSolver",
"name": "slider-solver-cv",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": "slider, captcha, opencv, computer-vision",
"author": "Your Name",
"author_email": "SkyAerope <skyaerope@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/62/18/2ebae7d979bd164570bb2b22656ca8fc7e354c7369bc4198919383769474/slider_solver_cv-0.1.1.tar.gz",
"platform": null,
"description": "# SliderSolver\n\n\n\n\n\n\n\u4e00\u4e2a\u7528\u4e8e\u8bc6\u522b\u6ed1\u5757\u9a8c\u8bc1\u7801\u7f3a\u53e3\u4f4d\u7f6e\u7684 Python \u5305\u3002\u901a\u8fc7\u56fe\u50cf\u5904\u7406\u6280\u672f\uff0c\u81ea\u52a8\u8bc6\u522b\u7f3a\u53e3\u5728\u80cc\u666f\u56fe\u4e2d\u7684\u4f4d\u7f6e\uff0c\u5e76\u8fd4\u56de\u7f3a\u53e3\u5de6\u8fb9\u7f18\u5230\u80cc\u666f\u56fe\u5de6\u8fb9\u7f18\u7684\u8ddd\u79bb\u3002\n\n## \u529f\u80fd\u7279\u70b9\n\n- \ud83c\udfaf \u7cbe\u51c6\u5b9a\u4f4d\uff1a\u4f7f\u7528 OpenCV \u6a21\u677f\u5339\u914d\u7b97\u6cd5\u7cbe\u786e\u8bc6\u522b\u7f3a\u53e3\u4f4d\u7f6e\n- \ud83d\uddbc\ufe0f \u8fb9\u7f18\u68c0\u6d4b\uff1a\u57fa\u4e8e Canny \u8fb9\u7f18\u68c0\u6d4b\u63d0\u9ad8\u5339\u914d\u51c6\u786e\u7387\n- \ud83d\udcca \u53ef\u89c6\u5316\u7ed3\u679c\uff1a\u81ea\u52a8\u5728\u80cc\u666f\u56fe\u4e0a\u6807\u8bb0\u8bc6\u522b\u4f4d\u7f6e\n- \ud83d\ude80 \u7b80\u5355\u6613\u7528\uff1a\u4ec5\u9700\u4e24\u884c\u4ee3\u7801\u5373\u53ef\u5b8c\u6210\u8bc6\u522b\n\n## \u5b89\u88c5\n\n### \u4ece\u6e90\u7801\u5b89\u88c5\n\n```bash\ngit clone https://github.com/SkyAerope/SliderSolver.git\ncd SliderSolver\npip install -e .\n```\n\n### \u4f7f\u7528 pip \u5b89\u88c5\n\n```bash\npip install slider-solver-cv\n```\n\n## \u4f9d\u8d56\n\n- Python >= 3.9\n- opencv-python >= 4.5.0\n- numpy >= 1.19.0\n- pillow >= 8.0.0\n\n## \u4f7f\u7528\u65b9\u6cd5\n\n### \u57fa\u672c\u7528\u6cd5\n\n```python\nfrom slider_solver import SliderSolver\n\n# \u521b\u5efa\u6c42\u89e3\u5668\u5b9e\u4f8b\nsolver = SliderSolver(\n bg_img_path='path/to/background.png', # \u80cc\u666f\u56fe\u8def\u5f84\n front_img_path='path/to/slider.png' # \u7f3a\u53e3\u56fe\u8def\u5f84\n)\n\n# \u8ba1\u7b97\u7f3a\u53e3\u4f4d\u7f6e\ndistance = solver.detect_distance()\nprint(f'\u7f3a\u53e3\u4f4d\u7f6e: {distance}px')\n```\n\n### \u5e26\u53ef\u89c6\u5316\u7684\u5b8c\u6574\u793a\u4f8b\n\n```python\nfrom slider_solver import SliderSolver\nimport os\n\ndef solve_slider_captcha():\n # \u56fe\u7247\u8def\u5f84\n bg_img = 'images/background.png'\n slider_img = 'images/slider.png'\n \n # \u68c0\u67e5\u6587\u4ef6\u662f\u5426\u5b58\u5728\n if not os.path.exists(bg_img) or not os.path.exists(slider_img):\n print(\"\u9519\u8bef\uff1a\u56fe\u7247\u6587\u4ef6\u4e0d\u5b58\u5728\")\n return\n \n # \u521b\u5efa\u6c42\u89e3\u5668\n solver = SliderSolver(bg_img, slider_img)\n \n # \u8ba1\u7b97\u4f4d\u7f6e\n distance = solver.detect_distance()\n print(f'\u2705 \u8bc6\u522b\u6210\u529f\uff01\u7f3a\u53e3\u4f4d\u7f6e\u5728 x = {distance}px')\n \n # \u53ef\u9009\uff1a\u7ed8\u5236\u6807\u8bb0\u7ebf\u5e76\u4fdd\u5b58\u7ed3\u679c\u56fe\u7247\n result_path = solver.draw_line(distance, bg_img)\n print(f'\ud83d\udcc1 \u7ed3\u679c\u56fe\u7247\u5df2\u4fdd\u5b58\u5230: {result_path}')\n \n return distance\n\nif __name__ == '__main__':\n solve_slider_captcha()\n```\n\n### \u81ea\u5b9a\u4e49\u4fdd\u5b58\u8def\u5f84\n\n```python\nfrom slider_solver import SliderSolver\n\nsolver = SliderSolver('background.png', 'slider.png')\ndistance = solver.detect_distance()\n\n# \u6307\u5b9a\u81ea\u5b9a\u4e49\u7684\u4fdd\u5b58\u8def\u5f84\nresult_path = solver.draw_line(\n x=distance,\n bg_img_path='background.png',\n target_path='output/marked_result.png' # \u53ef\u9009\uff0c\u4e0d\u6307\u5b9a\u5219\u81ea\u52a8\u751f\u6210\n)\nprint(f'\u7ed3\u679c\u4fdd\u5b58\u5230: {result_path}')\n```\n\n## API \u8bf4\u660e\n\n### SliderSolver \u7c7b\n\n#### `__init__(bg_img_path, front_img_path)`\n\n\u521d\u59cb\u5316\u6c42\u89e3\u5668\u3002\n\n**\u53c2\u6570\uff1a**\n\n- `bg_img_path` (str): \u80cc\u666f\u56fe\u7247\u7684\u6587\u4ef6\u8def\u5f84\n- `front_img_path` (str): \u7f3a\u53e3\u56fe\u7247\u7684\u6587\u4ef6\u8def\u5f84\n\n#### `detect_distance()`\n\n\u68c0\u6d4b\u7f3a\u53e3\u5728\u80cc\u666f\u56fe\u4e2d\u7684 x \u5750\u6807\u4f4d\u7f6e\u3002\n\n**\u8fd4\u56de\u503c\uff1a**\n\n- `int`: \u7f3a\u53e3\u8ddd\u79bb\u5de6\u8fb9\u754c\u7684\u50cf\u7d20\u8ddd\u79bb\n\n**\u793a\u4f8b\uff1a**\n\n```python\nsolver = SliderSolver('bg.png', 'slider.png')\ndistance = solver.detect_distance() # \u8fd4\u56de\u5982: 120\n```\n\n#### `draw_line(x, bg_img_path, target_path=None)`\n\n\u5728\u80cc\u666f\u56fe\u4e0a\u7ed8\u5236\u7ea2\u8272\u7ad6\u7ebf\u6807\u8bb0\u4f4d\u7f6e\u3002\n\n**\u53c2\u6570\uff1a**\n\n- `x` (int): \u7ad6\u7ebf\u7684 x \u5750\u6807\n- `bg_img_path` (str): \u80cc\u666f\u56fe\u7247\u8def\u5f84\n- `target_path` (str, \u53ef\u9009): \u7ed3\u679c\u56fe\u7247\u4fdd\u5b58\u8def\u5f84\uff0c\u4e0d\u6307\u5b9a\u5219\u81ea\u52a8\u751f\u6210\uff08\u6dfb\u52a0 `_result` \u540e\u7f00\uff09\n\n**\u8fd4\u56de\u503c\uff1a**\n\n- `str`: \u4fdd\u5b58\u7684\u7ed3\u679c\u56fe\u7247\u8def\u5f84\n\n**\u793a\u4f8b\uff1a**\n\n```python\nresult_path = solver.draw_line(120, 'bg.png') # \u81ea\u52a8\u4fdd\u5b58\u4e3a bg_result.png\n# \u6216\u6307\u5b9a\u8def\u5f84\nresult_path = solver.draw_line(120, 'bg.png', 'output/marked.png')\n```\n\n## \u5de5\u4f5c\u539f\u7406\n\n1. **\u8bfb\u53d6\u56fe\u7247**\uff1a\u52a0\u8f7d\u80cc\u666f\u56fe\u548c\u7f3a\u53e3\u56fe\n2. **\u9884\u5904\u7406**\uff1a\u53bb\u9664\u7f3a\u53e3\u56fe\u7684\u900f\u660e\u8fb9\u754c\n3. **\u7070\u5ea6\u8f6c\u6362**\uff1a\u5c06\u56fe\u7247\u8f6c\u6362\u4e3a\u7070\u5ea6\u56fe\n4. **\u8fb9\u7f18\u68c0\u6d4b**\uff1a\u4f7f\u7528 Canny \u7b97\u6cd5\u68c0\u6d4b\u8fb9\u7f18\n5. **\u6a21\u677f\u5339\u914d**\uff1a\u4f7f\u7528 TM_CCOEFF_NORMED \u65b9\u6cd5\u8fdb\u884c\u5339\u914d\n6. **\u8fd4\u56de\u7ed3\u679c**\uff1a\u8fd4\u56de\u5339\u914d\u5230\u7684 x \u5750\u6807\n7. **\u53ef\u9009\u53ef\u89c6\u5316**\uff1a\u8c03\u7528 `draw_line()` \u65b9\u6cd5\u7ed8\u5236\u6807\u8bb0\u7ebf\n\n## \u9879\u76ee\u7ed3\u6784\n\n```\nSliderSolver/\n\u251c\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 slider_solver/\n\u2502 \u251c\u2500\u2500 __init__.py # \u5305\u521d\u59cb\u5316\u6587\u4ef6\n\u2502 \u2514\u2500\u2500 solver.py # \u6838\u5fc3\u5b9e\u73b0\n\u251c\u2500\u2500 tests/ # \u6d4b\u8bd5\u76ee\u5f55\n\u2502 \u251c\u2500\u2500 __init__.py # \u6d4b\u8bd5\u5305\u521d\u59cb\u5316\n\u2502 \u251c\u2500\u2500 test_solver.py # \u5355\u5143\u6d4b\u8bd5\n\u2502 \u2514\u2500\u2500 test_images/ # \u6d4b\u8bd5\u56fe\u7247\n\u2502 \u251c\u2500\u2500 bg1.png # \u6d4b\u8bd5\u80cc\u666f\u56fe1\n\u2502 \u251c\u2500\u2500 bg2.png # \u6d4b\u8bd5\u80cc\u666f\u56fe2\n\u2502 \u251c\u2500\u2500 t1.png # \u6d4b\u8bd5\u6ed1\u5757\u56fe1\n\u2502 \u2514\u2500\u2500 t2.png # \u6d4b\u8bd5\u6ed1\u5757\u56fe2\n\u251c\u2500\u2500 setup.py # \u5b89\u88c5\u914d\u7f6e\uff08\u4f20\u7edf\u65b9\u5f0f\uff09\n\u251c\u2500\u2500 pyproject.toml # \u73b0\u4ee3 Python \u5305\u914d\u7f6e\n\u251c\u2500\u2500 requirements.txt # \u4f9d\u8d56\u5217\u8868\n\u251c\u2500\u2500 README.md # \u4f7f\u7528\u6587\u6863\n\u251c\u2500\u2500 LICENSE # \u8bb8\u53ef\u8bc1\n\u2514\u2500\u2500 .gitignore # Git \u5ffd\u7565\u89c4\u5219\n```\n\n## \u5f00\u53d1\u4e0e\u6d4b\u8bd5\n\n### \u5b89\u88c5\u5f00\u53d1\u4f9d\u8d56\n\n```bash\n# \u5b89\u88c5\u6240\u6709\u4f9d\u8d56\uff08\u5305\u62ec\u6d4b\u8bd5\u5de5\u5177\uff09\npip install -r requirements.txt\n```\n\n### \u8fd0\u884c\u6d4b\u8bd5\n\n`tests/test_images`\u4e2d\u542b\u6709\u793a\u4f8b\u56fe\u7247\uff0c\u53ef\u4ee5\u7528\u4e8e\u6d4b\u8bd5\u76ee\u7684\n\n```bash\n# \u8fd0\u884c\u6240\u6709\u6d4b\u8bd5\npytest tests/ -v\n\n# \u8fd0\u884c\u7279\u5b9a\u6d4b\u8bd5\npytest tests/test_solver.py::TestSliderSolver::test_detect_distance_case1 -v\n\n# \u663e\u793a\u8be6\u7ec6\u8f93\u51fa\npytest tests/ -v -s\n```\n\n### \u6d4b\u8bd5\u8986\u76d6\u7684\u529f\u80fd\n\n- \u2705 \u57fa\u672c\u7684\u8ddd\u79bb\u68c0\u6d4b\u529f\u80fd\n- \u2705 \u591a\u7ec4\u56fe\u7247\u7ec4\u5408\u6d4b\u8bd5\n- \u2705 \u9ed8\u8ba4\u8def\u5f84\u7684\u6807\u8bb0\u7ebf\u7ed8\u5236\n- \u2705 \u81ea\u5b9a\u4e49\u8def\u5f84\u7684\u6807\u8bb0\u7ebf\u7ed8\u5236\n- \u2705 \u65e0\u6548\u8def\u5f84\u7684\u9519\u8bef\u5904\u7406\n- \u2705 \u521d\u59cb\u5316\u53c2\u6570\u9a8c\u8bc1\n\n## \u8bb8\u53ef\u8bc1\n\n\u672c\u9879\u76ee\u91c7\u7528 Apache-2.0 \u8bb8\u53ef\u8bc1\u3002\u8be6\u89c1 [LICENSE](LICENSE) \u6587\u4ef6\u3002\n\n## \u8d21\u732e\n\n\u6b22\u8fce\u63d0\u4ea4 Issue \u548c Pull Request\uff01\n\n## \u4f5c\u8005\n\n- GitHub: [@SkyAerope](https://github.com/SkyAerope)\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "\u8bc6\u522b\u6ed1\u5757\u9a8c\u8bc1\u7801\u7f3a\u53e3\u4f4d\u7f6e",
"version": "0.1.1",
"project_urls": {
"Bug Reports": "https://github.com/SkyAerope/SliderSolver/issues",
"Homepage": "https://github.com/SkyAerope/SliderSolver",
"Source": "https://github.com/SkyAerope/SliderSolver"
},
"split_keywords": [
"slider",
" captcha",
" opencv",
" computer-vision"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "c9ff00a1fc50cef833c9041a3a49be441ed6018816afee965eb0f2c3145f9df8",
"md5": "56735c8d64f484602ca51a38121a88c0",
"sha256": "72383b3ce7afe1b22b7a5672ac03af8ee218fe415e7212e5557864c9e24ec3d3"
},
"downloads": -1,
"filename": "slider_solver_cv-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "56735c8d64f484602ca51a38121a88c0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 9628,
"upload_time": "2025-10-30T07:44:49",
"upload_time_iso_8601": "2025-10-30T07:44:49.584550Z",
"url": "https://files.pythonhosted.org/packages/c9/ff/00a1fc50cef833c9041a3a49be441ed6018816afee965eb0f2c3145f9df8/slider_solver_cv-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "62182ebae7d979bd164570bb2b22656ca8fc7e354c7369bc4198919383769474",
"md5": "b572aee99676cdeefd00a15f09383817",
"sha256": "01f93da851614e02d66079d8e013e8f3f76a62ba1cb417009318bb08425c4b8c"
},
"downloads": -1,
"filename": "slider_solver_cv-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "b572aee99676cdeefd00a15f09383817",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 10141,
"upload_time": "2025-10-30T07:44:50",
"upload_time_iso_8601": "2025-10-30T07:44:50.344145Z",
"url": "https://files.pythonhosted.org/packages/62/18/2ebae7d979bd164570bb2b22656ca8fc7e354c7369bc4198919383769474/slider_solver_cv-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-30 07:44:50",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "SkyAerope",
"github_project": "SliderSolver",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "opencv-python",
"specs": [
[
">=",
"4.5.0"
]
]
},
{
"name": "numpy",
"specs": [
[
">=",
"1.19.0"
]
]
},
{
"name": "pillow",
"specs": [
[
">=",
"8.0.0"
]
]
},
{
"name": "pytest",
"specs": [
[
">=",
"7.0.0"
]
]
}
],
"lcname": "slider-solver-cv"
}