C++使用OpenCV封装计算SFR

#pragma once
#include <string>
#include <map>

//此处包含你的opencv头文件和库文件
//#include <opencv2/opencv.hpp>

//此处[xxxx]代表你的opencv版本号
//#pragma comment(lib,"opencv_world[xxxx].lib")

/*
* @brief,空间响应频率算法类库
* @explain,SFR[Spatial Frequency Response]
* @notice,这个类库计算的SFR并不精确,仅供学习参考使用,如需使用请参考:
* http://www2.mitre.org/tech/mtf/  选择SFR.zip点击下载查看源代码.
* 如果摄像头需要自己定位ROI斜边,需自己准备特定的图形,并写算法来定位ROI的斜边.
* 此类库参考的地址:https://github.com/RayXie29/SFR_Calculation
*
* 计算步骤
* 0.获取垂直斜边的ROI
* 1.进行数据的归一化
* 2.计算图像每一行的像素矩心
* 3.对每行的矩心使用最小二乘法进行线性拟合,获得一条关于矩心的直线
* 4.重新定位ROI,获得ESF
* 5.对获得的ESF进行四倍超采样
* 6.通过差分运算获得LSF
* 7.对LSF应用汉明窗
* 8.进行DFT运算
*/

class SfrAlgorithm
{
public:
	/*
	* @brief,构造
	*/
	SfrAlgorithm();

	/*
	* @brief,析构
	*/
	~SfrAlgorithm();

	/*
	* @brief,获取最终错误
	* @return,std::string
	*/
	std::string getLastError() const;
	
	/*
	* @brief,计算Sfr
	* @param1,计算的区域(你图像斜边的ROI)
	* @param2,计算的值mtf曲线[第一个double代表frequency(频率),第二个double代表sfr的值]
	* @param3,伽马值
	* @return,bool
	*/
	bool calculateSfr(const cv::Mat& area, std::map<double, double>& value, double gamma = 1.0);
protected:

	/*
	* @brief,伽马解码
	* @param1,图像源
	* @param2,伽马强度
	* @return,bool
	*/
	bool gammaDecoding(cv::Mat& source, double gamma) const;

	/*
	* @brief,寻找质心
	* @param1,图像源
	* @param2,Y相移(多少高度)
	* @param3,中心质心偏移量
	* @return,std::vector<double>
	*/
	std::vector<double> findCentroid(cv::Mat& source, std::vector<double>& yShifts, double* centerCentroidOffset);

	/*
	* @brief,简单线性回归
	* @param1,质心相移
	* @param2,Y相移
	* @param3,截距
	* @param4,斜边的斜面
	* @return,void
	*/
	void simpleLinearRegression(const std::vector<double>& centroidShifts, const std::vector<double>& yShifts, double* intercept, double* slope) const;

	/*
	* @brief,减少行
	* @param1,斜边的斜面
	* @param2,图像高度
	* @return,void
	*/
	void reduceRows(double slope, int* height) const;

	/*
	* @brief,超采样
	* @param1,图像源
	* @param2,斜边的斜面
	* @param3,中心质心偏移量
	* @param4,图像宽度
	* @param5,图像高度
	* @param6,采样长度
	* @return,std::vector<double>
	*/
	std::vector<double> overSampling(const cv::Mat& source, double slope, double centerCentroidOffset, int height, int width, int* samplingLen);

	/*
	* @brief,汉明窗口
	* @param1,解样数据
	* @param2,解样长度
	* @return,std::vector<double>
	*/
	std::vector<double> hammingWindows(const std::vector<double>& desampling, int samplingLen) const;

	/*
	* @brief,离散傅里叶变换[DFT]
	* @param1,采样的数据
	* @param2,4倍采样数据大小
	* @return,bool
	*/
	bool discreteFourierTransform(std::vector<double>& data, int size) const;
protected:
	/*
	* @brief,设置最终错误
	* @param1,错误信息
	* @return,void
	*/
	void setLastError(const std::string& error);
private:
	//最终错误信息
	std::string m_lastError = "未知错误";
};
#include "SfrAlgorithm.h"

SfrAlgorithm::SfrAlgorithm()
{

}

SfrAlgorithm::~SfrAlgorithm()
{

}

std::string SfrAlgorithm::getLastError() const
{
	return m_lastError;
}

bool SfrAlgorithm::calculateSfr(const cv::Mat& area, std::map<double, double>& value, double gamma)
{
	cv::Mat roi = area.clone();
	if (roi.empty())
	{
		setLastError("计算Sfr的区域为空");
		return false;
	}

	if (roi.type() != CV_8UC1)
	{
		cv::cvtColor(roi, roi, CV_BGR2GRAY);
	}

	int height = roi.rows, width = roi.cols;

	//Do the gamma decoding to eliminate the gamma encoded by camera device 
	//进行伽玛解码,消除相机设备编码的伽玛  
	if (gamma > 0)
	{
		if (!gammaDecoding(roi, gamma))
		{
			return false;
		}
	}

	int i = 0, j = 0;

	//Center centroid offset
	//中心质心偏移量
	std::vector<double> yShifts(height);
	double centerCentroidOffset = 0;

	//Calculate the shifts between Centroids and Centroid of image center
	//计算图像中心质心和质心之间的位移
	std::vector<double> centroidShifts = findCentroid(roi, yShifts, &centerCentroidOffset);
	if (centroidShifts.empty())
	{
		return false;
	}

	//截距,斜边的斜面
	double intercept = 0, slope = 0;

	//simple linear regression for slanted edge fitting
	//简单的线性回归的倾斜边缘拟合  
	simpleLinearRegression(centroidShifts, yShifts, &intercept, &slope);
	if (slope < 0)
	{
		setLastError("未找到斜边");
		return false;
	}

	//checkSlope(slope, &height);

	//Truncate the number of rows of data to largest slope cycle which will have an integer number of full phase rotations
	//将数据的行数截断为最大的斜率周期,该周期将具有整数个全相位旋转
	reduceRows(slope, &height);

	//update the centerCentroidOffset to the offset between original mid point of image and reference mid point we calculated
	//更新centerCentroidOffset为我们计算的原始图像中点与参考中点之间的偏移量  
	centerCentroidOffset += 0.5 + intercept - static_cast<double>(width / 2);

	printf("intercept %.2f,slope %.2f,reduce %d,offset %.2f\n",
		intercept, slope, height, centerCentroidOffset);

	//Mapping the pixel value of original image into a sampling data which the length is 4 times of original image width
	//This step is for concentrating the amount of change of original pixel values
	//将原始图像的像素值映射为长度为原始图像宽度4倍的采样数据  
	//这一步是为了集中原始像素值的变化量
	int samplingLen = width * 4;
	std::vector<double> overSamplingData = overSampling(roi, slope, centerCentroidOffset, height, width, &samplingLen);

	if (overSamplingData.empty())
	{
		setLastError("4倍超采样计算失败");
		return false;
	}

	//Using hamming window to filter the ripple signal of two side of data
	//利用汉明窗对数据两边的纹波信号进行滤波
	overSamplingData = hammingWindows(overSamplingData, samplingLen);

	//discrete four transform
	if (!discreteFourierTransform(overSamplingData, samplingLen))
	{
		setLastError("离散傅里叶变换失败");
		return false;
	}

	width = int(samplingLen / 4);
	for (i = 0; i <= width; ++i)
	{
		const double frequency = (double)i / (double)width;
		const double mtf = overSamplingData[i] / overSamplingData[0];
		if (isnan(mtf) || isinf(mtf))
		{
			continue;
		}
		value.insert(std::make_pair(frequency, mtf * 100));
	}
	return true;
}

bool SfrAlgorithm::gammaDecoding(cv::Mat& source, double gamma) const
{
	if (source.channels() != 1)
	{
		return false;
	}

	for (int i = 0; i < source.rows; ++i)
	{
		uchar* ptr = source.ptr(i);
		for (int j = 0; j < source.cols; ++j)
		{
			ptr[j] = 255 * (pow((double)ptr[j] / 255, 1 / gamma));
		}
	}
	return true;
}

std::vector<double> SfrAlgorithm::findCentroid(cv::Mat& source, std::vector<double>& yShifts, double* centerCentroidOffset)
{
	std::vector<double> centroidShifts(source.rows);
	int i = 0, j = 0;
	const int height = source.rows, width = source.cols;

	//cv::Mat tempSrc(source.size(), CV_8UC1);
	cv::Mat tempSrc;

	//Do the bilaterFilter on template roi image to make sure we can find the slanted edge more acurrate
	//是否对模板roi图像进行bilaterFilter,使图像更平滑,以确保我们可以更准确地找到倾斜的边缘
	//cv::bilateralFilter(source, tempSrc, 3, 50, 10);
	cv::medianBlur(source, tempSrc, 3);

	// calculate the centroid of every row in roi
	// centroid formuld: Total moments/Total amount
	//计算roi中每一行的质心
	//质心公式:Total moments/Total amount
	for (i = 0; i < height; ++i)
	{
		double molecule = 0, denominator = 0, temp = 0;
		uchar* tempSrcPtr = tempSrc.ptr<uchar>(i);
		for (j = 0; j < width - 1; ++j)
		{
			temp = (double)tempSrcPtr[j + 1] - (double)tempSrcPtr[j];
			molecule += temp * (double)j;
			denominator += temp;
		}
		centroidShifts[i] = molecule / denominator;
	}

	for (i = 0; i < centroidShifts.size(); ++i)
	{
		if (isinf(centroidShifts[i]) || isnan(centroidShifts[i]))
		{
			setLastError("质心偏移存在无效数据");
			return {};
		}
	}

	//Eliminate the noise far from slant edge(+/- 10 pixels)
	//消除远离倾斜边缘的噪声(+/- 10像素)
#if 0
	tempSrc = source.clone();
	//cv::GaussianBlur(source, source, cv::Size(3, 3), 0);
	cv::medianBlur(source, source, 5);

	int interval = 5;
	for (i = 0; i < height; ++i)
	{
		uchar* srcPtr = source.ptr<uchar>(i);
		uchar* tempPtr = tempSrc.ptr<uchar>(i);
		for (j = int(centroidShifts[i]) - interval; j < int(centroidShifts[i]) + interval; ++j)
		{
			if (j >= 0 && j < width)
			{
				srcPtr[j] = tempPtr[j];
			}
		}
	}

#endif
	//check whether the edge in image is too close to the image corners
	//检查图像的边缘是否太靠近图像的边角
	if (centroidShifts[0] < 2.0 || width - centroidShifts[0] < 2.0)
	{
		//std::cerr << "The edge in roi is too close to the image corners" << std::endl;
		setLastError("计算区域的边缘过于靠近图像的角点");
		return {};
	}

	if (centroidShifts[height - 1] < 2 || width - centroidShifts[height - 1] < 2)
	{
		//std::cerr << "The edge in roi is too close to the image corners" << std::endl;
		setLastError("计算区域的边缘过于靠近图像的角点");
		return {};
	}

	const int halfYSize = height / 2;

	//Centroid of image center
	//图像中心的质心
	const double centerCentroid = centroidShifts[halfYSize];
	*centerCentroidOffset = centerCentroid;
	for (i = 0; i < height; ++i)
	{
		//Calculate the Shifts between Centroids and Centroid of image center
		//计算图像中心的质心和质心之间的位移
		centroidShifts[i] -= centerCentroid;
		//Calculate the shifts between height of image center and each row
		//计算图像中心和每一行之间的高度偏移
		yShifts[i] = (double)i - (double)halfYSize;
	}
	return centroidShifts;
}

void SfrAlgorithm::simpleLinearRegression(const std::vector<double>& centroidShifts, const std::vector<double>& yShifts, double* intercept, double* slope) const
{
	const int ySize = yShifts.size();
	double xsquare = 0, xsum = 0, ysum = 0, xavg = 0, yavg = 0;
	int i = 0;

#if MITRE_IOS_SFR
	for (i = 0; i < ySize; ++i)
	{
		ysum += yShifts[i];
		xsum += centroidShifts[i];
	}
	xavg = xsum / (double)ySize;
	yavg = ysum / (double)ySize;

	//simple linear regession
	for (i = 0; i < ySize; ++i)
	{
		const double temp = centroidShifts[i] - xavg;
		*slope += temp * yShifts[i];
		xsquare += temp * temp;
	}

	*slope /= xsquare;
	*intercept = (ysum - xsum * (*slope)) / (double)ySize;
#else
	for (i = 0; i < ySize; ++i)
	{
		yavg += yShifts[i];
		xavg += centroidShifts[i];
	}
	xavg /= (double)ySize;
	yavg /= (double)ySize;

	//simple linear regession
	for (i = 0; i < ySize; ++i)
	{
		const double temp = centroidShifts[i] - xavg;
		*slope += temp * (yShifts[i] - yavg);
		xsquare += temp * temp;
	}

	*slope /= xsquare;
	*intercept = yavg - (*slope) * xavg;
#endif
	return;
}

void SfrAlgorithm::reduceRows(double slope, int* height) const
{
	const double absslope = abs(slope);

#if MITRE_IOS_SFR
	/*
	计算用于高度的行数:新窗口将从上移和下移的行数<高度,
	这样一个整数由边进行x过渡的次数;例如,如果我们有
	坡度为10(在跳过一个像素之前,边缘向下移动10行
	水平),且高度=35,则新高度将为30(一个10的整数倍,小于35)
	*/
	const int cycle = (int)(*height * absslope);
	if ((cycle / absslope) <= *height)
	{
		*height = (int)(cycle / absslope);
	}
#else
	const double cycle = (double)*height / absslope;
	if (absslope * cycle < (double)*height)
	{
		*height = (int)(absslope * cycle);
	}
#endif
	return;
}

std::vector<double> SfrAlgorithm::overSampling(const cv::Mat& source, double slope, double centerCentroidOffset, int height, int width, int* samplingLen)
{
	std::vector<double> rowShifts(height, 0);

	int i = 0, j = 0, k = 0;
	int halfY = height >> 1;

	//calculate the pixel shift of each row to align the centroid of each row as close as possible
	for (i = 0; i < height; ++i)
	{
		rowShifts[i] = (static_cast<double>(i) - static_cast<double>(halfY)) / slope + centerCentroidOffset;
	}

	//dataMap -> to record the index of pixel after shift
	//datas -> to record the original pixel value 
	std::vector<double> dataMap(height * width, 0);
	std::vector<double> datas(height * width, 0);

	for (i = 0, k = 0; i < height; ++i)
	{
		const int baseIndex = width * i;
		for (j = 0; j < width; ++j)
		{
			dataMap.at(baseIndex + j) = static_cast<double>(j) - rowShifts.at(i);
			datas.at(baseIndex + j) = static_cast<double>(source.at<uchar>(i, j));
		}
	}

	std::vector<double> samplingBar(*samplingLen, 0);
	std::vector<int> mappingCount(*samplingLen, 0);

	//Start to mapping the original data to 4x sampling data and record the count of each pixel in 4x sampling data
	for (i = 0; i < height * width; ++i)
	{
		const int mappingIndex = static_cast<int>(4 * dataMap[i]);
		if (mappingIndex >= 0 && mappingIndex < *samplingLen)
		{
			samplingBar.at(mappingIndex) = samplingBar.at(mappingIndex) + datas[i];
			mappingCount.at(mappingIndex)++;
		}
	}
	//average the pixel value in 4x sampling data, if the pixel value in pixel is zero, copy the value of close pixel

	try
	{
		for (i = 0; i < *samplingLen; ++i)
		{
			j = 0;
			k = 1;
			if (mappingCount.at(i) == 0)
			{
				if (i == 0)
				{
					while (!j)
					{
						if (mappingCount.at(i + k) != 0)
						{
							double v1 = (double)mappingCount.at(i + k);
							double v2 = samplingBar.at(i + k) / v1;
							samplingBar.at(i) = v2;
							j = 1;
						}
						else
						{
							++k;
						}
					}
				}
				else
				{
					while (!j && ((i - k) >= 0))
					{
						if (mappingCount.at(i - k) != 0)
						{
							/* Don't divide by counts since it already happened in previous iteration */
							samplingBar.at(i) = samplingBar.at(i - k);
							j = 1;
						}
						else
						{
							++k;
						}
					}

					if ((i - k) < 0)
					{
						k = 1;
						while (!j)
						{
							if (mappingCount.at(i + k) != 0)
							{
								samplingBar.at(i) = samplingBar.at(i + k) / ((double)mappingCount.at(i + k));
								j = 1;
							}
							else
							{
								++k;
							}
						}
					}
				}
			}
			else
			{
				samplingBar.at(i) = samplingBar.at(i) / (double)mappingCount.at(i);
			}
		}
	}
	catch (const std::exception& e)
	{
		setLastError("overSampling out of range exception");
		return{};
	}


	// reduce the length of sampling data 
	// because the datas at the edge are only matters, we truncate the data close to the two side of sampling data
	// which has very small contribution to the result

	//truncating the data smaller than 10% and bigger than 90% of original length
	const int originalSamplingLen = *samplingLen;
	*samplingLen = *samplingLen * 0.8;

	std::vector<double> desampling(*samplingLen, 0);
	//derivative sampling data(which is ESF) to get the line spread function
	for (i = originalSamplingLen * 0.1, j = 1; i < originalSamplingLen && j < *samplingLen; ++i, ++j)
	{
		desampling[j] = samplingBar[i + 1] - samplingBar[i];
	}
	return desampling;
}

std::vector<double> SfrAlgorithm::hammingWindows(const std::vector<double>& desampling, int samplingLen) const
{
	int i = 0, j = 0;
	std::vector<double> tempData(samplingLen);

	//We want to shift the peak data to the center of line spread function data
	//Because we will do the hamming window later, this will keep the important data away from filtering
	//In case there are two peaks, we use two variable to record the peak data position
	int lLocation = -1, rLocation = -1;

	double samplingMax = 0;
	for (i = 0; i < samplingLen; ++i)
	{
		if (fabs(desampling[i]) > fabs(samplingMax))
		{
			samplingMax = desampling[i];
		}
	}

	for (i = 0; i < samplingLen; ++i)
	{
		if (abs(desampling[i] - samplingMax) <= 0.000001)
		{
			if (lLocation < 0)
			{
				lLocation = i;
			}
			rLocation = i;
		}
	}

	//the shift amount 
	const int peakOffset = (rLocation + lLocation) / 2 - samplingLen / 2;

	if (peakOffset)
	{
		for (i = 0; i < samplingLen; ++i)
		{
			const int newIndex = i - peakOffset;
			if (newIndex >= 0 && newIndex < samplingLen)
			{
				tempData[newIndex] = desampling[i];
			}
		}
	}
	else
	{
		for (i = 0; i < samplingLen; ++i)
		{
			tempData[i] = desampling[i];
		}
	}

	//do the hamming window filtering
	for (int i = 0; i < samplingLen; ++i)
	{
		tempData[i] *= (0.54 - 0.46 * cos(2.0f * CV_PI * (double)i / (double(samplingLen) - 1.0f)));
	}
	return tempData;
}

bool SfrAlgorithm::discreteFourierTransform(std::vector<double>& data, int size) const
{
	//                n-1              k
	// DFT ==> X[k] = Σ  x[n]e^(-j2π - n)
	//                n=0              N
	int i = 0, j = 0;
	std::complex<double>* arr = new(std::nothrow) std::complex<double>[size];
	if (!arr)
		return false;

	for (i = 0; i < size; ++i)
	{
		arr[i] = std::complex<double>(data[i], 0);
	}

	double twoPi = 2.0f * CV_PI;
	for (i = 0; i < size / 2.0; ++i)
	{
		std::complex<double> temp = 0;
		for (j = 0; j < size; ++j)
		{
			double w = twoPi * (double)i * (double)j / (double)size;
			std::complex<double> deg(cos(w), -sin(w));
			temp += arr[j] * deg;
		}
		data[i] = sqrt(temp.real() * temp.real() + temp.imag() * temp.imag());
	}
	delete[] arr;
	return true;
}

void SfrAlgorithm::setLastError(const std::string& error)
{
	m_lastError = error;
}

项目展示:

753c8c717e4047cb998f34f6c147febc.png

此项目所使用的OpenCV下载链接:

https://download.csdn.net/download/qq_31629063/87016402icon-default.png?t=N7T8https://download.csdn.net/download/qq_31629063/87016402

已封装好的C++ DLL链接https://download.csdn.net/download/qq_31629063/89318449icon-default.png?t=N7T8https://download.csdn.net/download/qq_31629063/89318449

DLL头文件

#pragma once

#include <map>
#include <mutex>
#include <functional>

//vc++目录,包含目录设置opencv目录
#include <opencv2/opencv.hpp>

//vc++目录,库目录设置opencv目录
#if defined(_WIN64)
#if defined(_DEBUG)
#pragma comment(lib, "opencv_world3414_x64d.lib")
#else
#pragma comment(lib, "opencv_world3414_x64.lib")
#endif // _DEBUG
#else
#if defined(_DEBUG)
#pragma comment(lib, "opencv_world3414d.lib")
#else
#pragma comment(lib, "opencv_world3414.lib")
#endif // _DEBUG
#endif // _WIN64

//#define SFR_BUILD_STATIC

#if defined(SFR_BUILD_STATIC)
#define SFR_DLL_EXPORT
#else
#if defined(SFR_BUILD_DYNAMIC)
#define SFR_DLL_EXPORT __declspec(dllexport)
#else
#define SFR_DLL_EXPORT __declspec(dllimport)
#endif // SFR_BUILD_DYNAMIC
#pragma warning(push)
#pragma warning(disable:4251)
#define SFR_DISABLE_WARNING
#endif // SFR_BUILD_STATIC

/*
* @brief 空间响应频率算法类库
* SFR[Spatial Frequency Response]
* 计算步骤
* 0.获取垂直斜边的ROI
* 1.进行数据的归一化
* 2.计算图像每一行的像素矩心
* 3.对每行的矩心使用最小二乘法进行线性拟合,获得一条关于矩心的直线
* 4.重新定位ROI,获得ESF
* 5.对获得的ESF进行四倍超采样
* 6.通过差分运算获得LSF
* 7.对LSF应用汉明窗
* 8.进行DFT运算
*/

namespace sfr {

	//数据
	struct SFR_DLL_EXPORT Data {
		//构造
		Data();

		//析构
		~Data();

		//SFR频率
		double frequency;

		//SFR中心值
		double center;

		//SFR周边值
		double circum;

		//计算SFR间隔(ms)
		double interval;

		//视场角百分比
		double fovp;
	};

	//启用
	struct SFR_DLL_EXPORT Enable {
		//构造
		Enable();

		//析构
		~Enable();

		//绘制定位中心
		bool drawLocateCenter;

		//绘制结果PASS
		bool drawResultPass;

		//绘制视场角
		bool drawFovp;
	};

	//频率
	struct SFR_DLL_EXPORT Frequency {
		//中心
		double center;

		//左上
		double leftTop;

		//右上
		double rightTop;

		//左下
		double leftBottom;

		//右下
		double rightBottom;

		//运动位置
		long motionPosition;

		double& operator*();

		double* _ptr0 = nullptr;

		double* _ptr1 = nullptr;

		//总数
		double total() const;
	};

	//定位类型,目前仅支持两种定位算法
	enum LocateType  {
		//黑色扇形&白色背景
		BLACK_SECTOR_WITH_WHITE_BACKGROUND,

		//白色扇形&黑色背景
		WHITE_SECTOR_WITH_BLACK_BACKGROUND,

		//黑色梯形&白色背景
		BLACK_TRAPEZOID_WITH_WHITE_BACKGROUND,

		//白色梯形&黑色背景
		WHITE_TRAPEZOID_WITH_BLACK_BACKGROUND,
	};

	//最大区域 中心,左上,右上,左下,右下
	static const int MAX_AREA_SIZE = 5;

	//区域
	struct SFR_DLL_EXPORT Area {
		//构造
		Area();

		//析构
		~Area();

		//此区域的x坐标
		int x;

		//此区域的y坐标
		int y;

		//此区域的宽度
		int width;

		//此区域的高度
		int height;

		//感兴趣的区域
		struct {
			//roi的宽度
			int width;

			//roi的高度
			int height;

			//x坐标偏移
			int xOffset;

			//y坐标偏移
			int yOffset;
		} roi;//斜边的ROI

		//定位类型
		int locateType;

		//边缘降噪多少个像素点
		int denoisePixel;

		//此区域的二值化阈值
		double threshold;

		double _value = 0;

		bool _result = false;

		double _tick = 0;

		double _time = 0;

		cv::Point2f _point0;

		cv::Point2f _point1;

		cv::Rect _rect;

		cv::Rect _roi;

		bool _roiOk = false;

		std::map<double, double> _curve;

		std::mutex _mutex;

		std::function<void(int index, const cv::Mat& mat)> _grab = nullptr;

		static int _size;

		/*
		* @brief 开始抓取此区域
		* @param[in] index 区域索引
		* @param[in] mat cv::Mat
		* @return void
		*/
		void startGrab(const std::function<void(int index, const cv::Mat& mat)>& func);

		/*
		* @brief 停止抓取此区域
		* @return void
		*/
		void stopGrab();
	};

	//位置
	enum Position {
		//中心区域
		CENTER,

		//左上区域
		LEFT_TOP,

		//右上区域
		RIGHT_TOP,

		//左下区域
		LEFT_BOTTOM,

		//右下区域
		RIGHT_BOTTOM
	};

	//绘图
	struct SFR_DLL_EXPORT Paint {
		//构造
		Paint();

		//析构
		~Paint();

		//文本缩放
		double textScale;

		//文本颜色,不涉及结果颜色
		cv::Scalar textColor;

		//文本粗细
		int textThickness;

		//区域矩形框颜色
		cv::Scalar areaRectColor;

		//区域矩形框粗细
		int areaRectThickness;

		//ROI矩形框颜色
		cv::Scalar roiRectColor;

		//ROI矩形框粗细
		int roiRectThickness;

		//定位线条颜色
		cv::Scalar locateLineColor;

		//定位线条的粗细
		int locateLineThickness;

		//定位线条的长度
		int locateLineLength;

		//中心线条颜色
		cv::Scalar centerLineColor;

		//中心线条的粗细
		int centerLineThickness;

		//视场角线条颜色
		cv::Scalar fovLineColor;

		//视场角线条粗细
		int fovLineThickness;
	};

	class SFR_DLL_EXPORT Algorithm {
	public:
		/*
		* @brief 构造
		*/
		Algorithm();

		/*
		* @brief 构造
		* @param[in] area 区域
		* @param[in] data 数据
		* @param[in] enable 启用
		* @param[in] paint 绘图
		*/
		Algorithm(sfr::Area* area, sfr::Data* data, sfr::Enable* enable, sfr::Paint* paint = nullptr);

		/*
		* @brief 析构
		*/
		~Algorithm();

		/*
		* @brief 初始化
		* @param[in] area 区域
		* @param[in] data 数据
		* @param[in] enable 启用
		* @param[in] paint 绘图
		* @return void
		*/
		void initialize(sfr::Area* area, sfr::Data* data, sfr::Enable* enable, sfr::Paint* paint = nullptr);

		/*
		* @brief 是否计算[指定区域]
		* @param[in] index 区域索引
		* @return bool
		*/
		bool isCalculate(int index);

		/*
		* @brief 获取交叉线中心
		* @param[in] index 区域索引
		* @param[in] source 图像源(整个图像)
		* @return bool
		*/
		bool getCrossLineCenter(int index, const cv::Mat& source);

		/*
		* @brief 计算SFR的ROI
		* @param[in] index 区域索引
		* @param[in] thresold 误差阈值(允许与上次获取的坐标相差+-threshold,则返回上次的坐标点)
		* @return bool
		*/
		bool calculateRoi(int index, float threshold = 2.0f);

		/*
		* @brief 计算SFR
		* @param[in] index 区域索引
		* @param[in] source 图像源(整个图像)
		* @return bool
		*/
		bool calculateSfr(int index, const cv::Mat& source);

		/*
		* @brief 将数据输出在图像上
		* @param[in] index 区域索引
		* @param[in|out] source 图像源(整个图像)
		* @return void
		*/
		void putText(int index, cv::Mat& source);

		/*
		* @brief 区域是否通过
		* @return bool
		*/
		bool isPass() const;

		/*
		* @brief SFR的ROI[非线程安全]
		* @param[in] index 区域索引
		* @return cv::Rect
		*/
		cv::Rect roi(int index) const;

		/*
		* @brief SFR的值[线程安全]
		* @param[in] index 区域索引
		* @return double
		*/
		double value(int index);

		/*
		* @brief SFR的结果[线程安全]
		* @param[in] index 区域索引
		* @return bool
		*/
		bool result(int index);

		/*
		* @brief MTF曲线[线程安全]
		* @param[in] index 区域索引
		* @return MTF曲线MAP
		*/
		std::map<double, double> curve(int index);

	protected:

		/*
		* @brief 定位中心
		* @param[in|out] img 图像
		* @param[in] color 虚线颜色
		* @param[in] thickness 线条粗细
		* @return void
		*/
		void locateCenter(cv::Mat& img, const cv::Scalar& color = CV_RGB(255, 250, 205), int thickness = 1) const;

		/*
		* @brief 画视场角
		* @param[in] img 图像
		* @param[in] color 虚线颜色
		* @param[in] thickness 线条粗细
		* @return void
		*/
		void drawFov(cv::Mat& img, const cv::Scalar& color = CV_RGB(255, 250, 205), int thickness = 1);

		/*
		* @brief 计算SFR
		* @param[in] area 计算的区域
		* @param[out] value SFR的值
		* @param[out] curve MTF曲线
		* @return bool
		*/
		bool calculatesfr(const cv::Mat& area, double& value,
			std::map<double, double>* curve = nullptr);

		/*
		* @brief 获取交叉点
		* @param[in] line1S 线条1起点
		* @param[in] line1E 线条1终点
		* @param[in] line2S 线条2起点
		* @param[in] line2E 线条2终点
		* @param[in] value 交叉坐标
		* @return bool
		*/
		bool getCrossPoint(const cv::Point2i& line1S, const cv::Point2i& line1E, const cv::Point2i& line2S, const cv::Point2i& line2E, cv::Point2f& value) const;

		/*
		* @brief 画点线
		* @param[in|out] img 图像
		* @param[in] p1 起点
		* @param[in] p2 终点
		* @param[in] color 颜色
		* @param[in] thickness 线条粗细
		* @return void
		*/
		void drawPointLine(cv::Mat& img, cv::Point2f p1, cv::Point2f p2, const cv::Scalar& color, int thickness) const;

		/*
		* @brief 画虚线
		* @param[in|out] mat 图像
		* @param[in] p1 起点
		* @param[in] p2 终点
		* @param[in] color 颜色
		* @param[in] thickness 线条粗细
		* @return void
		*/
		void drawDottedLine(cv::Mat& img, cv::Point2f p1, cv::Point2f p2, const cv::Scalar& color, int thickness) const;

		/*
		* @brief 默认将数据输出在图像上
		* @param[in] index 区域索引
		* @param[in|out] source 图像源(整个图像)
		* @return void
		*/
		void putTextDefault(int index, cv::Mat& source);

		/*
		* @brief 自定义将数据输出在图像上
		* @param[in] index 区域索引
		* @param[in|out] source 图像源(整个图像)
		* @return void
		*/
		void putTextCustom(int index, cv::Mat& source);

	private:
		std::mutex m_mutex;
		sfr::Area* m_area = nullptr;
		sfr::Data* m_data = nullptr;
		sfr::Enable* m_enable = nullptr;
		sfr::Paint* m_paint = nullptr;
	};
}

#ifdef SFR_DISABLE_WARNING
#pragma warning(pop)
#endif

Demo代码

#include <stdio.h>
#include <stdlib.h>
#include <Sfr/sfr.h>
#pragma comment(lib, "sfr.lib")

int main(int argc, char** argv)
{
	//计算频率在0.125的sfr
	sfr::Algorithm alg;
	sfr::Area area[5];
#if 1
	//中心
	area[0].x = 1503;
	area[0].y = 1146;
	//左上
	area[1].x = 385;
	area[1].y = 183;

	//右上
	area[2].x = 2743;
	area[2].y = 179;

	//左下
	area[3].x = 422;
	area[3].y = 2010;

	//右下
	area[4].x = 2688;
	area[4].y = 2114;

	for (int i = 0; i < 5; ++i) {
		area[i].width = 800;
		area[i].height = 650;
		area[i].locateType = sfr::BLACK_TRAPEZOID_WITH_WHITE_BACKGROUND;
		area[i].roi.xOffset = -15;
		area[i].roi.yOffset = -100;
		area[i].roi.width = 40;
		area[i].roi.height = 50;
		area[i].threshold = 85;
		area[i].denoisePixel = 5;
	}

	sfr::Data data;
	sfr::Enable enable;
	enable.drawFovp = true;
	enable.drawLocateCenter = true;
	enable.drawResultPass = true;

	sfr::Paint paint;
	paint.textScale = 4.3;
	paint.textThickness = 2;
	paint.roiRectThickness = 2;
	paint.areaRectThickness = 2;
	paint.locateLineThickness = 4;
	paint.locateLineLength = 30;
	paint.locateLineColor = CV_RGB(255, 0, 0);

	cv::Mat mat = cv::imread("img_trapezoid.jpg");
	alg.initialize(area, &data, &enable, &paint);
	for (int i = 0; i < 5; ++i) {
		alg.getCrossLineCenter(i, mat);
		alg.calculateRoi(i);
		alg.calculateSfr(i, mat);
		alg.putText(i, mat);
	}
	cv::imwrite("img_trapezoid_sfr.jpg", mat);
#else
	//中心
	area[0].x = 1570;
	area[0].y = 1218;
	//左上
	area[1].x = 148;
	area[1].y = 182;

	//右上
	area[2].x = 3110;
	area[2].y = 122;

	//左下
	area[3].x = 120;
	area[3].y = 2295;

	//右下
	area[4].x = 3085;
	area[4].y = 2318;

	for (int i = 0; i < 5; ++i) {
		area[i].width = 800;
		area[i].height = 650;
		area[i].locateType = sfr::BLACK_SECTOR_WITH_WHITE_BACKGROUND;
		area[i].roi.xOffset = -25;
		area[i].roi.yOffset = -100;
		area[i].roi.width = 40;
		area[i].roi.height = 50;
		area[i].threshold = 85;
		area[i].denoisePixel = 5;
	}

	sfr::Data data;
	sfr::Enable enable;
	enable.drawFovp = true;
	enable.drawLocateCenter = true;
	enable.drawResultPass = true;

	sfr::Paint paint;
	paint.textScale = 4.3;
	paint.textThickness = 2;
	paint.roiRectThickness = 2;
	paint.areaRectThickness = 2;
	paint.locateLineThickness = 4;
	paint.locateLineLength = 30;
	paint.locateLineColor = CV_RGB(255, 0, 0);

	cv::Mat mat = cv::imread("img_sector.jpg");
	alg.initialize(area, &data, &enable, &paint);
	for (int i = 0; i < 5; ++i) {
		alg.getCrossLineCenter(i, mat);
		alg.calculateRoi(i);
		alg.calculateSfr(i, mat);
		alg.putText(i, mat);
	}
	cv::imwrite("img_sector_sfr.jpg", mat);
#endif
	system("pause");
	return 0;
}

 效果图展示

演示

 

  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值