切割图片随机组合再还原,滑块验证缺口坐标识别, 图片无损放大

# -*- coding: utf-8 -*-
"""
@Time    : 2023/2/6  006 下午 17:49
@Author  : Jan
@File    : 图片处理.py
"""

""" { 切割图片随机组合再还原 & 滑块验证缺口坐标识别 & 图片无损放大 } """

import cv2
import requests
import numpy as np
from io import BytesIO
from PIL import Image, ImageDraw


def dec(function):
    return function()


@dec
class SlideCrack(object):
    def __int__(self):
        pass

    def __call__(self, target: [str, bytes], slide: [str, bytes], out: str = 'out'):
        """
        :param target: 背景图片path
        :param slide: 缺口图片path
        :param out: 输出图片path
        """
        self.out = out + '.png'
        # 1、本地文件路径
        if isinstance(target, str):
            self.bg = target
            self.bg_img = cv2.imread(self.bg)
        if isinstance(slide, str):
            self.tp = slide
            self.tp_img = cv2.imread(self.tp)

        # 2、二进制 ---> numpy数组
        if isinstance(target, bytes):
            self.bg_img = self.bytes_to_np(target)
        if isinstance(slide, bytes):
            self.tp_img = self.bytes_to_np(slide)

        return self.discern()

    # 二进制 ---> numpy数组
    @classmethod
    def bytes_to_np(self, byte: bytes):
        data = np.asarray(bytearray(byte), dtype="uint8")
        image = cv2.imdecode(data, cv2.IMREAD_COLOR)
        return image

    # 二进制 ---> PIL
    @classmethod
    def bytes_to_pil(self, byte: bytes):
        img = Image.open(BytesIO(byte))
        return img

    # numpy数组 ---> 二进制
    @classmethod
    def np_to_byte(self, image) -> bytes:
        res = cv2.imencode('.png', image)[1].tobytes()
        return res

    # numpy数组 ---> 二进制 ---> PIL
    @classmethod
    def np_to_pil(self, image):
        data = self.np_to_byte(image)
        img = self.bytes_to_pil(data)
        return img

    # PIL ---> 二进制
    @classmethod
    def pil_to_byte(self, img) -> bytes:
        bytesIO = BytesIO()  # 注意:针对每个图片都要新建,这里并不代表赋值,感觉像是新建一个实例化对象
        img.save(bytesIO, format='PNG')  # format='JPEG',实际未保存
        res = bytesIO.getvalue()
        return res

    # PIL ---> 二进制 ---> numpy数组
    @classmethod
    def pil_to_np(self, img):
        data = self.pil_to_byte(img)
        image = self.bytes_to_np(data)
        return image

    @classmethod
    def show(self, im: [any, bytes]):
        if isinstance(im, bytes):
            img = self.bytes_to_pil(im)
        else:
            img = self.np_to_pil(im)
        img.show()

        # 不推荐,会暂停占用程序运行时间
        # cv2.imshow('Show', im)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()

    @staticmethod
    def clear_white(img):
        rows, cols, channel = img.shape
        min_x = min_y = 255
        max_x = max_y = 0
        for x in range(1, rows):
            for y in range(1, cols):
                t = set(img[x, y])
                if len(t) >= 2:
                    if x <= min_x:
                        min_x = x
                    elif x >= max_x:
                        max_x = x
                    if y <= min_y:
                        min_y = y
                    elif y >= max_y:
                        max_y = y
        return img[min_x:max_x, min_y: max_y]

    def template_match(self, target, slide):
        th, tw = slide.shape[:2]
        result = cv2.matchTemplate(target, slide, cv2.TM_CCOEFF_NORMED)
        # 寻找矩阵(一维数组当作向量,用Mat定义) 中最小值和最大值的位置
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        tl = max_loc
        br = (tl[0] + tw, tl[1] + th)
        return tl, br

    # 绘制矩形边框,将匹配区域标注出来
    @classmethod
    def image_draw_save(self, target: [any, bytes], start, end, offset=0):
        if isinstance(target, bytes):
            target = self.bytes_to_np(target)
        # target:目标图像
        # start:矩形定点
        # end:矩形的终点
        # (0,0,255):矩形边框颜色
        # 1:矩形边框线粗细
        start = (lambda x: (x[0] - offset, x[1] - offset))(start)
        end = (end[0] + offset, end[1] + offset)

        # # 方式一
        # cv2.rectangle(target, start, end, (0, 0, 255), 1)
        # a = 'x, y'  # 类别名称
        # b = start  # 置信度
        # font = cv2.FONT_HERSHEY_SIMPLEX  # 定义字体
        # imgzi = cv2.putText(target, '{} = {}'.format(a, b), (start[0], start[1] - 15), font, 1, (0, 0, 255), 2)
        #                   图像,            文字内容,      坐标(右上角坐标)           字体,大小,  颜色,    字体厚度
        # 显示,保存
        # cv2.imwrite(self.out, target)
        # self.show(imgzi)

        # 方式二,使用PIL
        img = self.np_to_pil(target)
        a = ImageDraw.ImageDraw(img)
        a.rectangle((start, end), fill=None, outline='red', width=1)

        # 显示,保存
        # img.save(self.out)
        img.show()

    def discern(self):
        # 清除图片的空白区域
        self.bg_img = self.clear_white(self.bg_img)
        self.tp_img = self.clear_white(self.tp_img)

        # 转换图片为灰色
        bg_gray = cv2.cvtColor(self.bg_img, cv2.COLOR_RGB2GRAY)
        tp_gray = cv2.cvtColor(self.tp_img, cv2.COLOR_RGB2GRAY)

        # 识别图片边缘
        bg_edge = cv2.Canny(bg_gray, 100, 200)
        tp_edge = cv2.Canny(tp_gray, 100, 200)

        # 转换图片格式
        bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
        tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)

        # 获取缺口起止坐标
        start, end = self.template_match(bg_pic, tp_pic)
        self.image_draw_save(self.bg_img, start, end, offset=2)  # 可调整线框偏移量

        print("point:", start)
        return start


# 图片无损放大
def retinapad_picture(src: [str, bytes], multiple=2):
    def judg(src: [str, bytes]):
        img = None
        # 1、本地文件路径
        if isinstance(src, str):
            img = cv2.imread(src)
        # 2、二进制 ---> numpy数组
        if isinstance(src, bytes):
            img = SlideCrack.bytes_to_np(src)
        return img

    img = judg(src)
    height, width = img.shape[:2]
    image = SlideCrack.np_to_pil(img)

    li = []
    for y in range(height):  # 循环读取图片的每一个像素点的RGBA值 获取图片的Y轴有多少像素 也相当于长度
        tmp = []
        for x in range(width):  # 获取图片的X轴有多少像素 也相当于宽度
            # image.getpixel((a, aa))  用来获取图片某位置的RGBA像素值
            tmp = tmp + [image.getpixel((x, y))] * multiple  # 读取某坐标的像素值并将元组为列表进行存储   Multiple是倍数
        li += tmp * multiple  # Multiple是倍数  如果是2倍 则生成两个同样的颜色文件   在后期进行单行输出多次 确保以像素点进行放大

    # 图像缩放  要将原图进行翻倍放大  然后在原图的基础上进行绘图
    result = cv2.resize(img, (width * multiple, height * multiple))
    IMAGE = SlideCrack.np_to_pil(result)
    # 转化为RGBA
    RGBA_IMG = IMAGE.convert("RGBA")
    RGBA_IMG.putdata(li)  # 写入图片
    RGBA_IMG.show()


# 切割图片,按几行几列分割
def get_split_img(img, row=2, col=2) -> dict:
    width, height = img.size
    x, y = width // col, height // row
    img = img.resize((x * col, y * row))  # 非整数倍切割时对其缩小至整倍数
    width, height = img.size
    ims = {}
    key = 0
    for j in range(0, height, y):
        for i in range(0, width, x):
            # image = SlideCrack.pil_to_np(img)
            # im = image[j:j + y, i:i + x]  # 使用 OpenCV 模块,裁剪坐标为[y0:y1, x0:x1]
            # im = SlideCrack.np_to_pil(im)

            im = img.crop((i, j, i + x, j + y))  # 使用 Pillow 模块,坐标(left, upper, right, lower)
            ims.update({key: im})
            key += 1
    return ims


def get_random_img(ims, img, row=2):
    pass
    x = y = 0
    col = len(ims) // row
    width, height = ims[0].size
    result = Image.new(ims[0].mode, (width * col, height * row))
    for im in ims:
        result.paste(im, box=(x * width, y))
        x += 1
        if x % col == 0:
            y += height
            x -= col
    result = result.resize(img.size)  # 此处缩放回原来大小会有误差导致还原后失真,不过有真实拼凑的间隙感,可选项
    result.show()
    return result

    # 上下拼接
    # width, height = ims[0].size
    # result = Image.new(ims[0].mode, (width, height * len(ims)))
    # for j, im in enumerate(ims):
    #     result.paste(im, box=(0, j * height))
    # result.show()

    # 左右拼接
    # width, height = ims[0].size
    # result = Image.new(ims[0].mode, (width * len(ims), height))
    # for j, im in enumerate(ims):
    #     result.paste(im, box=(j * width, 0))
    # result.show()


def get_recover_img(img, serilize: [list, tuple], row=2):
    col = len(serilize) // row
    ims = get_split_img(img, row=row, col=col)

    index = sorted(range(len(serilize)), key=lambda x: serilize[x])  # 还原初始位置索引
    ims = [ims[i] for i in index]

    result = get_random_img(ims, img, row)
    return result


def random_combined(ims, serilize: list = None):
    # 打乱列表顺序,随机排序
    lr = [i for i in range(len(ims))]
    serilize = np.random.choice(lr, len(lr), replace=False) if not serilize else serilize
    ims = [ims[i] for i in serilize]
    return ims, list(serilize)


def main(img, row=2, col=2, serilize: list = None):
    # 切割图片
    ims = get_split_img(img, row=row, col=col)

    # 打乱后的图片
    ims, serilize = random_combined(ims, serilize)

    # # 1、继续打乱切割后指定的位置
    # def other(im, row, col, serilize: list = None):
    #     ims4 = get_split_img(im, row=row, col=col)
    #     ims4, serilize1 = random_combined(ims4, serilize=serilize)
    #     out = get_random_img(ims4, img=img, row=row)
    #     return out
    #
    # # ims[serilize.index(4)] = other(ims[serilize.index(4)], row=3, col=3, serilize=None)  # 原图的中间 serilize.index(4)
    # ims[4] = other(ims[4], row=3, col=3, serilize=None)  # 打乱后的中间 4
    # 2、放个缩小图
    ims[(row * col) // 2] = img.resize(ims[0].size)  # 这样无告警提示

    # 拼接打乱后的图片
    out = get_random_img(ims, img, row=row)
    # out.save('C:/Users/11390/Desktop/22.PNG')

    # 还原
    result = get_recover_img(out, serilize=serilize, row=row)


if __name__ == '__main__':
    pass
    # 切割图片并随机排列再还原
    url = 'https://uploadfiles.nowcoder.com/images/20230206/993219759_1675676787618/156005C5BAF40FF51A327F1C34F2975B'
    data = requests.get(url).content
    # SlideCrack.show(data)
    img = SlideCrack.bytes_to_pil(data)

    img = img.convert('RGBA')  # (此处有个小坑) 需要图像数据的预处理,从'P','RGBA','RGB'等多种 mode 的图像转换

    # 切割行列可以是任意整数,按宽高比例整倍数最佳
    row = 7
    col = 7
    main(img, row=row, col=col)

    # # 放大图片
    # k = 8
    # url = 'http://ydgw.yundasys.com:31620/upload/image/20220826/aa710e4b01c732cb2050ba5db845af76.png'
    # data = requests.get(url).content
    # SlideCrack.show(data)
    # src = SlideCrack.bytes_to_np(data)
    # y, x = src.shape[:2]
    # res = cv2.resize(src, (x * k, y * k))
    # SlideCrack.show(SlideCrack.np_to_byte(res))
    #
    # # 无损放大
    # retinapad_picture(data, k)

全部评论

相关推荐

像好涩一样好学:这公司我也拿过 基本明确周六加班 工资还凑活 另外下次镜头往上点儿
点赞 评论 收藏
分享
无敌虾孝子:喜欢爸爸还是喜欢妈妈
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务