机器视觉作业--背景差分
·
鼠标选择多边形区域并显示
代码:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
vector<Point> mousePoints;
Point points;
void on_mouse(int event, int x, int y, int flags, void* userdata)
{
Mat& image = *(Mat*)userdata;
if (event == EVENT_LBUTTONDOWN)
{
points = Point(x, y);
mousePoints.push_back(points);
circle(image, points, 4, Scalar(255, 255, 255), -1);
imshow("Select Area", image);
}
}
int selectPolygon(const Mat& srcMat, Mat& dstMat)
{
vector<vector<Point>> contours;
Mat selectMat;
Mat mask = Mat::zeros(srcMat.size(), CV_8UC1);
if (!srcMat.empty())
{
srcMat.copyTo(selectMat);
srcMat.copyTo(dstMat);
}
else
{
cout << "Failed to read image!" << endl;
return -1;
}
namedWindow("Select Area");
setMouseCallback("Select Area", on_mouse, &selectMat);
imshow("Select Area", selectMat);
waitKey(0);
destroyWindow("Select Area");
contours.push_back(mousePoints);
if (contours[0].size() < 3)
{
cout << "Failed to form polygon!" << endl;
return -1;
}
drawContours(mask, contours, 0, Scalar(255), -1);
Mat maskedImage;
srcMat.copyTo(maskedImage, mask);
maskedImage.copyTo(dstMat);
return 0;
}
int main()
{
Mat srcMat = imread("D:/C++/coin.png");
if (srcMat.empty())
{
cout << "Failed to read image!" << endl;
return -1;
}
Mat dstMat;
if (selectPolygon(srcMat, dstMat) != 0)
{
cout << "Failed to select polygon!" << endl;
return -1;
}
imshow("Original", srcMat);
imshow("Selected Area", dstMat);
waitKey(0);
return 0;
}
效果:
os:各种多边形区域都可。
背景减法:
代码:
#include<iostream>
#include<opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>
using namespace std;
using namespace cv;
//函数声明
int calcGaussianBackground(vector<Mat> srcMats, Mat& meanMat, Mat& varMat);
int gaussianThreshold(Mat srcMat, Mat meanMat, Mat varMat, float weight, Mat& dstMat);
int main()
{
//cv:utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);//不再输出日志
//或
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_ERROR);//只输出错误日志
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "Unable to open cap!" << endl;
return -1;
}
//用来计算背景模型的图像
vector<Mat> srcMats; //向量
//参数设置
int nBg = 200; //用来建立背景模型的数量
float wVar = 3; //方差权重3δ
int cnt = 0; //视频帧
Mat frame, meanMat, varMat, dstMat;
while (true)
{
cap >> frame;
cvtColor(frame, frame, COLOR_BGR2GRAY);
if (frame.empty()) {
cout << "Unable to read frame!" << endl;
return -1;
}
//前面的nBg帧,计算背景
if (cnt < nBg)
{
//push_back在vector类中作用为在vector尾部加入一个数据
srcMats.push_back(frame); //将每一帧push中srcMats向量中
if (cnt == 0)
{
cout << "reading frame" << endl;
}
}
else if (cnt == nBg) //200帧读完
{
//计算模型
meanMat.create(frame.size(), CV_8UC1);
varMat.create(frame.size(), CV_32FC1); //float使其更精确
//varMat.create(frame.size(), CV_8UC1);
cout << "calculating background models" << endl;
calcGaussianBackground(srcMats, meanMat, varMat);
}
else
{
//背景差分
dstMat.create(frame.size(), CV_8UC1);
//利用均值mat和方差mat计算背景差分
gaussianThreshold(frame, meanMat, varMat, wVar, dstMat);
imshow("result", dstMat);
imshow("frame", frame);
//显示图片,延时30ms,必须要加waitKey(),否则无法显示图像
//等待键盘相应,按下ESC键退出
if (waitKey(30) == 27) {
destroyAllWindows();
break;
}
}
cnt++;
}
return 0;
}
//背景计算模型
int calcGaussianBackground(vector<Mat> srcMats, Mat& meanMat, Mat& varMat)
{
int rows = srcMats[0].rows;
int cols = srcMats[0].cols;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
int sum = 0;
float var = 0;
//求均值,srcMats.size()为frame数量
for (int a = 0; a < srcMats.size(); a++)
{
sum += srcMats[a].at<uchar>(i, j);
}
meanMat.at<uchar>(i, j) = sum / srcMats.size();
//求方差,对高斯分布来说方差越大,数据分布不集中
for (int a = 0; a < srcMats.size(); a++)
{
var += pow(srcMats[a].at<uchar>(i, j) - meanMat.at<uchar>(i, j), 2);
}
varMat.at<float>(i, j) = var / srcMats.size();
//varMat.at<uchar>(i, j) = var / srcMats.size();
}
}
return 0;
}
//高斯分布二值化:输入图像,均值,方差,方差权重,输出图像
int gaussianThreshold(Mat srcMat, Mat meanMat, Mat varMat, float weight, Mat& dstMat)
{
int srcI;
int meanI;
int dstI;
int rows = srcMat.rows;
int cols = srcMat.cols;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
srcI = srcMat.at<uchar>(i, j);
meanI = meanMat.at<uchar>(i, j);
int dif = abs(srcI - meanI); //差值
int th = weight * varMat.at<float>(i, j); //对每个像素得到不同的阈值
//int th = weight * varMat.at<uchar>(i, j);
if (dif > th)
{
dstMat.at<uchar>(i, j) = 255;
}
else
{
dstMat.at<uchar>(i, j) = 0;
}
}
}
return 0;
}
效果图:
背景差分:
老师给的源码出来的效果,会有多个滑动按钮,而且result窗口频繁闪烁,都已修改。
对于 Result 窗口闪烁的问题,可能是由于在每一帧中都创建了一个名为 Result 的窗口,并且在每次迭代时都使用 imshow("Result", twoMat) 来显示二值化的结果。这会导致窗口频繁地关闭和重新打开,从而导致闪烁效果。解决方法是将窗口的创建和显示操作移至主循环之外,只创建一次窗口并在需要时更新窗口内容。
至于多个滑动条的问题,可能是由于在每一帧中都创建了滑动条,并且没有正确删除之前创建的滑动条。解决方法是将滑动条的创建操作移至主循环之外,并在主循环中仅更新滑动条的值,而不是重复创建滑动条。
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>
using namespace std;
using namespace cv;
int sub_threshold = 0;
Mat frame;
Mat bgMat, diffMat, twoMat;
void threshold_track(int, void*)
{
threshold(diffMat, twoMat, sub_threshold, 255, THRESH_BINARY);
imshow("Result", twoMat);
}
int main()
{
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_ERROR); // 只输出错误日志
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "Unable to open video!" << endl;
return -1;
}
int cnt = 0;
const int BIN_WIDTH = 3;
const int BIN_HEIGHT = 50;
const int ESC_KEY = 27;
const int DELAY_MS = 30;
namedWindow("twoMat", WINDOW_AUTOSIZE);
namedWindow("diffMat", WINDOW_AUTOSIZE);
namedWindow("Result", WINDOW_AUTOSIZE);
createTrackbar("threshold", "Result", &sub_threshold, 255, threshold_track);
while (true)
{
cap >> frame;
cvtColor(frame, frame, COLOR_BGR2GRAY);
if (cnt == 0)
{
frame.copyTo(bgMat);
}
else
{
absdiff(frame, bgMat, diffMat);
threshold_track(0, 0);
imshow("twoMat", twoMat);
imshow("diffMat", diffMat);
}
if (waitKey(DELAY_MS) == ESC_KEY)
{
break;
}
cnt++;
}
cap.release();
destroyAllWindows();
return 0;
}
效果:

鼠标选择一个地方,然后做差分,并通过直方图展示。
代码:
#include<iostream>
#include<opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>
using namespace std;
using namespace cv;
//函数声明
void on_mouse(int EVENT, int x, int y, int flags, void* userdata);
int drawHist(cv::Mat& histMat, float* srcHist, int bin_width, int bin_heght);
vector<Point> mousePoints;
Point points;
Point vP;//观察的位置
//鼠标响应函数
void on_mouse(int EVENT, int x, int y, int flags, void* userdata)
{
Mat hh = *(Mat*)userdata; //强制类型转换
switch (EVENT)
{
case EVENT_LBUTTONDOWN://鼠标左键按下
{
vP.x = x;//point类中的对象,鼠标点击位置
vP.y = y;
mousePoints.push_back(points);//push_back在vector末尾插入一个元素,mousePoints结构为point的一个结构体向量
circle(hh, points, 4, Scalar(255, 255, 255), -1);
//line(hh, points, points, CV_RGB(255, 0, 0), 1, 8, 0);
imshow("mouseCallback", hh);//去掉试试看效果
}
break;
}
}
//绘制直方图
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);//max_element()求最大值
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;
}
int main()
{
//cv::utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);//不再输出日志
//或
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_ERROR);//只输出错误日志
VideoCapture cap(0);
//如果视频打开失败
if (!cap.isOpened())
{
cout << "Unable to open video!" << endl;
return -1;
}
int cnt = 0;//视频帧
int bin_width = 3;
int bin_height = 50;
float histgram[256] = { 0 };
Mat histMat;
while (1)
{
Mat frame; //存储帧
Mat grayMat;
cap >> frame;
if (frame.empty())
{
cout << "Unable to read frame!" << endl;
return -1;
}
//第一帧选取像素
if (cnt == 0)
{
Mat selectMat;
frame.copyTo(selectMat);
namedWindow("mouseCallback");
imshow("mouseCallback", selectMat);
setMouseCallback("mouseCallback", on_mouse, &selectMat);
waitKey(0);
destroyAllWindows();
}
cvtColor(frame, grayMat, COLOR_BGR2GRAY);
//获得鼠标点击点的像素灰度值
int index = grayMat.at<uchar>(vP.y, vP.x);
//直方图相应bin加1
histgram[index]++;
//绘制直方图
drawHist(histMat, histgram, bin_width, bin_height);
drawMarker(frame, vP, Scalar(255, 255, 255));
imshow("frame", frame);
imshow("histMat", histMat);
if (waitKey(30) == 27) {
destroyAllWindows();
break;
}
cnt++;
}
return 0;
}
效果:

通过傅里叶变换计算图片的频谱分布,并通过鼠标左键进行水印嵌入,展示嵌入前和嵌入后的效果。
代码:
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
vector<Point> mousePoints;
Point points;//定义Point类
void on_mouse(int EVENT, int x, int y, int flags, void* userdata);
int selectPolygon(Mat srcMat, Mat& dstMat);
void IIFFTT(Mat src, Mat& magMat);
void on_mouse(int EVENT, int x, int y, int flags, void* userdata)
{
Mat hh = *(Mat*)userdata;
Point p(x, y);
switch (EVENT)
{
case EVENT_LBUTTONDOWN://鼠标左键按下
{
points.x = x;//point类中的对象
points.y = y;
mousePoints.push_back(points);//push_back在vector末尾插入一个元素,mousePoints结构为point的一个结构体向量
circle(hh, points, 2, Scalar(255, 255, 255), -1);
putText(hh, "7", p, FONT_HERSHEY_PLAIN, 2, Scalar(255, 255, 255), 1, LINE_8);
imshow("mouseCallback", hh);//去掉试试看效果
}
break;
}
}
int selectPolygon(Mat srcMat, Mat& dstMat)
{
cv::Mat selectMat;
if (!srcMat.empty())
{
srcMat.copyTo(selectMat);
srcMat.copyTo(dstMat);
}
else
{
std::cout << "failed to read image!:" << std::endl;
return -1;
}
namedWindow("mouseCallback", WINDOW_NORMAL);
imshow("mouseCallback", selectMat);
setMouseCallback("mouseCallback", on_mouse, &selectMat);
waitKey(0);
destroyAllWindows();
return 0;
}
void IIFFTT(Mat src, Mat& dstMat)
{
int m = getOptimalDFTSize(src.rows); //2,3,5的倍数有更高效率的傅里叶变换
int n = getOptimalDFTSize(src.cols);
Mat padded;
//把灰度图像放在左上角,在右边和下边扩展图像,扩展部分填充为0;
copyMakeBorder(src, padded, 0, m - src.rows, 0, n - src.cols, BORDER_CONSTANT, Scalar::all(0));
//planes[0]为dft变换的实部,planes[1]为虚部,ph为相位, plane_true=mag为幅值
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
Mat planes_true = Mat_<float>(padded);
Mat ph = Mat_<float>(padded);
Mat complexImg;
//多通道complexImg既有实部又有虚部
merge(planes, 2, complexImg);
//对上边合成的mat进行傅里叶变换,***支持原地操作***,傅里叶变换结果为复数.通道1存的是实部,通道二存的是虚部
dft(complexImg, complexImg);
//把变换后的结果分割到两个mat,一个实部,一个虚部,方便后续操作
split(complexImg, planes);
//---------------此部分目的为更好地显示幅值---后续恢复原图时反着再处理一遍-------------------------
magnitude(planes[0], planes[1], planes_true);//幅度谱mag
phase(planes[0], planes[1], ph);//相位谱ph
//Mat A = planes[0];
//Mat B = planes[1];
Mat mag = planes_true;
mag += Scalar::all(1);//对幅值加1,log=(1+sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
//计算出的幅值一般很大,达到10^4,通常没有办法在图像中显示出来,需要对其进行log求解。
log(mag, mag);
//取矩阵中的最大值,便于后续还原时去归一化,minMaxLoc计算最大/最小矩阵
double maxVal;
minMaxLoc(mag, 0, &maxVal, 0, 0);
//修剪频谱,如果图像的行或者列是奇数的话,那其频谱是不对称的,因此要修剪
mag = mag(Rect(0, 0, mag.cols & -2, mag.rows & -2));
ph = ph(Rect(0, 0, mag.cols & -2, mag.rows & -2));
//这里为什么&上-2具体查看opencv文档
//其实是为了把行和列变成偶数 -2的二进制是11111111.......10 最后一位是0
Mat _magI = mag.clone();
//将幅度归一化到可显示范围。
normalize(_magI, _magI, 0, 1, NORM_MINMAX);
//imshow("before rearrange", _magI);
//获取中心坐标
int cx = mag.cols / 2;
int cy = mag.rows / 2;
//这里是以中心为标准,把mag图像分成四部分,重新排列傅里叶图像中的象限,使原点位于图像中心
Mat tmp;
Mat q0(mag, Rect(0, 0, cx, cy));
Mat q1(mag, Rect(cx, 0, cx, cy));
Mat q2(mag, Rect(0, cy, cx, cy));
Mat q3(mag, Rect(cx, cy, cx, cy));
//交换象限中心化
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
normalize(mag, mag, 0, 1, NORM_MINMAX);
//imshow("频谱图.jpg", mag);
selectPolygon(mag, mag);
/*--------------------------------------------------*/
Mat proceMag;
proceMag = mag * 255;
//前述步骤反着来一遍,目的是为了逆变换回原图
Mat q00(mag, Rect(0, 0, cx, cy));
Mat q10(mag, Rect(cx, 0, cx, cy));
Mat q20(mag, Rect(0, cy, cx, cy));
Mat q30(mag, Rect(cx, cy, cx, cy));
//交换象限
q00.copyTo(tmp);
q30.copyTo(q00);
tmp.copyTo(q30);
q10.copyTo(tmp);
q20.copyTo(q10);
tmp.copyTo(q20);
mag = mag * maxVal;//将归一化的矩阵还原
exp(mag, mag);//对应于前述去对数
mag = mag - Scalar::all(1);//对应前述+1
polarToCart(planes_true, ph, planes[0], planes[1]);//polarToCart坐标转换:极-笛卡尔;由幅度谱mag和相位谱ph恢复实部planes[0]和虚部planes[1]
merge(planes, 2, complexImg);//将实部虚部合并
//-----------------------傅里叶的逆变换-----------------------------------
Mat ifft(Size(src.cols, src.rows), CV_8UC1);
//傅里叶逆变换
idft(complexImg, ifft, DFT_REAL_OUTPUT);
normalize(ifft, ifft, 0, 1, NORM_MINMAX);
Mat dst;
Rect rect(0, 0, src.cols, src.rows);
dst = ifft(rect);
dst = dst * 255;
dst.convertTo(dstMat, CV_8UC1);
//imshow("频谱图添字后图像", magMat);
//imshow("原灰度图", src);
//waitKey(0);
}
int main()
{
Mat dstMat;
Mat srcMat = imread("D:/C++/coin.png", 0);
if (srcMat.empty()) {
cout << "Faild open file." << endl;
return -1;
}
IIFFTT(srcMat, dstMat);
imshow("频谱图添字后图像", dstMat);
imshow("原灰度图", srcMat);
waitKey(0);
return 0;
}
效果:

鼠标选择点连接区域,然后将该区域赋值为0.
代码:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
vector<Point> mousePoints;
Point points;
void on_mouse(int EVENT, int x, int y, int flags, void* userdata);
int selectPolygon(Mat srcMat, Mat& dstMat, Mat& maskMat);
int IFFT(Mat src, Mat& dstMat, Mat maskMat);
void on_mouse(int EVENT, int x, int y, int flags, void* userdata)
{
Mat hh = *(Mat*)userdata;
Point p(x, y);
switch (EVENT)
{
case EVENT_LBUTTONDOWN:
{
points.x = x;
points.y = y;
mousePoints.push_back(points);
circle(hh, points, 4, Scalar(255, 255, 255), -1);
line(hh, points, points, CV_RGB(255, 0, 0), 1, 8, 0);
imshow("mouseCallback", hh);
}
break;
}
}
int selectPolygon(Mat srcMat, Mat& dstMat, Mat& maskMat)
{
vector<vector<Point>> contours;
Mat selectMat;
Mat m = Mat::zeros(srcMat.size(), CV_8UC1);
m = Scalar(255);
if (!srcMat.empty())
{
srcMat.copyTo(selectMat);
srcMat.copyTo(dstMat);
}
else
{
cout << "failed to read image!" << endl;
return -1;
}
namedWindow("mouseCallback");
imshow("mouseCallback", selectMat);
setMouseCallback("mouseCallback", on_mouse, &selectMat);
waitKey(0);
destroyAllWindows();
contours.push_back(mousePoints);
if (contours[0].size() < 3)
{
cout << "failed to form polygon!" << endl;
return -1;
}
drawContours(m, contours, 0, Scalar(0), -1);
m.copyTo(maskMat);
dstMat = srcMat & maskMat;
return 0;
}
int IFFT(Mat src, Mat& dstMat, Mat maskMat)
{
Mat dst;
int m = getOptimalDFTSize(src.rows);
int n = getOptimalDFTSize(src.cols);
Mat padded;
copyMakeBorder(src, padded, 0, m - src.rows, 0, n - src.cols, BORDER_CONSTANT, Scalar::all(0));
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
Mat planes_true = Mat_<float>(padded);
Mat ph = Mat_<float>(padded);
Mat complexImg;
merge(planes, 2, complexImg);
dft(complexImg, complexImg);
split(complexImg, planes);
magnitude(planes[0], planes[1], planes_true);
phase(planes[0], planes[1], ph);
Mat A = planes[0];
Mat B = planes[1];
Mat mag = planes_true;
mag += Scalar::all(1);
log(mag, mag);
double maxVal;
minMaxLoc(mag, 0, &maxVal, 0, 0);
mag = mag(Rect(0, 0, mag.cols & -2, mag.rows & -2));
ph = ph(Rect(0, 0, mag.cols & -2, mag.rows & -2));
Mat _magI = mag.clone();
Mat _ph = ph.clone();
Mat mask = Mat::zeros(mag.size(), CV_8UC1);
bitwise_and(maskMat, 1, mask);
_magI = _magI.mul(mask);
_magI = _magI(Rect(0, 0, _magI.cols & -2, _magI.rows & -2));
_ph = _ph(Rect(0, 0, _ph.cols & -2, _ph.rows & -2));
mag -= Scalar::all(1);
exp(mag, mag);
polarToCart(mag, ph, planes[0], planes[1]);
merge(planes, 2, complexImg);
idft(complexImg, complexImg);
split(complexImg, planes);
normalize(planes[0], dst, 0, 1,NORM_MINMAX);
dst.copyTo(dstMat);
return 0;
}
int main()
{
Mat srcMat = imread("D:/C++/coin.png", 0);
Mat dstMat, maskMat;
if (srcMat.empty())
{
cout << "failed to read image!" << endl;
return -1;
}
selectPolygon(srcMat, dstMat, maskMat);
imshow("Original Image", srcMat);
imshow("Frequency Selected Image", dstMat);
waitKey(0);
destroyAllWindows();
return 0;
}
效果:


魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)