本文數據代碼獲取方式:在公眾號 小張Python 後臺,回復關鍵字:聖誕樹
大家好,我是 zeroing,前天是聖誕節,不知道大家過開心不,反正我是挺開心的,晚上室友都出去了自己一個人在宿舍,為所欲為~ 真的是爽歪歪~
本篇文章將用 Python 來實現圖片中的聖誕樹的識別、標記,可理解為計算機視覺中的物體檢測,先聲明一下哈這裡沒有用到神經網絡,都是傳統方法
先看一下效果,以下是原圖
下面是最終檢測出來的效果圖:
圖中的聖誕樹的外輪廓都用紅線給標記出來了,效果看起來還不錯吧~,下面是算法實現的整體思路,分為三個部分
1,提取圖片特徵點(根據圖像明亮度,色調,飽和度)上面展示的6張圖像中,因為彩燈原因,聖誕樹在整個圖片中呈現出偏亮、色調偏暖,與背景偏冷、偏青色形成對比;
根據上面提到的思路先對聖誕樹上特徵點進行提取,這裡對圖像分別以亮度、色調、飽和度三個角度對圖像做了條件篩選,篩選出圖像中目標特徵點集,篩選標準如下
1,做亮度篩選時,先將RGB 轉化為灰度圖,提取灰度值大於220的區域(原圖標準 0-255)2,把圖像將RGB(0-255) 轉化為 HSV(0-1)顏色空間,提取 HSV 中 hue (色調通道)值小於 0.2 或大於 0.95 的區域,小於 0.2 是為了提取圖片中偏黃色,紅色的特徵點,大於 0.95 對應聖誕樹邊緣的紫紅色區域3,圖像 HSV 顏色空間中,提取 saturation(飽和度) 和 value(值) 大於 0.7 的部分 ;這裡簡單介紹一下 HSV ,HSV 為圖片的一種顏色空間,與 RGB 三通道相似,RGB 分別表示紅、綠、藍三種通道;而 HSV 則代表 hue(色調),saturation(飽和度), value (亮度);
色調H:用角度度量,取值範圍為0°~360°,從紅色開始按逆時針方向計算,紅色為0°,綠色為120°,藍色為240°。它們的補色是:黃色為60°,青色為180°,品紅為300°;(本文將0-300度轉化為 0-1.0 範圍數值)亮度V:取值範圍為0.0(黑色)~1.0(白色)。根據上面三個篩選條件,對圖像進行處理,最終得到一個黑白相間的二值化圖像,這裡用 numpy 中的logical_and 和 logical_or 方法來聚合上面的三種條件;
從上圖可以看到,圖片中的黑點即提取到的特徵點(聖誕樹),基本大致輪廓已經出來了,但會有少許噪點,見圖二、圖四,建築中的燈光、地平線特徵也被提取出來了,但這些不是我們所需要的,所以需要下面的一個步驟:聚類,來剔除這些噪點
2,用 DBSCAN 算法對特徵點進行聚類上一步得到特徵點之後,下面就對特徵點集進行聚類,關於點集聚類,這裡用基於空間密度的 DNSCAN 算法,這個算法已經被封裝到 scikit-learn包中,使用時直接調用即可,但因為涉及一些參數設置問題,使用時需要注意兩個參數:
eps ,算法中的一個參數,表示類與類樣本間的最大距離,對於不同數據集和距離函數這個參數需要設置不同的值;這裡設置的是 圖片對角線長度的0.04倍,這樣的話既能適應大解析度圖片,也能適用於小解析度的圖片min_samples ,假設以某一點為中心,周圍的樣本數量(包括樣本本身) ;值太小時,最終類別會太多,值太大時,最終類別太少;本文設置為 10 ;特徵點分類後,最終將聖誕樹特徵點部分全部標為紅色,效果如下:
描邊擴張後效果:
可以看到圖 2,3,4 中的特徵點分別分為兩類,用不同的顏色進行標記;後面再做一次條件篩選:只取圖片中特徵點數量最多的類(聖誕樹),就可以把圖像中的噪點去除
3,對目標特徵點集計算凸包,在原圖上繪製最後這一步就簡單多了,有了特徵點集,利用 scipy 包 中的 ConvexHull 方法計算 凸包 ,之後再利用matplotlib 將凸包在原圖上進行繪製
小結文章中的一些技術點是值得借鑑,例如前面提到的用色調、飽和度作為閾值條件來篩選特徵點,及後面的 DBSCAN 聚類算法的使用;這些 Idea 不僅局限在聖誕樹上,也可以用於檢測其它的一些物體上面來,但需要多思考,多實踐
最後在這裡提一下為什麼聚類算法這裡用 DBSCAN,而不是經典的 KMeans;因為 KMeans 分類時需要設置類別數量(類別數量是我們提前沒有辦法確定的),並且在分類時僅以歐式距離作為參考,最終分類結果並不理想,參照下圖
KMeans 算法
DBSCAN 算法
文章中用到核心代碼
from PIL import Image
import numpy as np
import scipy
import matplotlib.colors as colors
from sklearn.cluster import DBSCAN
from math import ceil,sqrt
'''
Inputs:
rgbimg: M,N,3 numpy 包含 uint(0-255) color image
hueleftthr: Scalar constant to maximum hue in yellow-green region
huerightthr: Scalar constant to maximum allowed hue in blue-purple region
satthr: Scalar constant to select minimum allow saturation
valthre: Scalar constant to select minimum allow value
monothr: Scalar constant to select minimum allow monochrome
maxpoints: Scalar constant maximum number of pixels to forward to the DBSCAN clustering algoritm
proxthresh: Proximity threshold to use for DBSCAN, as da fraction of the diagonal size of thre image
接近閾值佔圖像對角線尺寸
Outputs:
borderseg: [K,2,2] Nested list containing K pairs of x- and y- pixel values for drawimg the tree border
X: [P,2] List of pixels that passed the threshold step
labels: [Q,2] List of cluster labels for points in Xslice(see below)
Xslice: [Q,2] Reduced list of pixels to be passed to DBSCAN
'''
'''實現腳本'''
def findtree(rgbimg,
hueleftthr = 0.2,
huerightthr = 0.95,
satthr =0.7,
valthr = 0.7,
monothr = 220,
maxpoints = 5000,
proxthresh = 0.04):
# 將 RGB 圖像轉化為 灰度圖
grayimg = np.asarray(Image.fromarray(rgbimg).convert('L'))
# 將 rbg => hsv(float [0,1.0])
hsvimg = colors.rgb_to_hsv(rgbimg.astype(float)/255)
# 二值化閾值圖像初始化
binimg = np.zeros((rgbimg.shape[0],rgbimg.shape[1]))
#1, heu < 0.2 or hue > 0.95(red or yellow)
#2, saturated and bright both greater than 0.7
# 滿足以上條件被認為是聖誕樹上的燈
boolidx = np.logical_and(
np.logical_and(
np.logical_or((hsvimg[:,:,0]<hueleftthr),
(hsvimg[:,:,0]>huerightthr)),
(hsvimg[:,:,1]>satthr)),
(hsvimg[:,:,2]>valthr))
# 找到滿足 hsv 標準的像素,賦值為255
binimg[np.where(boolidx)] = 255
# 添加像素來滿足garay brightness 條件
binimg[np.where(grayimg>monothr)] = 255
# 用 DBSCAN 聚類算法分割這些點
X = np.transpose(np.where(binimg==255))
Xslice = X
nsample = len(Xslice)
if nsample > maxpoints:
# 確保樣本數不超過 DNSCAN 算法最大限度
Xslice = X[range(0,nsample,int(ceil(float(nsample/maxpoints))))] # 將樣本每隔幾個採樣一次
# 將 DNSCAN 閾值接近像素單位,並運行 DBSCAN
pixproxthr = proxthresh * sqrt(binimg.shape[0]**2 + binimg.shape[1]**2) # 對角巷長*proxthresh
db = DBSCAN(eps = pixproxthr,min_samples=10).fit(Xslice) # 擬合樣本
labels = db.labels_.astype(int)
# 尋找最大聚類
unique_labels = set(labels)
maxclustpt = 0
for k in unique_labels:
class_numbers = [index[0] for index in np.argwhere(labels==k)]
if(len(class_numbers) > maxclustpt):
points = Xslice[class_numbers]
hull = scipy.spatial.ConvexHull(points) # 建立凸包
maxclustpt = len(class_numbers)
borderseg = [[points[simplex,0], points[simplex,1]] for simplex in hull.simplices]
return borderseg,X,labels,Xslice啟動腳本
'''
@author:zeroing
@wx公眾號:小張Python
'''
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from findtree import findtree
import os
path_dir = 'D:/ceshi_11/findtree'
path_list = [os.path.join(path_dir,str(i)) for i in os.listdir(path_dir)]
# 初始化figure size
fgsz = (16,8)
figthresh = plt.figure(figsize = fgsz,facecolor ='w')
figclust = plt.figure(figsize = fgsz,facecolor ='w')
figcltwo = plt.figure(figsize = fgsz,facecolor = 'w')
figborder = plt.figure(figsize = fgsz,facecolor = 'w')
figorigin = plt.figure(figsize = fgsz,facecolor = 'w')
# 每張圖設置一個 窗口名
figthresh.canvas.set_window_title('Thresholded HSV and Monochrome Brightness')
figclust.canvas.set_window_title('DBSCAN Clusters (Raw Pixel Output)')
figcltwo.canvas.set_window_title('DBSCAN Clusters (Slightly Dilated for Display)')
figborder.canvas.set_window_title('Trees with Borders')
figorigin.canvas.set_window_title("Original Image")
for ii,name in enumerate(path_list):
# 打開圖片
rgbimg = np.asarray(Image.open(str(name)))
# 運行腳本找到 bordeseg,X,Labels,Xslce
borderseg,X,labels,Xslice = findtree(rgbimg)
# 展示閾值分割後的圖像
axthresh = figthresh.add_subplot(2,3,ii+1)
axthresh.set_xticks([])
axthresh.set_yticks([])
binimg = np.zeros((rgbimg.shape[0],rgbimg.shape[1]))
for v,h in X:
binimg[v,h] = 255 # 初步篩選之後坐標點
axthresh.imshow(binimg,interpolation = 'nearest',cmap = 'Greys')
# Display color-coded clusters
axclust = figclust.add_subplot(2,3,ii+1)
axclust.set_xticks([])
axclust.set_yticks([])
axcltwo = figcltwo.add_subplot(2,3,ii+1)
axcltwo.set_xticks([])
axcltwo.set_yticks([])
axcltwo.imshow(binimg,interpolation = 'nearest',cmap = 'Greys')
clustimg = np.ones(rgbimg.shape)
unique_labels = set(labels)
# 為每個聚類生成單個顏色
plcol = cm.rainbow_r(np.linspace(0,1,len(unique_labels)))
print('plcol',plcol)
for lbl,pix in zip(labels,Xslice):
for col,unqlbl in zip(plcol,unique_labels):
if lbl == unqlbl:
# -1 表示無聚類成員
if lbl == -1:
col = [0.0,0.0,0.0,1.0]
for ij in range(3):
clustimg[pix[0],pix[1],ij] = col[ij]
# 擴張 圖像,用於更好展示
axcltwo.plot(pix[1],pix[0],'o',markerfacecolor= col,markersize = 1,markeredgecolor = col)
axclust.imshow(clustimg)
axcltwo.set_xlim(0,binimg.shape[1]-1)
axcltwo.set_ylim(binimg.shape[0],-1)
# 在原圖樹邊緣進行繪製
axborder = figborder.add_subplot(2,3,ii+1)
axborder.set_axis_off()
axborder.imshow(rgbimg,interpolation ='nearest')
for vseg,hseg in borderseg:
axborder.plot(hseg,vseg,'g-',lw =3)
axborder.set_xlim(0,binimg.shape[1]-1)
axborder.set_ylim(binimg.shape[0],-1)
# 保存原圖
origin_fig1 = figorigin.add_subplot(2, 3, ii + 1)
origin_fig1.set_axis_off()
origin_fig1.imshow(rgbimg, interpolation='nearest')
axborder.set_xlim(0, binimg.shape[1] - 1)
axborder.set_ylim(binimg.shape[0], -1)
# axborder.savefig("D:/ceshi_11/findtree/final_")
print(name,'Sucessfully find it !!!!!!!!')
plt.show()好了,以上就是本篇文章的全部內容,如果覺得內容不錯,求贊、求分享、求留言;最後感謝大家的閱讀!
參考連結:https://stackoverflow.com/questions/20772893/how-to-detect-a-christmas-tree