【学习记录】图片行列切割与子图行列拼接之中央裁剪法

写在前面 :本博客仅作记录学习之用,部分图片来自网络,如需使用请注明出处,同时如有侵犯您的权益,请联系删除!

前言

本博客仅为学习记录之用,目的在于后续若需要相关的有资可查。在言语上恐有诸多纰漏,如有错误,欢迎指出交流学习!
本博客所包含的大致内容: 大图片的行列分割;基于子图中央裁剪的行列拼接;

分割与拼接

拼接问题

由于子图的大小,长宽很难保证一致,所以直接resize到期望大小会导致图像变形,反之不resize会导致拼接可能存在黑边的情况。
故本文拼接不同于直接将子图resize到期望大小后进行行列的拼接,本文采取的方法是在子图片中央截取期望子图长宽比例的部分,再resize到期望大小后进行拼接(当然也支持直接resize后拼接,设置相关标志位即可),目的在于避免图片变形而舍弃了边缘的小部分的信息。仅仅个人的想法,如有其他的方法,欢迎留言交流哈!

分割和拼接的用途

  1. 将大图片切割后的小图片可用于移动设备的检测减少内存消耗;
  2. 同时也可用于小目标检测,小目标一直是检测任务难题,对于大图片而言,小目标在resize之后会很容易丢失;
  3. 不同图片拼接成大图可以用于丰富数据集的背景,进一步进行训练可提高网络的鲁棒性。

函数实现

切割函数

def cut_image(pic_path, save_path, cr=None, in_flag=True):
    '''
    :param pic_path: 图片路径
    :param save_path: 保存路径
    :param cr: 行列数
    :param in_flag: 是否手动选择切割图片数
    :return: None
    '''
    pics = natsort.natsorted(os.listdir(pic_path), alg=natsort.ns.PATH)
    num = len(pics)
    print(f'共能以CR={cr}则共能切割{num}张图')
    if in_flag:
        num_pics = int(input('期望切割图片数量:'))
        if num_pics > len(pics):
            num_pics = num
            warm = f'期望图片数目过多,只能切割{num}张图片'
            warnings.warn(warm)
        elif num_pics < 0:
            raise ValueError('num_pics must bigger than zero!')

        elif num_pics == 0:
            print('直接退出!')
            sys.exit()
        else:
            num_pics = num_pics
    else:
        num_pics = num
    print(f'切割的图片已保存到 {save_path} ')
    for pic_name in tqdm(pics[0:num_pics:1]):
        image = Image.open(os.path.join(pic_path, pic_name))
        width, height = image.size
        item_width = int(width / cr[0])
        item_height = int(height / cr[1])
        box_list = []
        image_list = []
        for j in range(0, cr[1]):
            for i in range(0, cr[0]):
                box = (i * item_width, j * item_height, (i + 1) * item_width, (j + 1) * item_height)
                box_list.append(box)
            image_list = [image.crop(box) for box in box_list]
        if len(image_list) > 0:
            save_images(image_list, save_path, pic_name.split('.')[0])
        else:
            raise ValueError('check the img or count_w and count_h')

拼接函数

我们事先假设期望的目标是位于图片中央,因此采取中央裁剪。针对具体的图片也可以修改参数(width/2,width/3)加以判断即可进行从左往右,或者从右往左进行等比例裁剪。

def image_compose(pic_path, pic_save_path='ping', cr=None, wh=None, in_flag=True):
    '''
    :param pic_path: 图片的路径,type:char
    :param pic_save_path: 图片保存位置,type:char
    :param cr: 子图拼接的行列,type:[]
    :param wh: 子图的大小, type:[]
    :param in_flag:是否手动输入拼接图片数量,in_flag=False可关闭手动,type:bool
    :return: 无
    '''
    image_names = natsort.natsorted(os.listdir(pic_path), alg=natsort.ns.PATH)  # 自然数命名文件
    num = int(len(image_names) / cr[0] / cr[1])
    print(f'子图共计{len(image_names)}张,以CR={cr},WH={wh}则共能拼接{num}张图')
    if in_flag:
        num_pics = int(input('期望拼接图片数量:'))
        if num_pics > num:
            num_pics = num
            warm = f'期望图片数目过多,只能拼接{num}张图片'
            warnings.warn(warm)
        elif num_pics < 0:
            raise ValueError('num_pics must bigger than zero!')
        elif num_pics == 0:
            print('直接退出!')
            sys.exit()
        else:
            num_pics = num_pics
    else:
        num_pics = num

    i = 0
    for num in tqdm(range(1, num_pics+1)):
        to_image = Image.new('RGB', (cr[0] * wh[0], cr[1] * wh[1]))   # 创建一个新图
        for y in range(1, cr[1] + 1):
            for x in range(1, cr[0] + 1):
                from_image = Image.open(pic_path + image_names[i])
                crop_image = modify_scale(from_image, wh, crop_flag=False)  # 尺寸不一致时进行裁剪,crop_flag=False则直接缩放
                to_image.paste(crop_image, ((x - 1) * wh[0], (y - 1) * wh[1]))  # 把子图贴在指定位置
                i = i + 1
        to_image.save(pic_save_path + f'/CR-{cr}-WH-{wh}_' + str(num) + '.jpg')  # 保存新图,以子图行列+子图尺寸+自然数命名
    print(f'拼接的图片已保存到 {pic_save_path} ')

中央裁剪法

def modify_scale(pic, WH=None,crop_flag=True):
    '''
    解决黑边,截取图片中心期望比例的部分进行缩放
    :param pic: 输入图片
    :param WH: 期望子图宽高
    :param crop_flag:是否需要中间截取
    :return: 调整尺寸的图片
    '''
    if pic is None:
        raise ValueError('check the picture type!')
    else:
        width, height = pic.size  # 原图片宽高

    if WH[0] == width and WH[1] == height:
        return pic

    elif WH[0]/WH[1] == width/height:
        pic = pic.resize((WH[0], WH[1]), Image.ANTIALIAS)
        return pic
    else:
        if crop_flag:
            if width/height > WH[0]/WH[1]:
                new_width = height * (WH[0]/WH[1])
                new_height = height
                box = [int(width/2-new_width/2), 0, int(width/2+new_width/2), new_height]
            else:
                new_height = width * (WH[1] / WH[0])
                new_width = width
                box = [0, int(height/2 - new_height/2), new_width, int(height/2 + new_height/2)]

            crop_pic = pic.crop(box)  # Image.crop(left, up, right, below)
            crop_pic = crop_pic.resize((WH[0], WH[1]), Image.ANTIALIAS)  # Image.ANTIALIAS 高质量缩放
        else:
            crop_pic = pic.resize((WH[0], WH[1]), Image.ANTIALIAS)
        return crop_pic

完整代码

主程序提供期望子图的大小以及行列数,还有图片的路径即可运行,切割是按照所给的切割行列数,进行相同的大小的切割,而拼接则是裁剪实际子图中央与期望子图比例的部分进行缩放及拼接,下面的图片是拼接的整体思路。
拼接原理说明

# !/usr/bin/env python
# -*- coding:utf-8 -*-
# @Author  : Xiaodong
# @file    : single2image.py
# @Function: Split and stitch images

import os
import sys
from PIL import Image
from tqdm import tqdm
import natsort
import warnings


def cut_image(pic_path, save_path, cr=None, in_flag=True):
    '''
    :param pic_path: 图片路径
    :param save_path: 保存路径
    :param cr: 行列数
    :param in_flag: 是否手动选择切割图片数
    :return: None
    '''
    pics = natsort.natsorted(os.listdir(pic_path), alg=natsort.ns.PATH)
    num = len(pics)
    print(f'共能以CR={cr}则共能切割{num}张图')
    if in_flag:
        num_pics = int(input('期望切割图片数量:'))
        if num_pics > len(pics):
            num_pics = num
            warm = f'期望图片数目过多,只能切割{num}张图片'
            warnings.warn(warm)
        elif num_pics < 0:
            raise ValueError('num_pics must bigger than zero!')

        elif num_pics == 0:
            print('直接退出!')
            sys.exit()
        else:
            num_pics = num_pics
    else:
        num_pics = num
    print(f'切割的图片已保存到 {save_path} ')
    for pic_name in tqdm(pics[0:num_pics:1]):
        image = Image.open(os.path.join(pic_path, pic_name))
        width, height = image.size
        item_width = int(width / cr[0])
        item_height = int(height / cr[1])
        box_list = []
        image_list = []
        for j in range(0, cr[1]):
            for i in range(0, cr[0]):
                box = (i * item_width, j * item_height, (i + 1) * item_width, (j + 1) * item_height)
                box_list.append(box)
            image_list = [image.crop(box) for box in box_list]
        if len(image_list) > 0:
            save_images(image_list, save_path, pic_name.split('.')[0])
        else:
            raise ValueError('check the img or count_w and count_h')


def save_images(image_list, save__path, img_name):
    '''
    :param image_list: 图片列表
    :param save__path: 保存路径
    :param img_name: 图片名字,用于命名
    :return: None
    '''
    index = 0
    for image in image_list:
        if image.mode == "RGBA" or image.mode == "L":
            image = image.convert('RGB')
        image.save(save__path + '/' + str(img_name) + '_' + str(index) + '.jpg')
        index += 1


def modify_scale(pic, WH=None,crop_flag=True):
    '''
    解决黑边,截取图片中心期望比例的部分进行缩放
    :param pic: 输入图片
    :param WH: 期望子图宽高
    :param crop_flag:是否需要中间截取
    :return: 调整尺寸的图片
    '''
    if pic is None:
        raise ValueError('check the picture type!')
    else:
        width, height = pic.size  # 原图片宽高

    if WH[0] == width and WH[1] == height:
        return pic

    elif WH[0]/WH[1] == width/height:
        pic = pic.resize((WH[0], WH[1]), Image.ANTIALIAS)
        return pic
    else:
        if crop_flag:
            if width/height > WH[0]/WH[1]:
                new_width = height * (WH[0]/WH[1])
                new_height = height
                box = [int(width/2-new_width/2), 0, int(width/2+new_width/2), new_height]
            else:
                new_height = width * (WH[1] / WH[0])
                new_width = width
                box = [0, int(height/2 - new_height/2), new_width, int(height/2 + new_height/2)]

            crop_pic = pic.crop(box)  # Image.crop(left, up, right, below)
            crop_pic = crop_pic.resize((WH[0], WH[1]), Image.ANTIALIAS)  # Image.ANTIALIAS 高质量缩放
        else:
            crop_pic = pic.resize((WH[0], WH[1]), Image.ANTIALIAS)
        return crop_pic



def image_compose(pic_path, pic_save_path='ping', cr=None, wh=None, in_flag=True):
    '''
    :param pic_path: 图片的路径,type:char
    :param pic_save_path: 图片保存位置,type:char
    :param cr: 子图拼接的行列,type:[]
    :param wh: 子图的大小, type:[]
    :param in_flag:是否手动输入拼接图片数量,in_flag=False可关闭手动,type:bool
    :return: 无
    '''
    image_names = natsort.natsorted(os.listdir(pic_path), alg=natsort.ns.PATH)  # 自然数命名文件
    num = int(len(image_names) / cr[0] / cr[1])
    print(f'子图共计{len(image_names)}张,以CR={cr},WH={wh}则共能拼接{num}张图')
    if in_flag:
        num_pics = int(input('期望拼接图片数量:'))
        if num_pics > num:
            num_pics = num
            warm = f'期望图片数目过多,只能拼接{num}张图片'
            warnings.warn(warm)
        elif num_pics < 0:
            raise ValueError('num_pics must bigger than zero!')
        elif num_pics == 0:
            print('直接退出!')
            sys.exit()
        else:
            num_pics = num_pics
    else:
        num_pics = num

    i = 0
    for num in tqdm(range(1, num_pics+1)):
        to_image = Image.new('RGB', (cr[0] * wh[0], cr[1] * wh[1]))   # 创建一个新图
        for y in range(1, cr[1] + 1):
            for x in range(1, cr[0] + 1):
                from_image = Image.open(pic_path + image_names[i])
                crop_image = modify_scale(from_image, wh, crop_flag=False)  # 尺寸不一致时进行裁剪,crop_flag=False则直接缩放
                to_image.paste(crop_image, ((x - 1) * wh[0], (y - 1) * wh[1]))  # 把子图贴在指定位置
                i = i + 1
        to_image.save(pic_save_path + f'/CR-{cr}-WH-{wh}_' + str(num) + '.jpg')  # 保存新图,以子图行列+子图尺寸+自然数命名
    print(f'拼接的图片已保存到 {pic_save_path} ')


if __name__ == '__main__':

    IMAGE_W = 2000  # 期望每张子图的大小,无需和实际尺寸一致
    IMAGE_H = 500
    COLUMN = 1  # 列
    ROW = 4     # 行,此时即4张图拼成4行1列,或者将图片分解为4行1列,取决于后续函数选择
    IMAGES_PATH = 'king/'  # 子图路径
    SAVE_PATH = 'ping/'  # 保存路径

    CR = [COLUMN, ROW]
    WH = [IMAGE_W, IMAGE_H]

    if len(os.listdir(IMAGES_PATH)) >= ROW*COLUMN:
        if not os.path.exists(SAVE_PATH):  # 生成保存路径
            os.makedirs(SAVE_PATH)
        cut_image(IMAGES_PATH, save_path=SAVE_PATH, cr=CR)  # 调用函数,分割
        image_compose(SAVE_PATH, SAVE_PATH, CR, WH)  # 调用函数,拼接
    else:
        raise ValueError('Please specify the number of ROW and COLUMN !'
                         'The solutions of the error are decrease the ROW and COLUMN or enlarge the number of images')

分割检测实例

以裂隙检测为例,这里讲述的是分割检测的思想,实际效果不太好是网络还未完全收敛(CPU比较慢)。由于强行将大图压缩后传入网络,图片信息丢失很严重,为避免这种情况,将图片分割后逐一进行检测,后续进行拼接回原图是一种可行的方法。

原图

原图在5000px6000px左右,网络输入一般608或者1024,直接压缩则会丢失很多,导致压缩后分辨率很低,容易误检。
裂隙原图

分割子图

此处将图片分割为8份,如下效果,在依次进行检测,图片基本上就变成1000+的大小,能够不用较大压缩而输入网络。

检测拼接

将检测后的结果依次拼接,实现全图检测,此处是按照分割的行列及大小,才能实现完美复原,否则会采取中央裁剪进行拼接。
分割检测后拼接

丰富背景

背景丰富不是本文的重点,仅作示例(图片来自LFW),如下图。

对比拼接

这里展现的是直接resize和中央裁剪的对比,展现下中央裁剪的优势,对比效果如下。

上面对比看起来差别不大,那可继续往下看,对比效果明显很多,原因在于期望子图的大小不一样,同样是八张图片,拼接没有按照实际子图大小设置,而强行进行拉伸。可以看出当期望子图长宽和实际比例相近时,两者差别不大。当与时间比例差别较大时,中央裁剪尽管丢失边缘,但至少没有很大的变形,视觉感知更好。

致谢

欲尽善本文,因所视短浅,怎奈所书皆是瞽言蒭议。行文至此,诚向予助与余者致以谢意。

共勉之语

默默耕耘,是为了在明天硕果累累;沉淀积累,是为了在未来厚积薄发。成功不是一蹴而就,唯有潜心笃志、积蓄力量,才能迎来花开之时。(人民日报)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值