依旧是用易盾的https://dun.163.com/trial/inference


多刷新几次会发现拆分后的验证码图片有几个亮度进行了提升,所以直接匹配像素值是不行的,得计算梯度并进行归一化处理
因为八个小图片看class后面从0-7区别,所以可以返回交换的列表,比如[3,5]就是3和5交换。
,首先下载nnumpy和requests两个包

# 使用提醒:
# 1. xbot包提供软件自动化、数据表格、Excel、日志、AI等功能
# 2. package包提供访问当前应用数据的功能,如获取元素、访问全局变量、获取资源文件等功能
# 3. 当此模块作为流程独立运行时执行main函数
# 4. 可视化流程中可以通过"调用模块"的指令使用此模块
import xbot
from xbot import print, sleep
from .import package
from .package import variables as glv
import cv2
import numpy as np
import requests
from PIL import Image
import io
def main(args):
pass
# ------------------------- 1. 下载图片 -------------------------
def load_image_from_url(url):
"""
从URL下载图片并转换为OpenCV格式(BGR)
参数:
url: 图片的网络地址
返回:
OpenCV格式的图片数组(BGR色彩空间)
"""
# 发送HTTP请求获取图片数据
resp = requests.get(url)
# 使用PIL打开图片并转换为RGB模式
img = Image.open(io.BytesIO(resp.content)).convert("RGB")
# 将PIL图片转换为numpy数组,然后从RGB转换为OpenCV的BGR格式
return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
# ------------------------- 2. 切图 -------------------------
def split_image(img, rows=2, cols=4):
"""
将图像分割成多个小块
参数:
img: 输入图像(numpy数组格式,可以是灰度图或彩色图)
rows: 纵向分割的行数,默认为2
cols: 横向分割的列数,默认为4
返回:
blocks: 包含所有图像块的列表,按行优先顺序排列
"""
# 获取图像的高度和宽度(如果是彩色图,取前两个维度)
h, w = img.shape[:2]
# 计算每个小块的高度和宽度(使用整数除法确保块大小为整数)
bh, bw = h // rows, w // cols
blocks = []
# 按行遍历
for i in range(rows):
# 按列遍历
for j in range(cols):
# 切割图像块
# 行范围: i*bh 到 (i+1)*bh
# 列范围: j*bw 到 (j+1)*bw
block = img[i * bh:(i + 1) * bh, j * bw:(j + 1) * bw]
blocks.append(block)
return blocks
# ------------------------- 3. 边缘提取 -------------------------
def get_edges(block):
"""
提取图像块的四条边缘像素值
参数:
block: 输入图像块
返回:
字典,包含四条边缘的像素数组
- top: 上边缘(第一行所有像素)
- bottom: 下边缘(最后一行所有像素)
- left: 左边缘(第一列所有像素)
- right: 右边缘(最后一列所有像素)
"""
# 将彩色图转换为灰度图,简化边缘提取
gray = cv2.cvtColor(block, cv2.COLOR_BGR2GRAY)
return {
"top": gray[0, :], # 第一行所有列
"bottom": gray[-1, :], # 最后一行所有列
"left": gray[:, 0], # 第一列所有行
"right": gray[:, -1] # 最后一列所有行
}
# ------------------------- 4. 边缘相似度 -------------------------
def edge_similarity(e1, e2):
"""
计算两条边缘的相似度(使用均方误差)
参数:
e1: 第一条边缘的像素数组
e2: 第二条边缘的像素数组
返回:
均方误差值,值越小表示两条边缘越相似
"""
# 归一化处理:减去均值除以标准差,抵抗亮度变化的影响
# 加1e-5防止除零错误
e1 = (e1 - np.mean(e1)) / (np.std(e1) + 1e-5)
e2 = (e2 - np.mean(e2)) / (np.std(e2) + 1e-5)
# 计算均方误差(Mean Squared Error)
# 值越小表示两条曲线越接近
return np.mean((e1 - e2) ** 2)
# ------------------------- 5. ORB特征 -------------------------
# 创建ORB特征检测器(Oriented FAST and Rotated BRIEF)
orb = cv2.ORB_create()
# 创建暴力匹配器,使用汉明距离(适用于ORB的二进制描述子)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
def feature_score(img1, img2):
"""
计算两个图像块的特征匹配数量
参数:
img1: 第一个图像块
img2: 第二个图像块
返回:
匹配的特征点数量,数量越多表示两图像越相似
"""
# 检测关键点并计算描述子
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 如果任一图像没有检测到特征点,返回0
if des1 is None or des2 is None:
return 0
# 使用暴力匹配器进行特征匹配
matches = bf.match(des1, des2)
# 返回匹配点的数量
return len(matches)
# ------------------------- 6. 综合评分 -------------------------
def match_score(blockA, blockB):
"""
计算两个图像块的综合匹配分数(用于判断是否为左右相邻)
参数:
blockA: 左侧图像块
blockB: 右侧图像块
返回:
综合分数,值越小表示两个块越可能是左右相邻关系
"""
# 获取两个块的边缘信息
edgesA = get_edges(blockA)
edgesB = get_edges(blockB)
# 边缘匹配分数:比较A的右边缘和B的左边缘
# 边缘相似度越小(均方误差小),说明边缘越匹配
edge_score = edge_similarity(edgesA["right"], edgesB["left"])
# 取相邻的边缘区域进行特征匹配
# 从A块右侧取20像素宽的区域
right_part = blockA[:, -20:]
# 从B块左侧取20像素宽的区域
left_part = blockB[:, :20]
# 特征匹配分数:匹配的特征点数量
feat_score = feature_score(right_part, left_part)
# 融合两个分数:
# - edge_score: 越小越好(边缘相似)
# - feat_score: 越大越好(特征匹配多)
# 因此用 edge_score - 0.05 * feat_score
# 整体分数越小,说明两个块越可能是相邻的
return edge_score - 0.05 * feat_score
# ------------------------- 7. 构建评分矩阵 -------------------------
def build_score_matrix(blocks):
"""
构建所有图像块之间的匹配分数矩阵
参数:
blocks: 图像块列表
返回:
n x n 的矩阵,mat[i][j] 表示块i拼接到块j右侧的匹配分数
值越小表示越匹配,对角线设置为无穷大
"""
n = len(blocks)
# 初始化矩阵为无穷大
mat = np.full((n, n), np.inf)
# 计算每对块之间的匹配分数
for i in range(n):
for j in range(n):
# 跳过自身匹配
if i == j:
continue
# 计算块i拼接到块j右侧的分数
mat[i][j] = match_score(blocks[i], blocks[j])
return mat
# ------------------------- 8. 贪心拼接(2x4专用) -------------------------
def solve_puzzle(blocks):
"""
使用贪心算法求解拼图顺序
参数:
blocks: 打乱顺序的图像块列表
返回:
排序后的块索引列表(从左到右、从上到下的顺序)
"""
n = len(blocks)
# 构建匹配分数矩阵
score_mat = build_score_matrix(blocks)
# 寻找最左上角的块
# 左上角的块应该左边和上边都没有相邻块
# 因此它的左边匹配分数和上边匹配分数都应该很大(不匹配)
corner_scores = []
for i in range(n):
# 找出所有块拼到i左边的分数中的最小值(最可能拼到i左边的块)
left_min = np.min(score_mat[:, i])
# 找出所有块拼到i上边的分数中的最小值(最可能拼到i上边的块)
top_min = np.min(score_mat[:, i]) # 注意:这里应该使用score_mat[:, i]作为上边匹配
# 分数越大,说明越不适合有左边或上边的块,越可能是左上角
corner_scores.append(left_min + top_min)
# 选择总分最大的块作为起始块(左上角)
start = np.argmax(corner_scores)
# 贪心拼接:从左上角开始,每次选择最匹配右边的块
order = [start]
used = set(order)
# 逐块拼接(总共8块:2行x4列)
for _ in range(1, n):
last = order[-1]
# 找出所有未使用的块中,拼接到last右边最匹配的块
# candidates: (块索引, 匹配分数)
candidates = [(j, score_mat[last][j]) for j in range(n) if j not in used]
# 选择匹配分数最小的块(分数越小越匹配)
next_block = min(candidates, key=lambda x: x[1])[0]
order.append(next_block)
used.add(next_block)
return order
# ------------------------- 9. 找交换位置 -------------------------
def find_swap(order):
"""
找出需要交换的两个块的位置
参数:
order: 贪心算法得到的排序结果
返回:
需要交换的两个索引位置(从0开始)
"""
# 正确的顺序应该是 [0, 1, 2, 3, 4, 5, 6, 7]
correct = list(range(8))
# 找出所有位置错误的块索引
diff = []
for i in range(8):
if order[i] != correct[i]:
diff.append(i)
# 如果恰好有两个位置错误,返回这两个位置
# 否则返回所有错误位置(用于调试)
if len(diff) == 2:
return diff
else:
return diff # 如果不止2个,返回全部错位
# ------------------------- 10. 主函数 -------------------------
def solve_from_url(url):
"""
从URL下载图片并求解拼图交换位置
参数:
url: 拼图图片的网络地址
返回:
需要交换的两个块的位置(从0开始计数)
"""
# 1. 下载图片
img = load_image_from_url(url)
# 2. 将图片切割成2行4列的8个小块
blocks = split_image(img, 2, 4)
# 3. 使用贪心算法求解正确的拼图顺序
order = solve_puzzle(blocks)
# 4. 找出需要交换的两个块
swap = find_swap(order)
# 5. 输出结果
print("预测顺序:", order)
print("需要交换:", swap)
return swap
接下来是rpa调用
效果展示: