鼠标选择多边形区域并显示

 代码:

#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;
}

 效果:

 

 

Logo

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

更多推荐