鼠标响应函数
cpp
//鼠标响应函数
void on_mouse(int EVENT, int x, int y, int flags, void* userdata)
{
Mat hh;
hh = *(Mat*)userdata;
switch (EVENT)
{
case EVENT_LBUTTONDOWN:
{
vP.x = x;
vP.y = y;
drawMarker(hh, vP, Scalar(255, 255, 255));
//circle(hh, vP, 4, cvScalar(255, 255, 255), -1);
imshow(wName, hh);
return;
}
break;
}
}
drawMarker(hh, vP, Scalar(255, 255, 255));
这个是画一个十字符号 标记一个点
绘制直方图和以前篇幅一样
cpp
//绘制直方图
int drawHist(cv::Mat& histMat, float* srcHist, int bin_width, int bin_heght)
{
histMat.create(bin_heght, 256 * bin_width, CV_8UC3);
histMat = Scalar(255, 255, 255);
float maxVal = *std::max_element(srcHist, srcHist + 256);
for (int i = 0; i < 256; i++) {
Rect binRect;
binRect.x = i * bin_width;
float height_i = (float)bin_heght * srcHist[i] / maxVal;
binRect.height = (int)height_i;
binRect.y = bin_heght - binRect.height;
binRect.width = bin_width;
rectangle(histMat, binRect, CV_RGB(255, 0, 0), -1);
}
return 0;
}
统计视频一个点不受大影响的时候直方图是高斯分布的(灰度)
cpp
int index = grayMat.at<uchar>(vP.y, vP.x);
选取刚才选中的点
cpp
histgram[index]++;
在对应的直方图加1
cpp
drawHist(histMat, histgram, bin_width, bin_heght);
drawMarker(frame, vP, Scalar(255, 255, 255));
这里还要画一个drawmaker因为第二遍就不会调用了
vp要是全局变量
完整代码:
cpp
int main() {
// 验证某一背景像素值呈高斯分布
VideoCapture cap(0);
int cnt = 0;
float histgram[256] = { 0 };
Mat histMat;
int bin_width = 3;
int bin_heght = 100;
while (1)
{
Mat frame;
Mat grayMat;
cap >> frame;
if (cnt == 0)
{
Mat selectMat;
frame.copyTo(selectMat);
imshow(wName, selectMat);
setMouseCallback(wName, on_mouse, &selectMat);
waitKey(0);
destroyAllWindows();
}
cvtColor(frame, grayMat, COLOR_BGR2GRAY);
int index = grayMat.at<uchar>(vP.y, vP.x);
histgram[index]++;
drawHist(histMat, histgram, bin_width, bin_heght);
drawMarker(frame, vP, Scalar(255, 255, 255));
imshow("frame", frame);
imshow("histMat", histMat);
if (waitKey(30) == 27) {
destroyAllWindows();
break;
}
cnt++;
}
return 0;
}
当然还有一些变量需要自己设置全局变量
直接拿原图和新图直接做差分
cpp
VideoCapture cap(0);
int cnt = 0;
Mat frame;
while (1) {
cap>> frame;
cvtColor(frame, frame, COLOR_BGR2GRAY);
if (cnt == 0) {
//第一帧,获得背景图像
frame.copyTo(bgMat);
}
else {
//第二帧开始背景差分
//背景图像和当前图像相减
absdiff(frame, bgMat, subMat);
//差分结果二值化
namedWindow("Result", WINDOW_AUTOSIZE);
//滑动条创建
cv::createTrackbar("threshold", "Result", &sub_threshold, 255, threshold_track);
threshold_track(0, 0);
imshow("frame", frame);
}
if (waitKey(30) == 27) {
destroyAllWindows();
break;
}
cnt++;
}
其中
cpp
absdiff(frame, bgMat, subMat);
如果摄像机是固定的,那么我们可以认为场景(背景)大多数情况下是不变的,而只有前景(被跟踪的目标)会运动,这样就可以建立背景模型。通过比较当前帧和背景模型,就能轻松地跟踪目标运动情况了。这里,最容易想到的比较方式就是当前帧减去背景模型了
将差分的图像二值化 这里创建了滑动条 bar
cpp
void threshold_track(int, void*)//这里就是定义的一个回调函数,里面是canny相关的操作
{
threshold(subMat, bny_subMat, sub_threshold, 255, CV_THRESH_BINARY);
imshow("Result", bny_subMat);
}
运用了高斯差分 因为本身图像的点都符合高斯分布,收光照等等影响,而这些都不能被考虑进移动物
cpp
int nBg = 200;
cap >> frame;
cvtColor(frame, frame, COLOR_BGR2GRAY);
if (cnt <= nBg) {
srcMats.push_back(frame);
if (cnt == 0) {
std::cout << "--- reading frame --- " << std::endl;
}
else {
std::cout << "-";
if (cnt % 50 == 0)std::cout << std::endl;
}
}
这里是前200张帧是为了获取高斯分布
计算图像的平均值和方差(灰度)
cpp
int calcGaussianBackground(std::vector<cv::Mat> srcMats, cv::Mat& meanMat, cv::Mat& varMat)
{
int rows = srcMats[0].rows;
int cols = srcMats[0].cols;
for (int h = 0; h < rows; h++)
{
for (int w = 0; w < cols; w++)
{
int sum = 0;
float var = 0;
//求均值
for (int i = 0; i < srcMats.size(); i++) {
sum += srcMats[i].at<uchar>(h, w);
}
meanMat.at<uchar>(h, w) = (uchar)(sum / srcMats.size());
//求方差
for (int i = 0; i < srcMats.size(); i++) {
var += (float)pow((srcMats[i].at<uchar>(h, w) - meanMat.at<uchar>(h, w)), 2);
}
varMat.at<float>(h, w) = var / srcMats.size();
}
}
return 0;
}
利用平均值和方差来判断是否是入侵背景的前景
cpp
int gaussianThreshold(cv::Mat srcMat, cv::Mat meanMat, cv::Mat varMat, float weight, cv::Mat& dstMat)
{
int rows = srcMat.rows;
int cols = srcMat.cols;
for (int h = 0; h < rows; h++)
{
for (int w = 0; w < cols; w++)
{
int dif = abs(srcMat.at<uchar>(h, w) - meanMat.at<uchar>(h, w));
int th = (int)(weight * varMat.at<float>(h, w));
if (dif > th) {
dstMat.at<uchar>(h, w) = 255;
}
else {
dstMat.at<uchar>(h, w) = 0;
}
}
}
return 0;
}
这里的weight是权重,可以代表差异到什么程度就是前景
完整代码:
cpp
VideoCapture cap(0);
std::vector<cv::Mat> srcMats;
int nBg = 200;
float wVar = 3;
int cnt = 0;
bool calcModel = true;
cv::Mat frame;
cv::Mat meanMat;
cv::Mat varMat;
cv::Mat dstMat;
while (1)
{
cap >> frame;
cvtColor(frame, frame, COLOR_BGR2GRAY);
if (cnt <= nBg) {
srcMats.push_back(frame);
if (cnt == 0) {
std::cout << "--- reading frame --- " << std::endl;
}
else {
std::cout << "-";
if (cnt % 50 == 0)std::cout << std::endl;
}
}
else {
if (calcModel) {
std::cout << std::endl << "calculating background models" << std::endl;
//计算模型
meanMat.create(frame.size(), CV_8UC1);
varMat.create(frame.size(), CV_32FC1);
//调用计算模型函数
calcGaussianBackground(srcMats, meanMat, varMat);
}
calcModel = false;
//背景差分
dstMat.create(frame.size(), CV_8UC1);
//利用均值mat和方差mat,计算差分
gaussianThreshold(frame, meanMat, varMat, wVar, dstMat);
imshow("result", dstMat);
imshow("frame", frame);
}
if (waitKey(30) == 27) {
destroyAllWindows();
break;
}
cnt++;
}
opencv自带的背景差分方式
cpp
// OPENCV的自带背景差分方式
VideoCapture cap(0);
Mat inputFrame, frame, foregroundMask, foreground, background;
int method = 0;
Ptr<BackgroundSubtractor> model;
if (method == 0) {
model = createBackgroundSubtractorKNN();
}
else if (method == 1) {
model = createBackgroundSubtractorMOG2();
}
else {
cout << "Can not create background model using provided method: '" << method << "'" << endl;
}
bool doUpdateModel = true;
bool doSmoothMask = false;
while (1) {
cap >> frame;
model->apply(frame, foregroundMask, doUpdateModel ? -1 : 0);
imshow("image", frame);
if (doSmoothMask)
{
GaussianBlur(foregroundMask, foregroundMask, Size(11, 11), 3.5, 3.5);
threshold(foregroundMask, foregroundMask, 10, 255, THRESH_BINARY);
}
if (foreground.empty())
foreground.create(frame.size(), frame.type());
foreground = Scalar::all(0);
frame.copyTo(foreground, foregroundMask);
imshow("foreground mask", foregroundMask);
imshow("foreground image", foreground);
model->getBackgroundImage(background);
if (!background.empty())
imshow("mean background image", background);
const char key = (char)waitKey(30);
if (key == 27 || key == 'q') // ESC
{
cout << "Exit requested" << endl;
break;
}
else if (key == ' ')
{
doUpdateModel = !doUpdateModel;
cout << "Toggle background update: " << (doUpdateModel ? "ON" : "OFF") << endl;
}
else if (key == 's')
{
doSmoothMask = !doSmoothMask;
cout << "Toggle foreground mask smoothing: " << (doSmoothMask ? "ON" : "OFF") << endl;
}
}
return 0;
}
S是是否平滑 会用高斯滤波来平滑图像
空格是是否更新背景
目前不是太懂这里的代码 希望后续学到这里后会明白