Skip to content

目标识别

什么是图像轮廓

  • 什么是图像轮廓:具有相同颜色强度连续点的曲线
  • 图像轮廓的作用:可以用于图形分析、物体的识别和检测

注意点

为了检测的准确性,需要先对图像进行二值化或Canny操作
画轮廓时会修改输入的图像,最好将原始图像copy一份,以便于后续分析

查找轮廓

  • 查找轮廓API:findContours(img, mode, ApproximationMode...)

    • 输入值
      1. img: 源
      2. mode: 模式(常用的4种)
        1. RETR_EXTERNAL = 0: 只检测外轮廓
        2. RETR_LIST = 1: 检测的轮廓不建立等级关系(只要找到轮廓就扔进去)
        3. RETR_CCOMP = 2: 每层最多两级
        4. RETR_TREE = 3: 按树形存储轮廓
      3. ApproximationMode:
        1. CHAIN_APPROX_NONE: 保存所有轮廓上的点
        2. CHAIN_APPROX_SIMPLE: 只保存角点
    • 返回值
      1. contours: 所有轮廓的列表
      2. hierarchy: 层级(查找的轮廓有没有层级关系...)
    import cv2
    import numpy as np
    
    img = cv2.imread('./contours1.jpeg')
    
    # 置为灰度
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 二值化
    ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
    
    # 轮廓查找 - 外轮廓
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    print(contours)
    
    '''
    (
        array(
            [
                [[  0,   0]],
                [[  0, 435]],
                [[345, 435]],
                [[345,   0]]
            ],
        dtype=int32),
    )
    '''
    
    
    # 轮廓查找 - 检测的轮廓不建立等级关系
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    print(contours)
    
    '''
    (
        array(
            [
                [[ 36, 130]],
                [[ 37, 129]],
                [[310, 129]],
                [[311, 130]],
                [[311, 400]],
                [[310, 401]],
                [[ 37, 401]],
                [[ 36, 400]]
            ],
            dtype=int32
        ),
        array(
            [
                [[ 36,  35]],
                [[ 37,  34]],
                [[308,  34]],
                [[309,  35]],
                [[309,  39]],
                [[308,  40]],
                [[ 37,  40]],
                [[ 36,  39]]
            ],
            dtype=int32
        ),
        array(
            [
                [[  0,   0]],
                [[  0, 435]],
                [[345, 435]],
                [[345,   0]]
            ],
            dtype=int32
        )
    )
    '''
    

绘制轮廓

  • API:drawContours(img, contours, contoursIdx, color, thickness ...)

    1. img: 图像源。
    2. contours: findContours找到的轮廓返回参数。
    3. contoursIdx: 所有的轮廓数据是有顺序的,该参数表示绘制指定轮廓。-1表示绘制所有轮廓。
    4. color: 轮廓的颜色。
    5. thickness: 轮廓的线宽,-1是全部填充。

    import cv2
    import numpy as np
    
    img = cv2.imread('./contours1.jpeg')
    
    # 置为灰度
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 二值化
    ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
    
    # 轮廓查找 - 外轮廓
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
    # 绘制轮廓
    cv2.drawContours(img, contours, -1, (0, 0, 255), 2)
    
    cv2.imshow('img', img)
    cv2.waitKey(0)
    
    Image title

轮廓的面积与周长

  • 场景:比如我们检测到很多碎小的轮廓,这些轮廓可能不是我们计算的范围之内,我们需要过滤掉它,这个时候我们就可以使用面积来进行过滤,小于多少就可以跳过。
  • 轮廓面积的API:contourArea(contour)
    1. contour: 轮廓
  • 轮廓周长的API:arcLength(curve, closed)
    1. curve: 曲线(轮廓)
    2. closed: 轮廓是否闭合,闭合为true 非闭合为false
      import cv2
      # 计算面积
      area = cv2.contourArea(contours[0])
      print("area=%d"%(area))
      '''
      像素的面积
      area=150075
      '''
      
      # 计算周长
      len = cv2.arcLength(contours[0], True)
      print("len=%d"%(len))
      '''
      像素的周长
      len=1560
      '''
      

多边形逼近与凸包

  • 区别

    • 多边形逼近:节省计算与存储,只保留特征点
    • 凸包:只保留轮廓,支持有些时候我们只需要有轮廓的场景
      Image title
  • 多边形逼近API:approxPolyDP(curve, epsilon, closed)

    1. curve: 曲线(轮廓)
    2. epsilon: 精度
    3. closed: 是否是闭合的
      import cv2
      import numpy as np
      # 画出以点的线条
      def drawShape(src, points):
          i = 0
          while i < len(points):
              if(i == len(points) - 1):
                  x,y = points[i][0]
                  x1,y1 = points[0][0]
                  cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 3)
              else:
                  x,y = points[i][0]
                  x1,y1 = points[i+1][0]
                  cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 3)
              i = i + 1
      
      img = cv2.imread('./hand.png')
      
      # 置为灰度
      gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
      
      # 二值化
      ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
      
      # 轮廓查找 - 外轮廓
      contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
      
      # 多边形逼近
      approx = cv2.approxPolyDP(contours[0], 20, True)
      # 绘制轮廓
      drawShape(img, approx)
      
      cv2.imshow('img', img)
      cv2.waitKey(0)
      
      Image title
  • 凸包API:convexHull(points, clockwise)

    1. points: 点(轮廓)
    2. clockwise: 绘制方向顺时针还是逆时针
      import cv2
      import numpy as np
      # 画出以点的线条
      def drawShape(src, points):
          i = 0
          while i < len(points):
              if(i == len(points) - 1):
                  x,y = points[i][0]
                  x1,y1 = points[0][0]
                  cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 3)
              else:
                  x,y = points[i][0]
                  x1,y1 = points[i+1][0]
                  cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 3)
              i = i + 1
      
      img = cv2.imread('./hand.png')
      
      # 置为灰度
      gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
      
      # 二值化
      ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
      
      # 轮廓查找 - 外轮廓
      contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
      
      # 凸包
      hull = cv2.convexHull(contours[0])
      # 绘制
      drawShape(img, hull)
      
      cv2.imshow('img', img)
      cv2.waitKey(0)
      
      Image title

外接矩阵

  • 最小外接矩阵与最大外接矩阵的区别
    Image title
  • 最小外接矩阵:我们需要知道一个物体有没有旋转,并且知道旋转角度,就可以使用最小外接矩形。
  • 最小外接矩阵API:minAreaRect(points)
    • points: 物体的坐标点(轮廓)
    • 返回值 RotateRect:
      1. x, y 起始点
      2. width, height 宽和高
      3. angle 角度
  • 最大外接矩阵API:boundingRect(array)

    • array: 轮廓
    • 返回 Rect

    import cv2
    
    img = cv2.imread('./hello.jpeg')
    
    # 置为灰度
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 二值化
    ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
    
    # 轮廓查找 - 外轮廓
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
    r = cv2.minAreaRect(contours[1])
    # 只获取坐标
    box = cv2.boxPoints(r)
    # 类型转换
    box = np.int0(box)
    cv2.drawContours(img, [box], 0, (0,0, 255), 2)
    
    x,y,w,h = cv2.boundingRect(contours[1])
    cv2.rectangle(img, (x, y), (x+w,y+h), (255,0,0), 2)
    
    cv2.imshow('img', img)
    # cv2.imshow('bin', binary)
    cv2.waitKey(0)
    
    Image title

车辆统计 - 总览

  • 涉及到的知识点
    1. 基本图像运算与处理
    2. 形态学
    3. 轮廓查找
    4. 加载视频
    5. 通过形态学识别车辆
    6. 对车辆进行统计
    7. 显示车辆统计信息

车辆统计 - 视频加载

import cv2
from cv2 import VideoCapture
import numpy as np

cap = VideoCapture('./video.mp4')

while True:
    ret, frame = cap.read()

    if(ret == True):
        cv2.imshow('car', frame)

    key = cv2.waitKey(1)
    if(key == 27):
        break

cap.release()
cv2.destroyAllWindows()

Image title

车辆统计 - 去背景

  • API:createBackgroundSubtractorMOG
    import cv2
    from cv2 import VideoCapture
    import numpy as np
    
    cap = VideoCapture('./video.mp4')
    
    bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()
    
    while True:
        ret, frame = cap.read()
    
        if(ret == True):
            # 灰度
            cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 去噪(高斯)
            blur = cv2.GaussianBlur(frame, (3,3), 5)
            # 去背影
            mask = bgsubmog.apply(blur)
            cv2.imshow('car', mask)
    
        key = cv2.waitKey(1)
        if(key == 27):
            break
    
    cv2.createBackgroundSubtractorMOG2
    
    cap.release()
    cv2.destroyAllWindows()
    

Image title

车辆统计 - 形态学处理

from ast import For
import cv2
from cv2 import VideoCapture
import numpy as np

cap = VideoCapture('./video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

#形态学kernel
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

while True:
    ret, frame = cap.read()

    if(ret == True):
        # 灰度
        cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        # 去噪(高斯)
        blur = cv2.GaussianBlur(frame, (3,3), 5)
        # 去背影
        mask = bgsubmog.apply(blur)
        # 腐蚀, 去掉图中小斑块
        erode = cv2.erode(mask, kernel)
        # 膨胀,还原放大
        dilate = cv2.dilate(erode, kernel, iterations = 3)
        #闭操作,去掉物体内部的小块
        close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
        close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)

        # 查找轮廓
        cnts, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        # 根据查找到的轮廓获取到坐标 绘制矩形框
        for (i, c) in enumerate(cnts):
            (x,y,w,h) = cv2.boundingRect(c)
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2)

        cv2.imshow('car', frame)

    key = cv2.waitKey(1)
    if(key == 27):
        break

cap.release()
cv2.destroyAllWindows()

Image title

以上图例中,对于轮廓查找的结果还有小范围的一些值,导致图像中标记的有些杂乱(轮廓查找会查找到车牌 车窗等轮廓)。可以在下面逻辑处理中将小范围的轮廓范围忽略,进一步优化。

车辆统计 - 逻辑处理与显示信息

from ast import For
import cv2
from cv2 import VideoCapture
import numpy as np

min_w = 90
min_h = 90

#检测线的高度
line_high = 550

#线的偏移
offset = 7

#统计车的数量
carno =0

#存放有效车辆的数组
cars = []

def center(x, y, w, h):
    x1 = int(w/2)
    y1 = int(h/2)
    cx = x + x1
    cy = y + y1

    return cx, cy

cap = VideoCapture('./video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

#形态学kernel
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

while True:
    ret, frame = cap.read()

    if(ret == True):
        #灰度
        cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        # 去噪(高斯)
        blur = cv2.GaussianBlur(frame, (3,3), 5)
        # 去背影
        mask = bgsubmog.apply(blur)
        # 腐蚀, 去掉图中小斑块
        erode = cv2.erode(mask, kernel)
        # 膨胀,还原放大
        dilate = cv2.dilate(erode, kernel, iterations = 3)
        #闭操作,去掉物体内部的小块
        close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
        close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)

        # 查找轮廓
        cnts, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        #画一条检测线
        cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 3)

        for (i, c) in enumerate(cnts):
            (x,y,w,h) = cv2.boundingRect(c)
            # 对车辆的宽高进行判断
            # 以验证是否是有效的车辆
            isValid = ( w >= min_w ) and ( h >= min_h) 
            if( not isValid):
                continue

            # 到这里都是有效的车
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2)
            # 获取轮廓的中心点
            cpoint = center(x, y, w, h)
            # 将车辆放到数组里
            cars.append(cpoint)
            # 画车的中心点
            cv2.circle(frame, (cpoint), 5, (0,0,255), -1)

            for (x, y) in cars:
                if( (y > line_high - offset) and (y < line_high + offset ) ):
                    carno +=1
                    cars.remove((x , y ))
                    print(carno)

        cv2.putText(frame, "Cars Count:" + str(carno), (500, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,0,0), 5)
        cv2.imshow('video', frame)

    key = cv2.waitKey(1)
    if(key == 27):
        break

cap.release()
cv2.destroyAllWindows()

Image title