// 该文件是 OpenCV 项目的一部分。 它受此发行版顶层目录和 http://opencv.org/license.html 中的 LICENSE 文件中的许可条款的约束
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/video.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, const char** argv)
{
const String keys = "{c camera | 0 | 使用来自相机的视频流 (device index starting from 0) }"
"{fn file_name | | 使用视频文件作为输入 }"
"{m method | mog2 | 方法:背景减法算法 ('knn', 'mog2')}"
"{h help | | 显示帮助信息}";
CommandLineParser parser(argc, argv, keys);
parser.about("此示例演示背景分割.");
if (parser.has("help"))
{
parser.printMessage();
return 0;
}
int camera = parser.get<int>("camera");
String file = parser.get<String>("file_name");
String method = parser.get<String>("method");
if (!parser.check())//检查是否有解析错误的现象;
{
parser.printErrors();
return 1;
}
VideoCapture cap;
if (file.empty())//文件为空,打开相机
cap.open(camera);
else
{
file = samples::findFileOrKeep(file); //找到文件路径 // ignore gstreamer pipelines
cap.open(file.c_str());//打开文件
}
if (!cap.isOpened())//打开视频流失败
{
cout << "Can not open video stream: '" << (file.empty() ? "<camera>" : file) << "'" << endl;
return 2;
}
Ptr<BackgroundSubtractor> model;//背景提取器
if (method == "knn")
model = createBackgroundSubtractorKNN();//创建KNN背景提取器
else if (method == "mog2")
model = createBackgroundSubtractorMOG2();//创建MOG2背景提取器
if (!model)
{
cout << "无法使用提供的方法创建背景模型: '" << method << "'" << endl;
return 3;
}
cout << "按 <space> 切换后台模型更新" << endl;
cout << "按“s”切换前景蒙版平滑" << endl;
cout << "按 ESC 或 'q' 退出" << endl;
bool doUpdateModel = true;
bool doSmoothMask = false;
Mat inputFrame, frame, foregroundMask, foreground, background;
for (;;)
{
// 准备输入帧
cap >> inputFrame;
if (inputFrame.empty())
{
cout << "完成读取:空帧Finished reading: empty frame" << endl;
break;//结束循环
}
const Size scaledSize(640, 640 * inputFrame.rows / inputFrame.cols);//目标缩放尺寸
resize(inputFrame, frame, scaledSize, 0, 0, INTER_LINEAR);//缩放输入帧图像
// 将图像帧传递给背景模型
model->apply(frame, foregroundMask, doUpdateModel ? -1 : 0);//计算前景掩码
// 显示已处理的帧
imshow("image", frame);
// show foreground image and mask (with optional smoothing) 显示前景图像和mask(可选平滑)
if (doSmoothMask)
{
GaussianBlur(foregroundMask, foregroundMask, Size(11, 11), 3.5, 3.5);//高斯滤波
threshold(foregroundMask, foregroundMask, 10, 255, THRESH_BINARY); // 二值化操作
}
if (foreground.empty())
foreground.create(scaledSize, frame.type());
foreground = Scalar::all(0);//前景置为黑色 cvScalar(255,0,0),实际意思是B=255,当然是蓝色的
frame.copyTo(foreground, foregroundMask);//
imshow("foreground mask", foregroundMask);
imshow("foreground image", foreground);
// show background image
model->getBackgroundImage(background);
if (!background.empty())
imshow("mean background image", background );
// interact with user
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;
}
笔记:
//一、 opencv学习笔记——cv::CommandLineParser函数详解
命令行解析类CommandLineParser
该类的作用主要用于命令行的解析,也就是分解命令行的作用。以前版本没这个类时,如果要运行带参数的.exe,必须在命令行中输入文件路径以及各种参数,并且输入的参数格式要与代码中的if语句判断内容格式一样,很不方便。另外如果想要更改输入格式的话在主函数文件中要相应更改很多地方。现在有了这个类,只需要改keys里面的内容就可以了
在OpenCV源码中,其声明位于头文件 utility中。因此在使用前,一般需要进行头文件包含。
#include <opencv2/core/utility.hpp>
类成员
public:
CommandLineParser (int argc, const char *const argv[], const String &keys);//构造函数
CommandLineParser (const CommandLineParser &parser);//拷贝构造函数
~CommandLineParser ();//析构函数
void about (const String &message);
bool check () const;
template<typename T >
T get (const String &name, bool space_delete=true) const;
template<typename T >
T get (int index, bool space_delete=true) const;
String getPathToApplication () const;
bool has (const String &name) const;
CommandLineParser & operator= (const CommandLineParser &parser);//赋值运算符重载
void printErrors () const;
void printMessage () const;
protected:
void getByIndex (int index, bool space_delete, int type, void *dst) const;
void getByName (const String &name, bool space_delete, int type, void *dst) const;
Impl * impl;
注释的几条函数为常用函数:
1、构造函数------接收命令行输入的指令
2、get-----获得指定的参数的内容
3、has----在get之前可以先检查是否含有此指令
4、check---在使用这些参数之前,检查是否有解析错误的现象;
//二、OpenCV3-Python之MOG2、KNN和GMG背景分割器 BackgroundSubtractor
在上篇博文《OpenCV3-Python简单移动目标跟踪》中介绍了简单移动目标跟踪方法,也提到在实际应用中,由于场景复杂其跟踪效果并不好;因此,本篇介绍常用的几种更有效的跟踪方法——背景分割器。
https://www.yyearth.com/index.php?aid=243
2.1 背景分割器
OpenCV3中常用的背景分割器有三种:Mixture of Gaussians(MOG2)、K-Nearest(KNN)、Geometric Multigid(GMG) 。
OpenCV中提供了一个称为BackgroundSubtractor的类,在分割前景和背景时有两方面的优势:
<1> 专门用来进行视频分析,即:BackgroundSubtractor类会对每帧的环境进行 "学习" ;
<2> 可以计算阴影,通过检测阴影,可排除检测图像的阴影区域,从而更关注实际特征;
根据以上效果对比知:
MOG2算法阴影检测并不完美,但它有助于将目标轮廓按原始形状进行还原;KNN的精确性和阴影检测能力较好,即使是相邻对象也没有在一起检测,运动检测的结果相当精确。
2.2 opencv::BackgroundSubtraction基本原理
https://www.cnblogs.com/osbreak/p/11753366.html
背景消除
BS算法
- 图像分割(GMM – 高斯混合模型)
- 机器学习(KNN –K个最近邻)
BackgroundSubtractor (父类)
- BackgroundSubtractorMOG2
- BackgroundSubtractorKNN
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char**) {
VideoCapture capture;
capture.open("D:/images/video_004.avi");
if (!capture.isOpened()) {
printf("could not find the video file...\n");
return -1;
}
// create windows
Mat frame;
Mat bsmaskMOG2, bsmaskKNN;
namedWindow("input video", CV_WINDOW_AUTOSIZE);
namedWindow("MOG2", CV_WINDOW_AUTOSIZE);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
// MOG2 BS
Ptr<BackgroundSubtractor> pMOG2 = createBackgroundSubtractorMOG2();
// KNN BS
Ptr<BackgroundSubtractor> pKNN = createBackgroundSubtractorKNN();
while (capture.read(frame)) {
imshow("input video", frame);
pMOG2->apply(frame, bsmaskMOG2);
morphologyEx(bsmaskMOG2, bsmaskMOG2, MORPH_OPEN, kernel, Point(-1, -1));
imshow("MOG2", bsmaskMOG2);
pKNN->apply(frame, bsmaskKNN);
morphologyEx(bsmaskKNN, bsmaskKNN, MORPH_OPEN, kernel, Point(-1, -1));
imshow("KNN", bsmaskKNN);
char c = waitKey(100);
if (c == 27) {
break;
}
}
capture.release();
waitKey(0);
return 0;
}
2.3 KNN背景分割器
传统的前景背景分割方法有GrabCut,分水岭算法,当然也包括一些阈值分割的算法。但是这些算法在应用中往往显得鲁棒性较弱,达不到一个好的分割效果。
现代的背景分割算法融入了机器学习的一些方法来提高分类的效果。如KNN,混合高斯(MOG2),Geometric Multigrid。这些算法的基本原理就是对每一帧图像的环境进行学习,从而推断出背景区域。
opencv的BackgroundSubtractor提供了这些现代的背景分割算法。
2.3.1 思想
定义1个KNN背景分割器对象
定义视频对象
while True:
一帧帧读取视频
计算前景掩码
二值化操作
膨胀操作
查找轮廓
轮廓筛选
画出轮廓(在原图像)
显示图像帧,
2.3.2 代码
#-*- coding:utf-8 -*-
import cv2
import numpy as np
# 1.常见一个BackgroundSubtractorKNN接口
bs = cv2.createBackgroundSubtractorKNN(detectShadows=True)
#2.读取视频
camera = cv2.VideoCapture('traffic.flv')
#定义卷积核圆形
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
while True:
ret,frame = camera.read()
#3. apply()函数计算了前景掩码
fgmask = bs.apply(frame)
#4. 获得前景掩码(含有白色值以及阴影的灰色值),通过设定阈值将非白色(244~255)的所有像素都设为0,而不是1;
th = cv2.threshold(fgmask.copy(),244,255,cv2.THRESH_BINARY)[1] #二值化操作
dilated = cv2.dilate(th,kernel,iterations =2) #5.膨胀操作
#cv2.getStructuringElement 构建一个椭圆形的核
#3x3卷积核中有2个1那就设置为1
#6. findContours函数参数说明cv2.RETR_EXTERNAL只检测外轮廓,
# cv2.CHAIN_APPROX_SIMPLE只存储水平,垂直,对角直线的起始点。
image,contours,hier = cv2.findContours(dilated,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #查找轮廓
for c in contours: #从list列表取出每个轮廓
if cv2.contourArea(c) < 1500: #进行轮廓筛选 轮廓面积小于1500
continue
(x,y,w,h) = cv2.boundingRect(c)
cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow("mog",fgmask)
cv2.imshow("thresh",th)
cv2.imshow("detection",frame)
if cv2.waitKey(100) & 0xff == ord("q"):
break
camera.release()
cv2.destroyAllWindows()
# coding:utf-8
import cv2
import numpy as np
#from MyCvUtils import MyCvUtils
#datapath = "D:/imgData/video/"
bs = cv2.createBackgroundSubtractorKNN(detectShadows=True)
camera = cv2.VideoCapture("traffic.flv")
ret, frame = camera.read()
while True:
ret, frame = camera.read()
# 计算前景掩码,包含 前景的白色值 以及 阴影的灰色值
fgmask = bs.apply(frame)
# 前景区域二值化,将非白色(0-244)的非前景区域(包含背景以及阴影)均设为0,前景的白色(244-255)设置为255
th = cv2.threshold(fgmask.copy(), 244, 255, cv2.THRESH_BINARY)[1]
# 前景区域形态学处理
th = cv2.erode(th, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=2)
dilated = cv2.dilate(th, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 3)), iterations=2)
# 绘制前景图像的轮廓矩形
image, contours, hier = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
# 对轮廓设置最小区域,对检测结果降噪
if cv2.contourArea(c) > 1000:
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 0), 2)
cv2.imshow("mog", fgmask)
cv2.imshow("thresh", th)
cv2.imshow("diff", frame & cv2.cvtColor(fgmask, cv2.COLOR_GRAY2BGR))
cv2.imshow("detection", frame)
if (cv2.waitKey(30) & 0xFF) == 27:
break
if (cv2.waitKey(30) & 0xFF) == ord('q'):
break
camera.release()
cv2.destroyAllWindows()
//三、 OpenCV高斯滤波GaussianBlur https://blog.csdn.net/godadream/article/details/81568844
图像处理中,常用的滤波算法有均值滤波、中值滤波以及高斯滤波等。
三种滤波器的对比
滤波器种类 基本原理 特点
均值滤波 使用模板内所有像素的平均值代替模板中心像素灰度值 易收到噪声的干扰,不能完全消除噪声,只能相对减弱噪声
中值滤波 计算模板内所有像素中的中值,并用所计算出来的中值体改模板中心像素的灰度值 对噪声不是那么敏感,能够较好的消除椒盐噪声,但是容易导致图像的不连续性
高斯滤波 对图像邻域内像素进行平滑时,邻域内不同位置的像素被赋予不同的权值 对图像进行平滑的同时,同时能够更多的保留图像的总体灰度分布特征
GaussianBlur函数
函数原型:
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT);
参数详解如下:
src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
ksize,高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数(并不能理解)。或者,它们可以是零的,它们都是由sigma计算而来。
sigmaX,表示高斯核函数在X方向的的标准偏差。
sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
#include "stdafx.h"
#include <opencv2/opencv.hpp>
int main()
{
// 创建两个窗口,分别显示输入和输出的图像
cv::namedWindow("Example2-5_in", cv::WINDOW_AUTOSIZE);
cv::namedWindow("Example2-5_out", cv::WINDOW_AUTOSIZE);
// 读取图像,并用输入的窗口显示输入图像
cv::Mat img = cv::imread("C:\\Users\\Bello\\Desktop\\test.jpg", -1);
cv::imshow("Example2-5_in", img);
// 声明输出矩阵
cv::Mat out;
// 进行平滑操作,可以使用GaussianBlur()、blur()、medianBlur()或bilateralFilter()
// 此处共进行了两次模糊操作
cv::GaussianBlur(img, out, cv::Size(5, 5), 3, 3);
cv::GaussianBlur(out, out, cv::Size(5, 5), 3, 3);
// 在输出窗口显示输出图像
cv::imshow("Example2-5_out", out);
// 等待键盘事件
cv::waitKey(0);
// 关闭窗口并释放相关联的内存空间
cv::destroyAllWindows();
return 0;
}
//四、 [OpenCV实战]43 使用OpenCV进行背景分割
https://blog.csdn.net/LuohenYJ/article/details/108002515
python opencv 图片前景与背景的分割
https://blog.csdn.net/Dawn__Z/article/details/82115160
'''
Extract panel :kmeans聚类
'''
import cv2
import numpy as np
import math
def panelAbstract(srcImage):
# read pic shape
imgHeight,imgWidth = srcImage.shape[:2]
imgHeight = int(imgHeight);imgWidth = int(imgWidth)
# 均值聚类提取前景:二维转一维
imgVec = np.float32(srcImage.reshape((-1,3)))
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,10,1.0)
flags = cv2.KMEANS_RANDOM_CENTERS
ret,label,clusCenter = cv2.kmeans(imgVec,2,None,criteria,10,flags)
clusCenter = np.uint8(clusCenter)
clusResult = clusCenter[label.flatten()]
imgres = clusResult.reshape((srcImage.shape))
imgres = cv2.cvtColor(imgres,cv2.COLOR_BGR2GRAY)
bwThresh = int((np.max(imgres)+np.min(imgres))/2)
_,thresh = cv2.threshold(imgres,bwThresh,255,cv2.THRESH_BINARY_INV)
threshRotate = cv2.merge([thresh,thresh,thresh])
# 确定前景外接矩形
#find contours
imgCnt,contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
minvalx = np.max([imgHeight,imgWidth]);maxvalx = 0
minvaly = np.max([imgHeight,imgWidth]);maxvaly = 0
maxconArea = 0;maxAreaPos = -1
for i in range(len(contours)):
if maxconArea < cv2.contourArea(contours[i]):
maxconArea = cv2.contourArea(contours[i])
maxAreaPos = i
objCont = contours[maxAreaPos]
# 旋转校正前景
rect = cv2.minAreaRect(objCont)
for j in range(len(objCont)):
minvaly = np.min([minvaly,objCont[j][0][0]])
maxvaly = np.max([maxvaly,objCont[j][0][0]])
minvalx = np.min([minvalx,objCont[j][0][1]])
maxvalx = np.max([maxvalx,objCont[j][0][1]])
if rect[2] <=-45:
rotAgl = 90 +rect[2]
else:
rotAgl = rect[2]
if rotAgl == 0:
panelImg = srcImage[minvalx:maxvalx,minvaly:maxvaly,:]
else:
rotCtr = rect[0]
rotCtr = (int(rotCtr[0]),int(rotCtr[1]))
rotMdl = cv2.getRotationMatrix2D(rotCtr,rotAgl,1)
imgHeight,imgWidth = srcImage.shape[:2]
#图像的旋转
dstHeight = math.sqrt(imgWidth *imgWidth + imgHeight*imgHeight)
dstRotimg = cv2.warpAffine(threshRotate,rotMdl,(int(dstHeight),int(dstHeight)))
dstImage = cv2.warpAffine(srcImage,rotMdl,(int(dstHeight),int(dstHeight)))
dstRotimg = cv2.cvtColor(dstRotimg,cv2.COLOR_BGR2GRAY)
_,dstRotBW = cv2.threshold(dstRotimg,127,255,0)
imgCnt,contours, hierarchy = cv2.findContours(dstRotBW,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
maxcntArea = 0;maxAreaPos = -1
for i in range(len(contours)):
if maxcntArea < cv2.contourArea(contours[i]):
maxcntArea = cv2.contourArea(contours[i])
maxAreaPos = i
x,y,w,h = cv2.boundingRect(contours[maxAreaPos])
#提取前景:panel
panelImg = dstImage[int(y):int(y+h),int(x):int(x+w),:]
return panelImg
if __name__=="__main__":
srcImage = cv2.imread('mouse.png')
a=panelAbstract(srcImage)
cv2.imshow('figa',a)
cv2.waitKey(0)
cv2.destroyAllWindows()
//五、 Opencv中copyTo()函数的使用方法
https://www.cnblogs.com/phoenixdsg/p/8420716.html
在Mat矩阵类的成员函数中copyTo(roi , mask)函数是非常有用的一个函数,尤其是后面的mask可以实现蒙版的功能,我们用几个实例来说明它的作用。我们要注意mask的数据类型,必须是CV_8U,且通道数或者是1,或者与roi一致。
首先我们令mask为尺寸与roi一致的1矩阵:
int main()
{
Mat img1=imread("D:/CodeWork/MyImage/baboon.jpg",0);
Mat img2=imread("D:/CodeWork/MyImage/linux.jpg",0);
imshow("initial img1",img1);
Mat roi=img1(Rect(0,0,img2.cols,img2.rows));
//分别令像素值为1、0,以及令mask=img2.clone();观察输出结果
Mat mask(roi.rows,roi.cols,roi.depth(),Scalar(1));
img2.copyTo(roi,mask);
imshow("logan img2",img2);
imshow("after mask img1",img1);
waitKey();
return 0;
}
https://blog.csdn.net/moiraz/article/details/53489399
我们已经知道的是,使用copyTo函数可以得到一个复制的矩阵。
A.copyTo(B);
就可以得到和A一毛一样的矩阵B。当然需要事先声明B。并且两者可以互不相关的做各种操作。
copyTo还有一个重构函数copyTo(B,MASK)。意思是可以得到一个附加掩膜MASK的矩阵B。我们从图像的角度来看这个函数的作用。
首先需要生成一张掩膜MASK,一般情况下这个膜和你需要操作的对象图像一样大。生成方法见下面例子:
Mat MASK(A.rows,A.cols,CV_8UC3,Scalar(0,0,0));//生成一个三通道的彩色掩膜,初始化为黑色。
Mat MASK(A. rows ,A. cols , CV_8UC1 , Scalar ( 0 ));//生成一个灰度的掩膜,初始化为黑色。
Mat MASK=Mat::zeros(A.size( ), CV_8UC3);//生成一个三通道的彩色掩膜,初始化为黑色。需要改成灰度的只需把CV_8UC3改为CV_8UC1。
对一幅图加一个掩膜顾名思义,就是想要盖住图片的某一部分。所以使用A.copyTo(B,MASK)之后得到的是A被MASK掩盖后的图像。因为初始化的掩膜时黑色的,如果直接加上去整个图片都会被掩盖了,所以需要把一部分你不想盖住的位置改成别的颜色。这里就可以用到前面的设置ROI的算法。
Mat mm=mask(Rect(0,0,mask.cols/2,mask.rows));//设置一个只有掩膜一半大小的ROI
mm={Scalar(255,255,255)};//把ROI中的像素值改为白色。
得到的掩膜效果如下图:
对于灰度图像,掩盖后在图像中掩膜中所有像素值对应为0的点变为黑色(被掩盖),其他点(所有非0值)和原来一致。
对于三通道彩色图像,某个通道中所有在掩膜中值为0的点在该通道上的像素值变为0,其他所有非0值保持和原来不变。例如当对绿色和蓝色通道加掩膜时会呈现一种图像被盖了一层红色的效果。(因为蓝色和绿色被掩盖了)
不管是灰度图像还是彩色图像,只有掩膜中像素值为0的点会对图像产生掩盖效果。
|