最近遇到一个图片切割后拼接的滑动验证码,拖动把图片位置还原,要识别他的滑动距离,看起来也不复杂,想着没必要用宰牛刀(机器学习),简单记录一下识别的两种方法:
原理是通过计算像素的欧氏距离来确定上半部分第一张图的长度,像素欧氏距离越小,越趋近同一块位置,说明那个位置是分割点。代码如下:
class Concat: def __init__(self, big, rate, draw=True, out="result.png"): "rate下半部分图片高度" self.draw = draw self.out = out self.x = 590 self.y = 360 self.rate_y = self.y - rate self.img = Image.open(BytesIO(big)).convert("RGBA") if isinstance(big, bytes) else Image.open(big).convert("RGBA") self.rate_y_x_1 = [self.img.getpixel((i, self.rate_y - 1)) for i in range(0, self.x)] self.rate_y_x_2 = [self.img.getpixel((i, self.rate_y)) for i in range(0, self.x)] def calculate_point_distance(self, x1, x2): """ 计算两点间欧氏距离 """ if len(x1) != len(x2): raise Exception("x1 x2 维度不同") return math.sqrt(sum([(int(x2[i]) - int(x1[i])) * (int(x2[i]) - int(x1[i])) for i in range(len(x1))])) def calculate_list_distance(self, list1, list2): """ 计算两数组间欧氏距离 """ distance = 0 if len(list1) != len(list2): raise Exception("list1 list2 长度不同") for i in range(len(list1)): distance += self.calculate_point_distance(list1[i], list2[i]) return distance def discern(self): result = [] for i in range(self.x): if i == 0: result.append(self.calculate_list_distance(self.rate_y_x_1, self.rate_y_x_2)) else: tmp = self.rate_y_x_1.pop(-1) self.rate_y_x_1.insert(0, tmp) result.append(self.calculate_list_distance(self.rate_y_x_1, self.rate_y_x_2)) result = result.index(min(result)) if self.draw: draw = ImageDraw.Draw(self.img) self.draw_pic(draw, [(self.x - result, 0), (self.x - result, 360)], fill='red', width=2, gap=10) return self.x - result def draw_pic(self, draw, pos_list, fill, width, gap): x_begin, y_begin = pos_list[0] x_end, y_end = pos_list[1] for _y in range(y_begin, y_end, gap): draw.line([(x_begin, _y), (x_begin, _y + gap / 2)], fill=fill, width=width) self.img.save(self.out)
识别效果如下:
先转成灰度图在自适应二值化,然后通过LSD算法找出直线位置坐标,即拼接位置,代码如下:
import cv2 as cv class Concat: def __init__(self, rate, origin): self.rate = rate #下半部分图片高度 self.origin = origin self.x = 590 #图片宽高 self.y = 360 def discren(self): origin = cv.imread(self.origin) # 原图 src = origin[:self.rate, :, :] # 切割 gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY) # 转为单通道灰度图 binary = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 5) # 自适应阈值二值化 edges = cv.Canny(binary, 50, 150, apertureSize=3) # 边缘检测 lsd = cv.createLineSegmentDetector(0) # LSD检测 lines = lsd.detect(edges) pos_y = 0 # 直线长度记录 pos_arr = None # 直线起始终止点位置数组 for line in lines[0]: x1, y1, x2, y2 = list(map(lambda x: int(round(x)), line[0])) if (abs(x2 - x1) < 1) and (abs(y2 - y1) > 20): if abs(y2 - y1) > pos_y: pos_y = abs(y2 - y1) pos_arr = [(x1, y1), (x2, y2)] if pos_arr: print("图片拼接位置为:", pos_arr[0][0]) cv.line(origin, (pos_arr[0][0], 0), (pos_arr[1][0], self.x), (0, 255, 0), 2) cv.imwrite("new_img.jpg", origin) cv.imshow("Original", origin) # cv.imshow("Edges", edges) cv.waitKey(0) cv.destroyAllWindows() else: raise Exception("没找到位置") if __name__ == '__main__': cc = Concat(180, r"bgImage.jpg") cc.discren()
识别效果如下: