OpenCV計算機視覺學習(12)——影像量化處理&影像取樣處理(K-Means聚類量化,區域性馬賽克處理)

戰爭熱誠發表於2020-11-27

如果需要處理的原圖及程式碼,請移步小編的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

相關文章