美文网首页
如何检测图像中的圣诞树, Python 可以帮你来实现

如何检测图像中的圣诞树, Python 可以帮你来实现

作者: 小张Python | 来源:发表于2020-12-27 00:35 被阅读0次

    本文数据代码获取方式,公众号:小张Python,后台回复关键字:圣诞树

    大家好,我是 zeroing,前天是圣诞节,不知道大家过开心不,反正我是挺开心的,晚上室友都出去了自己一个人在宿舍,为所欲为~ 真的是爽歪歪~

    本篇文章将用 Python 来实现图片中的圣诞树的识别、标记,可理解为计算机视觉中的物体检测,先声明一下哈这里没有用到神经网络,都是传统方法

    先看一下效果,以下是原图

    Snipaste_2020-12-26_14-45-50

    下面是最终检测出来的效果图:

    Snipaste_2020-12-26_14-46-39

    图中的圣诞树的外轮廓都用红线给标记出来了,效果看起来还不错吧~,下面是算法实现的整体思路,分为三个部分

    1,提取图片特征点(根据图像明亮度,色调,饱和度)

    上面展示的6张图像中,因为彩灯原因,圣诞树在整个图片中呈现出偏亮、色调偏暖,与背景偏冷、偏青色形成对比;

    Snipaste_2020-12-26_15-18-53

    根据上面提到的思路先对圣诞树上特征点进行提取,这里对图像分别以亮度、色调、饱和度三个角度对图像做了条件筛选,筛选出图像中目标特征点集,筛选标准如下

    • 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 范围数值)

    • 饱和度S:取值范围为0.0~1.0;

    • 亮度V:取值范围为0.0(黑色)~1.0(白色)。

    根据上面三个筛选条件,对图像进行处理,最终得到一个黑白相间的二值化图像,这里用 numpy 中的logical_andlogical_or 方法来聚合上面的三种条件;

    Snipaste_2020-12-26_15-48-34

    从上图可以看到,图片中的黑点即提取到的特征点(圣诞树),基本大致轮廓已经出来了,但会有少许噪点,见图二、图四,建筑中的灯光、地平线特征也被提取出来了,但这些不是我们所需要的,所以需要下面的一个步骤:聚类,来剔除这些噪点

    2,用 DBSCAN 算法对特征点进行聚类

    上一步得到特征点之后,下面就对特征点集进行聚类,关于点集聚类,这里用基于空间密度的 DNSCAN 算法,这个算法已经被封装到 scikit-learn包中,使用时直接调用即可,但因为涉及一些参数设置问题,使用时需要注意两个参数:

    • eps ,算法中的一个参数,表示类与类样本间的最大距离,对于不同数据集和距离函数这个参数需要设置不同的值;这里设置的是 图片对角线长度的0.04倍,这样的话既能适应大分辨率图片,也能适用于小分辨率的图片

    • min_samples ,假设以某一点为中心,周围的样本数量(包括样本本身) ; 值太小时,最终类别会太多,值太大时,最终类别太少;本文设置为 10 ;

    特征点分类后,最终将圣诞树特征点部分全部标为红色,效果如下:

    Snipaste_2020-12-26_16-29-26

    描边扩张后效果:

    Snipaste_2020-12-26_16-19-55

    可以看到图 2,3,4 中的特征点分别分为两类,用不同的颜色进行标记;后面再做一次条件筛选:只取图片中特征点数量最多的类(圣诞树),就可以把图像中的噪点去除

    3,对目标特征点集计算凸包,在原图上绘制

    最后这一步就简单多了,有了特征点集,利用 scipy 包 中的 ConvexHull 方法计算 凸包 ,之后再利用matplotlib 将凸包在原图上进行绘制

    Snipaste_2020-12-26_14-46-39

    小结

    文章中的一些技术点是值得借鉴,例如前面提到的用色调、饱和度作为阈值条件来筛选特征点,及后面的 DBSCAN 聚类算法的使用;这些 Idea 不仅局限在圣诞树上,也可以用于检测其它的一些物体上面来,但需要多思考,多实践

    最后在这里提一下为什么聚类算法这里用 DBSCAN,而不是经典的 KMeans;因为 KMeans 分类时需要设置类别数量(类别数量是我们提前没有办法确定的),并且在分类时仅以欧式距离作为参考,最终分类结果并不理想,参照下图

    KMeans 算法

    Snipaste_2020-12-26_16-50-56

    DBSCAN 算法

    Snipaste_2020-12-26_16-51-10

    文章中用到核心代码

    <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n55" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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
    ​</pre>

    启动脚本

    <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n57" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">'''
    @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()</pre>

    好了,以上就是本篇文章的全部内容,如果觉得内容不错,求赞、求分享、求留言;最后感谢大家的阅读!

    参考链接:https://stackoverflow.com/questions/20772893/how-to-detect-a-christmas-tree

    相关文章

      网友评论

          本文标题:如何检测图像中的圣诞树, Python 可以帮你来实现

          本文链接:https://www.haomeiwen.com/subject/ubbnnktx.html