我在openCV中编写了一些代码,想要找到一个非常大的矩阵数组的中值(单通道灰度,浮点数)。
我尝试了几种方法,例如对数组进行排序(使用std :: sort)并选择中间条目,但在与matlab中的中位函数进行比较时,它非常慢。确切地说 - 在matlab中需要0.25秒才能在openCV中花费超过19秒。
我的输入图像最初是尺寸为3840x2748(~1050万像素)的12位灰度图像,转换为浮点数(CV_32FC1),其中所有值现在都映射到范围[0,1]并且在某个点上我通过调用代码请求中值:
double myMedianValue = medianMat(Input);
函数medianMat是:
double medianMat(cv::Mat Input){
Input = Input.reshape(0,1); // spread Input Mat to single row
std::vector<double> vecFromMat;
Input.copyTo(vecFromMat); // Copy Input Mat to vector vecFromMat
std::sort( vecFromMat.begin(), vecFromMat.end() ); // sort vecFromMat
if (vecFromMat.size()%2==0) {return (vecFromMat[vecFromMat.size()/2-1]+vecFromMat[vecFromMat.size()/2])/2;} // in case of even-numbered matrix
return vecFromMat[(vecFromMat.size()-1)/2]; // odd-number of elements in matrix
}
我将函数medinaMat本身和各个部分计时 - 正如预期的瓶颈在于:
std::sort( vecFromMat.begin(), vecFromMat.end() ); // sort vecFromMat
这里有人有一个有效的解决方案吗?
谢谢!
修改 我尝试过使用Adi Shavit答案中给出的std :: nth_element。
函数medianMat现在读作:
double medianMat(cv::Mat Input){
Input = Input.reshape(0,1); // spread Input Mat to single row
std::vector<double> vecFromMat;
Input.copyTo(vecFromMat); // Copy Input Mat to vector vecFromMat
std::nth_element(vecFromMat.begin(), vecFromMat.begin() + vecFromMat.size() / 2, vecFromMat.end());
return vecFromMat[vecFromMat.size() / 2];}
运行时间从19秒降低到3.5秒。在使用中值函数的Matlab中,这仍然不到0.25秒......
答案 0 :(得分:21)
排序和获取中间元素不是找到中位数的最有效方法。它需要O(n log n)次操作。
使用C ++,您应该使用std::nth_element()
并使用中间迭代器。这是O(n)操作:
nth_element
是 部分排序 算法,可重新排列[first, last)
中的元素,以便:
nth
指向的元素会更改为该位置中出现的任何元素 如果[first, last)
已排序 。- 这个新的第n个元素之前的所有元素都小于或等于新的第n个元素之后的元素。
此外,您的原始数据是12位整数。您的实现做了一些事情,使得与Matlab的比较成问题:
vector<double>
假设您的图像在内存中是连续的,这是OpenCV的默认值,您应该使用CV_16C1
,并在reshape()
另一个应该非常快的选择是简单地构建图像的直方图 - 这是图像上的单个传递。然后,处理直方图,找到对应于每一侧像素的一半的bin - 这最多只能通过 bins 。
OpenCV文档有several tutorials on如何构建直方图。获得直方图后,累积bin值,直到通过3840x2748 / 2。这个箱子是你的中位数。
答案 1 :(得分:6)
从原始数据中找到它可能会更快。
由于原始数据具有12位值,因此只有
4096种不同的可能值。那是一张漂亮的小桌子!
在一次传递中浏览所有数据,并计算每个值的数量
你有。这是O(n)操作。然后很容易找到中位数,
只计算表格两端的onAfterRendering : function() {
$('document').ready(function(){
sap.ui.getCore().byId('input').focus();
});
}
个项目。
答案 2 :(得分:6)
行。
我在发布问题之前实际尝试了这个问题,并且由于一些愚蠢的错误,我取消了它作为解决方案的资格......无论如何它是:
我基本上为原始输入创建了一个直方图,其中包含2 ^ 12 = 4096个二进制位,计算CDF并对其进行标准化,使其从0映射到1,并找到CDF中等于或大于的最小索引。 0.5。然后我将该指数除以12 ^ 2,从而找到所请求的中值。现在它运行0.11秒(这是在调试模式下没有大量优化),这不到Matlab所需时间的一半。
这是函数(在我的情况下,nVals = 4096对应12位值):
double medianMat(cv::Mat Input, int nVals){
// COMPUTE HISTOGRAM OF SINGLE CHANNEL MATRIX
float range[] = { 0, nVals };
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
cv::Mat hist;
calcHist(&Input, 1, 0, cv::Mat(), hist, 1, &nVals, &histRange, uniform, accumulate);
// COMPUTE CUMULATIVE DISTRIBUTION FUNCTION (CDF)
cv::Mat cdf;
hist.copyTo(cdf);
for (int i = 1; i <= nVals-1; i++){
cdf.at<float>(i) += cdf.at<float>(i - 1);
}
cdf /= Input.total();
// COMPUTE MEDIAN
double medianVal;
for (int i = 0; i <= nVals-1; i++){
if (cdf.at<float>(i) >= 0.5) { medianVal = i; break; }
}
return medianVal/nVals; }