[视觉实战案例]Qt+OpenCV实现USB摄像头监测移动物体并录制视频功能(帧差法)

樊建
• 阅读 1853

1、背景介绍

最近手边的零食总是莫名其妙的减少,为了抓到一个元凶来帮我续零食,就想着使用手边的usb摄像头来实现一个动态物体监测和保存视频的功能,不过这里使用最简单的帧差法来实现物体的运动监测。

2、使用OpenCV的帧差法实现运动物体监测

  • 开发环境
    Qt5.9 + OpenCV
  • 硬件
    Logitech摄像头

    2.1 帧差法介绍

    运动物体图像在相邻两帧间差别较大,两帧差值后进行简单的图像处理,较容易判断是否存在物体移动,类似于剪纸动画,本例中使用帧差后判断阈值分割后的面积来确定是否存在物体运动。帧差法用前一帧图像作为当前帧的背景模型具有较好的实时性,其背景不积累,且更新速度快、算法简单、计算量小。算法的不足在于对环境噪声较为敏感,阈值的选择相当关键,选择过低不足以抑制图像中的噪声,过高则忽略了图像中有用的变化。对于比较大的、颜色一致的运动目标,有可能在目标内部产生空洞,无法完整地提取运动目标。

    2.2 帧差法部分实现代码

    将当前帧图像和上一帧图像进行灰度化,然后高斯滤波后做图像差值,选定合适的二值化阈值分割,最后对分割处理的区域面积进行判定。

          Mat grayframePre,frameDet;
          Mat frameNow,grayframeNow;
          cvtColor(matFrame,grayframeNow,COLOR_RGB2GRAY);
          cvtColor(framePre,grayframePre,COLOR_RGB2GRAY);
          GaussianBlur(grayframeNow,grayframeNow,Size(21,21),0,0);
          GaussianBlur(grayframePre,grayframePre,Size(21,21),0,0);
          absdiff(grayframeNow,grayframePre,frameDet);
          framePre = matFrame;
          threshold(frameDet,frameDet,20,255,THRESH_BINARY);
          Mat element = getStructuringElement(0,Size(3,3));
          vector<vector<Point>> contours;
          dilate(frameDet,frameDet,element);
          findContours(frameDet,contours,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
          qDebug()<<"Num"<<contours.size();
          QString SavePath = "D:/ImgPath/" + QString::number(VideoNum) + "_track.avi";
          if(contours.size()==0)
          {
              if(writer.isOpened())
              {
                  writer.release();
              }
              if(isSaveFrame)
              {
                  isSaveFrame = false;
                  VideoNum++;
              }
          }
          else
          {
              for(int i=0;i<contours.size();i++)
              {
                  double area = contourArea(contours[i]);
                  if(area < 100)continue;
                  else
                  {
                      qDebug()<<"有物体运动!";
                      if(!isSaveFrame)
                      {
                          int fourcc = writer.fourcc('M', 'J', 'P', 'G');
                          writer.open(SavePath.toStdString(),fourcc,10,Size(frameWidth,frameHeight),true);
                          isSaveFrame = true;
                      }
                      else
                      {
                          writer.write(matFrame);
                      }
                      break;
                  }
              }
          }
      }
      else
      {
          framePre = matFrame;
      }

    3、在Qt平台下使用opencv对运动物体进行监测

    widget.h
    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QWidget>
    #include "opencv2/opencv.hpp"
    #include <QTimer>
    
    using namespace cv;
    
    namespace Ui {
    class Widget;
    }
    
    class Widget : public QWidget
    {
      Q_OBJECT
    
    public:
      explicit Widget(QWidget *parent = 0);
      ~Widget();
    
    private slots:
      void on_btnOpenVedio_clicked();
      void on_btnQuit_clicked();
      void readFrame();
    
      void on_ckb_Track_clicked(bool checked);
    
    private:
      Ui::Widget *ui;
    
      bool openCam;
      bool isTrack=false;
      bool isSaveFrame = false;
      QTimer *timer;
      VideoCapture *cap;
      Mat framePre;
      int fps,frameWidth,frameHeight;
      VideoWriter writer;
    
      int VideoNum = 0;
    
    
      //Mat转换QImage
      QImage cvMat2QImage(const cv::Mat& mat);
    };
    
    #endif // WIDGET_H
    widget.cpp
    #pragma execution_character_set("utf-8")
    #include "widget.h"
    #include "ui_widget.h"
    #include <iostream>
    #include <QDebug>
    using namespace std;
    
    Widget::Widget(QWidget *parent) :
      QWidget(parent),
      ui(new Ui::Widget)
    {
      ui->setupUi(this);
      timer = new QTimer(this);
      timer->stop();
      connect(timer,SIGNAL(timeout()),this,SLOT(readFrame()));
      openCam = true;
    
      cap = new VideoCapture(0);
      frameWidth = cap->get(CAP_PROP_FRAME_WIDTH);
      frameHeight = cap->get(CAP_PROP_FRAME_HEIGHT);
      fps = cap->get(CAP_PROP_FPS);
      qDebug()<<"width"<<frameWidth<<frameHeight<<fps;
    }
    
    Widget::~Widget()
    {
      delete ui;
    }
    
    void Widget::on_btnOpenVedio_clicked()
    {
      if(openCam)
      {
          ui->btnOpenVedio->setText("关闭摄像头");
          timer->start(30);
      }
      else {
          ui->btnOpenVedio->setText("打开摄像头");
          timer->stop();
      }
      openCam = !openCam;
    }
    
    
    QImage Widget::cvMat2QImage(const cv::Mat &mat)
    {
      switch ( mat.type() )
      {
      // 8-bit  4 channel
      case CV_8UC4:
      {
          QImage image( (const uchar*)mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_RGB32 );
          return image;
      }
    
          // 8-bit  3 channel
      case CV_8UC3:
      {
          QImage image( (const uchar*)mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_RGB888 );
          return image.rgbSwapped();
      }
    
          // 8-bit  1 channel
      case CV_8UC1:
      {
          static QVector<QRgb>  sColorTable;
          // only create our color table once
          if ( sColorTable.isEmpty() )
          {
              sColorTable.resize( 256 );
              for ( int i = 0; i < 256; ++i )
              {
                  sColorTable[i] = qRgb( i, i, i );
              }
          }
          QImage image( (const uchar*)mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_Indexed8 );
          image.setColorTable( sColorTable );
          return image;
      }
    
      default:
          qDebug("Image format is not supported: depth=%d and %d channels\n", mat.depth(), mat.channels());
          qWarning() << "cvMatToQImage - cv::Mat image type not handled in switch:" << mat.type();
          break;
      }
    
      return QImage();
    }
    
    
    void Widget::on_btnQuit_clicked()
    {
      timer->stop();
      cap->release();
      close();
    }
    
    void Widget::readFrame()
    {
      Mat matFrame;
      cap->read(matFrame);
      if(isTrack)
      {
          Mat grayframePre,frameDet;
          Mat frameNow,grayframeNow;
          cvtColor(matFrame,grayframeNow,COLOR_RGB2GRAY);
          cvtColor(framePre,grayframePre,COLOR_RGB2GRAY);
          GaussianBlur(grayframeNow,grayframeNow,Size(21,21),0,0);
          GaussianBlur(grayframePre,grayframePre,Size(21,21),0,0);
          absdiff(grayframeNow,grayframePre,frameDet);
          framePre = matFrame;
          threshold(frameDet,frameDet,20,255,THRESH_BINARY);
          Mat element = getStructuringElement(0,Size(3,3));
          vector<vector<Point>> contours;
          dilate(frameDet,frameDet,element);
          findContours(frameDet,contours,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
          qDebug()<<"Num"<<contours.size();
          QString SavePath = "D:/ImgPath/" + QString::number(VideoNum) + "_track.avi";
          if(contours.size()==0)
          {
              if(writer.isOpened())
              {
                  writer.release();
              }
    
              if(isSaveFrame)
              {
                  isSaveFrame = false;
                  VideoNum++;
              }
          }
          else
          {
              for(int i=0;i<contours.size();i++)
              {
                  double area = contourArea(contours[i]);
                  if(area < 100)continue;
                  else
                  {
                      qDebug()<<"有物体运动!";
                      if(!isSaveFrame)
                      {
                          int fourcc = writer.fourcc('M', 'J', 'P', 'G');
                          writer.open(SavePath.toStdString(),fourcc,10,Size(frameWidth,frameHeight),true);
                          isSaveFrame = true;
                      }
                      else
                      {
                          writer.write(matFrame);
                      }
                      break;
                  }
              }
          }
      }
      else
      {
          framePre = matFrame;
      }
      QImage Qimg = cvMat2QImage(matFrame);
      ui->picshow->setPixmap(QPixmap::fromImage(Qimg));
    
    }
    
    void Widget::on_ckb_Track_clicked(bool checked)
    {
      if(checked)
      {
          isTrack = true;
      }
      else {
          isTrack = false;
      }
    
    }

    4、界面效果展示

    [视觉实战案例]Qt+OpenCV实现USB摄像头监测移动物体并录制视频功能(帧差法)
    打开摄像头后,可以进行采集视频操作,勾选“打开追踪”,程序会调用帧差算法判定是否有运动物体,如果有物体运动,就保存运动时的视频。
    [视觉实战案例]Qt+OpenCV实现USB摄像头监测移动物体并录制视频功能(帧差法)

5、总结

首先,两帧差是比较基础的检测运动物体的方法,虽然其运算速度快,但其无法过滤光照或微小抖动的干扰,而且运动目标会出现“重影”导致出现内部空洞。三帧差法是在相邻帧差法基础上改进的算法,在一定程度上优化了运动物体双边,粗轮廓的现象,相比之下,三帧差法比相邻帧差法更适用于物体移动速度较快的情况,比如道路上车辆的智能监控。

点赞
收藏
评论区
推荐文章
红橙Darren 红橙Darren
3年前
NDK开发前奏 - 实现支付宝人脸识别功能
1.基于AndroidStudio的opencv配置与使用先推荐一本书《计算机视觉算法与应用》,相信用过OpenCV的哥们都知道这是用来干啥的,这里我就不再啰嗦。只说一下他的应用领域:人机互动、物体识别、图像分割、人脸识别、动作识别、运动跟踪、机器人、运动分析、机器视觉、结构分析、汽车安全驾驶等等。这次我们主要用它来做人脸识别,注意人脸
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Linux下V4L2捕捉画面+H264压缩视频+帧缓冲显示视频————结合三个部分工作
前面三篇文章分别介绍了视频捕获、h264视频压缩、帧缓冲显示的实现,现在将他们结合起来摄像头采集到的数据,需要交给视频压缩线程、显示线程使用,那么我采用的方法是使用队列及链表来实现:1.摄像头采集到数据后,分别放入两个处理线程队列中,并将相关信息放入链表中2.两个线程处理完成数据后,调用回调函数,从链表里找到对应的节点,然后释
Wesley13 Wesley13
3年前
unity 无限循环2D游戏原理与实践
一,目录二,实现原理1,无限循环游戏都有一个特点,就是动态产生和销毁游戏场景。在unity中,动态克隆一个物体是使用Instantiate()来产生一个物体,动态的放置在场景中。销毁一个物体使用Destroy()。2,Instantiate()是一个比较消耗资源的方法,在游戏运作中大量克隆和销毁物体,将降低游戏性能,甚至卡壳。在实际开发中,使用的
Stella981 Stella981
3年前
Python骚操作:利用Python获取摄像头并实时控制人脸!
实现流程从摄像头获取视频流,并转换为一帧一帧的图像,然后将图像信息传递给opencv这个工具库处理,返回灰度图像(就像你使用本地静态图片一样)程序启动后,根据监听器信息,使用一个while循环,不断的加载视频图像,然后返回给opencv工具呈现图像信息。创建一个键盘事件监听,按下"d"键,则开始执行面部匹配,并进行面具加
Wesley13 Wesley13
3年前
Unity中鼠标拖动物体移动的算法
鼠标拖动物移动在游戏设计上用的非常多,像飞机射击游戏,我来分享下我的思路:  我的思路: 因为鼠标的屏幕坐标是二维的,物体是世界坐标,三维的。首先将鼠标的屏幕坐标转换为三维的世界坐标,这样为了计算鼠标与物体位置之间的距离,再将移动后的鼠标的屏幕坐标转换为世界坐标,加上之前的距离量,将和值赋给物体坐标,就可以实现了。C算法实现:IEnumera
绣鸾 绣鸾
1年前
After Effects 2024(v24.0.2) for Mac
是一款由Adobe公司开发的专业的视频特效和动态图形设计软件,它可以帮助用户创建各种令人惊叹的视觉效果,例如粒子系统、合成特效、绿屏抠像等。该软件支持动画制作,包括关键帧动画、形状动画、运动跟踪等工具,可以创建复杂的动态动画和运动图形。在视频合成和编辑方面
视频卡顿受哪些因素影响?
卡顿指通过接收的视频帧的时间间隔来判定,帧间距超过时间600ms为卡,上限为6000ms。卡顿受到以下因素的影响:发送端上行网络差丢包高如果发送端用户的网络较差,会导致视频发送丢包高,发送码率下降,接收端视频体验卡顿。发送端的视频采集帧率不稳定,接收端看到
美凌格栋栋酱 美凌格栋栋酱
4个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
流浪剑客 流浪剑客
1年前
Mac专业的视频特效处理工具:After Effects 2024 for Mac中文版
是一款由Adobe公司开发的专业的视频特效和动态图形设计软件,它可以帮助用户创建各种令人惊叹的视觉效果,例如粒子系统、合成特效、绿屏抠像等。该软件支持动画制作,包括关键帧动画、形状动画、运动跟踪等工具,可以创建复杂的动态动画和运动图形。在视频合成和编辑方面
绣鸾 绣鸾
1年前
视频特效制作After Effects 2024 for Mac
是一款由Adobe公司开发的专业的视频特效和动态图形设计软件,它可以帮助用户创建各种令人惊叹的视觉效果,例如粒子系统、合成特效、绿屏抠像等。该软件支持动画制作,包括关键帧动画、形状动画、运动跟踪等工具,可以创建复杂的动态动画和运动图形。在视频合成和编辑方面