我正在使用OpenCV版本3.4.5。我试图了解OpenCV中cv::Mat
的行为。我将一些8位矩阵相乘并将它们加起来。结果似乎不一致。
OpenCV是否在每次乘法之后或每次加法之后立即将浮点结果转换回8bit,或者这里发生了某些“融合乘法加法”?
#include <iostream>
#include <opencv2/opencv.hpp>
int main() {
for (int value = 1; value < 10; value++) {
cv::Mat im(1, 1, CV_8U, value);
cv::Mat result[10];
result[0] = im*0.1f;
result[1] = im*0.1f + im*0.1f;
result[2] = im*0.1f + im*0.1f + im*0.1f;
result[3] = im*0.1f + im*0.1f + im*0.1f + im*0.1f;
result[4] = im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f;
result[5] = im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f;
result[6] = im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f;
result[7] = im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f;
result[8] = im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f;
result[9] = im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f + im*0.1f;
std::cout << "base value: " << value << std::endl;
for (int i = 0; i < 10; i++) {
std::cout << i + 1 << ": " << int(result[i].at<uint8_t>(0, 0)) << "\t";
}
std::cout << std::endl;
}
return 0;
}
输出为:
base value: 1
1: 0 2: 0 3: 0 4: 0 5: 0 6: 0 7: 0 8: 0 9: 0 10: 0
base value: 2
1: 0 2: 0 3: 0 4: 0 5: 0 6: 0 7: 0 8: 0 9: 0 10: 0
base value: 3
1: 0 2: 1 3: 1 4: 1 5: 1 6: 1 7: 1 8: 1 9: 1 10: 1
base value: 4
1: 0 2: 1 3: 1 4: 1 5: 1 6: 1 7: 1 8: 1 9: 1 10: 1
base value: 5
1: 0 2: 1 3: 2 4: 2 5: 2 6: 2 7: 2 8: 2 9: 2 10: 2
base value: 6
1: 1 2: 1 3: 2 4: 3 5: 4 6: 5 7: 6 8: 7 9: 8 10: 9
base value: 7
1: 1 2: 1 3: 2 4: 3 5: 4 6: 5 7: 6 8: 7 9: 8 10: 9
base value: 8
1: 1 2: 2 3: 3 4: 4 5: 5 6: 6 7: 7 8: 8 9: 9 10: 10
base value: 9
1: 1 2: 2 3: 3 4: 4 5: 5 6: 6 7: 7 8: 8 9: 9 10: 10
最佳答案
在OpenCV中,出于性能原因,不会立即评估cv::Mat
之间的操作。
它使用延迟评估。如果执行A = B*0.1 + C*0.1
(A
,B
,C
为cv::Mat
),则会创建一个保存cv::MatExpr
,B
以及其比例值C
和0.1
的0.1
。并且仅在操作次数超过3或分配给实际的cv::Mat
时才进行评估。
如果不将B*0.1 + C*0.1
分配给实际的cv::Mat
,则不会进行评估。
如果执行A = B*0.1 + C*0.1 + D*0.1
,则会创建一个保存cv::MatExpr
和B*0.1
的C*0.1
,然后创建另一个保存最后cv::MatExpr
的评估和cv::MatExpr
的D*0.1
。这就是为什么操作超过3是荒谬的。评估期间,由于类型不是浮点数,因此0.x丢失。
为避免此问题,只需将type设置为CV_32F
或CV_64F
即可。
您的im*0.1f + im*0.1f + im*0.1f + ...
实际上是这样工作的。
result = im*0.1f + im*0.1f + im*0.1f + im*0.1f + ...
∧ │ │ │ │
│ MatExpr MatExpr MatExpr MatExpr ...
│ │ │ │ │
│ └────┬────┘ │ │
│ MatExpr │ │
│eval │ │ │
│ └>────────┬───<┘ │
│ eval MatExpr │
│ │ │
│ └>────────┬───<┘
│ eval MatExpr
│ ... ...
│
│ └>────────┬───<┘
│ eval MatExpr
│ │
└<──────────────────────────────────────────<┘
matop.cpp
cv::Mat
* double
class MatOp_AddEx : public MatOp
{
public:
void assign(const MatExpr& expr, Mat& m, int type=-1) const;
void add(const MatExpr& e1, const Scalar& s, MatExpr& res) const;
static void makeExpr(MatExpr& res, const Mat& a, const Mat& b, double alpha, double beta, const Scalar& s=Scalar());
// erased others for clarity
};
static MatOp_AddEx g_MatOp_AddEx; // only visible in matop.cpp
MatExpr operator * (const Mat& a, double s)
{
MatExpr e;
MatOp_AddEx::makeExpr(e, a, Mat(), s, 0); // meaning a*s + NULL-mat*0
return e;
}
inline void MatOp_AddEx::makeExpr(MatExpr& res, const Mat& a, const Mat& b, double alpha, double beta, const Scalar& s)
{
res = MatExpr(&g_MatOp_AddEx, 0, a, b, Mat(), alpha, beta, s); // MatExpr constructor
}
cv::MatExpr
+ cv::MatExpr
MatExpr operator + (const MatExpr& e1, const MatExpr& e2)
{
MatExpr en;
e1.op->add(e1, e2, en); // MatOp_AddEx inherits MatOp
return en;
}
void MatOp::add(const MatExpr& e1, const MatExpr& e2, MatExpr& res) const
{
CV_INSTRUMENT_REGION()
if( this == e2.op )
{
double alpha = 1, beta = 1;
Scalar s;
Mat m1, m2;
if( isAddEx(e1) && (!e1.b.data || e1.beta == 0) )
{
m1 = e1.a;
alpha = e1.alpha;
s = e1.s;
}
else
e1.op->assign(e1, m1); // <- Evaluation. Remember that type is set to auto(-1) by default
if( isAddEx(e2) && (!e2.b.data || e2.beta == 0) )
{
m2 = e2.a;
beta = e2.alpha;
s += e2.s;
}
else
e2.op->assign(e2, m2); // <- Evaluation
MatOp_AddEx::makeExpr(res, m1, m2, alpha, beta, s);
}
else
e2.op->add(e1, e2, res); // <- Evaluation
}
void MatOp_AddEx::assign(const MatExpr& e, Mat& m, int _type) const
{
Mat temp, &dst = _type == -1 || e.a.type() == _type ? m : temp;
if( e.b.data )
{
if( e.s == Scalar() || !e.s.isReal() )
{
if( e.alpha == 1 )
{
if( e.beta == 1 )
cv::add(e.a, e.b, dst);
else if( e.beta == -1 )
cv::subtract(e.a, e.b, dst);
else
cv::scaleAdd(e.b, e.beta, e.a, dst);
}
else if( e.beta == 1 )
{
if( e.alpha == -1 )
cv::subtract(e.b, e.a, dst);
else
cv::scaleAdd(e.a, e.alpha, e.b, dst);
}
else
cv::addWeighted(e.a, e.alpha, e.b, e.beta, 0, dst);
if( !e.s.isReal() )
cv::add(dst, e.s, dst);
}
else
cv::addWeighted(e.a, e.alpha, e.b, e.beta, e.s[0], dst);
}
else if( e.s.isReal() && (dst.data != m.data || fabs(e.alpha) != 1))
{
e.a.convertTo(m, _type, e.alpha, e.s[0]);
return;
}
else if( e.alpha == 1 )
cv::add(e.a, e.s, dst);
else if( e.alpha == -1 )
cv::subtract(e.s, e.a, dst);
else
{
e.a.convertTo(dst, e.a.type(), e.alpha);
cv::add(dst, e.s, dst);
}
if( dst.data != m.data )
dst.convertTo(m, m.type());
}