最近遇到一个图片切割后拼接的滑动验证码,拖动把图片位置还原,要识别他的滑动距离,看起来也不复杂,想着没必要用宰牛刀(机器学习),简单记录一下识别的两种方法:

bgImage.jpg

  1. 原理是通过计算像素的欧氏距离来确定上半部分第一张图的长度,像素欧氏距离越小,越趋近同一块位置,说明那个位置是分割点。代码如下:

    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)

    识别效果如下:result.png

  2. 先转成灰度图在自适应二值化,然后通过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()
    

识别效果如下:

new_img-urpn.jpg