30

机器视觉实战1:Ball Tracking With OpenCV

 4 years ago
source link: https://niyanchun.com/mvia-1-ball-tracking-with-opencv.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文是机器视觉实战系列第1篇,该项目实现的一个小球的检测和运动轨迹跟踪,效果如下:

ym6vMbN.gif

原文出处: Ball Tracking with OpenCV .

项目学习

相比于原文,我对代码做了一些小修改,并且加了一些中文注释,方便你理解。整个项目代码如下:

import argparse
import time
from collections import deque
import cv2
import imutils
import numpy as np
from imutils.video import VideoStream

# 命令行参数
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video", help="path to video")
ap.add_argument("-b", "--buffer", type=int, default=64, help="max buffer size")
args = vars(ap.parse_args())

# 绿色球的HSV色域空间范围
greenLower = (29, 86, 6)
greenUpper = (64, 255, 255)
pts = deque(maxlen=args["buffer"])

# 判断是读入的视频文件,还是摄像头实时采集的,这里作区分是因为两种情况下后面的有些操作是有区别的
if args.get("video", None) is None:
    useCamera = True
    print("video is none, use camera...")
    vs = VideoStream(src=0).start()
else:
    useCamera = False
    vs = cv2.VideoCapture(args["video"])
    time.sleep(2.0)

while True:
    frame = vs.read()
    # 摄像头返回的数据格式为(帧数据),而从视频抓取的格式为(grabbed, 帧数据),grabbed表示是否读到了数据
    frame = frame if useCamera else frame[1]

    # 对于从视频读取的情况,frame为None表示数据读完了
    if frame is None:
        break

    # resize the frame(become small) to process faster(increase FPS)
    frame = imutils.resize(frame, width=600)
    # blur the frame to reduce high frequency noise, and allow
    # us to focus on the structural objects inside the frame
    # 通过高斯滤波去除掉一些高频噪声,使得重要的数据更加突出
    blurred = cv2.GaussianBlur(frame, (11, 11), 0)
    # convert frame to HSV color space
    hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)
    # handles the actual localization of the green ball in the frame
    # inRange的作用是根据阈值进行二值化:阈值内的像素设置为白色(255),阈值外的设置为黑色(0)
    mask = cv2.inRange(hsv, greenLower, greenUpper)

    # A series of erosions and dilations remove any small blobs that may be left on the mask
    # 腐蚀(erode)和膨胀(dilate)的作用:
    # 1. 消除噪声;
    # 2. 分割(isolate)独立的图像元素,以及连接(join)相邻的元素;
    # 3. 寻找图像中的明显的极大值区域或极小值区域
    mask = cv2.erode(mask, None, iterations=2)
    mask = cv2.dilate(mask, None, iterations=2)

    # 寻找轮廓,不同opencv的版本cv2.findContours返回格式有区别,所以调用了一下imutils.grab_contours做了一些兼容性处理
    cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    center = None

    # only proceed if at least one contour was found
    if len(cnts) > 0:
        # find the largest contour in the mask, then use it to compute the minimum enclosing circle
        # and centroid
        c = max(cnts, key=cv2.contourArea)
        ((x, y), radius) = cv2.minEnclosingCircle(c)
        M = cv2.moments(c)
        # 对于01二值化的图像,m00即为轮廓的面积, 一下公式用于计算中心距
        center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))

        # only proceed if the radius meets a minimum size
        if radius > 10:
            # draw the circle and centroid on the frame, then update the list of tracked points
            cv2.circle(frame, (int(x), int(y)), int(radius), (0, 255, 255), 2)
            cv2.circle(frame, center, 5, (0, 0, 255), -1)

        pts.appendleft(center)

    for i in range(1, len(pts)):
        # if either of the tracked points are None, ignore them
        if pts[i - 1] is None or pts[i] is None:
            continue

        # compute the thickness of the line and draw the connecting line
        thickness = int(np.sqrt(args["buffer"] / float(i + 1)) * 2.5)
        cv2.line(frame, pts[i - 1], pts[i], (0, 0, 255), thickness)

    cv2.imshow("Frame", frame)
    key = cv2.waitKey(1) & 0xFF

    if key == ord("q"):
        break

if useCamera:
    vs.stop()
else:
    vs.release()

cv2.destroyAllWindows()

该项目主要使用OpenCV实现,里面重要的地方我都已经加了注释说明,这里再理一下整个流程:

  1. 处理命令行参数。
  2. 定义绿色球在HSV色域空间的颜色范围,后面会根据这个范围进行二值化,从而实现轮廓检测。
  3. 区分一下是直接从摄像头读取的数据还是处理的已经拍好的视频,这里作区分是因为两种情况下,后面有些代码要做不同处理。
  4. 循环处理视频中的帧:

    1. 获取1帧的图像。对于从已有视频读的情况,如果读到的帧为空,则表示处理视频处理完了。
    2. 调整图片大小,主要是缩小一下图片,提高处理速度。
    3. 通过高斯滤波去除掉一些高频噪声,使得重要的数据更加突出。
    4. 将图像从BGR色域转换到HSV色域。
    5. 根据绿色球在HSV色域的颜色范围进行二值化。这样处理之后,绿色就会变成白色,其它都会变成黑色,方便后面提取球的轮廓。后面进行的腐蚀和膨胀操作也是为了消除噪声点(毛刺),更准确的提取小球轮廓。
    6. 提取轮廓。
    7. 后面就是根据提取出来的轮廓,画了一些小球的边沿,以及运动轨迹,就不细述了,代码里面很清楚。

最后,对于没有图像处理基础的人来说,可能不了解其中一些处理的作用,这里我把一些关键步骤处理的效果图放上来,方便你理解(以其中一帧图片为例):

  • resize后的效果:

uY7jEzR.png!web

这一步除了尺寸有变化,其它都和原图一样。

  • 高斯滤波后的效果:

imyIbeY.png!web

滤掉了高频噪音,看着变模糊了。

  • 转换到HSV色域后的效果:

neaURzJ.png!web

  • 二值化后的效果:

Yz26Zvm.png!web

  • 腐蚀和膨胀后的效果:

mQf67rr.png!web

最后,附上代码: code .


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK