使用某些系数将图像转换为灰度的最快方法
Fastest way of converting image to grayscale using certain coefficients
我需要使用自定义公式将 cv::Mat
转换为灰度。输入矩阵的每个通道都必须乘以某个系数。
这是操作的伪代码:
Y = 0.2126*R + 0.7152*G + 0.0722*B
输入矩阵为 CV_32FC3
,输出必须为 CV_32FC1.
使用 2 个循环并按顺序计算每个像素的简单循环似乎不够快。
int rows = src.rows, cols = src.cols;
for (int row = 0; row < rows; row++){
const float* src_ptr = src.ptr<float>(row);
float* dst_ptr = dst.ptr<float>(row);
for (int col = 0; col < cols; col++){
dst_ptr[col] = ( 0.0722 * src_ptr[0] ) + ( 0.7152 * src_ptr[1] ) + ( 0.2126 * src_ptr[2]);
src_ptr += 3;
}
}
有没有更有效的方法?我希望使用 parallel_for_
循环,但我自己弄不明白。
这是我一直在研究的无效解决方案:
void MyOperator::getIntensity(const cv::Mat& src, cv::Mat& dst){
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0,nElements) , BGR2rec709Parallel((float*)src.data, (float*)dst.data));
}
class BGR2rec709Parallel : public cv::ParallelLoopBody
{
private:
float *src;
float *dst;
public:
BGR2rec709Parallel(float* src_ptr, float* dst_ptr) : src(src_ptr), dst(dst_ptr) {}
virtual void operator()( const cv::Range &r ) const
{
for (int i = r.start; i != r.end; ++i)
{
dst[i] = ( 0.0722 * src[i] ) + ( 0.7152 * src[i+1] ) + ( 0.2126 * src[i+2]);
}
}
virtual ~BGR2rec709Parallel();
};
主要问题是您没有正确索引源数据。
for (int i = r.start; i != r.end; ++i)
{
dst[i] = ( 0.0722 * src[i] )
+ ( 0.7152 * src[i+1] )
+ ( 0.2126 * src[i+2]);
}
让我们想象一下 r.start == 0
和 r.end == 2
。此代码相当于:
dst[0] = ( 0.0722 * src[0] ) + ( 0.7152 * src[1] ) + ( 0.2126 * src[2]);
dst[1] = ( 0.0722 * src[1] ) + ( 0.7152 * src[2] ) + ( 0.2126 * src[3]);
请注意,来自 src
的值最终被重复使用——这当然是不可取的。问题是 src
包含的值是 dst
的 3 倍,因此它的索引应该增长 3 倍快。
for (int i = r.start; i != r.end; ++i)
{
dst[i] = ( 0.0722 * src[i * 3] )
+ ( 0.7152 * src[i * 3 + 1] )
+ ( 0.2126 * src[i * 3 + 2]);
}
这应该可以使并行化版本正常工作,但是还有更多需要改进的地方。
可以通过使系数 float
而不是 double
(例如 0.0722f
而不是 0.0722
)。这是以牺牲一些精度为代价的,但避免了不必要的转换(并且可以更好地向量化)。
不要使用 C 风格的转换。在 BGR2rec709Parallel((float*)src.data, (float*)dst.data)
中你应该使用 reinterpret_cast<float>
。或者甚至更好,正如您在第一个版本中使用的那样,利用 cv::Mat::ptr
(即 src.ptr<float>()
、dst.ptr<float>()
)。
您使用parallel_for_
的方式不理想:
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0, nElements), /* ... */);
您没有指定第三个参数 (nstripes
)。根据我的观察(OpenCV 3.1.0/MSVS2013 和 3.4.3/MSVC2015),结果是 operator()
被调用的范围大小为 1。这会导致一些相当讨厌的开销,尤其是当大小范围1对应一个像素。
将 nstripes
设置为 cv::getNumThreads()
可以看到显着的改进。这将导致每个工作线程将工作拆分为 1 个范围,范围大小相似。
并行版本不能再处理不连续的 Mat
s(例如,获取更大图像的 ROI 的结果),而第一个版本可以。
为了解决这个问题,parallel_for_
应该使用行而不是像素,它的上下文应该是对输入和输出 Mat
的引用而不是数据指针。
将工作拆分为与线程数相等的条带数在这里并不重要,因为处理一行已经包含大量工作,但我们仍然可以这样做。
哦,还有一个要提。 for (int i = r.start; i != r.end; ++i)
-- 这里的 !=
是自找麻烦,如果你把 i
增加超过 1,最好在这里使用 <
。
最终版本如下所示:
class BGR2rec709ParallelC
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelC(cv::Mat const& src, cv::Mat& dst)
: src(src), dst(dst)
{
CV_Assert(src.type() == CV_32FC3);
CV_Assert(dst.type() == CV_32FC1);
CV_Assert(src.size() == dst.size());
}
virtual void operator()(const cv::Range &r) const
{
for (int row(r.start); row < r.end; ++row) {
convert_row(src.ptr<float>(row), dst.ptr<float>(row));
}
}
private:
void convert_row(float const* src_ptr, float * dst_ptr) const
{
for (int i(0); i != src.cols; ++i) {
dst_ptr[i] = (0.0722f * src_ptr[i * 3])
+ (0.7152f * src_ptr[i * 3 + 1])
+ (0.2126f * src_ptr[i * 3 + 2]);
}
}
private:
cv::Mat const& src;
cv::Mat& dst;
};
void get_intensity_v4(cv::Mat const& src, cv::Mat& dst)
{
parallel_for_(cv::Range(0, src.rows)
, BGR2rec709ParallelC(src, dst)
, cv::getNumThreads());
}
比较各种实现的性能的完整测试程序:
#include <opencv2/opencv.hpp>
void get_intensity_base(cv::Mat const& src, cv::Mat& dst)
{
cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY);
}
void get_intensity_v1a(cv::Mat const& src, cv::Mat& dst)
{
int rows = src.rows, cols = src.cols;
for (int row(0); row < rows; ++row) {
float const* src_ptr = src.ptr<float>(row);
float* dst_ptr = dst.ptr<float>(row);
for (int col(0); col < cols; ++col, src_ptr += 3) {
dst_ptr[col] = static_cast<float>((0.0722 * src_ptr[0])
+ (0.7152 * src_ptr[1])
+ (0.2126 * src_ptr[2]));
}
}
}
void get_intensity_v1b(cv::Mat const& src, cv::Mat& dst)
{
int rows = src.rows, cols = src.cols;
for (int row(0); row < rows; ++row) {
float const* src_ptr = src.ptr<float>(row);
float* dst_ptr = dst.ptr<float>(row);
for (int col(0); col < cols; ++col, src_ptr += 3) {
dst_ptr[col] = (0.0722f * src_ptr[0])
+ (0.7152f * src_ptr[1])
+ (0.2126f * src_ptr[2]);
}
}
}
class BGR2rec709ParallelA
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelA(float const* src, float* dst) : src(src), dst(dst) {}
virtual void operator()(cv::Range const& r) const
{
for (int i(r.start); i < r.end; ++i) {
dst[i] = static_cast<float>((0.0722 * src[i * 3])
+ (0.7152 * src[i * 3 + 1])
+ (0.2126 * src[i * 3 + 2]));
}
}
private:
float const* src;
float* dst;
};
class BGR2rec709ParallelB
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelB(float const* src, float* dst) : src(src), dst(dst) {}
virtual void operator()(cv::Range const& r) const
{
for (int i(r.start); i < r.end; ++i) {
dst[i] = (0.0722f * src[i * 3])
+ (0.7152f * src[i * 3 + 1])
+ (0.2126f * src[i * 3 + 2]);
}
}
private:
float const* src;
float* dst;
};
template <typename LoopBody>
void get_intensity_v2(cv::Mat const& src, cv::Mat& dst)
{
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0, nElements)
, LoopBody(src.ptr<float>(), dst.ptr<float>()));
}
template <typename LoopBody>
void get_intensity_v3(cv::Mat const& src, cv::Mat& dst)
{
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0, nElements)
, LoopBody(src.ptr<float>(), dst.ptr<float>())
, cv::getNumThreads());
}
class BGR2rec709ParallelC
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelC(cv::Mat const& src, cv::Mat& dst)
: src(src), dst(dst)
{
CV_Assert(src.type() == CV_32FC3);
CV_Assert(dst.type() == CV_32FC1);
CV_Assert(src.size() == dst.size());
}
virtual void operator()(const cv::Range &r) const
{
for (int row(r.start); row < r.end; ++row) {
convert_row(src.ptr<float>(row), dst.ptr<float>(row));
}
}
private:
void convert_row(float const* src_ptr, float * dst_ptr) const
{
for (int i(0); i != src.cols; ++i) {
dst_ptr[i] = (0.0722f * src_ptr[i * 3])
+ (0.7152f * src_ptr[i * 3 + 1])
+ (0.2126f * src_ptr[i * 3 + 2]);
}
}
private:
cv::Mat const& src;
cv::Mat& dst;
};
void get_intensity_v4(cv::Mat const& src, cv::Mat& dst)
{
parallel_for_(cv::Range(0, src.rows)
, BGR2rec709ParallelC(src, dst)
, cv::getNumThreads());
}
cv::Mat test(std::string const& name
, cv::Mat const& input
, void(*fn)(cv::Mat const&, cv::Mat&))
{
cv::Mat output(input.size(), CV_32FC1); // pre-allocate
std::cout << name << "\n";
int64 min_ticks(0x7FFFFFFFFFFFFFFF);
for (int i(0); i < 32; ++i) {
int64 t_start(cv::getTickCount());
fn(input, output);
int64 t_stop(cv::getTickCount());
min_ticks = std::min(min_ticks, t_stop - t_start);
}
std::cout << " >= " << min_ticks << " ticks\n";
return output;
}
cv::Mat3f make_test_data(int rows, int cols)
{
cv::Mat m(rows, cols, CV_16UC3);
cv::randu(m, 0, 0x10000);
cv::Mat3f result;
m.convertTo(result, CV_32FC3, 1.0 / 0xFFFF);
return result;
}
int main()
{
cv::Mat input(make_test_data(4096, 4096));
test("Base", input, get_intensity_base);
cv::Mat out_v1a = test("V1A", input, get_intensity_v1a);
cv::Mat out_v1b = test("V1B", input, get_intensity_v1b);
cv::Mat out_v2a = test("V2A", input, get_intensity_v2<BGR2rec709ParallelA>);
cv::Mat out_v2b = test("V2B", input, get_intensity_v2<BGR2rec709ParallelB>);
cv::Mat out_v3a = test("V3A", input, get_intensity_v3<BGR2rec709ParallelA>);
cv::Mat out_v3b = test("V3B", input, get_intensity_v3<BGR2rec709ParallelB>);
cv::Mat out_v4 = test("V4", input, get_intensity_v4);
std::cout << "Differences V1A vs V2A: " << cv::countNonZero(out_v1a != out_v2a) << "\n";
std::cout << "Differences V1B vs V2B: " << cv::countNonZero(out_v1b != out_v2b) << "\n";
std::cout << "Differences V1B vs V3B: " << cv::countNonZero(out_v1b != out_v3b) << "\n";
std::cout << "Differences V1B vs V4: " << cv::countNonZero(out_v1b != out_v4) << "\n";
return 0;
}
控制台输出(OpenCV 3.1.0/MSVC2013/x64/i7-4930K):
Base
>= 126365 ticks
V1A
>= 500890 ticks
V1B
>= 331197 ticks
V2A
>= 746851 ticks
V2B
>= 704011 ticks
V3A
>= 148181 ticks
V3B
>= 134176 ticks
V4
>= 133750 ticks
Differences V1A vs V2A: 0
Differences V1B vs V2B: 0
Differences V1B vs V3B: 0
Differences V1B vs V4: 0
控制台输出(OpenCV 3.4.3/MSVC2015/x64/i7-4930K):
Base
>= 123620 ticks
V1A
>= 503707 ticks
V1B
>= 331801 ticks
V2A
>= 1768515 ticks
V2B
>= 1710579 ticks
V3A
>= 145451 ticks
V3B
>= 135767 ticks
V4
>= 131438 ticks
Differences V1A vs V2A: 0
Differences V1B vs V2B: 0
Differences V1B vs V3B: 0
Differences V1B vs V4: 0
注意:请注意这里的细粒度 parallel_for_
版本有多差!
更新:
正如 Nuzhny, here's an implementation using cv::Mat::forEach
所建议的以及一个 lambda。
void get_intensity_v5(cv::Mat const& src, cv::Mat& dst)
{
CV_Assert(src.type() == CV_32FC3);
CV_Assert(dst.type() == CV_32FC1);
CV_Assert(src.size() == dst.size());
dst.forEach<float>(
[&](float& pixel, int const* po) -> void
{
cv::Vec3f const& in_pixel(src.at<cv::Vec3f>(po));
pixel = (0.0722f * in_pixel[0])
+ (0.7152f * in_pixel[1])
+ (0.2126f * in_pixel[2]);
}
);
}
额外的控制台输出:
V5
>= 123071 ticks
Differences V1B vs V5: 0
在这一点上,老实说,我无法解释为什么它表现更好——forEach
实现使用 parallel_for_
按行分块...
我需要使用自定义公式将 cv::Mat
转换为灰度。输入矩阵的每个通道都必须乘以某个系数。
这是操作的伪代码:
Y = 0.2126*R + 0.7152*G + 0.0722*B
输入矩阵为 CV_32FC3
,输出必须为 CV_32FC1.
使用 2 个循环并按顺序计算每个像素的简单循环似乎不够快。
int rows = src.rows, cols = src.cols;
for (int row = 0; row < rows; row++){
const float* src_ptr = src.ptr<float>(row);
float* dst_ptr = dst.ptr<float>(row);
for (int col = 0; col < cols; col++){
dst_ptr[col] = ( 0.0722 * src_ptr[0] ) + ( 0.7152 * src_ptr[1] ) + ( 0.2126 * src_ptr[2]);
src_ptr += 3;
}
}
有没有更有效的方法?我希望使用 parallel_for_
循环,但我自己弄不明白。
这是我一直在研究的无效解决方案:
void MyOperator::getIntensity(const cv::Mat& src, cv::Mat& dst){
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0,nElements) , BGR2rec709Parallel((float*)src.data, (float*)dst.data));
}
class BGR2rec709Parallel : public cv::ParallelLoopBody
{
private:
float *src;
float *dst;
public:
BGR2rec709Parallel(float* src_ptr, float* dst_ptr) : src(src_ptr), dst(dst_ptr) {}
virtual void operator()( const cv::Range &r ) const
{
for (int i = r.start; i != r.end; ++i)
{
dst[i] = ( 0.0722 * src[i] ) + ( 0.7152 * src[i+1] ) + ( 0.2126 * src[i+2]);
}
}
virtual ~BGR2rec709Parallel();
};
主要问题是您没有正确索引源数据。
for (int i = r.start; i != r.end; ++i)
{
dst[i] = ( 0.0722 * src[i] )
+ ( 0.7152 * src[i+1] )
+ ( 0.2126 * src[i+2]);
}
让我们想象一下 r.start == 0
和 r.end == 2
。此代码相当于:
dst[0] = ( 0.0722 * src[0] ) + ( 0.7152 * src[1] ) + ( 0.2126 * src[2]);
dst[1] = ( 0.0722 * src[1] ) + ( 0.7152 * src[2] ) + ( 0.2126 * src[3]);
请注意,来自 src
的值最终被重复使用——这当然是不可取的。问题是 src
包含的值是 dst
的 3 倍,因此它的索引应该增长 3 倍快。
for (int i = r.start; i != r.end; ++i)
{
dst[i] = ( 0.0722 * src[i * 3] )
+ ( 0.7152 * src[i * 3 + 1] )
+ ( 0.2126 * src[i * 3 + 2]);
}
这应该可以使并行化版本正常工作,但是还有更多需要改进的地方。
可以通过使系数 float
而不是 double
(例如 0.0722f
而不是 0.0722
)。这是以牺牲一些精度为代价的,但避免了不必要的转换(并且可以更好地向量化)。
不要使用 C 风格的转换。在 BGR2rec709Parallel((float*)src.data, (float*)dst.data)
中你应该使用 reinterpret_cast<float>
。或者甚至更好,正如您在第一个版本中使用的那样,利用 cv::Mat::ptr
(即 src.ptr<float>()
、dst.ptr<float>()
)。
您使用parallel_for_
的方式不理想:
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0, nElements), /* ... */);
您没有指定第三个参数 (nstripes
)。根据我的观察(OpenCV 3.1.0/MSVS2013 和 3.4.3/MSVC2015),结果是 operator()
被调用的范围大小为 1。这会导致一些相当讨厌的开销,尤其是当大小范围1对应一个像素。
将 nstripes
设置为 cv::getNumThreads()
可以看到显着的改进。这将导致每个工作线程将工作拆分为 1 个范围,范围大小相似。
并行版本不能再处理不连续的 Mat
s(例如,获取更大图像的 ROI 的结果),而第一个版本可以。
为了解决这个问题,parallel_for_
应该使用行而不是像素,它的上下文应该是对输入和输出 Mat
的引用而不是数据指针。
将工作拆分为与线程数相等的条带数在这里并不重要,因为处理一行已经包含大量工作,但我们仍然可以这样做。
哦,还有一个要提。 for (int i = r.start; i != r.end; ++i)
-- 这里的 !=
是自找麻烦,如果你把 i
增加超过 1,最好在这里使用 <
。
最终版本如下所示:
class BGR2rec709ParallelC
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelC(cv::Mat const& src, cv::Mat& dst)
: src(src), dst(dst)
{
CV_Assert(src.type() == CV_32FC3);
CV_Assert(dst.type() == CV_32FC1);
CV_Assert(src.size() == dst.size());
}
virtual void operator()(const cv::Range &r) const
{
for (int row(r.start); row < r.end; ++row) {
convert_row(src.ptr<float>(row), dst.ptr<float>(row));
}
}
private:
void convert_row(float const* src_ptr, float * dst_ptr) const
{
for (int i(0); i != src.cols; ++i) {
dst_ptr[i] = (0.0722f * src_ptr[i * 3])
+ (0.7152f * src_ptr[i * 3 + 1])
+ (0.2126f * src_ptr[i * 3 + 2]);
}
}
private:
cv::Mat const& src;
cv::Mat& dst;
};
void get_intensity_v4(cv::Mat const& src, cv::Mat& dst)
{
parallel_for_(cv::Range(0, src.rows)
, BGR2rec709ParallelC(src, dst)
, cv::getNumThreads());
}
比较各种实现的性能的完整测试程序:
#include <opencv2/opencv.hpp>
void get_intensity_base(cv::Mat const& src, cv::Mat& dst)
{
cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY);
}
void get_intensity_v1a(cv::Mat const& src, cv::Mat& dst)
{
int rows = src.rows, cols = src.cols;
for (int row(0); row < rows; ++row) {
float const* src_ptr = src.ptr<float>(row);
float* dst_ptr = dst.ptr<float>(row);
for (int col(0); col < cols; ++col, src_ptr += 3) {
dst_ptr[col] = static_cast<float>((0.0722 * src_ptr[0])
+ (0.7152 * src_ptr[1])
+ (0.2126 * src_ptr[2]));
}
}
}
void get_intensity_v1b(cv::Mat const& src, cv::Mat& dst)
{
int rows = src.rows, cols = src.cols;
for (int row(0); row < rows; ++row) {
float const* src_ptr = src.ptr<float>(row);
float* dst_ptr = dst.ptr<float>(row);
for (int col(0); col < cols; ++col, src_ptr += 3) {
dst_ptr[col] = (0.0722f * src_ptr[0])
+ (0.7152f * src_ptr[1])
+ (0.2126f * src_ptr[2]);
}
}
}
class BGR2rec709ParallelA
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelA(float const* src, float* dst) : src(src), dst(dst) {}
virtual void operator()(cv::Range const& r) const
{
for (int i(r.start); i < r.end; ++i) {
dst[i] = static_cast<float>((0.0722 * src[i * 3])
+ (0.7152 * src[i * 3 + 1])
+ (0.2126 * src[i * 3 + 2]));
}
}
private:
float const* src;
float* dst;
};
class BGR2rec709ParallelB
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelB(float const* src, float* dst) : src(src), dst(dst) {}
virtual void operator()(cv::Range const& r) const
{
for (int i(r.start); i < r.end; ++i) {
dst[i] = (0.0722f * src[i * 3])
+ (0.7152f * src[i * 3 + 1])
+ (0.2126f * src[i * 3 + 2]);
}
}
private:
float const* src;
float* dst;
};
template <typename LoopBody>
void get_intensity_v2(cv::Mat const& src, cv::Mat& dst)
{
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0, nElements)
, LoopBody(src.ptr<float>(), dst.ptr<float>()));
}
template <typename LoopBody>
void get_intensity_v3(cv::Mat const& src, cv::Mat& dst)
{
int nElements = src.cols * src.rows;
parallel_for_(cv::Range(0, nElements)
, LoopBody(src.ptr<float>(), dst.ptr<float>())
, cv::getNumThreads());
}
class BGR2rec709ParallelC
: public cv::ParallelLoopBody
{
public:
BGR2rec709ParallelC(cv::Mat const& src, cv::Mat& dst)
: src(src), dst(dst)
{
CV_Assert(src.type() == CV_32FC3);
CV_Assert(dst.type() == CV_32FC1);
CV_Assert(src.size() == dst.size());
}
virtual void operator()(const cv::Range &r) const
{
for (int row(r.start); row < r.end; ++row) {
convert_row(src.ptr<float>(row), dst.ptr<float>(row));
}
}
private:
void convert_row(float const* src_ptr, float * dst_ptr) const
{
for (int i(0); i != src.cols; ++i) {
dst_ptr[i] = (0.0722f * src_ptr[i * 3])
+ (0.7152f * src_ptr[i * 3 + 1])
+ (0.2126f * src_ptr[i * 3 + 2]);
}
}
private:
cv::Mat const& src;
cv::Mat& dst;
};
void get_intensity_v4(cv::Mat const& src, cv::Mat& dst)
{
parallel_for_(cv::Range(0, src.rows)
, BGR2rec709ParallelC(src, dst)
, cv::getNumThreads());
}
cv::Mat test(std::string const& name
, cv::Mat const& input
, void(*fn)(cv::Mat const&, cv::Mat&))
{
cv::Mat output(input.size(), CV_32FC1); // pre-allocate
std::cout << name << "\n";
int64 min_ticks(0x7FFFFFFFFFFFFFFF);
for (int i(0); i < 32; ++i) {
int64 t_start(cv::getTickCount());
fn(input, output);
int64 t_stop(cv::getTickCount());
min_ticks = std::min(min_ticks, t_stop - t_start);
}
std::cout << " >= " << min_ticks << " ticks\n";
return output;
}
cv::Mat3f make_test_data(int rows, int cols)
{
cv::Mat m(rows, cols, CV_16UC3);
cv::randu(m, 0, 0x10000);
cv::Mat3f result;
m.convertTo(result, CV_32FC3, 1.0 / 0xFFFF);
return result;
}
int main()
{
cv::Mat input(make_test_data(4096, 4096));
test("Base", input, get_intensity_base);
cv::Mat out_v1a = test("V1A", input, get_intensity_v1a);
cv::Mat out_v1b = test("V1B", input, get_intensity_v1b);
cv::Mat out_v2a = test("V2A", input, get_intensity_v2<BGR2rec709ParallelA>);
cv::Mat out_v2b = test("V2B", input, get_intensity_v2<BGR2rec709ParallelB>);
cv::Mat out_v3a = test("V3A", input, get_intensity_v3<BGR2rec709ParallelA>);
cv::Mat out_v3b = test("V3B", input, get_intensity_v3<BGR2rec709ParallelB>);
cv::Mat out_v4 = test("V4", input, get_intensity_v4);
std::cout << "Differences V1A vs V2A: " << cv::countNonZero(out_v1a != out_v2a) << "\n";
std::cout << "Differences V1B vs V2B: " << cv::countNonZero(out_v1b != out_v2b) << "\n";
std::cout << "Differences V1B vs V3B: " << cv::countNonZero(out_v1b != out_v3b) << "\n";
std::cout << "Differences V1B vs V4: " << cv::countNonZero(out_v1b != out_v4) << "\n";
return 0;
}
控制台输出(OpenCV 3.1.0/MSVC2013/x64/i7-4930K):
Base
>= 126365 ticks
V1A
>= 500890 ticks
V1B
>= 331197 ticks
V2A
>= 746851 ticks
V2B
>= 704011 ticks
V3A
>= 148181 ticks
V3B
>= 134176 ticks
V4
>= 133750 ticks
Differences V1A vs V2A: 0
Differences V1B vs V2B: 0
Differences V1B vs V3B: 0
Differences V1B vs V4: 0
控制台输出(OpenCV 3.4.3/MSVC2015/x64/i7-4930K):
Base
>= 123620 ticks
V1A
>= 503707 ticks
V1B
>= 331801 ticks
V2A
>= 1768515 ticks
V2B
>= 1710579 ticks
V3A
>= 145451 ticks
V3B
>= 135767 ticks
V4
>= 131438 ticks
Differences V1A vs V2A: 0
Differences V1B vs V2B: 0
Differences V1B vs V3B: 0
Differences V1B vs V4: 0
注意:请注意这里的细粒度 parallel_for_
版本有多差!
更新:
正如 Nuzhny, here's an implementation using cv::Mat::forEach
所建议的以及一个 lambda。
void get_intensity_v5(cv::Mat const& src, cv::Mat& dst)
{
CV_Assert(src.type() == CV_32FC3);
CV_Assert(dst.type() == CV_32FC1);
CV_Assert(src.size() == dst.size());
dst.forEach<float>(
[&](float& pixel, int const* po) -> void
{
cv::Vec3f const& in_pixel(src.at<cv::Vec3f>(po));
pixel = (0.0722f * in_pixel[0])
+ (0.7152f * in_pixel[1])
+ (0.2126f * in_pixel[2]);
}
);
}
额外的控制台输出:
V5
>= 123071 ticks
Differences V1B vs V5: 0
在这一点上,老实说,我无法解释为什么它表现更好——forEach
实现使用 parallel_for_
按行分块...