Python对图像进行m*n的均匀分块/拼接还原/patch/切片--转载自 “wanzhen4330”

本文详细介绍了如何通过四舍五入法和图像缩放法对图像进行网格划分,解决分块存储、裁剪和长宽整除问题,并展示了分块后的图像显示、拼接还原的过程。涉及numpy库的多个函数应用,适合图像处理初学者和进阶者参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本思路:根据图像尺寸创建一个 ( m + 1 ) × ( n + 1 )个均匀的网格顶点坐标,对于图像块来说每个图像块的左上角和右下角可以唯一确定一个图像块,这样就可以利用网格顶点坐标对原始图像进行裁剪。

1. 读取原始图像

import numpy as np 
import matplotlib.pyplot as plt 
import cv2

img = cv2.imread('cat.jpg')
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)

h, w = img.shape[0], img.shape[1]
fig1 = plt.figure('原始图像')
plt.imshow(img)
plt.axis('off')
plt.title('Original image')
print( '\t\t\t   原始图像形状:\n', '\t\t\t',img.shape ) 

在这里插入图片描述
在这里插入图片描述

2. 网格划分

这里有三个要点:图像的存储问题,图像的裁剪方法,长宽不能整除的问题。下面逐一介绍

2.1 分块后图像的存储问题

由于事先不知道m和n的具体值,因此也就不知道图像的分块数目具体是多少个。必须想个办法来存储这m × n个图像分块,创建m × n个变量来存储也是不可取的,因为这样会让程序变得很复杂。
  原作者想到一个很简单的方法:增加维度的方法。创建一个五维的张量来存放各个划分后的子图像块,其中前面两维表示该图像块在原图像的位置(如第i行第j列的分块),后面三个维度表示每个子图像块的具体内容(R、G、B的值)。

a = np.zeros([m-1, n-1, int(h1.0/(m-1)+0.5),
int(w
1.0/(n-1)+0.5),3], np.uint8)

np.uint8 的用法,见附录(本文末尾)
example = np.random.randint(0,10,(5,6,7))
(5,6,7)对应片,行,列,即:5片6行7列

2.2 图像的裁剪

直接利用numpy的切片功能将每个网格的左上角和右下角的坐标值所确定的图像块的像素直接写入五维张量即可。这里只需要注意网格顶点坐标的确认,在处理的时候横坐标和纵坐标对应的维度不要搞反
  * 其中,行,列,y的索引是行,x的索引是列。 你找第几行的时候 是以y轴坐标指定的。

gx, gy = np.meshgrid(np.linspace(0, w, n),np.linspace(0, h, m))
a[i,j,…]=img[gy[i][j]:gy[i+1][j+1], gx[i][j]:gx[i+1][j+1],:]

np.meshgrid 的用法,见附录(本文末尾)
np.linspace 的用法,见附录(本文末尾)

2.3 图像长宽的整除问题

上一步还存在一个很大的bug:如果图像的长宽不能分别被m和n整除就会带来网格点非整数的问题,还会带来部分图像分块的大小与其他图像分块不一致的问题。举个例子,比如我的图像尺寸是3 × 3,现在要把这个图像分成1 × 2个图像块,那么第2列像素就要一分为二才能做到真正的均匀划分,这显然是不可能的。

方法一:四舍五入法

我们做个简单的处理,直接对网格顶点的坐标进行四舍五入取整,这样一来,第2个像素就被划分到左边的分块了,但是这样会带来一个新的问题,即每个图像块的大小不一致。还沿用前面的例子,尺寸为3 × 3会被划分为 3 × 2和3 × 1两个图像块,这会导致图像分块的不均匀,也会导致前面的五维张量不合理。一般来说,我们要分块的图像都是很大的,而划分的块数一般不会特别多,所以舍入误差带来的几个像素的差异完全可以忽略,针对这种情况,一个比较好的思路就是在五维张量的填充中只对已知位置的像素进行填充,而把其他位置的像素值仍然设置为0。
完整程序为:

def divide_method1(img,m,n):#分割成m行n列
    h, w = img.shape[0],img.shape[1]
    gx=np.round(gx).astype(np.int)
    gy=np.round(gy).astype(np.int)
# np.round / np.astype 的用法,见附录
    divide_image = np.zeros([m-1, n-1, int(h*1.0/(m-1)+0.5), int(w*1.0/(n-1)+0.5),3], np.uint8)
    #这是一个五维的张量,前面两维表示分块后图像的位置(第m行,第n列),后面三维表示每个分块后的图像信息
    for i in range(m-1):
        for j in range(n-1):      
            divide_image[i,j,0:gy[i+1][j+1]-gy[i][j], 0:gx[i+1][j+1]-gx[i][j],:]= img[
                gy[i][j]:gy[i+1][j+1], gx[i][j]:gx[i+1][j+1],:]
                #这样写比a[i,j,...]=要麻烦,但是可以避免网格分块的时候,有些图像块的比其他图像块大一点或者小一点的情况引起程序出错
    return divide_image

方法二:图像缩放法

这个方法很简单,将图像缩放一下,让其满足整除关系即可。

def divide_method2(img,m,n):#分割成m行n列
    h, w = img.shape[0],img.shape[1]
    grid_h=int(h*1.0/(m-1)+0.5)#每个网格的高
    grid_w=int(w*1.0/(n-1)+0.5)#每个网格的宽
    
    #满足整除关系时的高、宽
    h=grid_h*(m-1)
    w=grid_w*(n-1)
    
    #图像缩放
    img_re=cv2.resize(img,(w,h),cv2.INTER_LINEAR)
    # 也可以用img_re=skimage.transform.resize(img, (h,w)).astype(np.uint8)
    #plt.imshow(img_re)
    gx, gy = np.meshgrid(np.linspace(0, w, n),np.linspace(0, h, m))
    gx=gx.astype(np.int)
    gy=gy.astype(np.int)

    divide_image = np.zeros([m-1, n-1, grid_h, grid_w,3], np.uint8)
    #这是一个五维的张量,前面两维表示分块后图像的位置(第m行,第n列),后面三维表示每个分块后的图像信息
    
    for i in range(m-1):
        for j in range(n-1):      
            divide_image[i,j,...]=img_re[
            gy[i][j]:gy[i+1][j+1], gx[i][j]:gx[i+1][j+1],:]
    return divide_image

方法三:非均分方法

采用numpy的切片方法可以简单进行处理:

 def create_image_block(self, image,row_number,col_number):
        block_row = np.array_split(image, row_number, axis = 0)#垂直方向切割,得到很多横向长条
        print(image.shape)
        img_blocks = []
        for block in block_row:
            block_col = np.array_split(block, col_number, axis = 1)#水平方向切割,得到很多图像块
            img_blocks += [block_col]
            
        #print(img_blocks[-1][-1].shape)
        cv2.imshow("block image",img_blocks[2][1])#第3行第2列图像块

3. 显示分块后的图像

def display_blocks(divide_image):#    
    m,n=divide_image.shape[0],divide_image.shape[1]
    for i in range(m):
        for j in range(n):
            plt.subplot(m,n,i*n+j+1)
            plt.imshow(divide_image[i,j,:])
            plt.axis('off')
            plt.title('block:'+str(i*n+j+1))

方法一:四舍五入法

m=3
n=4
divide_image1=divide_method1(img,m+1,n+1)#该函数中m+1和n+1表示网格点个数,m和n分别表示分块的块数
fig2 = plt.figure('分块后的子图像:四舍五入法')
display_blocks(divide_image1)

在这里插入图片描述

方法二:图像缩放法

divide_image2=divide_method2(img,m+1,n+1)#该函数中m+1和n+1表示网格点个数,m和n分别表示分块的块数
fig3 = plt.figure('分块后的子图像:图像缩放法')
display_blocks(divide_image2)

在这里插入图片描述

4.分块图像的还原

主要目的是将分块后的图像拼接起来,还原成一幅完整的大图像。有两个方法:
  1.使用opencv。其实opencv自带的有图像拼接的函数,hconcat函数:用于两个Mat矩阵或者图像的水平拼接;vconcat函数:用于两个Mat矩阵或者图像的垂直拼接。
  2.自己动手写。图像拼接是图像分块的逆过程,首先创建一个空的还原后的图像,然后将对于位置填充上对应的像素即可。
  由于事先不知道具体分成多少块,使用opencv拼接图像是很麻烦的。为了简单,我们还是选择第2种方法,自己动手丰衣足食!

def image_concat(divide_image):
    m,n,grid_h, grid_w=[divide_image.shape[0],divide_image.shape[1],#每行,每列的图像块数
                       divide_image.shape[2],divide_image.shape[3]]#每个图像块的尺寸

    restore_image = np.zeros([m*grid_h, n*grid_w, 3], np.uint8)
    restore_image[0:grid_h,0:]
    for i in range(m):
        for j in range(n):
            restore_image[i*grid_h:(i+1)*grid_h,j*grid_w:(j+1)*grid_w]=divide_image[i,j,:]
    return restore_image

下面分别显示‘四舍五入法’和‘图像缩放法’得到的分块图像的还原结果。

fig4 = plt.figure('分块图像的还原')
restore_image1=image_concat(divide_image1)#四舍五入法分块还原
restore_image2=image_concat(divide_image2)#图像缩放法分块还原
plt.subplot(1,2,1)
plt.imshow(restore_image1)
plt.axis('off')
plt.title('Rounding')
plt.subplot(1,2,2)
plt.imshow(restore_image2)
plt.axis('off')
plt.title('Scaling')
print('\t\t\t还原后的图像尺寸')
print('\t‘四舍五入法’:', restore_image1.shape,'\t''‘图像缩放法’:', restore_image2.shape)
plt.show()

在这里插入图片描述
在这里插入图片描述

5. 图像缩放法的分块 / 拼接完整代码

import numpy as np 
import matplotlib.pyplot as plt 
import cv2

img = cv2.imread('test.jpg') 
# 此处填写图像文件的名称,代码中的填写方式是将图片与代码放在同一目录(文件夹)下
# 即,若想仿照代码中的方式填写,应将图片与代码放在同一目录(文件夹)下
m=3	# 此处设置将图片分为m行
n=3	# 此处设置将图片分为m列

img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
h, w = img.shape[0], img.shape[1]
fig1 = plt.figure('原始图像')
plt.imshow(img)
plt.axis('off')
plt.title('Original image')
print( '\t\t\t   原始图像形状:\n', '\t\t\t',img.shape ) 

def divide_method2(img,m,n):#分割成m行n列
    h, w = img.shape[0],img.shape[1]
    grid_h=int(h*1.0/(m-1)+0.5)#每个网格的高
    grid_w=int(w*1.0/(n-1)+0.5)#每个网格的宽
    
    #满足整除关系时的高、宽
    h=grid_h*(m-1)
    w=grid_w*(n-1)
    
    #图像缩放
    img_re=cv2.resize(img,(w,h),cv2.INTER_LINEAR)
    # 也可以用img_re=skimage.transform.resize(img, (h,w)).astype(np.uint8)
    #plt.imshow(img_re)
    gx, gy = np.meshgrid(np.linspace(0, w, n),np.linspace(0, h, m))
    gx=gx.astype(np.int)
    gy=gy.astype(np.int)

    divide_image = np.zeros([m-1, n-1, grid_h, grid_w,3], np.uint8)
    #这是一个五维的张量,前面两维表示分块后图像的位置(第m行,第n列),后面三维表示每个分块后的图像信息
    
    for i in range(m-1):
        for j in range(n-1):      
            divide_image[i,j,...]=img_re[
            gy[i][j]:gy[i+1][j+1], gx[i][j]:gx[i+1][j+1],:]
    return divide_image
# 呈现分割的子块图像
def display_blocks(divide_image):#    
    m,n=divide_image.shape[0],divide_image.shape[1]
    for i in range(m):
        for j in range(n):
            plt.subplot(m,n,i*n+j+1)
            plt.imshow(divide_image[i,j,:])
            plt.axis('off')
            plt.title('block:'+str(i*n+j+1))
 
divide_image2=divide_method2(img,m+1,n+1)#该函数中m+1和n+1表示网格点个数,m和n分别表示分块的块数
fig3 = plt.figure('分块后的子图像:图像缩放法')
display_blocks(divide_image2)

# 图像拼接           
def image_concat(divide_image):
    m,n,grid_h, grid_w=[divide_image.shape[0],divide_image.shape[1],#每行,每列的图像块数
                       divide_image.shape[2],divide_image.shape[3]]#每个图像块的尺寸

    restore_image = np.zeros([m*grid_h, n*grid_w, 3], np.uint8)
    # restore_image[0:grid_h,0:]
    for i in range(m):
        for j in range(n):
            restore_image[i*grid_h:(i+1)*grid_h,j*grid_w:(j+1)*grid_w]=divide_image[i,j,:]
    return restore_image


fig4 = plt.figure('分块图像的还原')
restore_image2=image_concat(divide_image2)#图像缩放法分块还原
# 对比原图和拼接图--以下8行代码
plt.subplot(1,2,1)  # 设置原图位置
plt.imshow(img)
plt.axis('off')
plt.title('Original')
plt.subplot(1, 2, 2)  # 设置拼接图位置
plt.imshow(restore_image2)
plt.axis('off')
plt.title('Scaling')
print('\t\t\t还原后的图像尺寸')
print('\t''‘图像缩放法’:', restore_image2.shape)
plt.show()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6. 四舍五入法的分块 / 拼接完整代码

import numpy as np
import matplotlib.pyplot as plt
import cv2

img = cv2.imread('test.jpg')
# 此处填写图像文件的名称,代码中的填写方式是将图片与代码放在同一目录(文件夹)下
# 即,若想仿照代码中的方式填写,应将图片与代码放在同一目录(文件夹)下
m = 5  # 此处设置将图片分为m行
n = 4  # 此处设置将图片分为m列

img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w = img.shape[0], img.shape[1]
fig1 = plt.figure('原始图像')
plt.imshow(img)
plt.axis('off')
plt.title('Original image')
print('\t\t\t   原始图像形状:\n', '\t\t\t', img.shape)


def divide_method1(img, m, n):  # 分割成m行n列
    h, w = img.shape[0], img.shape[1]
    
    grid_h = int(h * 1.0 / (m - 1) + 0.5)  # 每个网格的高
    grid_w = int(w * 1.0 / (n - 1) + 0.5)  # 每个网格的宽

    # 满足整除关系时的高、宽
    h = grid_h * (m - 1)
    w = grid_w * (n - 1)
    gx, gy = np.meshgrid(np.linspace(0, w, n), np.linspace(0, h, m))

    gx = np.round(gx).astype(np.int)
    gy = np.round(gy).astype(np.int)
    # np.round / np.astype 的用法,见附录
    divide_image = np.zeros([m - 1, n - 1, int(h * 1.0 / (m - 1) + 0.5), int(w * 1.0 / (n - 1) + 0.5), 3], np.uint8)
    # 这是一个五维的张量,前面两维表示分块后图像的位置(第m行,第n列),后面三维表示每个分块后的图像信息
    for i in range(m - 1):
        for j in range(n - 1):
            divide_image[i, j, 0:gy[i + 1][j + 1] - gy[i][j], 0:gx[i + 1][j + 1] - gx[i][j], :] = img[gy[i][j]:gy[i + 1][j + 1],gx[i][j]:gx[i + 1][j + 1], :]
            # 这样写比a[i,j,...]=要麻烦,但是可以避免网格分块的时候,有些图像块的比其他图像块大一点或者小一点的情况引起程序出错
    return divide_image

# 呈现分割的子块图像
def display_blocks(divide_image):  #
    m, n = divide_image.shape[0], divide_image.shape[1]
    for i in range(m):
        for j in range(n):
            plt.subplot(m, n, i * n + j + 1)  # 设置子图
            # plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0.15, hspace=0.15) # 设置子图间距
            # # 图与图之间的间距调整wspace=0.15,hspace=0.15
            plt.imshow(divide_image[i, j, :])
            plt.axis('off')
            plt.title('block:' + str(i * n + j + 1))

# 此处调用上述的所有函数,show sub-imgs of divided
divide_image1 = divide_method1(img, m + 1, n + 1)  # 该函数中m+1和n+1表示网格点个数,m和n分别表示分块的块数
fig3 = plt.figure('分块后的子图像:图像缩放法')
display_blocks(divide_image1)


# 图像拼接
def image_concat(divide_image):
    m, n, grid_h, grid_w = [divide_image.shape[0], divide_image.shape[1],  # 每行,每列的图像块数
                            divide_image.shape[2], divide_image.shape[3]]  # 每个图像块的尺寸

    restore_image = np.zeros([m * grid_h, n * grid_w, 3], np.uint8)
    # restore_image[0:grid_h, 0:]
    for i in range(m):
        for j in range(n):
            restore_image[i * grid_h:(i + 1) * grid_h, j * grid_w:(j + 1) * grid_w] = divide_image[i, j, :]
    return restore_image


fig4 = plt.figure('分块图像的还原')
restore_image1 = image_concat(divide_image1)  # 图像缩放法分块还原
# 对比原图和拼接图--以下8行代码
plt.subplot(1, 2, 1)  # 设置原图位置
plt.imshow(img)
plt.axis('off')
plt.title('Original')
plt.subplot(1, 2, 2)  # 设置拼接图位置
plt.imshow(restore_image1)
plt.axis('off')
plt.title('Scaling')
print('\t\t\t还原后的图像尺寸')
print('\t''‘图像缩放法’:', restore_image1.shape)
plt.show()

在这里插入图片描述
在这里插入图片描述
此处关于图片的显示问题,可以通过设置代码中的子图间距,来更好的按照您的要求显示子图。
在这里插入图片描述

附录:

1. 创建图像容器 dtype=np.uint8的理解

2. numpy.meshgrid()的理解

个人理解为:X轴的按列生成坐标,Y轴按行生成坐标。也就是此超链接中,评论所说的 “把两个数组的笛卡尔积内的元素的第一二个坐标分别放入两个矩阵中。” 其中,“第一二个坐标”,我个人理解为坐标标示中的x轴和y轴,如(a,b),分别将所有坐标中a位置的数值放入X矩阵,所有坐标中b位置的数值放入Y矩阵中。

3. numpy.linspace()的理解

4. numpy.round()的理解

5. numpy.astype()的理解

爬虫Python学习是指学习如何使用Python编程语言来进行网络爬取和数据提取的过程。Python是一种简单易学且功能强大的编程语言,因此被广泛用于爬虫开发。爬虫是指通过编写程序自动抓取网页上的信息,可以用于数据采集、数据分析、网站监测等多个领域。 对于想要学习爬虫的新手来说,Python是一个很好的入门语言。Python的语法简洁易懂,而且有丰富的第三方库和工具,如BeautifulSoup、Scrapy等,可以帮助开发者更轻松地进行网页解析和数据提取。此外,Python还有很多优秀的教程和学习资源可供选择,可以帮助新手快速入门并掌握爬虫技能。 如果你对Python编程有一定的基础,那么学习爬虫并不难。你可以通过观看教学视频、阅读教程、参与在线课程等方式来学习。网络上有很多免费和付费的学习资源可供选择,你可以根据自己的需求和学习风格选择适合自己的学习材料。 总之,学习爬虫Python需要一定的编程基础,但并不难。通过选择合适的学习资源和不断实践,你可以逐步掌握爬虫的技能,并在实际项目中应用它们。 #### 引用[.reference_title] - *1* *3* [如何自学Python爬虫? 零基础入门教程](https://blog.csdn.net/zihong523/article/details/122001612)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [新手小白必看 Python爬虫学习路线全面指导](https://blog.csdn.net/weixin_67991858/article/details/128370135)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值