OpenCV使用pthread實現多執行緒加速處理影象

目錄

    POSIX執行緒(POSIX threads),簡稱Pthreads,是執行緒的POSIX標準。Pthread是由POSIX提出的一套通用的執行緒庫,在linux平臺下,它被廣泛的支援,而在windows平臺下,需要下載pthreads-w32庫。之所以選擇Pthreads庫作為多執行緒處理庫,是因此Android NDK開發時,可以使用pthread在C++中實現多執行緒處理,這樣,可以方便OpenCV的影象加速處理和演算法的移植。

1.pthread多執行緒加速

     下面是使用多執行緒(3個子執行緒)實現OpenCV影象加速的的方法,基本思路就是:先將影象分塊,比如分成3塊,每塊使用一個子執行緒進行處理,處理完後再合併成一塊影象,這樣就實現了OpenCV多執行緒加速影象處理的方法。

// pthreadDemo.cpp : 定義控制檯應用程式的入口點。
//

#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <string>
#include <opencv2/opencv.hpp>
using namespace std;
#define THREAD_NUMS 3

/*paramThread用於傳遞執行緒需要的引數值*/
struct paramThread
{
	int w;
	int h;
	uchar * data;
};

/********************************************************
*	@brief       : 多執行緒處理函式
*	@param  args : 多執行緒傳入的引數
*	@return      : void
********************************************************/
void * threadProcess(void* args) {

	pthread_t myid = pthread_self();
	paramThread *para = (paramThread *)args;
	int w = para->w;
	int h = para->h;
	cv::Mat image(h,w,CV_8UC3,(uchar *)para->data);
	//cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
	cv::blur(image, image,cv::Size(7,7), cv::Point(-1, -1), cv::BORDER_REPLICATE);
	//printf("thread id = %d, w=%d, h=%d\n", myid, w,h);
	//cv::imshow("image", image); cv::waitKey(2000);
	pthread_exit(NULL);
	return NULL;
}

/********************************************************
*	@brief       : 實現影象分割,
*	@param  num  :  分割個數
*	@param  type : 0:垂直分割(推薦),1:水平分割(不推薦)
*	@return      : vector<cv::Mat>
*   PS:使用水平分割時(type=1),處理完後必須呼叫catImage進行拼接,
*   使用垂直分割時(type=0),可以不進行catImage,因為是對原圖進行操作的
********************************************************/
vector<cv::Mat> splitImage(cv::Mat image, int num,int type) {
	int rows = image.rows;
	int cols = image.cols;
	vector<cv::Mat> v;
	if (type == 0) {//垂直分割
		for (size_t i = 0; i < num; i++) {
			int star = rows / num*i;
			int end = rows / num*(i + 1);
			if (i == num - 1) {
				end = rows;
			}
			//cv::Mat b = image.rowRange(star, end);
			v.push_back(image.rowRange(star, end));
		}
	}
	else if (type == 1) {//水平分割
		for (size_t i = 0; i < num; i++){
			int star = cols / num*i;
			int end = cols / num*(i + 1);
			if (i == num - 1) {
				end = cols;
			}
			//cv::Mat b = image.colRange(star, end);
			/*解決水平分割的Bug:必須clone()*/
			v.push_back(image.colRange(star, end).clone());
		}
	}
	return  v;
}

/********************************************************
*	@brief       : 實現影象拼接,
*	@param  v    :  
*	@param  type : 0:垂直拼接,1:水平拼接
*	@return      : Mat
********************************************************/
cv::Mat catImage(vector<cv::Mat> v, int type) {
	cv::Mat dest= v.at(0);
	for (size_t i = 1; i < v.size(); i++)
	{
		if (type == 0)//垂直拼接
		{
			cv::vconcat(dest, v.at(i), dest);
		}
		else if (type == 1)//水平拼接
		{
			cv::hconcat(dest, v.at(i), dest);
		}
	}
	return dest;
}

int main() {
	string path = "D:\\imageEnhance\\images\\test.jpg";
	cv::Mat src = cv::imread(path);
	printf("image size =  w=%d, h=%d\n", src.cols, src.rows);

	cv::Mat image1 = src.clone();
	cv::Mat image2 = src.clone();
	cv::imshow("src", src); cv::waitKey(30);

	double T0 = static_cast<double>(cv::getTickCount());
	/*不使用多執行緒影象處理*/
	cv::blur(image1, image1, cv::Size(7, 7));
	double T1 = static_cast<double>(cv::getTickCount());

	/*使用多執行緒影象處理*/
	int type = 0;
	vector<cv::Mat> v = splitImage(image2, THREAD_NUMS, type);
	paramThread args[THREAD_NUMS];
	pthread_t pt[THREAD_NUMS];	//建立THREAD_NUMS個子執行緒
	for (size_t i = 0; i < THREAD_NUMS; i++)
	{
		args[i].h = v.at(i).rows;
		args[i].w = v.at(i).cols;
		args[i].data = v.at(i).data;
		pthread_create(&pt[i], NULL, &threadProcess, (void *)(&args[i]));
	}
	/*等待全部子執行緒處理完畢*/
	for (size_t i = 0; i < THREAD_NUMS; i++)
	{
		pthread_join(pt[i], NULL);
	}
	cv::Mat dest = catImage(v, type);
	double T2 = static_cast<double>(cv::getTickCount());
	printf("       run times = %3.3fms\n", (T1 - T0)*1000 / cv::getTickFrequency());
	printf("Thread run times = %3.3fms\n,", (T2 - T1)*1000 / cv::getTickFrequency());

	cv::imshow("dest", dest); cv::waitKey(30);
	cv::imshow("image2", image2); cv::waitKey(30);


	cv::waitKey(0);
	return 0;
}

     可以看到,對於一張3000*1877的大圖片,使用3個執行緒並行處理會比單執行緒處理快了將近4倍的速度。但比較尷尬的是,由於使用了影象分塊處理,進行影象blur模糊時,邊界會出現不規則的畫素,這時拼接在一起,會導致影象拼接的邊緣出現橫條的現象,見下面的模糊圖。

     一種決解的方法就是,可以在影象分割時,根據kernel size的大小的適當padding畫素,處理完再cutting這些padding的畫素,這部分我還沒有實現,有興趣的可以搞搞哈。

    這種使用pthread實現多執行緒影象處理加速方法,比較適合演算法過程跟邊界處理無關的情況,如RGB轉BGR,Gamma變換這些影象處理操作。

2.自己封裝的多執行緒cvThread類

    為了方面以後呼叫,這裡封裝了一個cvThread類:

    cvThread.h:

#pragma once
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <string>
#include <opencv2/opencv.hpp>
using namespace std;

class cvThread
{
public:
	cvThread();
	~cvThread();
	/*paramThread用於傳遞執行緒需要的引數值*/
	struct paramThread
	{
		int w;
		int h;
		uchar * data;
	};

	/********************************************************
	*	@brief       : 多執行緒要處理的影象操作
	*	@param  args : 多執行緒傳入的引數
	*	@return      : void
	********************************************************/
	static void * cvThreadBlur(void* args);

	/********************************************************
	*	@brief        : 多執行緒處理函式
	*	@param  image : 輸入/輸出Mat影象
	*	@param  type  : 分割型別0:垂直分割(推薦),1:水平分割(不推薦)
	*	@param  thread_num : 多執行緒個數
	*	@return      : void
	********************************************************/
	void  cvThreadProcess(cv::Mat &image, const int type, const int thread_num);
	void  cvThreadProcess(cv::Mat &image);
	/********************************************************
	*	@brief       : 實現影象分割,
	*	@param  num  :  分割個數
	*	@param  type : 0:垂直分割(推薦),1:水平分割(不推薦)
	*	@return      : vector<cv::Mat>
	*   PS:使用水平分割時(type=1),處理完後必須呼叫catImage進行拼接,
	*   使用垂直分割時(type=0),可以不進行catImage,因為是對原圖進行操作的
	********************************************************/
	vector<cv::Mat> splitImage(cv::Mat image, int num, int type);


	/********************************************************
	*	@brief       : 實現影象拼接,
	*	@param  v    :
	*	@param  type : 0:垂直拼接,1:水平拼接
	*	@return      : Mat
	********************************************************/
	cv::Mat catImage(vector<cv::Mat> v, int type);
};

    cvThread.cpp:

#include "cvThread.h"



cvThread::cvThread()
{
}


cvThread::~cvThread()
{
}
/********************************************************
*	@brief       : 多執行緒處理函式
*	@param  args : 多執行緒傳入的引數
*	@return      : void
********************************************************/
void * cvThread::cvThreadBlur(void* args) {
	pthread_t myid = pthread_self();
	paramThread *para = (paramThread *)args;
	int w = para->w;
	int h = para->h;
	cv::Mat image(h, w, CV_8UC3, (uchar *)para->data);
	/***************************************************/
	/*這裡實現多執行緒要處理的影象操作*/
	cv::blur(image, image, cv::Size(7, 7), cv::Point(-1, -1), cv::BORDER_REPLICATE);

	/***************************************************/
	pthread_exit(NULL);
	return NULL;
}



#define THREAD_NUMS 4
void  cvThread::cvThreadProcess(cv::Mat &image) {

	/*使用多執行緒影象處理*/
	int type = 0;
	vector<cv::Mat> v = splitImage(image, THREAD_NUMS, type);
	paramThread args[THREAD_NUMS];
	pthread_t pt[THREAD_NUMS];	//建立thread_num個子執行緒
	for (size_t i = 0; i < THREAD_NUMS; i++)
	{
		args[i].h = v.at(i).rows;
		args[i].w = v.at(i).cols;
		args[i].data = v.at(i).data;
		pthread_create(&pt[i], NULL, &cvThreadBlur, (void *)(&args[i]));
	}
	/*等待全部子執行緒處理完畢*/
	for (size_t i = 0; i < THREAD_NUMS; i++)
	{
		pthread_join(pt[i], NULL);
	}
	cv::Mat dest = catImage(v, type);
}

void  cvThread::cvThreadProcess(cv::Mat &image,const int type=0,const int thread_num=4) {
	/*使用多執行緒影象處理*/
	vector<cv::Mat> v = splitImage(image, thread_num, type);
	paramThread *args = new paramThread[thread_num];
	pthread_t *pt = new pthread_t[thread_num];	//建立thread_num個子執行緒
	for (size_t i = 0; i < thread_num; i++)
	{
		args[i].h = v.at(i).rows;
		args[i].w = v.at(i).cols;
		args[i].data = v.at(i).data;
		pthread_create(&pt[i], NULL, &cvThreadBlur, (void *)(&args[i]));
	}
	/*等待全部子執行緒處理完畢*/
	for (size_t i = 0; i < thread_num; i++)
	{
		pthread_join(pt[i], NULL);
	}
	/*PS:使用水平分割時(type = 1),處理完後必須呼叫catImage進行拼接,
	使用垂直分割時(type = 0),可以不進行catImage,因為是對原圖進行操作的*/
	if (type==1)
	{
		image = catImage(v, type);
	}
	delete []args;
	delete []pt;
}
/********************************************************
*	@brief       : 實現影象分割,
*	@param  num  :  分割個數
*	@param  type : 0:垂直分割(推薦),1:水平分割(不推薦)
*	@return      : vector<cv::Mat>
*   PS:使用水平分割時(type=1),處理完後必須呼叫catImage進行拼接,
*   使用垂直分割時(type=0),可以不進行catImage,因為是對原圖進行操作的
********************************************************/
vector<cv::Mat> cvThread::splitImage(cv::Mat image, int num, int type) {
	int rows = image.rows;
	int cols = image.cols;
	vector<cv::Mat> v;
	if (type == 0) {//垂直分割
		for (size_t i = 0; i < num; i++) {
			int star = rows / num*i;
			int end = rows / num*(i + 1);
			if (i == num - 1) {
				end = rows;
			}
			//cv::Mat b = image.rowRange(star, end);
			v.push_back(image.rowRange(star, end));
		}
	}
	else if (type == 1) {//水平分割
		for (size_t i = 0; i < num; i++) {
			int star = cols / num*i;
			int end = cols / num*(i + 1);
			if (i == num - 1) {
				end = cols;
			}
			//cv::Mat b = image.colRange(star, end);
			/*解決水平分割的Bug:必須clone()*/
			v.push_back(image.colRange(star, end).clone());
		}
	}
	return  v;
}

/********************************************************
*	@brief       : 實現影象拼接,
*	@param  v    :
*	@param  type : 0:垂直拼接,1:水平拼接
*	@return      : Mat
********************************************************/
cv::Mat cvThread::catImage(vector<cv::Mat> v, int type) {
	cv::Mat dest = v.at(0);
	for (size_t i = 1; i < v.size(); i++)
	{
		if (type == 0)//垂直拼接
		{
			cv::vconcat(dest, v.at(i), dest);
		}
		else if (type == 1)//水平拼接
		{
			cv::hconcat(dest, v.at(i), dest);
		}
	}
	return dest;
}

    測試方法main.cpp:

#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <string>
#include <opencv2/opencv.hpp>
#include "cvThread.h"
using namespace std;
int main() {
	string path = "D:\\imageEnhance\\images\\8-1.jpg";
	cv::Mat src = cv::imread(path);
	printf("image size =  w=%d, h=%d\n", src.cols, src.rows);

	cv::Mat image1 = src.clone();
	cv::Mat image2 = src.clone();
	cv::imshow("src", src); cv::waitKey(30);
	double T0 = static_cast<double>(cv::getTickCount());
	/*不使用多執行緒影象處理*/
	cv::blur(image1, image1, cv::Size(7, 7));
	double T1 = static_cast<double>(cv::getTickCount());
	/*使用多執行緒影象處理*/
	cvThread cvth;
	cvth.cvThreadProcess(image2,0,4);
	double T2 = static_cast<double>(cv::getTickCount());
	printf("       run times = %3.3fms\n", (T1 - T0)*1000 / cv::getTickFrequency());
	printf("Thread run times = %3.3fms\n,", (T2 - T1)*1000 / cv::getTickFrequency());
	cv::imshow("image1", image1); cv::waitKey(30);
	cv::imshow("image2", image2); cv::waitKey(30);

	cv::waitKey(0);
	return 0;
}