CUDA处理jpeg缩略图
缩略图的处理有很多种,比如之前写的go自带的image库,imagick库,libjpeg-turbo等等,今天来总结一下如何用CUDA处理jpeg的缩略图。
首先简单介绍一下CUDA,CUDA是Nvidia公司的GPU编程平台,通俗来说就是将CPU作为主机端,显卡作为设备端,将大批量的运算任务放在GPU上完成,这样做的好处就是能充分利用GPU的多核心运算能力优化程序,使程序效率大大提升。具体的理论内容可以自行百度学习,网上有很多参考资料。CUDA9.0更新了NPP库,在文档中说其图片处理的效率比英特尔的IPP要高出80到100倍,而当进行大批量的并行任务时,优化效果极其明显。
接下来说一下jpeg格式,jpeg格式相对于bmp格式就相当于mp3格式对于wav格式,都是对于原始数据的压缩,bmp格式是按照每个像素点的RGB三色值来存储的,我们来做一个简单的计算,一张800*600大小的bmp图片所占文件大小为800*600*3=1440000字节,大约是1.4mb,而经过jpeg压缩之后纯数据部分会缩小到200kb左右,最大的有损压缩率约为1/8,而这种有损对于人的肉眼是可以忽略不计的,相当于把12345.0000000001约等于12345一样。jpeg压缩的原理不再赘述,想要具体了解的可以看一下这篇文章:https://blog.csdn.net/shelldon/article/details/54234433,讲的很明白。对于jpeg的解码,我们应该了解其存储格式,推荐一篇博客:https://www.cnblogs.com/sddai/p/5666924.html,jpeg的各个部分字段都写的很清楚。
以上就是使用CUDA进行jpeg缩略图处理的理论基础,接下来我们来分析一下例程代码中存在的问题。
整个例程只是粗略体现了jpeg格式图片的解码,压缩和重新编码的过程,并未考虑任何异常处理、错误处理和内存泄漏的问题,所以如果要对大量图片进行处理需要对代码进行一些修改。
1. 首先是编译问题,官方提供了make文件,可以对make文件进行修改,如果需要封装成cgo来调用的话需要各个库的位置,具体编译参数如下:
-I/usr/local/cuda/samples/common/inc -I/usr/local/include -I/usr/local/cuda/include -I/usr/include -I/usr/local/cuda/samples/7_CUDALibraries/common/UtilNPP -I/usr/local/cuda/samples/7_CUDALibraries/common/FreeImage/include -L./ -L/usr/local/cuda/lib64 -L/usr/local/cuda/samples/7_CUDALibraries/common/FreeImage/lib -L/usr/local/cuda/samples/7_CUDALibraries/common/FreeImage/lib/linux -L/usr/local/cuda/samples/7_CUDALibraries/common/FreeImage/lib/linux/x86_64 -lnppig -lnppc -lfreeimage -lstdc++ -lm -lcudart -lnppisu -lnppicom
2. 在读取扫描头的函数即readScanHeader函数中,只考虑到了读取到了正确头结构时的处理,如果读取到了错误的信息,就会导致segmentfault,由于例程只支持彩色jpeg图片的处理,所以在读取到扫描头的nComponents字段时判断是否为3,如果不是则说明头部错误。
3. 在读取哈夫曼表的函数即readHuffmanTables函数中,也是只考虑了正确的情况,所以要分别判断nClass变量,nIdx变量和nCodeCount变量的值是否合法。
4. 函数内部用了两个宏,NPP_CHECK_NPP和NPP_CHECK_CUDA,分别是check一些操作是否成功完成的,宏的定义原型是:
#define NPP_ASSERT(C) do {if (!(C)) throw npp::Exception(" assertion faild!", __FILE__, __LINE__);} while(false) #define NPP_CHECK_CUDA(S) do {cudaError_t eCUDAResult; \ eCUDAResult = S; \ if (eCUDAResult != cudaSuccess)\ NPP_ASSERT(eCUDAResult == cudaSuccess);} while (false) #define NPP_CHECK_NPP(S) do {NppStatus eStatusNPP; \ eStatusNPP = S; \ if (eStatusNPP != NPP_SUCCESS)\ NPP_ASSERT(eStatusNPP == NPP_SUCCESS);} while (false)
可以看到就是判断操作的返回值是否是成功,不是就抛出异常,而由于jpeg格式的复杂性,程序经常不能正确读取各个头部,所以在每个check宏catch一下异常进行处理。
5. 在循环拆解头部结构时,如果只对于各个读取头部的函数的返回值进行判断来决定Compress函数的成功与否是不妥当的,jpeg头部很复杂,这样做的话10000张图片的处理成功率只有33%,但是在拆解头部时预先判断一些固定值是否符合条件就可以将成功率提高到85%以上,举个例子,0xFFC0是帧图像开始标记,我们可以判断此时nPos+7位置的值是否为3,如果不是则说明这个并不是真正的帧图像开始标记,所以我们continue,直到找到真正的位置才进行读取,这样就能极大提高成功率。
6. 程序定义了DivUp函数用了除法操作,但是预先没有判断除数是否为0,所以在处理时先判断除数是否合法。
7. 所有操作都没有内存回收,所以在所有异常处理以及函数正常退出处加上对应的回收函数。
8. 例程具有局限性,只能处理彩色的jpeg图片和baseline的jpeg图片,所以如果碰到了处理不了的图片可以调用其他的图片处理库进行缩略图生成。
全篇代码就不贴了,跑了20w张图片可以成功处理17w张,成功率尚可,日后参考一下其他开源库的源码对他的例程进行改进,争取提高成功率。