引言

最近在做医疗设备相关的项目,故在项目中大量用到了各类图像分割的算法,为了在图像中分割出特定目标,用到的算法可以有很多,比如阈值分割,多通道分割,边缘分割以及一些前沿的组合分割。而对大多数图像来说,分割的一大难点是将待识别的目标与背景分离,其中一种有效简单的方法就是二值化(并不都有效),本博客试着将二值化算法中的OTSU算法进行cuda改写。

任务要求

输入一张8bit的灰度图,通过CUDA在GPU中对图片实现otsu二值化,最后将结果输出至CPU并进行显示,要求输出图与用CPU内实现后的结果一致。

实现思路

关于OTSU(大津法)二值算法的具体实现思路,具体可见此博文 最大类间方差法(大津法OTSU) 
该文对最终的方差计算公式进行了一定的变形,减小了总体的计算量。 
通过对OTSU算法的阅读,可以发现在遍历计算最大类间方差时,程序存在着一定的时序性,故解决方案为通过并行计算出所有需要的数据,通过数组进行保存,在时序性算法部分(这里指最大值寻找)仍然利用串行的手法,如此完成算法的改写。 
实现过程: 
1.统计图像灰度直方图hist数组 
2.计算图像最大类间方差 
3.根据计算出的最佳阈值进行二值化操作

实现环境

VS2013 + CUDA7.5 + Opencv2.4.13

实现代码

#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <cuda.h>
#include <device_functions.h>
#include <iostream>
#include <string.h>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;/*
计算最大类间方差串行程序
只能在CPU端调用,需要将hist数组传出才可计算
计算量变大时(大图像)速度较慢
*/
__host__ int otsuThresh(int* hist, int imgHeight, int imgWidth)
{float sum = 0;for (int i = 0; i < 256; i++){sum += i * hist[i];}float w0 = 0, u0 = 0;float u = sum / (imgHeight * imgWidth);float val = 0, maxval = 0;float s = 0, n = 0;int thresh = 0;for (int i = 0; i < 256; i++){s += hist[i] * i;n += hist[i];w0 = n / (imgHeight * imgWidth);u0 = s / n;val = (u - u0) * (u - u0) * w0 / (1 - w0);if (val > maxval){maxval = val;thresh = i;}}return thresh;
}//灰度直方图统计
__global__ void imhistInCuda(unsigned char* dataIn, int* hist, int imgHeight, int imgWidth)
{int xIndex = threadIdx.x + blockIdx.x * blockDim.x;int yIndex = threadIdx.y + blockIdx.y * blockDim.y;if (xIndex < imgWidth && yIndex < imgHeight){atomicAdd(&hist[dataIn[yIndex * imgWidth + xIndex]], 1);}
}//计算最大类间方差CUDA改编程序
__global__ void OTSUthresh(const int* hist, float* sum, float* s, float* n, float* val, int imgHeight, int imgWidth, int* OtsuThresh)
{if (blockIdx.x == 0){int index = threadIdx.x;atomicAdd(&sum[0], hist[index] * index);}else{int index = threadIdx.x;if (index < blockIdx.x){atomicAdd(&s[blockIdx.x - 1], hist[index] * index);atomicAdd(&n[blockIdx.x - 1], hist[index]);}}__syncthreads(); //所有线程同步if (blockIdx.x > 0){int index = blockIdx.x - 1;float u = sum[0] / (imgHeight * imgWidth);float w0 = n[index] / (imgHeight * imgWidth);float u0 = s[index] / n[index];if (w0 == 1){val[index] = 0;}else{val[index] = (u - u0) * (u - u0) * w0 / (1 - w0);}}__syncthreads(); //所有线程同步if (threadIdx.x == 0 && blockIdx.x == 0){float maxval = 0;for (int i = 0; i < 256; i++){if (val[i] > maxval){maxval = val[i];OtsuThresh[0] = i;OtsuThresh[1] = val[i];}}}
}//阈值化
__global__ void otsuInCuda(unsigned char* dataIn, unsigned char* dataOut, int imgHeight, int imgWidth, int* hThresh)
{int xIndex = threadIdx.x + blockIdx.x * blockDim.x;int yIndex = threadIdx.y + blockIdx.y * blockDim.y;if (xIndex < imgWidth && yIndex < imgHeight){if (dataIn[yIndex * imgWidth + xIndex] > hThresh[0]){dataOut[yIndex * imgWidth + xIndex] = 255;}}
}int main()
{//传入灰度图Mat srcImg = imread("1.jpg", 0);int imgHeight = srcImg.rows;int imgWidth = srcImg.cols;//opencv实现OTSU二值化Mat dstImg1;threshold(srcImg, dstImg1, 0, 255, THRESH_OTSU);//CUDA改编Mat dstImg2(imgHeight, imgWidth, CV_8UC1, Scalar(0));//在GPU端开辟内存unsigned char* d_in;int* d_hist;cudaMalloc((void**)&d_in, imgHeight * imgWidth * sizeof(unsigned char));cudaMalloc((void**)&d_hist, 256 * sizeof(int));//传入灰度图至GPUcudaMemcpy(d_in, srcImg.data, imgHeight * imgWidth * sizeof(unsigned char), cudaMemcpyHostToDevice);dim3 threadsPerBlock1(32, 32);dim3 blocksPerGrid1((imgWidth + 32 - 1) / 32, (imgHeight + 32 - 1) / 32);imhistInCuda << <blocksPerGrid1, threadsPerBlock1 >> >(d_in, d_hist, imgHeight, imgWidth);float* d_sum;float* d_s;float* d_n;float *d_val;int* d_t;cudaMalloc((void**)&d_sum, sizeof(float));cudaMalloc((void**)&d_s, 256 * sizeof(float));cudaMalloc((void**)&d_n, 256 * sizeof(float));cudaMalloc((void**)&d_val, 256 * sizeof(float));cudaMalloc((void**)&d_t, 2 * sizeof(int));//定义最大类间方差计算并行规格,其中257为1 + 256,//第1个block用来计算图像灰度的sum,后256个block用于计算256个灰度对应的s, ndim3 threadsPerBlock2(256, 1);dim3 blocksPerGrid2(257, 1);OTSUthresh << <blocksPerGrid2, threadsPerBlock2 >> >(d_hist, d_sum, d_s, d_n, d_val, imgHeight, imgWidth, d_t);unsigned char* d_out;cudaMalloc((void**)&d_out, imgHeight * imgWidth * sizeof(unsigned char));otsuInCuda << <blocksPerGrid1, threadsPerBlock1 >> >(d_in, d_out, imgHeight, imgWidth, d_t);//输出结果图像cudaMemcpy(dstImg2.data, d_out, imgHeight * imgWidth * sizeof(unsigned char), cudaMemcpyDeviceToHost);调试用输出//int th[2] = { 0, 0 };//float n[256];//memset(n, 0, sizeof(n));//cudaMemcpy(th, d_t, 2 * sizeof(int), cudaMemcpyDeviceToHost);//cudaMemcpy(n, d_n, 256 * sizeof(float), cudaMemcpyDeviceToHost);cudaFree(d_in);cudaFree(d_out);cudaFree(d_hist);cudaFree(d_sum);cudaFree(d_s);cudaFree(d_n);cudaFree(d_val);cudaFree(d_t);imwrite("result1.jpg", dstImg1);imwrite("result2.jpg", dstImg2);return 0;
}

实现结果

贴上我大酷玩演唱会图,进行验证~

原图 

OpenCV实现结果图 

CUDA实现后图像 

关于本文以及CUDA的一些思考
在本文的OTSU算法中,其实在改编的过程中,一直怀疑把那段计算最大类间方差的串行代码(代码中host部分)改成部分并行部分串行并没有起到提速的作用,而事实上我自己做了测速,发现确实基本没什么区别,分析了下原因在于: 
1.计算量不大,GPU加深没发挥真正的作用 
2.改写的过程涉及时序的部分只能采用串行,而串行的效率GPU反而低于CPU 
3.算法还未优化至最佳 
而从刚开始接触CUDA到现在陆陆续续也写了不少CUDA的代码了,给刚入坑的朋友提几点不成熟的建议~: 
1.CUDA不是万能的,很多时候一些复杂的算法无法改写,尤其是涉及时序性的 
2.加速的关键在于对于GPU内存的使用规划以及原算法的性能 
3.有成熟的库函数能用的时候可不用CUDA,因为提速效果不是很明显(可能是我显卡渣的原因==),例如上面的OTSU算法,OpenCV实现20ms左右,CUDA实现10ms左右 
4.CUDA的时间花费大部分都在GPU传至CPU上(一般占总时间50%以上),所以在进行编码的时候能不传数据出来就尽量不要传,尽量在GPU中完成所有算法的实现,争取一进一出~ 
5.CUDA中传变量只能通过数组的形式,所以就算你的变量数量为1,也要定义数组,并把数组的头指针传给核函数

CUDA精进之路(五):图像处理——OTSU二值算法(最大类间方差法、大津法)相关推荐

  1. 【图像处理】——图像的二值化操作及阈值化操作(固定阈值法(全局阈值法——大津法OTSU和三角法TRIANGLE)和自适应阈值法(局部阈值法——均值和高斯法))

    目录 一.二值化的概念(实际上就是一个阈值化操作) 1.概念: 2.实现方法 3.常用方法 二.阈值类型 1.常见阈值类型(主要有五种类型) (1)公式描述 (2)图表描述 2.两种特殊的阈值算法(O ...

  2. 图像二值化之最大类间方差法(大津法,OTSU)

    参考文章1:图像二值化与otsu算法介绍 参考文章2:python opencv cv2.threshold() (将固定级别的阈值应用于每个数组元素)ThresholdTypes 最大类间方差法(大 ...

  3. 利用阈值分割原理,对给定图像编程实现二值、反二值、截断、反截断、大津阈值、自适应阈值等类型阈值图像分割,给出实现源码和结果图像。

    程序 import cv2 import numpy as np from matplotlib import pyplot as pltimg = cv2.imread('1.jpg', 0) # ...

  4. 二值化-大津法(OTSU)

    论文 Otsu N . A Threshold Selection Method from Gray-Level Histograms[J]. IEEE Transactions on Systems ...

  5. 图像二值化----otsu(最大类间方差法、大津算法)(二)

    转自:http://blog.stevenwang.name/ostu-threshold-56002.html OTSU算法也称最大类间差法,有时也称之为大津算法,被认为是图像分割中阈值选取的最佳算 ...

  6. OTSU最大类间方差实现图像的二值化

    OTSU方法又叫做最大类间方差,自适应选择一个合理的阈值,根据此阈值将灰度图像转换为二值图像,例如在车牌识别过程中,将摄像头拍到的图像转化为一副灰度图像,在提取车牌的有效信息中就需要图像的二值化化. ...

  7. 图像二值化——OTSU大津法

    最大类间方差法是由日本学者大津(Nobuyuki Otsu)于1979年提出的,是一种自适应的阈值确定的方法,又叫大津法,简称OTSU.它是按图像的灰度特性,将图像分成背景和目标两部分,或者说,是寻找 ...

  8. otsu阈值分割算法原理_大津法---OTSU算法

    简介: 大津法(OTSU)是一种确定图像二值化分割阈值的算法,由日本学者大津于1979年提出.从大津法的原理上来讲,该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景 ...

  9. Learn OpenCV ---- 大津法(Otsu‘s)阈值

    阅读原文 大津法(Otsu's)阈值 什么是图像阈值 图像阈值与图像分割 大津(Otsu)算法 什么是图像阈值 图像阈值化是一种基于像素强度大图像二值化方法.这种方法的输入通常是一个灰度图和一个阈值, ...

最新文章

  1. 如何在sqlite3连接中创建并调用自定义函数
  2. 扩增子统计绘图6韦恩图:比较组间共有和特有OTU或分类单元
  3. 倪光南:看好鸿蒙系统,坚持生态体系创新才能不被“卡脖子”
  4. 洛谷P4206 聪聪与可可
  5. 华为双系统是鸿蒙系统吗,华为p50pro是鸿蒙系统吗-华为p50pro有双系统吗
  6. LeetCode 2069. 模拟行走机器人 II(模拟)
  7. 狼抓兔子(HYSBZ-1001)
  8. java 并发框架源码_某网Java并发编程高阶技术-高性能并发框架源码解析与实战(云盘下载)...
  9. es6 日期字符串转日期_Pandas核心能力9:日期时间转换、提取、筛选
  10. linux打if语句如何换行,如何在Linux中的列内换行
  11. java常用的网关有哪几种_拼多多java开发一面、二面合并面经
  12. hdu 1978 How many ways
  13. 为极致的视频体验而设计:facebook新一代存储平台Bryce Canyon架构
  14. Atitit ati teck trend技术趋势资料包 C:\onedriver\OneDrive\Documents\0 it impttech topic\ati teck trend技术趋
  15. 小米597页招股书中的数据干货,全在这里了!
  16. 【IDEA】IDEA 格式化 代码技巧 idea 格式化 会加 <p> 标签
  17. 计算机应用基础自主学习,计算机应用基础教学中如何培养学生的自主学习能力...
  18. 海龟python词树_python海龟画树
  19. defineEmit
  20. mysql CONCAT和DATE_ADD函数的使用

热门文章

  1. Qt Quick编程(1)——QML的核心部分ECMAScript
  2. 【操作系统/OS笔记15】死锁的系统模型,死锁的处理办法,银行家算法与死锁检验算法
  3. 【数据结构笔记05】堆栈及其顺序存储、链式存储
  4. 听飞狐聊JavaScript设计模式系列07
  5. go 获取屏幕分辨率_CS:GO枪神的自我修养 高刷电竞显示器推荐
  6. verilog实现多周期处理器之——目录及总述
  7. 如何设置硬盘安装linux,linux用硬盘安装时所设置选项
  8. The Most Important Skill for Software Architects
  9. fedora 安装google浏览器失败,报错
  10. c#excel导入mysql_(转)C# Excel导入Access数据库的源码