【譯】手把手教你使用圖像處理利器OpenCV

做者:Muhammad Junaid Khalidpython

翻譯:老齊算法

與本文相關的圖書推薦:《數據準備和特徵工程》編程


概要

在本文中,將學習如何使用Python語言進行圖像處理,咱們不會侷限於一個單獨的庫或框架,然而,有一個庫的使用率將會是最高的,那就是OpenCV。咱們一開始會討論一些圖像處理,而後繼續探討不一樣的應用/場景,也就是圖像處理的用武之地。開始吧!bash

什麼是圖像處理?

在深刻研究圖像處理的方法以前,重要的是要了解什麼是圖像處理,特別是這項技術在處理大量圖片方面的角色。圖像處理完整的說法是「數字圖像處理」,常用圖像處理的領域是「計算機視覺」。對這兩個術語不要混淆,圖像處理算法和計算機視覺(CV)算法都以圖像爲輸入,然而,在圖像處理中,輸出也是圖像,而在計算機視覺中,輸出能夠是關於圖像的一些特徵或信息。微信

爲何須要圖像處理?

咱們收集或生成的數據大部分是原始數據,也就是說,因爲一些可能的緣由,這些數據不適合直接用於應用程序。所以,咱們須要首先分析它,執行必要的預處理,而後使用它——特別推薦《數據準備和特徵工程》,此書即爲這方面最佳讀物。markdown

例如,咱們正在嘗試構建一個關於貓的分類器。咱們的程序會把一個圖像做爲輸入,而後告訴咱們這個圖像是否包含一隻貓。構建這個分類器的第一步是收集數百張含有貓的圖片。一個常見的問題是,收集的全部圖片的大小都不相同,所以在將它們提供給模型進行訓練以前,須要調整它們的大小或者把它們進行預處理,使尺寸符合標準。框架

爲何圖像處理對於任何計算機視覺應用序都是必不可少的?以上提到的只是衆多緣由之一。dom

預備知識

爲了輕鬆地學習本文內容,你須要已經具有以下知識。機器學習

首先,應該具有必定的編程語言技能,本文使用的是Python語言,若是還沒有掌握此語言,推薦閱讀《跟老齊學Python:輕鬆入門》或《Python大學實用教程》。編程語言

其次,你應該瞭解什麼是機器學習以及它的基本工做原理。由於在本文中咱們將使用一些機器學習算法來進行圖像處理。

另外,若是你以前接觸過或掌握了OpenCV的基本知識,也會有所幫助。但這不是必需的。

還有,你必定要了解圖像在內存中到底是如何表示的。每幅圖像都由一組像素表示,即像素值矩陣。對於灰度圖像,像素值的範圍是0到255,它們表示該像素的強度。例如,若是你有一個20×20維的圖像,它將由一個20x20的矩陣表示(像素值總共是400)。

若是你正在處理彩色圖像,你應該知道它有三個通道——紅、綠、藍(RGB)。所以,一個彩色圖像有三個這樣的矩陣。

安裝

注意: 因爲咱們將經過Python使用OpenCV,因此你必須會實用它,前面推薦了關於Python的書籍。下面依次說明在不一樣操做系統中OpenCV的安裝方法:

  • Windows
$ pip install opencv-python
複製代碼
  • MacOS
$ brew install opencv3 --with-contrib --with-python3
複製代碼
  • Linux
$ sudo apt-get install libopencv-dev python-opencv
複製代碼

要檢查是否安裝成功,請在Python交互模式中運行如下命令:

import cv2
複製代碼

必備的基礎知識

在進行圖像處理以前,要先作一些準備。

在本文中,咱們將使用如下圖像:

注意: 爲了在本文中顯示該圖像,對其進行了縮放,可是咱們使用的圖像原始大小約爲1180x786。

你可能注意到圖像如今是彩色的,這意味着它由三個顏色通道表示,即紅色、綠色和藍色。咱們將把圖像轉換成灰度,並使用下面的代碼將圖像分割成單獨的通道。

找到圖像細節

使用imread()函數加載圖像後,咱們能夠獲得關於它的一些簡單屬性,好比像素的數量和尺寸:

import cv2

img = cv2.imread('rose.jpg')

print("Image Properties")
print("- Number of Pixels: " + str(img.size))
print("- Shape/Dimensions: " + str(img.shape))
複製代碼

Output:

Image Properties
- Number of Pixels: 2782440
- Shape/Dimensions: (1180, 786, 3)
複製代碼

將圖像分割成單獨的通道

如今,咱們將使用OpenCV將圖像分割成紅色、綠色和藍色的部分,並顯示它們:

from google.colab.patches import cv2_imshow

blue, green, red = cv2.split(img)    # Split the image into its channels
img_gs = cv2.imread('rose.jpg', cv2.IMREAD_GRAYSCALE)    # Convert image to grayscale

cv2_imshow(red) # Display the red channel in the image
cv2_imshow(blue) # Display the red channel in the image
cv2_imshow(green) # Display the red channel in the image
cv2_imshow(img_gs) # Display the grayscale version of image
複製代碼

爲了簡單起見,咱們只顯示灰度圖像。

圖像閾值

閾值的概念很是簡單。正如上面在圖像表示中所討論的,像素值能夠是0到255之間的任何值。假設咱們想要將一幅圖像轉二值化,即指定一個像素值爲0或1。爲此,咱們能夠設置閾值。例如,若是閾值(T)爲125,那麼全部大於125的像素將被賦值爲1,全部小於或等於該值的像素將被賦值爲0。下面,咱們經過代碼來更好地理解它。

將下面的圖像用上述方法進行轉換:

import cv2

# Read image
img = cv2.imread('image.png', 0)

# Perform binary thresholding on the image with T = 125
r, threshold = cv2.threshold(img, 125, 255, cv2.THRESH_BINARY)
cv2_imshow(threshold)
複製代碼

輸出:

正如你所看到的, 二值化以後,出現了兩個區域,即黑色區域(像素值0)和白色區域(像素值1)。原來, 咱們設置的閾值正好在圖像的中間,這就是爲何黑白值在那裏被分割。

應用

1:去除圖像中的噪聲

如今你已經對圖像處理的概念和用途有了基本的瞭解,接下來讓咱們來了解一下它的一些具體應用。

在大多數狀況下,咱們收集的原始數據有噪聲,也就是說,不須要的特徵使圖像很難被感知。雖然這些圖像能夠直接用於特徵抽取,可是算法的準確性會受到很大的影響。這就是爲何在將圖像傳遞給算法以得到更好的精度以前,要對圖像進行處理的緣由。

有許多不一樣類型的噪聲,如高斯噪聲,椒鹽噪聲等。咱們能夠經過應用濾波器來去除圖像中的噪聲,或者至少將其影響降到最低。在濾波器方面也有不少選擇,每個濾波器都有不一樣的優勢。所以,對於特定類型的噪聲來講,總有一個是最好的。

爲了更好地理解這一點,咱們將在上面的玫瑰色圖像的灰度版本中添加「鹽和胡椒粉」噪聲,而後嘗試使用不一樣的濾波器去除圖像中的噪聲,看看哪個最適合這種類型。

import numpy as np

# Adding salt & pepper noise to an image

def salt_pepper(prob):
      # Extract image dimensions
      row, col = img_gs.shape

      # Declare salt & pepper noise ratio
      s_vs_p = 0.5
      output = np.copy(img_gs)

      # Apply salt noise on each pixel individually
      num_salt = np.ceil(prob * img_gs.size * s_vs_p)
      coords = [np.random.randint(0, i - 1, int(num_salt))
            for i in img_gs.shape]
      output[coords] = 1

      # Apply pepper noise on each pixel individually
      num_pepper = np.ceil(prob * img_gs.size * (1. - s_vs_p))
      coords = [np.random.randint(0, i - 1, int(num_pepper))
            for i in img_gs.shape]
      output[coords] = 0
      cv2_imshow(output)

      return output

# Call salt & pepper function with probability = 0.5
# on the grayscale image of rose
sp_05 = salt_pepper(0.5)

# Store the resultant image as 'sp_05.jpg'
cv2.imwrite('sp_05.jpg', sp_05)
複製代碼

好的,咱們已經把噪聲添加到玫瑰圖像,這是它如今的樣子:

讓咱們如今應用不一樣的濾波器,並記下觀察結果,即每一個濾波器降噪的效果。

銳化濾波器

# Create our sharpening kernel, the sum of all values must equal to one for uniformity
kernel_sharpening = np.array([[-1,-1,-1],
                              [-1, 9,-1],
                              [-1,-1,-1]])

# Applying the sharpening kernel to the grayscale image & displaying it.
print("\n\n--- Effects on S&P Noise Image with Probability 0.5 ---\n\n")

# Applying filter on image with salt & pepper noise
sharpened_img = cv2.filter2D(sp_05, -1, kernel_sharpening)
cv2_imshow(sharpened_img)
複製代碼

在有椒鹽噪聲的圖像上應用濾波器獲得的圖像以下所示。經過與原始灰度圖的對比,咱們能夠看出,它把圖像調得太亮了,也沒法突出玫瑰上的亮點。所以,咱們能夠得出結論,銳化濾波器並不能去除噪聲。

銳化濾波器輸出:

中值濾波器

from scipy.ndimage import maximum_filter, minimum_filter

def midpoint(img):
    maxf = maximum_filter(img, (3, 3))
    minf = minimum_filter(img, (3, 3))
    midpoint = (maxf + minf) / 2
    cv2_imshow(midpoint)

print("\n\n---Effects on S&P Noise Image with Probability 0.5---\n\n")
midpoint(sp_05)
複製代碼

在有噪聲的圖像上應用中值濾波器,獲得的圖像以下所示。經過與原始灰度圖像的對比,咱們能夠看出,與上面的核方法同樣,圖像的亮度調高了不少,然而,它可以突出玫瑰上的亮斑(即噪聲)。所以,咱們能夠說,中值濾波器是比銳化濾波器更好的選擇,但它仍然不能徹底恢復原始圖像。

中值濾波器輸出:

逆諧波均值濾波器

注意: 對這些濾波器的工做原理的闡述,超出了本文範疇,讀者能夠在網上搜索,咱們仍是從應用的層面來研究。

def contraharmonic_mean(img, size, Q):
    num = np.power(img, Q + 1)
    denom = np.power(img, Q)
    kernel = np.full(size, 1.0)
    result = cv2.filter2D(num, -1, kernel) / cv2.filter2D(denom, -1, kernel)
    return result

print("\n\n--- Effects on S&P Noise Image with Probability 0.5 ---\n\n")
cv2_imshow(contraharmonic_mean(sp_05, (3,3), 0.5))
複製代碼

在有椒鹽噪聲的圖像上應用逆諧波均值濾波器(en.wikipedia.org/wiki/Contra…)獲得的圖像以下圖所示。經過與原始灰度圖像的對比,咱們能夠看到:它幾乎完美再現了原始圖像。它的強度或亮度級別與原圖是相同的,它突出了玫瑰上的亮點。所以,咱們能夠得出結論,逆諧波均值濾波器在處理椒鹽噪聲方面是很是有效的。

逆諧波均值濾波器輸出:

如今咱們已經找到了最佳濾波器,它能夠有效地把有噪聲的圖像恢復到原始圖像。咱們能夠繼續下一個應用了。

2:使用Canny算子進行邊緣檢測

到目前爲止,咱們使用的玫瑰圖像的背景是不變的,也就是黑色的,所以,咱們將把這個應用用於不一樣的圖像,以更好地展現算法的功效。緣由是,若是背景是恆定的,邊緣檢測任務就變得至關簡單,這不是咱們所但願的。

在本文開始部分,咱們提到了一個關於貓的分類器。如今咱們延用這個例子,看看圖像處理如何在其中扮演一個完整的角色。

在分類算法中,首先掃描圖像尋找「對象」。也就是說,當你輸入一幅圖像時,算法會找到圖像中的全部對象,而後將它們與你試圖尋找的對象進行特徵比較。對於貓分類器,它會將在圖像中找到的全部對象與貓圖像的特徵進行比較,若是找到匹配項,它會告訴咱們輸入圖像中包含了一隻貓。

對於這個貓分類器,僅以一張貓的圖像爲例,如下是咱們將要使用的圖像:

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

# Declaring the output graph's size
plt.figure(figsize=(16, 16))

# Convert image to grayscale
img_gs = cv2.imread('cat.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imwrite('gs.jpg', img_gs)

# Apply canny edge detector algorithm on the image to find edges
edges = cv2.Canny(img_gs, 100,200)

# Plot the original image against the edges
plt.subplot(121), plt.imshow(img_gs)
plt.title('Original Gray Scale Image')
plt.subplot(122), plt.imshow(edges)
plt.title('Edge Image')

# Display the two images
plt.show()
複製代碼

邊緣檢測輸出:

正如你所看到的,圖像中包含對象的部分(在本例中是一隻貓)已經經過邊緣檢測用虛線標出或分隔開。如今你必定想知道,什麼是邊緣檢測的Canny算子,它是怎麼工做的?如今就討論一下。

要理解上述內容,須要討論三個關鍵步驟。首先,它對圖像進行降噪,降噪方式與前面討論的方式相似。其次,它使用每一個像素的一階導數來找到邊緣。這背後的邏輯是,在邊緣存在的地方,會有一個忽然的強度變化,致使一階導數值達到峯值,從而使該像素成爲「邊緣像素」。

最後,進行滯後閾值化;上面咱們說過,在一個邊緣的一階導數值會有一個峯值,可是咱們沒有說:這個峯值須要有多高,才能被歸類爲一個邊緣——這叫作閾值!在本文的前面,咱們討論了什麼是簡單的閾值。遲滯閾值法是在此基礎上的一種改進,它利用兩個閾值來代替一個閾值。這背後的緣由是,若是閾值太高,咱們可能會錯過一些真正的邊緣(真負例),若是閾值太低,咱們會獲得不少被歸類爲邊緣的點,而實際上不是邊緣(假正例)。一個閾值設置爲高,一個設置爲低,將全部高於「高閾值」的點標識爲邊緣,而後對全部高於「低閾值」但低於「高閾值」的點進行評估;邊緣上的點肯定以後,與邊緣點靠近或相鄰的點也被肯定爲邊緣,其他的點被丟棄。

這些是Canny算子用於識別圖像邊緣的基本概念/方法。

譯者注: Canny算子是澳洲計算機科學家約翰·坎尼(John F. Canny)於1986年開發出來的一個多級邊緣檢測算法,其目標是找到一個最優的邊緣.

結論

在本文中,咱們學習瞭如何在不一樣的平臺(如Windows、MacOS和Linux)上安裝OpenCV,以及如何驗證安裝成功。OpenCV是Python中最流行的圖像處理庫。

接着咱們討論了什麼是圖像處理,以及它在機器學習的計算機視覺領域中的應用。咱們討論了一些常見的噪聲類型,以及如何使用不一樣的濾波器將噪聲從圖像中去除,以便在應用中使用這些圖像。

此外,咱們還了解了圖像處理如何在高端應用(如:對象檢測或分類)中發揮不可或缺的做用。請注意,這篇文章只是冰山一角,數字圖像處理還有更多的內容,不可能在一篇短文中所有涵蓋。請關注微信公衆號「老齊教室」,這裏還會刊發有關圖像處理的文章。

原文連接:stackabuse.com/introductio…

搜索技術問答的公衆號:老齊教室

在公衆號中回覆:老齊,可查看全部文章、書籍、課程。

相關文章
相關標籤/搜索