1. 程式人生 > >基於Python和OpenCV的影象目標檢測及分割

基於Python和OpenCV的影象目標檢測及分割

          基於Python和OpenCV的影象目標檢測及分割

本文在https://blog.csdn.net/sinat_36458870/article/details/78825571博主的部落格基礎上加了批處理部分,同時對多張圖片進行裁剪處理。

è¿éåå¾çæè¿°

環境:

例圖:谷歌,可愛的蟲子–image 
軟體:Anaconda 4.20,Opencv3.2 
OpenCv的安裝: 
1.1安裝Python3.5
1.2下載安裝opencv

具體思路如下:

1.獲取圖片

img_path = r'C:\Users\aixin\Desktop\chongzi.png'
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

 

原圖的樣子:

這裡寫圖片描述

2.轉換灰度並去噪聲

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (9, 9),0) 

我們可以得到這兩張圖,第一張是灰度圖,第二張是去噪之後的,另外說一下,去噪咱們有很多種方法,均值濾波器、高斯濾波器、中值濾波器、雙邊濾波器等。

這裡取高斯是因為高斯去噪效果是最好的。

è¿éåå¾çæè¿°

3.提取影象的梯度

gradX = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=1, dy=0)
gradY = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=0, dy=1)

gradient = cv2.subtract(gradX, gradY)
gradient = cv2.convertScaleAbs(gradient)

以Sobel運算元計算x,y方向上的梯度,之後在x方向上減去y方向上的梯度,通過這個減法,我們留下具有高水平梯度和低垂直梯度的影象區域。

此時,我們會得到

è¿éåå¾çæè¿°

4.繼續去噪聲

考慮到影象的孔隙 首先使用低通濾潑器平滑影象, 這將有助於平滑影象中的高頻噪聲。 低通濾波器的目標是降低影象的變化率。 
如將每個畫素替換為該畫素周圍畫素的均值, 這樣就可以平滑並替代那些強度變化明顯的區域。

對模糊影象二值化,顧名思義,就是把影象數值以某一邊界分成兩種數值。

blurred = cv2.GaussianBlur(gradient, (9, 9),0)
(_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)

此時,我們會得到 

è¿éåå¾çæè¿°

5.影象形態學

在這裡我們選取ELLIPSE核,採用CLOSE操作。

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25, 25))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)

此時,我們會得到 
這裡寫圖片描述

6.細節刻畫

從上圖我們可以發現和原圖對比,發現有細節丟失,這會干擾之後的昆蟲輪廓的檢測,要把它們擴充,分別執行4次形態學腐蝕與膨脹。

closed = cv2.erode(closed, None, iterations=4)
closed = cv2.dilate(closed, None, iterations=4)

此時,我們會得到 
這裡寫圖片描述

7.找出昆蟲區域的輪廓

此時用cv2.findContours()函式 
第一個引數是要檢索的圖片,必須是為二值圖,即黑白的(不是灰度圖)

(_, cnts, _) = cv2.findContours(
    引數一: 二值化影象
    closed.copy(),
    引數二:輪廓型別
    # cv2.RETR_EXTERNAL,             #表示只檢測外輪廓
    # cv2.RETR_CCOMP,                #建立兩個等級的輪廓,上一層是邊界
    # cv2.RETR_LIST,                 #檢測的輪廓不建立等級關係
    # cv2.RETR_TREE,                 #建立一個等級樹結構的輪廓
    # cv2.CHAIN_APPROX_NONE,         #儲存所有的輪廓點,相鄰的兩個點的畫素位置差不超過1
    引數三:處理近似方法
    # cv2.CHAIN_APPROX_SIMPLE,         #例如一個矩形輪廓只需4個點來儲存輪廓資訊
    # cv2.CHAIN_APPROX_TC89_L1,
    # cv2.CHAIN_APPROX_TC89_KCOS
    )

8.畫出輪廓

找到輪廓了,接下來,要畫出來的,即用cv2.drawContours()函式。

c = sorted(cnts, key=cv2.contourArea, reverse=True)[0]

# compute the rotated bounding box of the largest contour
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))

# draw a bounding box arounded the detected barcode and display the image
draw_img = cv2.drawContours(img.copy(), [box], -1, (0, 0, 255), 3)
cv2.imshow("draw_img", draw_img)

此時,我們會得到 
這裡寫圖片描述

9.裁剪

找到這四個點切出來就好,我們放大一點看一下細節

è¿éåå¾çæè¿°

Xs = [i[0] for i in box]
Ys = [i[1] for i in box]
x1 = min(Xs)
x2 = max(Xs)
y1 = min(Ys)
y2 = max(Ys)
hight = y2 - y1
width = x2 - x1
crop_img= img[y1:y1+hight, x1:x1+width]
cv2.imshow('crop_img', crop_img)

其實,box裡儲存的是綠色矩形區域四個頂點的座標。 我將按下圖紅色矩形所示裁剪昆蟲影象。 
找出四個頂點的x,y座標的最大最小值。新影象的高=maxY-minY,寬=maxX-minX 

 

最終得到了目標區域。

è¿éåå¾çæè¿°

附錄1.完整實現程式碼

#-*- coding: UTF-8 -*- 

import cv2
import numpy as np
import os

def get_image(path):
    #獲取圖片
    img=cv2.imread(path)
    gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

    return img, gray

def Gaussian_Blur(gray):
    # 高斯去噪
    blurred = cv2.GaussianBlur(gray, (9, 9),0)

    return blurred

def Sobel_gradient(blurred):
    # 索比爾運算元來計算x、y方向梯度
    gradX = cv2.Sobel(blurred, ddepth=cv2.CV_32F, dx=1, dy=0)
    gradY = cv2.Sobel(blurred, ddepth=cv2.CV_32F, dx=0, dy=1)

    gradient = cv2.subtract(gradX, gradY)
    gradient = cv2.convertScaleAbs(gradient)

    return gradX, gradY, gradient

def Thresh_and_blur(gradient):

    blurred = cv2.GaussianBlur(gradient, (9, 9),0)
    (_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)

    return thresh

def image_morphology(thresh):
    # 建立一個橢圓核函式
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25, 25))
    # 執行影象形態學, 細節直接查文件,很簡單
    closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
    closed = cv2.erode(closed, None, iterations=0)
    closed = cv2.dilate(closed, None, iterations=2)

    return closed

def findcnts_and_box_point(closed):
    # 這裡opencv3返回的是三個引數
    (_, cnts, _) = cv2.findContours(closed.copy(), 
        #cv2.RETR_EXTERNAL,             #表示只檢測外輪廓
    #cv2.RETR_CCOMP,                #建立兩個等級的輪廓,上一層是邊界
    cv2.RETR_LIST,                 #檢測的輪廓不建立等級關係
    #cv2.RETR_TREE,                   #建立一個等級樹結構的輪廓
    cv2.CHAIN_APPROX_NONE,           #儲存所有的輪廓點,相鄰的兩個點的畫素位置差不超過1
    #cv2.CHAIN_APPROX_SIMPLE,       #例如一個矩形輪廓只需4個點來儲存輪廓資訊
    #cv2.CHAIN_APPROX_TC89_L1,
    #cv2.CHAIN_APPROX_TC89_KCOS
        )
    c = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
    # compute the rotated bounding box of the largest contour
    rect = cv2.minAreaRect(c)
    box = np.int0(cv2.boxPoints(rect))

    return box

def drawcnts_and_cut(original_img, box):
    # 因為這個函式有極強的破壞性,所有需要在img.copy()上畫
    # draw a bounding box arounded the detected barcode and display the image
    draw_img = cv2.drawContours(original_img.copy(), [box], -1, (0, 0, 255), 3)

    Xs = [i[0] for i in box]
    Ys = [i[1] for i in box]
    x1 = min(Xs)
    x2 = max(Xs)
    y1 = min(Ys)
    y2 = max(Ys)
    hight = y2 - y1
    width = x2 - x1
    crop_img = original_img[y1:y1+hight, x1:x1+width]

    return draw_img, crop_img
'''
def walk():############批處理
    filename_rgb = r'D:\py_project\My_Project\dataset\trainingData'
    for filename in os.listdir(filename_rgb):              #listdir的引數是資料夾的路徑
        #img_path = r'D:\py_project\My_Project\296.png'
        #save_path = r'D:\py_project\My_Project\296_save.jpg'
        img_path = filename_rgb + ('/%s' % filename)
        save_path = r'D:\py_project\My_Project\dataset\trainingData_process'
        original_img, gray = get_image(img_path)
        blurred = Gaussian_Blur(gray)
        gradX, gradY, gradient = Sobel_gradient(blurred)
        thresh = Thresh_and_blur(gradient)
        closed = image_morphology(thresh)
        box = findcnts_and_box_point(closed)
        draw_img, crop_img = drawcnts_and_cut(original_img,box)



#    cv2.imshow('original_img', original_img)
#    cv2.imshow('blurred', blurred)
#    cv2.imshow('gradX', gradX)
#    cv2.imshow('gradY', gradY)
#    cv2.imshow('final', gradient)
#    cv2.imshow('thresh', thresh)
#    cv2.imshow('closed', closed)
#    cv2.imshow('draw_img', draw_img)
#    cv2.imshow('crop_img', crop_img)
#    cv2.waitKey(20171219)
        file_process_name = save_path + ('\%s' % filename)
        print(file_process_name)
        cv2.imwrite(file_process_name, crop_img)
'''
def walk2():######################單張圖片處理
    img_path = r'D:\py_project\My_Project\dataset\25.jpg'
    save_path = r'D:\py_project\My_Project\dataset\25_save.jpg'
    original_img, gray = get_image(img_path)
    blurred = Gaussian_Blur(gray)
    gradX, gradY, gradient = Sobel_gradient(blurred)
    thresh = Thresh_and_blur(gradient)
    closed = image_morphology(thresh)
    box = findcnts_and_box_point(closed)
    draw_img, crop_img = drawcnts_and_cut(original_img,box)



#    cv2.imshow('original_img', original_img)
#    cv2.imshow('blurred', blurred)
#    cv2.imshow('gradX', gradX)
#    cv2.imshow('gradY', gradY)
#    cv2.imshow('final', gradient)
#    cv2.imshow('thresh', thresh)
#    cv2.imshow('closed', closed)
#    cv2.imshow('draw_img', draw_img)
#    cv2.imshow('crop_img', crop_img)
#    cv2.waitKey(20171219)
    cv2.imwrite(save_path, crop_img)
walk2()

附錄2.本篇文章精華函式說明

# 用來轉化影象格式的
img = cv2.cvtColor(src, 
    COLOR_BGR2HSV # BGR---->HSV
    COLOR_HSV2BGR # HSV---->BGR
    ...)
# For HSV, Hue range is [0,179], Saturation range is [0,255] and Value range is [0,255]


# 返回一個閾值,和二值化影象,第一個閾值是用來otsu方法時候用的
# 不過現在不用了,因為可以通過mahotas直接實現
T = ret = mahotas.threshold(blurred)
ret, thresh_img = cv2.threshold(src, # 一般是灰度影象
    num1, # 影象閾值
    num2, # 如果大於或者num1, 畫素值將會變成 num2
# 最後一個二值化引數
    cv2.THRESH_BINARY      # 將大於閾值的灰度值設為最大灰度值,小於閾值的值設為0
    cv2.THRESH_BINARY_INV  # 將大於閾值的灰度值設為0,大於閾值的值設為最大灰度值
    cv2.THRESH_TRUNC       # 將大於閾值的灰度值設為閾值,小於閾值的值保持不變
    cv2.THRESH_TOZERO      # 將小於閾值的灰度值設為0,大於閾值的值保持不變
    cv2.THRESH_TOZERO_INV  # 將大於閾值的灰度值設為0,小於閾值的值保持不變
)
thresh = cv2.AdaptiveThreshold(src, 
    dst, 
    maxValue, 
    # adaptive_method 
    ADAPTIVE_THRESH_MEAN_C,      
    ADAPTIVE_THRESH_GAUSSIAN_C,      
    # thresholdType
    THRESH_BINARY, 
    THRESH_BINARY_INV, 
    blockSize=3,
    param1=5
)


# 一般是在黑色背景中找白色物體,所以原始影象背景最好是黑色
# 在執行找邊緣的時候,一般是threshold 或者是canny 邊緣檢測後進行的。
# warning:此函式會修改原始影象、
# 返回:座標位置(x,y), 
(_, cnts, _) = cv2.findContours(mask.copy(), 
    # cv2.RETR_EXTERNAL,             #表示只檢測外輪廓
    # cv2.RETR_CCOMP,                #建立兩個等級的輪廓,上一層是邊界
    cv2.RETR_LIST,                 #檢測的輪廓不建立等級關係
    # cv2.RETR_TREE,                   #建立一個等級樹結構的輪廓
    # cv2.CHAIN_APPROX_NONE,           #儲存所有的輪廓點,相鄰的兩個點的畫素位置差不超過1
    cv2.CHAIN_APPROX_SIMPLE,       #例如一個矩形輪廓只需4個點來儲存輪廓資訊
    # cv2.CHAIN_APPROX_TC89_L1,
    # cv2.CHAIN_APPROX_TC89_KCOS
   )
img = cv2.drawContours(src, cnts, whichToDraw(-1), color, line)


img = cv2.imwrite(filename, dst,  # 檔案路徑,和目標影象檔案矩陣

    # 對於JPEG,其表示的是影象的質量,用0-100的整數表示,預設為95
    # 注意,cv2.IMWRITE_JPEG_QUALITY型別為Long,必須轉換成int
    [int(cv2.IMWRITE_JPEG_QUALITY), 5] 
    [int(cv2.IMWRITE_JPEG_QUALITY), 95]
    # 從0到9,壓縮級別越高,影象尺寸越小。預設級別為3
    [int(cv2.IMWRITE_PNG_COMPRESSION), 5])
    [int(cv2.IMWRITE_PNG_COMPRESSION), 9])

# 如果你不知道用哪個flags,畢竟太多了哪能全記住,直接找找。
尋找某個函式或者變數
events = [i for i in dir(cv2) if 'PNG' in i]
print( events )

尋找某個變數開頭的flags
flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
print flags

批量讀取檔名字
import os
filename_rgb = r'C:\Users\aixin\Desktop\all_my_learning\colony\20170629'
for filename in os.listdir(filename_rgb):              #listdir的引數是資料夾的路徑
    print (filename)

 

附錄3.實驗效果圖

【轉載】https://blog.csdn.net/sinat_36458870/article/details/78825571