查看原文
其他

垃圾分类的正确姿势?用 OpenCV 人工智能图像识别技术来进行

沂水寒城 Python中文社区 2022-12-04

OpenCV是一款非常强大的图像处理工具,对于从事图像处理领域相关工作的人来说这个可以说是必不可少的一项工具,用起来也很方面,下吗是一段简单的介绍:

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。OpenCV用C++语言编写,它具有C ++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac OS,OpenCV主要倾向于实时视觉应用,并在可用时利用MMX和SSE指令, 如今也提供对于C#、Ch、Ruby,GO的支持。

OpenCV官网是https://opencv.org/,首页截图如下所示:

下面给出来几个学习OpenCV的链接:
https://docs.opencv.org/master/d9/df8/tutorial_root.html
 
https://docs.opencv.org/
 
https://www.zhihu.com/question/26881367
我们今天的内容主要是想基于OpenCV来实现对图像中我们关注的一些目标对象进行检测识别或者说是对其存在的区域位置进行挖掘,在开始这篇文章之前,我曾经看到了有人基于OpenCV实现了火焰或者是烟雾的检测,其实不管是类似的物体的检测也好不相关的物体识别检测也好,很大程度是比较通用的做法都是基于像素来完成最终的计算的。
这里我们以生活中最为常见的土地来作为要识别检测的目标对象进行实验,先来看一张网上找到的图片,如下所示:

接下来先看一下,最终的识别检测效果:

从上面的结果来看,比较完全地检测到了图中出现的土地的区域,最终,我们采用外切矩形的方式完成了对其轮廓数据的确定。
接下来,我们进入正文,在实际去实践之前,我们很有必要来了解一下几种比较常用的颜色空间,简单总结汇总如下:
RGB颜色空间:
R:Red 红色
G:Green 绿色
B:Blue 蓝色
 
HSV颜色空间:
H:Hue 色度
S:Saturation 饱和度
V:Value 亮度
 
HSI颜色空间:
H:Hue 色度
S:Saturation 饱和度
I:Intensity 强度
本质上来讲,不同的物体不同的对象自身的像素范围是不同的,在实际操作的时候基于像素区间可以过滤得到你所关注的对象,通常这样的操作会在HSV空间中进行,个人的理解是将原始的BGR或者是RGB的图像转化到HSV空间里面来确定目标对象的像素区间更为容易,我实际测试过,在RGB和BGR空间里面也是可以进行计算的,只不过不如HSV空间,这里就不再多讨论了,可以尝试别的方式,本文用的是HSV空间进行计算的。
最开始的时候去确定目标对象所处的像素空间是很笨拙的,主要是借助OpenCV和matplotlib实现的“人眼探索”,比较麻烦,后来在github社区里面找到了一个界面的实现,觉得很不错,就拿来用了,这里贴出来源码实现,如下所示:
#!usr/bin/env python
# encoding:utf-8
from __future__ import division
 
'''
功能:HSV空间图片色素范围查看器
'''

 
 
import cv2
import numpy as np
 
 
 
 
def nothing(x):
    pass
 
 
def colorLooker(pic='1.png'):
    '''
    HSV空间图片色素范围查看器
    '''

    #图像加载
    image = cv2.imread(pic)
    #窗口初始化
    cv2.namedWindow('image',cv2.WINDOW_NORMAL)
    #创建拖动条
    #Opencv中Hue取值范围是0-179
    cv2.createTrackbar('HMin''image', 0, 179, nothing)
    cv2.createTrackbar('SMin''image', 0, 255, nothing)
    cv2.createTrackbar('VMin''image', 0, 255, nothing)
    cv2.createTrackbar('HMax''image', 0, 179, nothing)
    cv2.createTrackbar('SMax''image', 0, 255, nothing)
    cv2.createTrackbar('VMax''image', 0, 255, nothing)
    #设置默认最大值
    cv2.setTrackbarPos('HMax''image', 179)
    cv2.setTrackbarPos('SMax''image', 255)
    cv2.setTrackbarPos('VMax''image', 255)
    #初始化设置
    hMin = sMin = vMin = hMax = sMax = vMax = 0
    phMin = psMin = pvMin = phMax = psMax = pvMax = 0
    while(1):
        #实时获取拖动条上的值
        hMin = cv2.getTrackbarPos('HMin''image')
        sMin = cv2.getTrackbarPos('SMin''image')
        vMin = cv2.getTrackbarPos('VMin''image')
        hMax = cv2.getTrackbarPos('HMax''image')
        sMax = cv2.getTrackbarPos('SMax''image')
        vMax = cv2.getTrackbarPos('VMax''image')
        #设定HSV的最大和最小值
        lower = np.array([hMin, sMin, vMin])
        upper = np.array([hMax, sMax, vMax])
        #BGR和HSV颜色空间转化处理
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, lower, upper)
        result = cv2.bitwise_and(image, image, mask=mask)
        #拖动改变阈值的同时,实时输出调整的信息
        if((phMin != hMin) | (psMin != sMin) | (pvMin != vMin) | (phMax != hMax) | (psMax != sMax) | (pvMax != vMax) ):
            print("(hMin = %d , sMin = %d, vMin = %d), (hMax = %d , sMax = %d, vMax = %d)" % (hMin , sMin , vMin, hMax, sMax , vMax))
            phMin = hMin
            psMin = sMin
            pvMin = vMin
            phMax = hMax
            psMax = sMax
            pvMax = vMax
        #展示由色素带阈值范围处理过的结果图片
        cv2.imshow('image', result)
        if cv2.waitKey(10) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()
 
 
 
 
if __name__ == '__main__':
    colorLooker(pic='1.png')
 
 
启动后截图如下所示:

借助界面中的拖动条可以很方便地进行调节,看到实时处理后的结果图片:

经过调节后最终的结果如下所示:

调整拖动条的同时,终端窗口输出如下所示:

到这里,我们已经获取到了所需要的各个维度的阈值数据了,就可以进行后面的处理了。
接下来我们基于上述阈值来进行区域挖掘计算,同样使用上述的图片,核心代码实现如下所示:
img=Image.open('1.png')
img=cv2.cvtColor(np.asarray(img),cv2.COLOR_RGB2BGR)  
frame=cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 
blur=cv2.GaussianBlur(frame, (21, 21), 0)
hsv=cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
h,w,way=img.shape
total=h*w
print('h: ', h, 'w: ', w, 'area: ', total)
#设置阈值数据
lower = [8, 67, 84]
upper = [85, 255, 255]
lower = np.array(lower, dtype="uint8")
upper = np.array(upper, dtype="uint8")
mask = cv2.inRange(hsv, lower, upper)
output = cv2.bitwise_and(hsv, hsv, mask=mask)
count = cv2.countNonZero(mask)
print('count: ', count)
now_ratio=round(int(count)/total,3)
print('now_ratio: ', now_ratio)
之后为了得到实际的轮廓区域,我们可以使用cv2.findContours方法来实现目标对象的区域挖掘计算,核心代码实现如下所示:
gray=cv2.cvtColor(output,cv2.COLOR_BGR2GRAY)
print('gray_shape: ', gray.shape)
ret,output=cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
'''
cv2.findContours:
在opencv中查找轮廓时,物体应该是白色而背景应该是黑色
contours, hierarchy = cv2.findContours(image,mode,method)
image:输入图像
mode:轮廓的模式。cv2.RETR_EXTERNAL只检测外轮廓;cv2.RETR_LIST检测的轮廓不建立等级关系;cv2.RETR_CCOMP建立两个等级的轮廓,上一层为外边界,内层为内孔的边界。如果内孔内还有连通物体,则这个物体的边界也在顶层;cv2.RETR_TREE建立一个等级树结构的轮廓。
method:轮廓的近似方法。cv2.CHAIN_APPROX_NOME存储所有的轮廓点,相邻的两个点的像素位置差不超过1;cv2.CHAIN_APPROX_SIMPLE压缩水平方向、垂直方向、对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需要4个点来保存轮廓信息;cv2.CHAIN_APPROX_TC89_L1,cv2.CV_CHAIN_APPROX_TC89_KCOS
contours:返回的轮廓
hierarchy:每条轮廓对应的属性
'''

contours,hierarchy=cv2.findContours(output,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
print('contours_num: ', len(contours))
count_dict={}
areas,lengths=0,0
for i in range(len(contours)):
    one=contours[i]
    one_lk=one.tolist()
    if len(one_lk)>=2:
        area=cv2.contourArea(one)
        length=cv2.arcLength(one, True)
        areas+=area
        lengths+=length
        left_list,right_list=[O[0][0] for O in one_lk],[O[0][1] for O in one_lk]
        minX,maxX,minY,maxY=min(left_list),max(left_list),min(right_list),max(right_list)
        A=abs(maxY-minY)*abs(maxX-minX)
        print('area: ', area, 'A: ', A, 'length: ', length)
        count_dict[i]=[A,area,length,[minX,maxX,minY,maxY]]
sorted_list=sorted(count_dict.items(), key=lambda e:e[1][0], reverse=True)
print(sorted_list[:10])
result['value']=count_dict
cv2.drawContours(img,contours,-1,(0,0,255),3)
完成轮廓的挖掘计算后,我们借助于OpenCV实现可视化,结果如下所示:

可以看到:已经大体上实现了我们所要的功能,但是美中不足的是里面有很多小的矩形框,这个是我们外切矩形设计的问题,没有考虑到过滤掉嵌套或者是包含的情况,所以这里就来处理一下:
if sorted_list:
    filter_list=filterBox(sorted_list[:5])
    for one_box in filter_list:
        print('one_box: ', one_box)
        A,area,length,[minX,maxX,minY,maxY]=one_box
        cv2.rectangle(img,(minX,maxY),(maxX,minY),(0,255,0),3)
此时的结果如下所示:

对应的计算结果输出如下所示:
('h: ', 336L, 'w: ', 500L, 'area: ', 168000L)
('count: ', 126387)
('now_ratio: ', 0.752)
('output_shape: ', (336L, 500L, 3L))
('gray_shape: ', (336L, 500L))
('contours_num: ', 64)
('area: ', 0.0, 'A: ', 2, 'length: ', 4.828427076339722)
('area: ', 0.0, 'A: ', 0, 'length: ', 4.0)
('area: ', 29.0, 'A: ', 44, 'length: ', 27.313708186149597)
('area: ', 42.5, 'A: ', 72, 'length: ', 34.72792184352875)
('area: ', 0.5, 'A: ', 6, 'length: ', 9.071067690849304)
('area: ', 0.0, 'A: ', 10, 'length: ', 11.656854152679443)
('area: ', 0.0, 'A: ', 1, 'length: ', 2.8284270763397217)
('area: ', 0.0, 'A: ', 2, 'length: ', 4.828427076339722)
('area: ', 1.5, 'A: ', 2, 'length: ', 5.414213538169861)
('area: ', 16.5, 'A: ', 36, 'length: ', 27.55634891986847)
('area: ', 5.0, 'A: ', 36, 'length: ', 37.79898953437805)
('area: ', 1.5, 'A: ', 3, 'length: ', 8.242640614509583)
('area: ', 0.0, 'A: ', 0, 'length: ', 2.0)
('area: ', 2.0, 'A: ', 2, 'length: ', 6.0)
('area: ', 360.0, 'A: ', 1026, 'length: ', 206.93607211112976)
('area: ', 44.0, 'A: ', 143, 'length: ', 59.94112479686737)
('area: ', 0.0, 'A: ', 1, 'length: ', 2.8284270763397217)
('area: ', 0.0, 'A: ', 1, 'length: ', 2.8284270763397217)
('area: ', 33.5, 'A: ', 60, 'length: ', 30.38477599620819)
('area: ', 76.5, 'A: ', 228, 'length: ', 63.35533845424652)
('area: ', 320.0, 'A: ', 792, 'length: ', 166.9949471950531)
('area: ', 16.0, 'A: ', 35, 'length: ', 21.313708305358887)
('area: ', 0.0, 'A: ', 8, 'length: ', 10.828427076339722)
('area: ', 21.0, 'A: ', 78, 'length: ', 37.79898953437805)
('area: ', 0.0, 'A: ', 1, 'length: ', 2.8284270763397217)
('area: ', 0.0, 'A: ', 2, 'length: ', 4.828427076339722)
('area: ', 0.0, 'A: ', 2, 'length: ', 4.828427076339722)
('area: ', 3.5, 'A: ', 25, 'length: ', 20.727921843528748)
('area: ', 1.5, 'A: ', 12, 'length: ', 13.071067690849304)
('area: ', 51.0, 'A: ', 121, 'length: ', 53.94112491607666)
('area: ', 0.0, 'A: ', 1, 'length: ', 2.8284270763397217)
('area: ', 32.5, 'A: ', 50, 'length: ', 27.899494767189026)
('area: ', 309.5, 'A: ', 722, 'length: ', 96.32590079307556)
('area: ', 34.0, 'A: ', 42, 'length: ', 22.485281229019165)
('area: ', 80970.5, 'A: ', 132699, 'length: ', 2718.5739262104034)
[(63, [132699, 80970.5, 2718.5739262104034, [1, 498, 67, 334]]), (24, [1026, 360.0, 206.93607211112976, [33, 60, 281, 319]]), (39, [792, 320.0, 166.9949471950531, [61, 94, 252, 276]]), (61, [722, 309.5, 96.32590079307556, [384, 422, 75, 94]]), (34, [228, 76.5, 63.35533845424652, [1, 13, 267, 286]]), (25, [143, 44.0, 59.94112479686737, [68, 81, 280, 291]]), (55, [121, 51.0, 53.94112491607666, [189, 200, 219, 230]]), (47, [78, 21.0, 37.79898953437805, [100, 113, 235, 241]]), (4, [72, 42.5, 34.72792184352875, [209, 221, 328, 334]]), (32, [60, 33.5, 30.38477599620819, [15, 25, 274, 280]])]
('one_box: ', [132699, 80970.5, 2718.5739262104034, [1, 498, 67, 334]])
为了更加直观地对比分析,我们将上面计算各个步骤中的对象数据进行可视化,借助于matplotlib绘制在同一张图上,结果如下所示:

左上角为原始图片,右下角为最终处理得到的图片,可以看到整个处理过程的变化。
到这里本文的内容就结束了,学习依旧在路上,欢迎交流,互相学习!

作者:沂水寒城,CSDN博客专家,个人研究方向:机器学习、深度学习、NLP、CV

Blog: http://yishuihancheng.blog.csdn.net


赞 赏 作 者



Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。

长按扫码添加“Python小助手” 

▼点击成为社区会员   喜欢就点个在看吧

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存