图像处理之前景检测(四)之自组织背景检测(SOBS)(转载)
一种基于自组织神经网络(
self-Organizing through artificial neural networks
)的背景减除算法(简称
SOBS
算法,全名:
Self-Organizing Background Subtraction
),
用于智能视频监控系统中的目标检测,该算法不仅对光照具有较强的鲁棒性,而且具有较强的实用性。
具体原理参考下文,其中含有算法作者论文链接,比较详细。
Belial_2010 基于自组织背景减除的运动目标检测算法
算法代码实现见:
ZengDong_1991 ackground Modeling and Foreground Detection -- SOBS
这里对于上述代码进行了些许的更改和简单的注释,环境为VS2015+opencv3.2 contrib版。如下:
头文件:BackgroundSubtractorSOBS.h
// Author : zengdong_1991
// Date : 2016-06-13
// HomePage : http://blog.csdn.net/zengdong_1991
// Email : 782083852@qq.com
#pragma once
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include "math.h"
//! defines the parameters
#define WEIGHTS_N 3 //weight vertor 大小 = N^2
#define FRAME_NUM_K 45
#define EPSILON1 1.0
#define EPSILON2 0.008
#define PARAM_C1 1.0
#define PARAM_C2 0.05
#define MAX_W 4.0
#define ALPHA_1 (PARAM_C1 / MAX_W)
#define ALPHA_2 (PARAM_C2 / MAX_W)
#define GAMA 0.7
#define BEATA 1.0
#define TAU_S 40
#define TAU_H 48
static const double m_weightW[WEIGHTS_N*WEIGHTS_N] = { 1.0, 2.0, 1.0, 2.0, 4.0, 2.0, 1.0, 2.0, 1.0 };//高斯权重的选择
class BackgroundSubtractorSOBS {
public:
//! full constructor
BackgroundSubtractorSOBS(
int trainFrameNum = FRAME_NUM_K,
double epsilon1 = EPSILON1,
double epsilon2 = EPSILON2,
double c1 = PARAM_C1,
double c2 = PARAM_C2,
double gama = GAMA,
double beta = BEATA,
double tauS = TAU_S,
double tauH = TAU_H,
double maxW = MAX_W);
//! default destructor
virtual ~BackgroundSubtractorSOBS();
//!
virtual void initialize(const cv::Mat& oInitImg, const cv::Mat& oROI);
//!
virtual void operator()(cv::InputArray image, cv::OutputArray fgmask, double learningRateOverride = 0);
public:
int m_trainFrameNum;
double m_epsilon1;
double m_epsilon2;
double m_c1;
double m_c2;
double m_maxW;
double m_alpha1;
double m_alpha2;
double m_gama;
double m_beta;
double m_tauS;
double m_tauH;
cv::Mat m_backgroundModel; //背景模型: n*rows, n*cols
size_t m_frameNum = 0;
cv::Mat m_ImgHSV;
cv::Mat m_oROI;
//! input image size
cv::Size m_oImgSize;
//! input image channel size
size_t m_nImgChannels;
size_t m_height;
size_t m_width;
size_t m_pixelNum;
//! input image type
int m_nImgType;
bool findTheMatchVector(const cv::Vec3b& piexlVal, const size_t x, const size_t y, size_t& offSet_x, size_t& offSet_y, double eta); //true 匹配,false没有匹配
void updateBackground(const cv::Vec3b& piexlVal, const size_t x, const size_t y, size_t& offSet_x, size_t& offSet_y, double alphaT);
double getDistance(const cv::Vec3b& curr, const cv::Vec3b& bg);
};
算法函数主体:BackgroundSubtractorSOBS.cpp
// Author : zengdong_1991
// Date : 2016-06-13
// HomePage : http://blog.csdn.net/zengdong_1991
// Email : 782083852@qq.com
#include "BackgroundSubtractorSOBS.h"
#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
BackgroundSubtractorSOBS::BackgroundSubtractorSOBS(int trainFrameNum,
double epsilon1,
double epsilon2,
double c1,
double c2,
double gama,
double beta,
double tauS,
double tauH,
double maxW)
: m_trainFrameNum(trainFrameNum)
, m_epsilon1(epsilon1)
, m_epsilon2(epsilon2)
, m_c1(c1)
, m_c2(c2)
, m_maxW(maxW)
, m_gama(gama)
, m_beta(beta)
, m_tauS(tauS)
, m_tauH(tauH)
{
m_alpha1 = m_c1 / m_maxW;
m_alpha2 = m_c2 / m_maxW;
std::cout << "#########################Current Parameters Begin###################" << std::endl;
std::cout << "m_trainFrameNum = " << m_trainFrameNum << std::endl;
std::cout << "m_epsilon1 = " << m_epsilon1 << std::endl;
std::cout << "m_epsilon2 = " << m_epsilon2 << std::endl;
std::cout << "m_c1 = " << m_c1 << std::endl;
std::cout << "m_c2 = " << m_c2 << std::endl;
std::cout << "m_maxW = " << m_maxW << std::endl;
std::cout << "m_alpha1 = " << m_alpha1 << std::endl;
std::cout << "m_alpha2 = " << m_alpha2 << std::endl;
std::cout << "m_gama = " << m_gama << std::endl;
std::cout << "m_beta = " << m_beta << std::endl;
std::cout << "m_tauS = " << m_tauS << std::endl;
std::cout << "m_tauH = " << m_tauH << std::endl;
std::cout << "#########################Current Parameters End###################" << std::endl;
}
BackgroundSubtractorSOBS::~BackgroundSubtractorSOBS() {
}
void BackgroundSubtractorSOBS::initialize(const cv::Mat& oInitImg, const cv::Mat& oROI)//初始化
{
// == init
CV_Assert(!oInitImg.empty() && oInitImg.cols > 0 && oInitImg.rows > 0);
CV_Assert(oInitImg.isContinuous());
CV_Assert(oInitImg.type() == CV_8UC3); //必须是彩色图像
if (oInitImg.type() == CV_8UC3)
{
std::vector<cv::Mat> voInitImgChannels;
cv::split(oInitImg, voInitImgChannels);
if (!cv::countNonZero((voInitImgChannels[0] != voInitImgChannels[1]) | (voInitImgChannels[2] != voInitImgChannels[1])))
std::cout << std::endl << "\tBackgroundSubtractorSuBSENSE : Warning, grayscale images should always be passed in CV_8UC1 format for optimal performance." << std::endl;
}
m_oImgSize = oInitImg.size();
m_nImgType = oInitImg.type();
m_nImgChannels = oInitImg.channels();
m_height = oInitImg.rows;
m_width = oInitImg.cols;
m_pixelNum = m_height * m_width;
m_frameNum = 1;
//将第一帧转换到 HSV 空间
cv::Mat hsv;
cv::cvtColor(oInitImg, hsv, CV_BGR2HSV); //H范围是 【0-->180】
//根据hsv初始化背景模型 大小为 (n*rows, n*cols)
m_backgroundModel.create(cv::Size(WEIGHTS_N*m_width, WEIGHTS_N*m_height), CV_8UC3);
for (size_t x = 0; x < m_height; x++)
{
for (size_t y = 0; y < m_width; y++)
{
cv::Vec3b val = hsv.at<cv::Vec3b>(x, y);
for (size_t i = WEIGHTS_N*x; i < WEIGHTS_N*(x + 1); i++)
{
for (size_t j = WEIGHTS_N*y; j < WEIGHTS_N*(y + 1); j++)
{
m_backgroundModel.at<cv::Vec3b>(i, j) = val;
}
}
}
}
//cv::Mat debugMat = m_backgroundModel;
}
void BackgroundSubtractorSOBS::operator()(cv::InputArray _image, cv::OutputArray _fgmask, double learningRateOverride)//对于后续图像进行对比操作,区分前景背景并颜色标注
{
// == process
cv::Mat oInputImg = _image.getMat();
CV_Assert(oInputImg.type() == m_nImgType && oInputImg.size() == m_oImgSize);
CV_Assert(oInputImg.isContinuous());
_fgmask.create(m_oImgSize, CV_8UC1);
cv::Mat oCurrFGMask = _fgmask.getMat(); //外界 mask 矩阵
oCurrFGMask = cv::Scalar_<uchar>(0);
//转换到HSV空间:
cv::cvtColor(oInputImg, m_ImgHSV, CV_BGR2HSV);
//cv::Mat debugMat = m_ImgHSV;
m_frameNum++;
size_t offSet_x = 0, offSet_y = 0; //匹配偏移量
double alphaT = m_alpha1 - (m_frameNum*(m_alpha1 - m_alpha2) / m_trainFrameNum); //公式(3)(4) a(T)
for (size_t x = 0; x < m_height; x++)
{
for (size_t y = 0; y < m_width; y++)
{
cv::Vec3b val = m_ImgHSV.at<cv::Vec3b>(x, y);
//calibration phase 执行 step 2--> 6
if (m_frameNum <= m_trainFrameNum)
{
//查找匹配
if (findTheMatchVector(val, x, y, offSet_x, offSet_y, m_epsilon1))
{
updateBackground(val, x, y, offSet_x, offSet_y, alphaT);
oCurrFGMask.at<uchar>(x, y) = 0;
}
else
{
oCurrFGMask.at<uchar>(x, y) = 255;
}
}
//on line phase 执行 step 2-->10
else
{
if (findTheMatchVector(val, x, y, offSet_x, offSet_y, m_epsilon2))
{
updateBackground(val, x, y, offSet_x, offSet_y, m_alpha2);
oCurrFGMask.at<uchar>(x, y) = 0;
}
/*else if (isShadow(val, x, y, offSet_x, offSet_y))
{
oCurrFGMask.at<uchar>(x, y) = 0;
}*/
else
{
oCurrFGMask.at<uchar>(x, y) = 255;
}
}
}
}
}
void BackgroundSubtractorSOBS::updateBackground(const cv::Vec3b& piexlVal, const size_t x, const size_t y, size_t& offSet_x, size_t& offSet_y, double alphaT)//背景更新
{
// cv::Vec3b* ptrVec = m_backgroundModel.ptr<cv::Vec3b>(0);
int pad = (WEIGHTS_N - 1) / 2;
//当前匹配像素的坐标是 (nx+offset_x, ny+offset_y), 更新其周围的 N*N像素,如下:
for (int i = -pad; i <= pad; i++)
{
int coordinateX = WEIGHTS_N*x + offSet_x + i;
if (coordinateX < 0 || coordinateX >= WEIGHTS_N*m_height) //边界保护
continue;
for (int j = -pad; j <= pad; j++)
{
int coordinateY = WEIGHTS_N*y + offSet_y + j;
if (coordinateY < 0 || coordinateY >= WEIGHTS_N*m_width) //边界保护
continue;
cv::Vec3b& pixelValBg = m_backgroundModel.at<cv::Vec3b>(coordinateX, coordinateY);
double alpha = alphaT * m_weightW[(i + pad)*WEIGHTS_N + j + pad]; //【未解决】 w 怎么归一化...现在是【1,2,1;...】
/*防止损失精度还是不这么做好了*///【未解决】 测试以下是否相等...
pixelValBg = (1 - alpha)*pixelValBg + alpha*piexlVal;
//cv::Vec3b At_1 = (1 - alpha)*pixelValBg;
//cv::Vec3b Pt = alpha * piexlVal;
//pixelValBg = At_1 + Pt;
//cv::Vec3b test = pixelValBg;
int a = 0;
}
}
}
bool BackgroundSubtractorSOBS::findTheMatchVector(const cv::Vec3b& piexlVal, const size_t x, const size_t y, size_t& offSet_x, size_t& offSet_y, double eta)//判断对应点是否符合属于背景条件
{
cv::Mat temp = m_backgroundModel;
//9个像素值...
for (size_t i = 0; i < WEIGHTS_N; i++)
{
for (size_t j = 0; j < WEIGHTS_N; j++)
{
cv::Vec3b piexlValBg = m_backgroundModel.at<cv::Vec3b>(WEIGHTS_N*x + i, WEIGHTS_N*y + j);
//当前像素与背景像素距离小于阈值
double dist = getDistance(piexlVal, piexlValBg);
if (dist < eta)
{
offSet_x = i;
offSet_y = j;
return true;
}
}
}
return false;
}
double BackgroundSubtractorSOBS::getDistance(const cv::Vec3b& curr, const cv::Vec3b& bg)//求取HSV Color Hexcone距离
{
//opencv BGR2HSV中的H分量范围属于【0-->180】,将其隐射回【0-->360°(2*pi)】
double hRatio = 2 * CV_PI / 180.0;
double sRatio = 1. / 255.;
double vRatio = 1. / 255.;
double h1 = (double)curr[0] * hRatio;
double s1 = (double)curr[1] * sRatio;
double v1 = (double)curr[2] * vRatio;
double h2 = (double)bg[0] * hRatio;
double s2 = (double)bg[1] * sRatio;
double v2 = (double)bg[2] * vRatio;
double distance = (v1*s1*cos(h1) - v2*s2*cos(h2))*(v1*s1*cos(h1) - v2*s2*cos(h2)) +
(v1*s1*sin(h1) - v2*s2*sin(h2))*(v1*s1*sin(h1) - v2*s2*sin(h2)) +
(v1 - v2)*(v1 - v2);
return (distance);
}
主函数:main.cpp
// Author : zengdong_1991
// Date : 2016-06-13
// HomePage : http://blog.csdn.net/zengdong_1991
// Email : 782083852@qq.com
#include <iostream>
#include<opencv.hpp>
#include "BackgroundSubtractorSOBS.h"
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
VideoCapture cap("E:\\素材\\2.mp4");
Mat frame;
cap >> frame;
BackgroundSubtractorSOBS bgs;
int key = 0;
bool initialized = false;
cv::Mat img_mask;
cv::Mat img_bkgmodel;
while (key != 'q')
{
cap>>frame ;
if (frame.empty() == 1) {
std::cerr << "Cannot open video!" << std::endl;
return 1;
}
cv::Mat img_input(frame);
if (!initialized)
{
bgs.initialize(img_input, cv::Mat());
initialized = true;
continue;
}
bgs(img_input, img_mask);
//cv::medianBlur(img_mask, img_mask, 5);
if (!img_mask.empty())
{
//cv::medianBlur(img_mask, img_mask, 5);
cv::imshow("output", img_mask);
}
cv::imshow("input", img_input);
cv::waitKey(1);
// key = cvWaitKey(1);
}
//cvDestroyAllWindows();
//cvReleaseCapture(&capture);
return 0;
}