OpenCV計算機視覺學習(12)——圖像量化處理&圖像採樣處理(K-Means聚類量化,局部馬賽克處理)

如果需要處理的原圖及代碼,請移步小編的GitHub地址

  傳送門:請點擊我

  如果點擊有誤:https://github.com/LeBron-Jian/ComputerVisionPractice

準備:圖像轉數組,數組轉圖像

  將RGB圖像轉換爲一維數組的代碼如下:

# 圖像二維像素轉換爲一維
img = cv2.imread(filename=img_path)
data = img.reshape((-1, 3))
data = np.float32(data)
print(img.shape, data.shape)

   我們打印出來結果,看看如下:

(67, 142, 3) (9514, 3)

   當我們處理完後,再將圖像轉換回uint8二維類型,代碼如下:

# 圖像轉換回uint8二維類型
centers2 = np.uint8(centers2)
res = centers2[labels2.flatten()]
dst2 = res.reshape((img.shape))

# 圖像轉換爲RGB顯示
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

 

1,圖像量化處理

  圖像通常是自然界景物的客觀反映,並以照片形式或視頻記錄的介質連續保存,獲取圖像的目標是從感知的數據中產生數字圖像,因此需要把連續的圖像數據離散化,轉換爲數字化圖像,其工作主要包括兩方面——量化和採樣。數字化幅度值稱爲量化,數字化座標值稱爲採樣。

  下面主要學習圖像量化和採樣處理的概念,並通過Python和OpenCV實現這些功能。

1.1  圖像量化概述

  所謂量化(Quantization),就是將圖像像素點對應亮度的連續變換區間轉換爲單個特定值的過程,即將原始灰度圖像的空間座標幅度值離散化。量化等級越多,圖像層次越豐富,灰度分辨率越高,圖像的質量也越好;量化等級越少,圖像層次欠豐富,灰度分辨率越低,會出現圖像輪廓分層的現象,降低了圖像的質量,下圖是將圖像的連續灰度值轉換爲0到255的灰度級的過程。

  量化後,圖像就被表示成一個整數矩陣,每個像素具有兩個屬性:位置和灰度。位置由行,列表示。灰度表示該像素位置上亮暗程度的整數。此數字矩陣M*N就作爲計算機處理的對象了,灰度級一般爲0~255(8bit量化)。如果量化等級爲2,則將使用兩種灰度級表示原始圖像的像素(0~255),灰度值小於128的取0,大於等於128的取128;如果量化等級爲4,則將使用四種灰度級表示原始圖像的像素,新圖像將分層爲四種顏色,0~64區間取0,64~128區間取64,128~192區間的取128,192~255區間取192,依次類推。

1.1.1  圖像彩色量化(減色處理)簡介

  RGB 的像素值在 0~255之間,我們想要用更少的內存空間表徵一張圖像時怎麼辦呢?首先是減色處理,將圖像用 32, 96, 160, 224 這 4 個像素值表示。即將圖像由256³壓縮至4³,RGB的值只取{32,96,160,224},這被稱作色彩量化。

1.2  圖像量化的代碼實現

  下面學習Python圖像量化處理相關代碼湊在哦,其核心流程是建立一張臨時圖片,接着循環遍歷原始圖像中所有像素點,判斷每個像素點應該屬於的量化等級,最後將臨時圖像展示,下面代碼學習將灰度圖像轉換爲兩種量化等級。

  代碼如下:

#_*_coding:utf-8_*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖片
img = cv2.imread('kd2.jpg')
# 獲取圖像的高度和寬度
height, width = img.shape[0], img.shape[1]

# 創建一幅圖像,內容使用零填充
new_img = np.zeros((height, width, 3), np.uint8)

# 圖像量化操作,量化等級爲2
for i in range(height):
    for j in range(width):
        for k in range(3):  # 對應BGR三通道
            if img[i, j][k] < 128:
                gray = 0
            else:
                gray = 129
            new_img[i, j][k] = np.uint8(gray)

# 顯示圖像
cv2.imshow('src', img)
cv2.imshow('new', new_img)
# 等待顯示
cv2.waitKey(0)
cv2.destroyAllWindows()

  下圖是輸出結果,它將圖像劃分爲兩種量化等級。

   下面的代碼分別比較了量化等級爲2, 4, 8 的量化處理效果。

# _*_coding:utf-8_*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖片
img = cv2.imread('kd2.jpg')
# 獲取圖像的高度和寬度
height, width = img.shape[0], img.shape[1]

# 創建一幅圖像,內容使用零填充
new_img1 = np.zeros((height, width, 3), np.uint8)
new_img2 = np.zeros((height, width, 3), np.uint8)
new_img3 = np.zeros((height, width, 3), np.uint8)

# 圖像量化操作,量化等級爲2
for i in range(height):
    for j in range(width):
        for k in range(3):  # 對應BGR三通道
            if img[i, j][k] < 128:
                gray = 0
            else:
                gray = 129
            new_img1[i, j][k] = np.uint8(gray)

# 圖像量化操作,量化等級爲4
for i in range(height):
    for j in range(width):
        for k in range(3):  # 對應BGR三通道
            if img[i, j][k] < 64:
                gray = 0
            elif img[i, j][k] < 128:
                gray = 64
            elif img[i, j][k] < 192:
                gray = 128
            else:
                gray = 192
            new_img2[i, j][k] = np.uint8(gray)

# 圖像量化操作,量化等級爲8
for i in range(height):
    for j in range(width):
        for k in range(3):  # 對應BGR三通道
            if img[i, j][k] < 32:
                gray = 0
            elif img[i, j][k] < 64:
                gray = 32
            elif img[i, j][k] < 96:
                gray = 64
            elif img[i, j][k] < 128:
                gray = 96
            elif img[i, j][k] < 160:
                gray = 128
            elif img[i, j][k] < 192:
                gray = 160
            elif img[i, j][k] < 224:
                gray = 192
            else:
                gray = 224
            new_img3[i, j][k] = np.uint8(gray)

# 用來正常顯示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']

# 顯示圖像
titles = [u'(a)原始圖像', u'(b)量化L2', u'(c)量化L4', u'(d)量化L8', ]
images = [img, new_img1, new_img2, new_img3]
for i in range(4):
    plt.subplot(2, 2, i+1), plt.imshow(images[i])
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

  結果如下:

1.3,圖像分割與聚類概述

1.3.1  圖像分割的定義

  圖像分割是圖像識別和計算機視覺至關重要的預處理方法。圖像分割就是把圖像分成若干個特定的,具有獨特性質的區域並提出感興趣目標的技術和過程。它是由圖像處理到圖像分析的關鍵步驟。現有的圖像分割方法主要分爲以下幾類:基於閾值的分割方法,基於區域的分割方法,基於邊緣的分割方法以及基於特定理論的分割方法等。從數學角度來看,圖像分割是將數字圖像劃分成互不相交的區域的過程。圖像分割的過程也是一個標記過程,即把屬於同一區域的像素賦予相同的編號。

1.3.2  聚類的定義

  一個聚類就是一組數據對象的集合,集合內各對象彼此相似,各集合間的對象彼此相差較大。將一組物理或抽象對象中類似的對象組織成若干組的過程就稱爲聚類過程。然而在聚類的過程中,我們涉及到的對象的數據類型除了常用的間隔數值屬性,二值屬性,符號,順序,比例數值屬性,混合類型屬性等外,還可能遇到圖像,音頻,視頻等多媒體數據對象。對於傳統的數據類型已經有了很多成熟的計算距離方法,這些方法包括:歐式,Manhattan,Minkowski距離公式,二值變量距離比較公式等等。然而對於多媒體數據對象,由於其特殊性,一致沒有一個很好地算法對其進行分類。

  聚類是一個將數據集劃分爲若干簇或類的過程,並使得同一簇內的數據對象具有較高的相似度,而不同組中的數據對象則是不相似的。相似或不相似的度量是基於數據對象描述屬性的取值來確定的。通常是利用(各對象間)距離來進行描述。

  下面要學習的是基於理論的圖像圖像分割方法,通過 K-Means聚類算法實現圖像分割或顏色分層處理。

1.4,K-Means 聚類量化處理

1.4.1  K-Means 聚類量化處理原理

  K-Means 聚類是最常用的聚類算法,最初起源於信號處理,其目的是將數據點劃分爲 K 個類簇,找到每個簇的中心並使其度量最小化。該算法的最大優點是簡單,便於理解,運算速度較快,缺點是隻能應用於連續性數據,並且要在聚類前指定聚類的類簇數。

  如果想了解K-Means算法的原理,請參考我這篇博客:

Python機器學習筆記:K-Means算法,DBSCAN算法

  下面是 K-Means 聚類算法的分析流程,步驟如下:

  • 第一步,確定K值,即將數據集聚集成K個類簇或小組。
  • 第二步,從數據集中隨機選擇K個數據點作爲質心(Centroid)或數據中心。
  • 第三步,分別計算每個點到每個質心之間的距離,並將每個點劃分到離最近質心的小組,跟定了那個質心。
  • 第四步,當每個質心都聚集了一些點後,重新定義算法選出新的質心。
  • 第五步,比較新的質心和老的質心,如果新質心和老質心之間的距離小於某一個閾值,則表示重新計算的質心位置變化不大,收斂穩定,則認爲聚類已經達到了期望的結果,算法終止。
  • 第六步,如果新的質心和老的質心變化很大,即距離大於閾值,則繼續迭代執行第三步到第五步,直到算法終止。

  下圖是對身高和體重進行聚類的算法,將數據集的人羣聚類成三類。

1.4.2  K-Means 聚類opencv源碼

  在opencv中,KMeans() 函數原型如下所示:

retval, bestLabels, centers = kmeans(data, K, bestLabels, criteria, attempts, 
flags[, centers])

  函數內變量的含義:

  • data表示聚類數據,最好是np.flloat32類型的N維點集,之所以是 np.float32 原因是這種數據類型運算速度快,同樣的數據下如果是 uint型數據將會特別慢
  • K表示聚類類簇數,opencv的kmeans分類是需要已知分類數的。
  • bestLabels表示預設的分類標籤,即輸出的整數數組,用於存儲每個樣本的聚類標籤索引,沒有的話爲None
  • criteria表示算法終止條件,即最大迭代次數或所需精度。在某些迭代中,一旦每個簇中心的移動小於criteria.epsilon,算法就會停止,迭代停止的選擇是一個含有三個元素的元組型數,格式爲(type,  max_iter, epsilon),其中 type 有兩種選擇:

    ——cv2.TERM_CRITERIA_EPS:精確度(誤差)滿足 epsilon停止

    ——cv2.TERM_CRITERIA_MAX_ITER:迭代次數超過 max_iter 停止

    ——cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,兩者合體,任意一個滿足結束。

  • attempts表示重複試驗kmeans算法的次數,算法返回產生最佳緊湊性的標籤
  • flags表示初始中心的選擇,兩種方法是cv2.KMEANS_PP_CENTERS ;和cv2.KMEANS_RANDOM_CENTERS
  • centers表示集羣中心的輸出矩陣,每個集羣中心爲一行數據

1.4.3  K-Means 聚類分割灰度圖像

  在圖像處理中,通過 K-Means聚類算法可以實現圖像分割,圖像聚類,圖像識別等操作,下面主要用來進行圖像顏色分割。假設存在一張 100*100像素的灰度圖像,它由 10000 個RGB灰度級組成,我們通過 K-Menas 可以將這些像素點聚類成 K 個簇,然後使用每個簇內的置心點來替換簇內所有的像素點,這樣就能實現在不改變分辨率的情況下量化壓縮圖像顏色,實現圖像顏色層級分割。

  下面使用該方法對灰度圖像顏色進行分割處理,需要注意,在進行 K-Means 聚類操作之前,需要將 RGB像素點轉換成一維的數組,再講各形式的顏色聚集在一起,形成最終的顏色分割。

# _*_coding:utf-8_*_
# coding: utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取原始圖像灰度顏色
img = cv2.imread('scenery.jpg', 0)
print(img.shape)

# 獲取圖像高度、寬度
rows, cols = img.shape[:]

# 圖像二維像素轉換爲一維
data = img.reshape((rows * cols, 1))
data = np.float32(data)

# 定義中心 (type,max_iter,epsilon)
criteria = (cv2.TERM_CRITERIA_EPS +
            cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)

# 設置標籤
flags = cv2.KMEANS_RANDOM_CENTERS

# K-Means聚類 聚集成4類
compactness, labels, centers = cv2.kmeans(data, 4, None, criteria, 10, flags)

# 生成最終圖像
dst = labels.reshape((img.shape[0], img.shape[1]))

# 用來正常顯示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']

# 顯示圖像
titles = [u'灰度圖像', u'聚類圖像']
images = [img, dst]
for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray'),
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

  原圖如下:

   輸出結果如下所示,左邊爲灰度圖,右邊爲K-Means聚類後的圖像,它將相似的顏色或區域聚集到一起。

   人物圖如下:

   效果如下:

1.4.4  K-Means 聚類對比分割彩色圖像

  下面代碼是對彩色圖像進行顏色分割處理,它將彩色圖像聚類成2類,4類和64類。

# coding: utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取原始圖像
img = cv2.imread('scenery.jpg')
print(img.shape)

# 圖像二維像素轉換爲一維
data = img.reshape((-1, 3))
data = np.float32(data)

# 定義中心 (type,max_iter,epsilon)
criteria = (cv2.TERM_CRITERIA_EPS +
            cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)

# 設置標籤
flags = cv2.KMEANS_RANDOM_CENTERS

# K-Means聚類 聚集成2類
compactness, labels2, centers2 = cv2.kmeans(data, 2, None, criteria, 10, flags)

# K-Means聚類 聚集成4類
compactness, labels4, centers4 = cv2.kmeans(data, 4, None, criteria, 10, flags)

# K-Means聚類 聚集成8類
compactness, labels8, centers8 = cv2.kmeans(data, 8, None, criteria, 10, flags)

# K-Means聚類 聚集成16類
compactness, labels16, centers16 = cv2.kmeans(data, 16, None, criteria, 10, flags)

# K-Means聚類 聚集成64類
compactness, labels64, centers64 = cv2.kmeans(data, 64, None, criteria, 10, flags)

# 圖像轉換回uint8二維類型
centers2 = np.uint8(centers2)
res = centers2[labels2.flatten()]
dst2 = res.reshape((img.shape))

centers4 = np.uint8(centers4)
res = centers4[labels4.flatten()]
dst4 = res.reshape((img.shape))

centers8 = np.uint8(centers8)
res = centers8[labels8.flatten()]
dst8 = res.reshape((img.shape))

centers16 = np.uint8(centers16)
res = centers16[labels16.flatten()]
dst16 = res.reshape((img.shape))

centers64 = np.uint8(centers64)
res = centers64[labels64.flatten()]
dst64 = res.reshape((img.shape))

# 圖像轉換爲RGB顯示
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
dst2 = cv2.cvtColor(dst2, cv2.COLOR_BGR2RGB)
dst4 = cv2.cvtColor(dst4, cv2.COLOR_BGR2RGB)
dst8 = cv2.cvtColor(dst8, cv2.COLOR_BGR2RGB)
dst16 = cv2.cvtColor(dst16, cv2.COLOR_BGR2RGB)
dst64 = cv2.cvtColor(dst64, cv2.COLOR_BGR2RGB)

# 用來正常顯示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']

# 顯示圖像
titles = [u'原始圖像', u'聚類圖像 K=2', u'聚類圖像 K=4',
          u'聚類圖像 K=8', u'聚類圖像 K=16', u'聚類圖像 K=64']
images = [img, dst2, dst4, dst8, dst16, dst64]
for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray'),
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

  我們依然採用上面的圖:

2,圖像採樣處理

2.1 圖像採樣處理概述

  圖像採樣(Image Sampling)是將一幅連續圖像在空間上分割成 M*N 個網格,每個網格用一個亮度值或灰度值來表示,其示意圖如下所示:

   圖像採樣的間隔越大,所得圖像像素數越少,空間分辨率越低,圖像質量越差,甚至出現馬賽克效益;相反,圖像採樣的間隔越小,所得圖像像素數越多,空間分辨率越高,圖像質量越好,但數據量會相應的增大。

  馬賽克原理:將圖像中選中區域的像素值用這個選中區域中的某一像素值或者隨機值替換。

  下圖中將指定區域的像素點值,全部改爲左上角第一個點的像素點值:

2.2  圖像採樣Python實現

  下面學習Python圖像採樣處理相關代碼操作。其核心流程是建立一張臨時圖片,設置需要採用的區域大小(如 16*16),接着循環遍歷原始圖像中所有像素點,採樣區域內的像素點賦值相同(如左上角像素點的灰度值),最終實現圖像採樣處理。代碼是進行16*16採樣的過程。

# _*_coding:utf-8_*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖片
img = cv2.imread('kd2.jpg')

# 獲取圖像的高度和寬度
height, width = img.shape[0], img.shape[1]
# print(img.shape)  # (352, 642, 3)
# 採樣轉換成 16*16 區域
numHeight, numWidth = height / 16, width / 16

# 創建一幅圖像,內容使用零填充
new_img1 = np.zeros((height, width, 3), np.uint8)

# 圖像循環採樣 16*16 區域
for i in range(16):
    # 獲取Y座標
    y = int(i * numHeight)
    for j in range(16):
        # 獲取 X 座標
        x = int(j * numWidth)
        # 獲取填充顏色,左上角像素點
        b = img[y, x][0]
        g = img[y, x][1]
        r = img[y, x][2]

        # 循環設置小區域採樣
        for n in range(int(numHeight)):
            for m in range(int(numWidth)):
                new_img1[y+n, x+m][0] = np.uint8(b)
                new_img1[y+n, x+m][1] = np.uint8(g)
                new_img1[y+n, x+m][2] = np.uint8(r)

# 顯示圖像
cv2.imshow('src', img)
cv2.imshow('new src', new_img1)
# 等待顯示
cv2.waitKey(0)
cv2.destroyAllWindows()

   結果如下:

   同樣,可以對彩色圖片進行採樣處理,下面的代碼將彩色風景採樣處理成 8*8的馬賽克區域。

# _*_coding:utf-8_*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖片
img = cv2.imread('kd2.jpg')

# 獲取圖像的高度和寬度
height, width = img.shape[0], img.shape[1]
# print(img.shape)  # (352, 642, 3)
# 採樣轉換成 8*8 區域
numHeight, numWidth = height / 8, width / 8

# 創建一幅圖像,內容使用零填充
new_img1 = np.zeros((height, width, 3), np.uint8)

# 圖像循環採樣 8*8 區域
for i in range(8):
    # 獲取Y座標
    y = int(i * numHeight)
    for j in range(8):
        # 獲取 X 座標
        x = int(j * numWidth)
        # 獲取填充顏色,左上角像素點
        b = img[y, x][0]
        g = img[y, x][1]
        r = img[y, x][2]

        # 循環設置小區域採樣
        for n in range(int(numHeight)):
            for m in range(int(numWidth)):
                new_img1[y+n, x+m][0] = np.uint8(b)
                new_img1[y+n, x+m][1] = np.uint8(g)
                new_img1[y+n, x+m][2] = np.uint8(r)

# 顯示圖像
cv2.imshow('src', img)
cv2.imshow('new src', new_img1)
# 等待顯示
cv2.waitKey(0)
cv2.destroyAllWindows()

  結果如下:

   但是上述代碼存在一個問題,就是當圖片的長度和寬度不能被採樣區域整除時,輸出圖像的最右邊和最下邊的區域沒有被採樣處理。這裏推薦大家做一個求餘運算,將不能整除部分的區域也進行採樣處理。

2.3   局部馬賽克處理

  前面學習了對整幅圖像做了採樣處理,那麼如何對圖像的局部區域進行馬賽克處理呢?

  實現用按下鼠標左鍵拖動時,在鼠標經過的路徑上打上馬賽克,而馬賽克的原理是將圖像中選中區域的像素用這個選中區域中的某一像素覆蓋,爲了不讓鼠標重複經過圖像中同一個的時候,選取不一樣的像素,該程序將在輸入圖片的時候,就實現了全圖的馬賽克效果。而當鼠標劃過的時候,程序只是將實現馬賽克的圖像的指定位置複製到顯示的圖像中。

  下面代碼實現了該功能,當鼠標按下時,它能夠給鼠標拖動的區域打上馬賽克,並按下 “s”鍵保存圖像到本地。

   代碼如下:

# _*_coding:utf-8_*_
import cv2
import numpy as np
import matplotlib.pyplot as plt


# 鼠標事件
def draw(event, x, y, flags, parma):
    global en
    # 鼠標左鍵按下開啓 en 鍵
    if event == cv2.EVENT_LBUTTONDOWN:
        en = True
    # 鼠標左鍵按下並且移動
    elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_LBUTTONDOWN:
        # 調用函數打馬賽克
        if en:
            drawMask(y, x)
        # 鼠標左鍵彈起結束操作
        elif event == cv2.EVENT_LBUTTONUP:
            en = False


# 圖像局部採用操作
def drawMask(x, y, size=10):
    # size*size 採樣處理
    m = int(x / size * size)
    n = int(y / size * size)
    # 10*10 區域設置爲同一像素值
    for i in range(int(size)):
        for j in range(int(size)):
            img[m+i][n+j] = img[m][n]

if __name__ == '__main__':
    # 讀取原始圖像
    img = cv2.imread('durant.jpg')
    # 設置鼠標右鍵開啓
    en = False

    # 打開對話框
    cv2.namedWindow('image')
    # 調用draw 函數設置鼠標操作
    cv2.setMouseCallback('image', draw)

    # 循環處理
    while(1):
        cv2.imshow('image', img)
        # 按 ESC鍵退出
        if cv2.waitKey(10) & 0xFF == 27:
            break
        # 按 s 鍵保存圖片
        elif cv2.waitKey(10) & 0xFF == 115:
            cv2.imwrite('save.jpg', img)
    # 退出窗口
    cv2.destroyAllWindows()

  打了馬賽克的圖片如下:

   我將他的號碼打碼了。

 

參考文獻:https://blog.csdn.net/Eastmount/article/details/89218513

https://blog.csdn.net/Eastmount/article/details/89287543

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