#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, ¢erCentroidOffset);
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;
}
项目展示:
此项目所使用的OpenCV下载链接:
已封装好的C++ DLL链接https://download.csdn.net/download/qq_31629063/89318449https://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;
}
效果图展示
演示