• OpenCV4 :并行计算cv::parallel_for_


    OpenCV4 :并行计算cv::parallel_for_

    在计算机视觉和图像处理领域,OpenCV(开源计算机视觉库)是一个非常强大和广泛使用的库。随着图像分辨率的提高和计算任务的复杂度增加,实时处理变得越来越困难。为了解决这个问题,OpenCV提供了并行处理能力,可以显著提高代码的性能。本文将介绍如何利用OpenCV的并行处理能力来优化图像处理任务。

    OpenCV的并行框架

    OpenCV自2.4版本以来就提供了一个并行框架,允许在多个核心或处理器上并行执行代码。该框架提供了一种简单且高效的方式来编写可以利用多核处理器的代码。OpenCV4继续沿用并扩展了这个并行框架,增加了对新硬件和平台的支持。

    官方文档中的并行框架教程为我们提供了详细的指南和示例代码,说明了如何使用OpenCV的cv::parallel_for_函数。

    cv::parallel_for_函数

    cv::parallel_for_函数是OpenCV并行框架的核心。该函数允许我们并行执行循环,每个循环迭代可以在不同的线程上执行。cv::parallel_for_函数接受一个cv::Range对象和一个实现了cv::ParallelLoopBody接口的对象。

    cv::parallel_for_(cv::Range(0, count), MyParallelLoopBody());
    
    • 1

    其中,MyParallelLoopBody需要实现cv::ParallelLoopBody接口的virtual void operator()(const cv::Range& range) const方法。

    并行卷积示例

    我们创建了两个并行卷积类:parallelConvparallelConvByRow,它们都继承了cv::ParallelLoopBody接口。parallelConv类按图像的每个像素并行执行卷积,而parallelConvByRow类则按图像的每行并行执行卷积。

    parallelConv

    parallelConv类的构造函数接受源图像、目标图像和卷积核作为参数。它还计算了卷积核的半径,并为源图像添加了边框以处理边界像素。

    class parallelConv : public cv::ParallelLoopBody
    {
    private:
    	Mat m_src;
    	Mat& m_dst;
    	Mat m_kernel;
    	int sz;
    
    public:
    	parallelConv(Mat src, Mat& dst, Mat kernel): m_src(src), m_dst(dst), m_kernel(kernel), sz(kernel.rows / 2)
    	{
    		cv::copyMakeBorder(src, m_src, sz, sz, sz, sz, cv::BORDER_REPLICATE);
    	}
    
    	virtual void operator()(const cv::Range& range) const override
    	{
    		for (int r = range.start; r < range.end; ++r)
    		{
    			auto [i, j] = std::div(r, m_dst.cols);
    			double value = 0;
    
    			for (int k = -sz; k <= sz; ++k)
    			{
    				auto sptr = m_src.ptr(i + sz + k);
    
    				for (int l = -sz; l <= sz; ++l)
    				{
    					value += m_kernel.at(k + sz, l + sz) * sptr[j + sz + l];
    				}
    			}
    			m_dst.at(i, j) = cv::saturate_cast(value);
    		}
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    operator()方法中,我们遍历了指定范围内的所有像素,并为每个像素执行卷积操作。

    parallelConvByRow

    parallelConv类类似,parallelConvByRow类也接受源图像、目标图像和卷积核作为参数,并为源图像添加了边框。

    class parallelConvByRow : public cv::ParallelLoopBody
    {
    private:
    	Mat m_src;
    	Mat& m_dst;
    	Mat m_kernel;
    	int sz;
    	int cols;
    
    public:
    	parallelConvByRow(Mat src, Mat& dst, Mat kernel)
    		: m_src(src), m_dst(dst), m_kernel(kernel), sz(kernel.rows / 2), cols(src.cols)
    	{
    		cv::copyMakeBorder(src, m_src, sz, sz, sz, sz, cv::BORDER_REPLICATE);
    	}
    
    	virtual void operator()(const cv::Range& range) const override
    	{
    		for (int i = range.start; i < range.end; ++i)
    		{
    			if (i >= m_dst.rows)
    			{
    				continue;
    			}
    			auto dptr = m_dst.ptr(i);
    
    			for (int j = 0; j < cols; ++j)
    			{
    				double value = 0;
    
    				for (int k = -sz; k <= sz; ++k)
    				{
    					auto sptr = m_src.ptr(i + sz + k);
    
    					for (int l = -sz; l <= sz; ++l)
    					{
    						value += m_kernel.at(k + sz, l + sz) * sptr[j + sz + l];
    					}
    				}
    				dptr[j] = cv::saturate_cast(value);
    			}
    		}
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    operator()方法中,我们遍历了指定范围内的所有行,并为每行的每个像素执行卷积操作。

    性能比较

    通过比较顺序卷积和两种并行卷积的执行时间,我们可以看到并行卷积显著提高了性能。尤其是在处理大图像或使用大卷积核时,这种性能提升尤为明显。

    	// 非并行方法
    	auto start_seq = std::chrono::high_resolution_clock::now();
    	seqConv(src, dst_seq, kernel);
    	auto end_seq = std::chrono::high_resolution_clock::now();
    	std::chrono::duration diff_seq = end_seq - start_seq;
    	std::cout << "Time taken by sequential method: " << diff_seq.count() << " s" << std::endl;
    
    	// 方法 1:整体遍历
    	auto start1 = std::chrono::high_resolution_clock::now();
    	parallelConv obj1(src, dst1, kernel);
    	cv::parallel_for_(cv::Range(0, src.rows * src.cols), obj1);
    	auto end1 = std::chrono::high_resolution_clock::now();
    	std::chrono::duration diff1 = end1 - start1;
    	std::cout << "Time taken by whole image traversal: " << diff1.count() << " s" << std::endl;
    
    	// 方法 2:按行遍历
    	auto start2 = std::chrono::high_resolution_clock::now();
    	parallelConvByRow obj2(src, dst2, kernel);
    	cv::parallel_for_(cv::Range(0, src.rows), obj2);
    	auto end2 = std::chrono::high_resolution_clock::now();
    	std::chrono::duration diff2 = end2 - start2;
    	std::cout << "Time taken by row-by-row traversal: " << diff2.count() << " s" << std::endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    Time taken by sequential method: 0.308864 s
    Time taken by whole image traversal: 0.2328 s
    Time taken by row-by-row traversal: 0.169044 s
    
    • 1
    • 2
    • 3

    image-20231017150538260

    image-20231017150442676

    完整代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using cv::Mat;
    
    void seqConv(Mat src, Mat& dst, Mat kernel)
    {
    	const int rows = src.rows, cols = src.cols;
    	dst = Mat(rows, cols, src.type());
    
    	int sz = kernel.rows / 2;
    	Mat src_padded;
    	cv::copyMakeBorder(src, src_padded, sz, sz, sz, sz, CV_HAL_BORDER_REPLICATE);
    
    	for (int i = 0; i < rows; ++i)
    	{
    		auto dptr = dst.ptr<uchar>(i);
    
    		for (int j = 0; j < cols; ++j)
    		{
    			double value = 0;
    			for (int k = -sz; k <= sz; ++k)
    			{
    				auto sptr = src_padded.ptr<uchar>(i + sz + k);
    
    				for (int l = -sz; l <= sz; ++l)
    				{
    					value += kernel.ptr<double>(k + sz)[l + sz] * sptr[j + sz + l];
    				}
    			}
    			dptr[j] = cv::saturate_cast<uchar>(value);
    		}
    	}
    }
    
    
    class parallelConv : public cv::ParallelLoopBody
    {
    private:
    	Mat m_src;
    	Mat& m_dst;
    	Mat m_kernel;
    	int sz;
    
    public:
    	parallelConv(Mat src, Mat& dst, Mat kernel): m_src(src), m_dst(dst), m_kernel(kernel), sz(kernel.rows / 2)
    	{
    		cv::copyMakeBorder(src, m_src, sz, sz, sz, sz, cv::BORDER_REPLICATE);
    	}
    
    	virtual void operator()(const cv::Range& range) const override
    	{
    		for (int r = range.start; r < range.end; ++r)
    		{
    			auto [i, j] = std::div(r, m_dst.cols);
    			double value = 0;
    
    			for (int k = -sz; k <= sz; ++k)
    			{
    				auto sptr = m_src.ptr(i + sz + k);
    
    				for (int l = -sz; l <= sz; ++l)
    				{
    					value += m_kernel.at<double>(k + sz, l + sz) * sptr[j + sz + l];
    				}
    			}
    			m_dst.at<uchar>(i, j) = cv::saturate_cast<uchar>(value);
    		}
    	}
    };
    
    class parallelConvByRow : public cv::ParallelLoopBody
    {
    private:
    	Mat m_src;
    	Mat& m_dst;
    	Mat m_kernel;
    	int sz;
    	int cols;
    
    public:
    	parallelConvByRow(Mat src, Mat& dst, Mat kernel)
    		: m_src(src), m_dst(dst), m_kernel(kernel), sz(kernel.rows / 2), cols(src.cols)
    	{
    		cv::copyMakeBorder(src, m_src, sz, sz, sz, sz, cv::BORDER_REPLICATE);
    	}
    
    	virtual void operator()(const cv::Range& range) const override
    	{
    		for (int i = range.start; i < range.end; ++i)
    		{
    			if (i >= m_dst.rows)
    			{
    				continue;
    			}
    			auto dptr = m_dst.ptr<uchar>(i);
    
    			for (int j = 0; j < cols; ++j)
    			{
    				double value = 0;
    
    				for (int k = -sz; k <= sz; ++k)
    				{
    					auto sptr = m_src.ptr(i + sz + k);
    
    					for (int l = -sz; l <= sz; ++l)
    					{
    						value += m_kernel.at<double>(k + sz, l + sz) * sptr[j + sz + l];
    					}
    				}
    				dptr[j] = cv::saturate_cast<uchar>(value);
    			}
    		}
    	}
    };
    
    int main(int argc, char* argv[])
    {
    	cv::setNumThreads(4);
    	Mat src = cv::imread(R"(C:\4.jpg)", cv::IMREAD_GRAYSCALE); // 读取灰度图像
    	if (src.empty())
    	{
    		std::cerr << "Could not read the image!" << std::endl;
    		return 1;
    	}
    
    	Mat kernel = (cv::Mat_<double>(7, 7) << 0, 0, 0, 0, 0, 0, 0,
    		0, 0, -1, -1, -1, 0, 0,
    		0, -1, -1, -1, -1, -1, 0,
    		0, -1, -1, 24, -1, -1, 0,
    		0, -1, -1, -1, -1, -1, 0,
    		0, 0, -1, -1, -1, 0, 0,
    		0, 0, 0, 0, 0, 0, 0);
    
    	Mat dst1, dst2, dst_seq;
    	dst1 = Mat::zeros(src.size(), src.type());
    	dst2 = Mat::zeros(src.size(), src.type());
    	parallelConv obj(src, dst1, kernel);
    	cv::parallel_for_(cv::Range(0, src.rows * src.cols), obj);
    
    	// 非并行方法
    	auto start_seq = std::chrono::high_resolution_clock::now();
    	seqConv(src, dst_seq, kernel);
    	auto end_seq = std::chrono::high_resolution_clock::now();
    	std::chrono::duration<double> diff_seq = end_seq - start_seq;
    	std::cout << "Time taken by sequential method: " << diff_seq.count() << " s" << std::endl;
    
    	// 方法 1:整体遍历
    	auto start1 = std::chrono::high_resolution_clock::now();
    	parallelConv obj1(src, dst1, kernel);
    	cv::parallel_for_(cv::Range(0, src.rows * src.cols), obj1);
    	auto end1 = std::chrono::high_resolution_clock::now();
    	std::chrono::duration<double> diff1 = end1 - start1;
    	std::cout << "Time taken by whole image traversal: " << diff1.count() << " s" << std::endl;
    
    	// 方法 2:按行遍历
    	auto start2 = std::chrono::high_resolution_clock::now();
    	parallelConvByRow obj2(src, dst2, kernel);
    	cv::parallel_for_(cv::Range(0, src.rows), obj2);
    	auto end2 = std::chrono::high_resolution_clock::now();
    	std::chrono::duration<double> diff2 = end2 - start2;
    	std::cout << "Time taken by row-by-row traversal: " << diff2.count() << " s" << std::endl;
    
    	cv::imshow("Original Image", src);
    	cv::imshow("Sequential Method", dst_seq);
    	cv::imshow("Whole Image Traversal", dst1);
    	cv::imshow("Row-by-Row Traversal", dst2);
    
    	cv::waitKey(0);
    
    	return 0;
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178

    公众号:coding日记

  • 相关阅读:
    Keras中model.evaluate() 返回的是 loss value 和 metrics values
    Kotlin高仿微信-第28篇-朋友圈-预览图片、预览小视频
    luckysheet的使用——13.开启表格非编辑模式(指定单元格可编辑,除此以外其他单元格全部不可编辑)
    Flume实时采集mysql数据到kafka中并输出
    typescript21-接口和类型别名的对比
    为什么用Selenium做自动化测试
    研发提效必备技能:手把手教你基于Docker搭建Maven私服仓库
    刷题之路:1216 - 【基础】数塔问题(递推求解)题解
    「容器管理系统」 1. 开篇:框架选型和环境搭建
    Python-将常用库写入到一个Python程序里面,后续使用直接导入这个文件即可,就相当于导入了所有的库,就不用每次都写一堆的import了
  • 原文地址:https://blog.csdn.net/qq_42896106/article/details/133884712