CUDA-基于累计直方图和共享内存的中值滤波算法

news2024/11/29 10:38:15

作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

实现原理

       基于累计直方图的中值滤波算法详情参见:

OpenCV-基于累计直方图的中值滤波算法-CSDN博客

       为了进一步提升算法性能,我尝试采用CUDA进行提速,但在实际实现中发现,如果在CUDA的核函数中频繁申请释放内存,其速度还不如CPU。因此再结合共享内存实现,在GPU编程中,共享内存是每个线程块共享的内存空间,它的读写速度比全局内存快得多。

功能函数代码

// 在GPU上执行中值滤波
__global__ void filterMedian_CUDA(const uchar* input, uchar* output, int rows, int cols, int filterWindowSize)
{
	__shared__ int sharedHis[256 * TILE_WIDTH * TILE_WIDTH];
	int i = blockIdx.y * blockDim.y + threadIdx.y;
	int j = blockIdx.x * blockDim.x + threadIdx.x;

	if (i < rows && j < cols)
	{
		int r = filterWindowSize / 2;
		int ms = max(i - r, 0);
		int me = min(i + r, rows - 1);
		int ns = max(j - r, 0);
		int ne = min(j + r, cols - 1);
		// 初始化
		int count = 0;
		int idx = ((threadIdx.y * blockDim.x + threadIdx.x)) * 256;;
		for (int k = 0; k < 256; ++k) 
		{
			sharedHis[idx + k] = 0;
		}
		// 直方图累计求中值
		for (int m = ms; m <= me; ++m)
		{
			for (int n = ns; n <= ne; ++n)
			{
				sharedHis[idx + input[m * cols + n]]++;
				count++;
			}
		}
		int count_ = 0;
		int thresh = count / 2 + 1;
		int mid = 0;
		for (int k = 0; k < 256; ++k)
		{
			count_ += sharedHis[idx + k];
			if (count_ >= thresh)
			{
				mid = k;
				break;
			}
		}
		output[i * cols + j] = mid;
	}
}

C++测试代码

Test.h

#include <math.h>
#include <vector>
#include <iostream>
#include <algorithm>
#include <opencv2/opencv.hpp>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>

using namespace std;
using namespace cv;

#define TILE_WIDTH 4

// 预准备过程
void warmupCUDA();

// 中值滤波-CUDA
cv::Mat filterMedian_CUDA(cv::Mat input, int filterWindowSize);

#endif // TEST_H

Test.cu

#include "Test.h"

// 预准备过程
void warmupCUDA()
{
	float *dummy_data;
	cudaMalloc((void**)&dummy_data, sizeof(float));
	cudaFree(dummy_data);
}

// 在GPU上执行中值滤波
__global__ void filterMedian_CUDA(const uchar* input, uchar* output, int rows, int cols, int filterWindowSize)
{
	__shared__ int sharedHis[256 * TILE_WIDTH * TILE_WIDTH];
	int i = blockIdx.y * blockDim.y + threadIdx.y;
	int j = blockIdx.x * blockDim.x + threadIdx.x;

	if (i < rows && j < cols)
	{
		int r = filterWindowSize / 2;
		int ms = max(i - r, 0);
		int me = min(i + r, rows - 1);
		int ns = max(j - r, 0);
		int ne = min(j + r, cols - 1);
		// 初始化
		int count = 0;
		int idx = ((threadIdx.y * blockDim.x + threadIdx.x)) * 256;;
		for (int k = 0; k < 256; ++k) 
		{
			sharedHis[idx + k] = 0;
		}
		// 直方图累计求中值
		for (int m = ms; m <= me; ++m)
		{
			for (int n = ns; n <= ne; ++n)
			{
				sharedHis[idx + input[m * cols + n]]++;
				count++;
			}
		}
		int count_ = 0;
		int thresh = count / 2 + 1;
		int mid = 0;
		for (int k = 0; k < 256; ++k)
		{
			count_ += sharedHis[idx + k];
			if (count_ >= thresh)
			{
				mid = k;
				break;
			}
		}
		output[i * cols + j] = mid;
	}
}

// 中值滤波-CUDA
cv::Mat filterMedian_CUDA(cv::Mat input, int filterWindowSize)
{
	int row = input.rows;
	int col = input.cols;
	int size = row * col * sizeof(uchar);
	uchar *d_input, *d_output;

	// 在GPU上分配内存
	cudaMalloc((void**)&d_input, size);
	cudaMalloc((void**)&d_output, size);

	// 将输入数据从主机内存复制到GPU内存
	cudaMemcpy(d_input, input.data, size, cudaMemcpyHostToDevice);

	// 计算CUDA核函数执行的网格和块大小
	dim3 threadsPerBlock(TILE_WIDTH, TILE_WIDTH);
	dim3 blocksPerGrid((col + threadsPerBlock.x - 1) / threadsPerBlock.x, (row + threadsPerBlock.y - 1) / threadsPerBlock.y);

	// 在GPU上执行中值滤波
	filterMedian_CUDA << <blocksPerGrid, threadsPerBlock >> > (d_input, d_output, row, col, filterWindowSize);
	
	// 将结果从GPU内存复制回主机内存
	cv::Mat output(row, col, CV_8UC1);
	cudaMemcpy(output.data, d_output, size, cudaMemcpyDeviceToHost);

	// 释放GPU内存
	cudaFree(d_input);
	cudaFree(d_output);

	return output;
}

main.cpp

#include <time.h>
#include <omp.h>
#include <vector>
#include <iostream>
#include <algorithm>
#include <opencv2/opencv.hpp>
#include "Test.h"

using namespace std;

// 基于累计直方图的中值滤波算法
cv::Mat filterMedian_CPU(cv::Mat input, int filterWindowSize)
{
	int row = input.rows;
	int col = input.cols;
	int r = filterWindowSize / 2;
	cv::Mat output(row, col, CV_8UC1);
#pragma omp parallel for
	for (int i = 0; i < row; ++i)
	{
		for (int j = 0; j < col; ++j)
		{
			int ms = std::max(i - r, 0);
			int me = std::min(i + r, row - 1);
			int ns = std::max(j - r, 0);
			int ne = std::min(j + r, col - 1);
			// 初始化
			int count = 0;
			int histogram[256] = { 0 };
			// 直方图累计求中值
			for (int m = ms; m <= me; ++m)
			{
				for (int n = ns; n <= ne; ++n)
				{
					histogram[input.at<uchar>(m, n)]++;
					count++;
				}
			}
			int count_ = 0;
			int thresh = count / 2 + 1;
			int mid = 0;
			for (int k = 0; k < 256; ++k)
			{
				count_ += histogram[k];
				if (count_ >= thresh)
				{
					mid = k;
					break;
				}
			}
			output.at<uchar>(i, j) = mid;
		}
	}
	return output;
}
int main(void)
{
	// 预准备过程
	warmupCUDA();
	// 测试
	cv::Mat input = cv::imread("test.jpg", 0);
	cv::Mat output0, output1;
	int filterWindowSize = 15;
	cout << "filterWindowSize:" << filterWindowSize << endl;
	cout << "size: " << input.cols << " * " << input.rows << endl;
	// 在CPU上执行中值滤波-累计直方图法
	clock_t sc, ec;
	sc = clock();
	output0 = filterMedian_CPU(input, filterWindowSize);
	ec = clock();
	cout << "His CPU time:" << double(ec - sc) / 1000 << endl;
	// 在GPU上执行中值滤波-累计直方图法
	clock_t sg, eg;
	sg = clock();
	output1 = filterMedian_CUDA(input, filterWindowSize);
	eg = clock();
	cout << "His GPU time:" << double(eg - sg) / 1000 << endl;
	// 检查结果
	int row = input.rows;
	int col = input.cols;
	bool flag = true;
	for (int i = 0; i < row; ++i)
	{
		for (int j = 0; j < col; ++j)
		{
			if (output0.at<uchar>(i, j) != output1.at<uchar>(i, j))
			{
				flag = false;
				break;
			}
		}
		if (!flag)
		{
			break;
		}
	}
	if (flag)
	{
		std::cout << "ok!" << std::endl;
	}
	else
	{
		std::cout << "ng!" << std::endl;
	}
	cv::Mat test0 = output0.clone();
	cv::Mat test1 = output1.clone();
	cout << "CUDA test." << endl;
	return 0;
}

测试效果 

       如上图所示,分别是原图、CPU结果和GPU结果,在速度方面,对1920*1080的图像,在窗口尺寸为15*15时,我的电脑运行速度分别是0.2s和0.1s。

       我在不同窗尺寸下进行了几轮测试,测试结果仅供参考。

       在CUDA编程中,内存访问是一个关键的性能瓶颈。频繁地申请和释放内存会增加内存管理的开销,这可能会导致程序性能下降。为了提高CUDA程序的效率,可以尽量减少内存分配和释放的次数。一种常见的方法是预先分配一定大小的内存,并在程序执行过程中重复使用这些内存。此外,可以考虑使用共享内存等技术来减少对全局内存的访问,以提高访存效率。

       如果函数有什么可以改进完善的地方,非常欢迎大家指出,一同进步何乐而不为呢~

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1670415.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

06、SpringBoot 源码分析 - SpringApplication启动流程六

SpringBoot 源码分析 - SpringApplication启动流程六 初始化基本流程SpringApplication的prepareEnvironment准备环境SpringApplication的getOrCreateEnvironment创建环境configureEnvironment配置环境ApplicationConversionService的getSharedInstance配置转换器 SpringApplic…

C# WinForm —— 17 MaskedTextBox 介绍

1. 简介 本质是文本框&#xff0c;但它可以通过掩码来区分输入的正确与否&#xff0c;可以控制输入的格式、长度 主要应用场景是&#xff1a;需要格式化输入信息的情况 2. 常用属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到,一般以 mtxt 开头AsciiOnly是否…

Ansible常用变量【下】

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 前言 在上一篇文章《Ansible常用变量【上】》中&#xff0c;学习了Ansible常用变量的前半部分&#xff0c;放了个五一假&#x…

USB转串口芯片CH341、CH372、CH374、CH375等的电路及 PCB 设计的重要注意事项

前言 USB芯片的电路和PCB设计参考及注意事项&#xff0c;含CH34X、CH37X等系列芯片的电路设计说明。涉及工作稳定性和抗干扰以及USB-HOST带电热插拔。基于 USB 芯片的电路及 PCB 设计的重要注意事项 版本&#xff1a;2E 1、摘要 本文主要针对以下因电路及 PCB 设计不佳而引起…

浮点数的由来及运算解析

数学是自然科学的皇后&#xff0c;计算机的设计初衷是科学计算。计算机的最基本功能是需要存储整数、实数&#xff0c;及对整数和实数进行算术四则运算。 但是在计算机从业者的眼中&#xff0c;我们知道的数学相关的基本数据类型通常是整型、浮点型、布尔型。整型又分为int8&a…

点是否在三角形内C++源码实现

原理 思路&#xff1a; 面积和&#xff1a; abc obcaocabo,应该有更简洁的方法&#xff0c;但是这个方法思路更简单 代码实现: 注意二维向量的叉乘后&#xff0c;是垂直于平面的向量&#xff0c;相当于z为0三维向量叉乘&#xff0c;所以只有z维度有值&#xff0c;xy0. flo…

【Nginx <一>⭐️】Nginx 的初步了解以及安装使用

目录 &#x1f44b;前言 &#x1f440;一、 Nginx 介绍 &#x1f331;二、 安装使用 &#x1f49e;️ 三、 总结 &#x1f4eb;四、 章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;前段时间主要在学习 Elasticsearch 相关的知识&#xff0c;花了两周的时间吧&#x…

排序-冒泡排序(bubble sort)

冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它重复地遍历待排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排序完成…

JavaWeb--13Mybatis(2)

Mybatis&#xff08;2&#xff09; 1 Mybatis基础操作1.1 需求和准备工作1.2 删除员工日志输入参数占位符 1.3 新增员工1.4 修改员工信息1.5 查询员工1.5.1 根据ID查询数据封装 1.5.3 条件查询 2 XML配置文件规范3 MyBatis动态SQL3.1 什么是动态SQL3.2 动态SQL-if更新员工 3.3 …

决策树学习记录

对于一个决策树的决策面&#xff1a; 他其实是在任意两个特征基础上对于所有的点进行一个分类&#xff0c;并且展示出不同类别的之间的决策面&#xff0c;进而可以很清楚的看出在这两个特征上各个数据点种类的分布。 对于多输出的问题&#xff0c;在利用人的上半张脸来恢复下半…

排序-选择排序(selection sort)

选择排序&#xff08;selection sort&#xff09;的工作原理非常简单&#xff1a;开启一个循环&#xff0c;每轮从未排序区间选择最小的元素&#xff0c;将其放到已排序区间的末尾。选择排序的主要特点包括&#xff1a; 时间复杂度&#xff1a; 无论最好、最坏还是平均情况&…

UE5 FARFilter筛选器使用方法

UE5 查找资源时可以用FARFilter进行筛选&#xff0c;之前可以用ClassNames进行筛选&#xff0c;但是5.1之后就弃用这个属性改成ClassPaths属性 构造一个FTopLevelAssetPath对象需要两个FName参数&#xff0c;但是没找到应该传什么 查找官方文档&#xff0c;明显是错误的&#x…

企业级WEB服务Nginx安装

企业级WEB服务Nginx安装 1. Nginx版本和安装方式 Mainline version 主要开发版本,一般为奇数版本号,比如1.19Stable version 当前最新稳定版,一般为偶数版本,如:1.20Legacy versions 旧的稳定版,一般为偶数版本,如:1.18Nginx安装可以使用yum或源码安装,但是推荐使用源码编译安…

苹果cms:伪静态设置教程

官方默认的网站模式是动态模式&#xff0c;动态模式下链接中自带有“index.php”想要去除网站链接中的index.php&#xff0c;首先需要开启网站的模式为伪静态模式。这样比动态模式那一长串的链接看着也舒服一些&#xff0c;最重要的是迎合搜索引擎的喜好加快收录提高排名。 1、…

Docker:1Panel安装及使用

1、简述 1Panel是一款现代化、开源的Linux服务器运维管理面板&#xff0c;于2023年3月推出&#xff0c;深度集成WordPress和Halo&#xff0c;一键完成域名绑定、SSL证书配置等操作&#xff0c;帮助用户实现快速建站&#xff0c;支持用户通过Web浏览器轻松管理Linux服务器&…

SpringBoot集成Curator实现Zookeeper基本操作

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Zookeeper是一个Ap…

全场景智能终端RK3288主板在智能垃圾回收项目的应用,支持鸿蒙,支持全国产化

全场景智能终端主板AIoT-3588A推出的智能化垃圾回收项目&#xff0c;旨在解决城市化进程中日益突出的垃圾处理问题。智能垃圾分类箱具备触屏操作、自动称重、分类投放以及电子语音播报提示等多项功能&#xff0c;居民能够经过分类积分卡、手机扫码、人脸识别等多种途径进行投放…

新书首发 |《低代码在制造行业数字化实践》正式出版!

得帆云出书了&#xff01; 得帆云团队编写的《低代码在制造行业数字化实践》正式出版&#xff01; 毫无疑问&#xff0c;对传统开发技术而言&#xff0c;低代码技术是一场技术革命&#xff0c;低代码技术将深刻地影响和改变企业的数字化转型和发展道路。 《低代码在制造行业…

全球家装水管十大名牌排行榜

现代家居装修中&#xff0c;水管都是采用埋墙式施工&#xff0c;为了保证日后的安全用水&#xff0c;最好就选用好的家装水管品牌&#xff0c;避免日后出现爆裂等现象&#xff0c;好的家装水管的质量才有所保障&#xff0c;下面就和下面分享一下全球家装水管十大名牌排行榜。 …

如何在创建之前检测 Elasticsearch 将使用哪个索引模板

作者&#xff1a;来自 Elastic Musab Dogan 概述 Elasticsearch 提供两种类型的索引模板&#xff1a;旧&#xff08;legacy&#xff09;索引模板和可组合 (composable) 索引模板。 Elasticsearch 7.8 中引入的可组合模板旨在替换旧模板&#xff0c;两者仍然可以在 Elasticsear…