用Python語言對任意圖像進行m*n的均勻分塊並拼接還原(思路非常清晰,步驟簡單)


  基本思路:根據圖像尺寸創建一個(m+1)×(n+1)(m+1) \times (n+1)個均勻的網格頂點座標,對於圖像塊來說每個圖像塊的左上角和右下角可以唯一確定一個圖像塊,這樣就可以利用網格頂點座標對原始圖像進行裁剪。
  注意:完整的Python源程序可以在我的CSDN上下載(點擊進入下載界面),沒有積分的小夥伴把下面的各個程序整理起來即可。

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.網格劃分,將圖像劃分爲m*n塊

  這裏有三個要點:圖像的存儲問題,圖像的裁剪方法,長寬不能整除的問題。下面逐一介紹

2.1分塊後圖像的存儲問題

  由於事先不知道m和n的具體值,因此也就不知道圖像的分塊數目具體是多少個。必須想個辦法來存儲這m×nm \times n個圖像分塊,創建m×nm \times n個變量來存儲也是不可取的,因爲這樣會讓程序變得很複雜。
  本人想到一個很簡單的方法:增加維度的方法。創建一個五維的張量來存放各個劃分後的子圖像塊,其中前面兩維表示該圖像塊在原圖像的位置(如第i行第j列的分塊),後面三個維度表示每個子圖像塊的具體內容(R、G、B的值)。

a = 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)

2.2圖像的裁剪

  直接利用numpy的切片功能將每個網格的左上角和右下角的座標值所確定的圖像塊的像素直接寫入五維張量即可。這裏只需要注意網格頂點座標的確認,在處理的時候橫座標和縱座標對應的維度不要搞反。

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],:]

2.3圖像長寬的整除問題

  上一步還存在一個很大的bug:如果圖像的長寬不能分別被m和n整除就會帶來網格點非整數的問題,還會帶來部分圖像分塊的大小與其他圖像分塊不一致的問題。舉個例子,比如我的圖像尺寸是3×33 \times 3,現在要把這個圖像分成1×21 \times 2個圖像塊,那麼第2列像素就要一分爲二才能做到真正的均勻劃分,這顯然是不可能的。

方法一:四捨五入法

  我們做個簡單的處理,直接對網格頂點的座標進行四捨五入取整,這樣一來,第2個像素就被劃分到左邊的分塊了,但是這樣會帶來一個新的問題,即每個圖像塊的大小不一致。還沿用前面的例子,尺寸爲3×33 \times 3會被劃分爲 3×23 \times 23×13 \times 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)

    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.參考文獻

[1]圖像的水平方向或者垂直方向拼接https://blog.csdn.net/mangobar/article/details/79656417

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章