准備1:OpenCV常用圖片轉換技巧
在進行計算機視覺模型訓練前,我們經常會用到圖像增強的技巧來獲取更多的樣本,但是有些深度學習框架中的方法對圖像的變換方式可能並不滿足我們的需求,所以掌握OpenCV中一些常用的圖像處理技巧對我們還是有很多幫助的。
圖像通道分離
我們知道每個圖像是由RGB三個顏色通道構成,所以我們可以使用split函數對原圖像的三個通道進行分離:
B, G, R = cv2.split(img)
注意:既然我們可以通過split()函數進行圖片的通道分類的情況,那么我們就可以借助這一方法來獲取img的通道數:
img_num = len(cv2.split(img)) or from PIL import Image image = Image.open(photo_path) img_num = len(image.split())
進行通道分離的后,我們就可以在每個通道上獨立的進行數值變換,變換完成后再來組合來生成新的圖像,比如提升圖像的亮度:
B,G,R = cv2.split(img) for i in (B,G,R): randint = random.randint(50,100) limit = 255-randint i[i>limit]=255 i[i<=limit]=randint+i[i<=limit] img_merge = cv2.merge((B,G,R)) cv2.imshow("img_merge",img_merge) key = cv2.waitKey() if key==27: cv2.destroyAllWindows()
圖像旋轉
還可以使用 warpAffine 函數根據我們的設定的角度完成圖像的旋轉:
M = cv2.getRotationMatrix2D((img.shape[1] / 2, img.shape[0] / 2), 30, 1) img_rotate = cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) cv2.imshow('img_rotate', img_rotate) key = cv2.waitKey(0) if key == 27: cv2.destroyAllWindows()
這里我們不對圖像進行縮放,旋轉角度為30度。
仿射變換
仿射變換允許圖像傾斜並且可以在任意兩個方向上發生伸縮。代碼如下:
def random_warp(img, row, col): height, width, channels = img.shape random_margin = 100 x1 = random.randint(-random_margin, random_margin) y1 = random.randint(-random_margin, random_margin) x2 = random.randint(width - random_margin - 1, width - 1) y2 = random.randint(-random_margin, random_margin) x3 = random.randint(width - random_margin - 1, width - 1) y3 = random.randint(height - random_margin - 1, height - 1) x4 = random.randint(-random_margin, random_margin) y4 = random.randint(height - random_margin - 1, height - 1) dx1 = random.randint(-random_margin, random_margin) dy1 = random.randint(-random_margin, random_margin) dx2 = random.randint(width - random_margin - 1, width - 1) dy2 = random.randint(-random_margin, random_margin) dx3 = random.randint(width - random_margin - 1, width - 1) dy3 = random.randint(height - random_margin - 1, height - 1) dx4 = random.randint(-random_margin, random_margin) dy4 = random.randint(height - random_margin - 1, height - 1) pts1 = np.float32([[x1, y1], [x2, y2], [x3, y3], [x4, y4]]) pts2 = np.float32([[dx1, dy1], [dx2, dy2], [dx3, dy3], [dx4, dy4]]) M_warp = cv2.getPerspectiveTransform(pts1, pts2) img_warp = cv2.warpPerspective(img, M_warp, (width, height)) return img_warp img_warp = random_warp(img, img.shape[0], img.shape[1]) cv2.imshow('img_warp', img_warp) key = cv2.waitKey(0) if key == 27: cv2.destroyAllWindows()
伽馬修正
伽馬修正提升圖像的對比度,讓圖像看起來更加的“明亮”。代碼如下:
def adjust_gamma(image, gamma=1.0): invGamma = 1.0/gamma table = [] for i in range(256): table.append(((i / 255.0) ** invGamma) * 255) table = np.array(table).astype("uint8") return cv2.LUT(image, table) img_gamma = adjust_gamma(img, 2) cv2.imshow("img",img) cv2.imshow("img_gamma",img_gamma) key = cv2.waitKey() if key == 27: cv2.destroyAllWindows()
准備2:下載並安裝cv2
下載輪子如下:
然后直接使用pip 命令即可:
pip install opencv_python-3.4.3-cp37-cp37m-win_amd64.whl
注意:現在OpenCV for Python 就是通過Numpy 進行綁定的。所以在使用時必須掌握一些Numpy的相關知識!圖像就是一個矩陣,在OpenCV for Python 中,圖像就是Numpy中的數組!
1,圖像的載入,顯示和保存
如果讀取圖像,只需要imread即可。
import cv2 # 獲取圖片 img_path = r'1.jpg' img = cv2.imread(img_path)
OpenCV目前支持讀取bmp,jpg,png,tiff等常用格式。
我們還可以查看圖像的一些基本屬性:
print(img) print(img.dtype) print(img.shape)
接着創建一個窗口
cv2.namedWindow("Image")
然后在窗口中顯示圖像
cv2.imshow('Image', img)
最后還要添加一句:
cv2.waitKey(0)
如果不添加最后一句,在IDLE中執行窗口直接無響應。在命令行中執行的話,則是一閃而過。
保存圖像很簡單,直接cv.imwrite即可。
cv2.imwrite(save_path, crop_img)
第一個參數是保存的路徑及文件名,第二個是圖像矩陣。其中,imwrite()有個可選的第三個參數,如下:
cv2.imwrite("cat.jpg", img,[int(cv2.IMWRITE_JPEG_QUALITY), 5])
第三個參數針對特定的格式: 對於JPEG,其表示的是圖像的質量,用0-100的整數表示,默認為95。 注意,cv2.IMWRITE_JPEG_QUALITY類型為Long,必須轉換成int。下面是以不同質量存儲的兩幅圖:
對於PNG,第三個參數表示的是壓縮級別。cv2.IMWRITE_PNG_COMPRESSION,從0到9,壓縮級別越高,圖像尺寸越小。默認級別為3:
cv2.imwrite("./cat.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0]) cv2.imwrite("./cat2.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
保存的圖像尺寸如下:
還有一種支持的圖像,一般不常用。
完整的程序為:
#!/usr/bin/env python # -*- coding: utf-8 -*- import cv2 # 獲取圖片 img_path = r'1.jpg' img = cv2.imread(img_path) cv2.namedWindow("Image") cv2.imshow('Image', img) cv2.waitKey(0) cv2.destroyAllWindows()
最后釋放窗口是個好習慣!,查看圖片效果如下:
2,轉換灰度並去噪聲
我們可以得到兩張圖片,第一張是灰度圖,第二章是去噪之后的。去噪有很多種方法,均值濾波法,高斯濾波法,中值濾波器,雙邊濾波器等。這里展示灰度化,高斯去噪的代碼:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (9, 9),0)
下圖展示效果,(這里取高斯是因為高斯去噪效果是最好的)
3,提取圖像的梯度
用Sobel算子計算x,y方向上的梯度,之后在x方向上 減去 y方向上的梯度,通過這個操作,會留下具有高水平梯度和低垂直梯度的圖像區域。
代碼入下:
# 提取圖像的梯度 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)
此時我們會得到如下的圖像:
4,繼續去噪聲
考慮到圖像的孔隙,首先使用低通濾波器平滑圖像,這將有助於平滑圖像中的高頻噪聲。低通濾波器的目的是降低圖像的變化率。
如果將每個像素替換為該圖像周圍像素的均值,這樣就可以平滑並替代那些強度變化明顯的區域。
對模糊圖像二值化,顧名思義就是把圖像數值以某一邊界分成兩種數值,梯度圖像中不大於90的任何像素都設置為0(黑色)。否則,像素設置為255(白色):
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, #存儲所有的輪廓點,相鄰的兩個點的像素位置差不超過1 # cv2.CHAIN_APPROX_NONE, 參數三:處理近似方法 #例如一個矩形輪廓只需4個點來保存輪廓信息 # cv2.CHAIN_APPROX_SIMPLE, # cv2.CHAIN_APPROX_TC89_L1, # cv2.CHAIN_APPROX_TC89_KCOS )
第一個參數是要檢索的圖片,必須是二值圖,即黑白的(不是灰度圖)。
# 這里opencv3返回的是三個參數 (cnts, _) = cv2.findContours( # 參數一:二值化圖像 closed.copy(), # 參數二:輪廓類型 cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE ) c = sorted(cnts, key=cv2.contourArea, reverse=True)[0] rect = cv2.minAreaRect(c) box = np.int0(cv2.boxPoints(rect)) draw_img = cv2.drawContours(img.copy(), [box], -1, (0, 0, 255), 3) cv2.imshow("draw_img", draw_img)
此時,會得到:
8,裁剪
圖像的裁剪最簡單的方式就是獲取圖像數組的切片,如下:
img_crop = img[100:300,100:300] cv2.imshow("img_crop", img_crop) key = cv2.waitKey() if key == 27: cv2.destroyAllWindows()
當然,這里我們直接找到四個點,切出來就OK.
其實,box里保存的是綠色矩陣區域四個頂點的坐標。我們按照下圖所示裁剪昆蟲圖像。
方法是找到四個頂點的x,y坐標的最大最小值。新圖像的高等於 max(Y) - min(Y),寬等於 max(X) - min(X)。
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)
9,完整代碼
#-*- coding: UTF-8 -*- import cv2 import numpy as np 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=4) closed = cv2.dilate(closed, None, iterations=4) return closed def findcnts_and_box_point(closed): # 這里opencv3返回的是三個參數 (_, cnts, _) = cv2.findContours(closed.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) 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(): img_path = r'C:\Users\aixin\Desktop\chongzi.png' save_path = r'C:\Users\aixin\Desktop\chongzi_save.png' 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) walk()
附錄代碼:
# 用來轉化圖像格式的 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)
參考文獻: