OpenCV使用pthread实现多线程加速处理图像
【尊重原创,转载请注明出处】https://blog.csdn.net/guyuealian/article/details/81912704
目录
POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。Pthread是由POSIX提出的一套通用的线程库,在linux平台下,它被广泛的支持,而在windows平台下,需要下载pthreads-w32库。之所以选择Pthreads库作为多线程处理库,是因此Android NDK开发时,可以使用pthread在C++中实现多线程处理,这样,可以方便OpenCV的图像加速处理和算法的移植。
pthreads-w32在Windows的配置方法,可参考:
https://blog.csdn.net/qianchenglenger/article/details/16907821
pthreads-w32-2-9-1:https://download.csdn.net/download/guyuealian/10623897
其他版本的地址:ftp://sourceware.org/pub/pthreads-win32
关于pthread的用法,可参考这个PDF:
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;
}