昆明做网站哪家好,长沙人才app,做app模板下载网站,wordpress禁止访问txt点击上方↑↑↑“OpenCV学堂”关注我作者网名#xff1a;laviewpbt是图像处理#xff0c;算法实现与加速优化方面的大神#xff01;其开发的imageshop软件大小只有1MB#xff0c;却实现了非常丰富与复杂的各种图像处理功能#xff0c;邮箱地址为#xff1a;Email: laview… 点击上方↑↑↑“OpenCV学堂”关注我作者网名laviewpbt是图像处理算法实现与加速优化方面的大神其开发的imageshop软件大小只有1MB却实现了非常丰富与复杂的各种图像处理功能邮箱地址为Email: laviewpbtsina.com博客地址https://www.cnblogs.com/Imageshop/开始原因今天一个朋友想使用我的SSE优化Demo里的双线性插值算法他已经在项目里使用了OpenCV因此我就建议他直接使用OpenCV朋友的程序非常注意效率和实时性(因为是处理视频)因此希望我能测试下我的速度和OpenCV相比到底那一个更有速度优势恰好前一段时间也有朋友有这方面的需求因此我就随意编写了一个测试程序如下所示IplImage *T cvLoadImage(F:\\1.JPG);IplImage *SrcImg cvCreateImage(cvSize(T-width, T-height), IPL_DEPTH_8U, 1);cvCvtColor(T, SrcImg, CV_BGR2GRAY);//IplImage *SrcImg cvLoadImage(F:\\3.jpg);cvNamedWindow(处理前, CV_WINDOW_AUTOSIZE);cvShowImage(处理前, SrcImg);IplImage *DestImg cvCreateImage(cvSize(SrcImg-width / 2, SrcImg-height / 2), SrcImg-depth, SrcImg-nChannels);LARGE_INTEGER t1, t2, tc;QueryPerformanceFrequency(tc);QueryPerformanceCounter(t1);for(int i0; i100; i) cvResize(SrcImg, DestImg, CV_INTER_CUBIC);QueryPerformanceCounter(t2);printf(Use Time:%f\n, (t2.QuadPart - t1.QuadPart) * 1000.0f / tc.QuadPart);cvNamedWindow(处理后, CV_WINDOW_AUTOSIZE);cvShowImage(处理后, DestImg);cvReleaseImage(SrcImg);cvReleaseImage(DestImg);cvReleaseImage(T);深入研究我使用了一张3000*2000的大图进行测试令我非常诧异的是执行100次这个函数耗时居然只有 Use Time:82.414300 ms每一帧都不到1ms目标图像的大小可是1500*1000的呢立马打开我自己的Demo同样的环境下测试100次耗时达到了450ms相差太多了要知道我那个可是SSE优化后的啊。有点不敢相信这个事实。接着我把CV_INTER_LINEAR(双线性)改为CV_INTER_NN(最近临)出来的结果是Use Time:78.921600 ms注意到没有时间比双线性的还要多感觉这完全不合乎逻辑啊。稍微冷静下来我认为这绝对不符合真理但是我心中已经隐隐约约知道大概为什么会出现这个情况于是我又做了下面几个测试。第一、换一副图像看看我把源图像的大小改为3001*2000测试结果为Use Time:543.837400 ms。把源图像的大小改为3000*2001测试结果为Use Time:541.567800 ms。把源图像的大小改为3001*2001测试结果为Use Time:547.325600 ms。第二源图像还是使用3000*2000大小把DestImg的大小修改为1501*1000测试结果为Use Time:552.432800 ms。把DestImg的大小修改为1500*1001测试结果为Use Time:549.956400 ms。把DestImg的大小修改为1501*1001测试结果为Use Time:551.371200 ms。这两个测试表明这种情况只在一、源图像的宽度和高度均为2的倍数时二、目标图像的宽度和高度都必须为源图像的一半时 时方有可能出现那么他们是充分条件了吗接着做试验。第三把插值方法改为其他的方式比如CV_INTER_CUBIC(三次立方)若其他参数都不变测试结果为Use Time:921.885900 ms。同样适使用三次立方源图大小修改为3000*2001测试结果为Use Time:953.748100 ms。适用三次立方源图大小不变目标图修改1501*1000测试结果为Use Time:913.735600 ms。可见此时无论怎么调整输入输出基本的耗时都差不多换成CV_INTER_AREA或CV_INTER_NN也能得到同样的结果。这第三个测试表明此异常现象还只有在三使用了双线性插值算法时才可能出现。这些条件就足够了吗接着看。第四其他条件暂时不动把测试代码修改如下IplImage *SrcImg cvLoadImage(F:\\1.jpg);cvNamedWindow(处理前, CV_WINDOW_AUTOSIZE);cvShowImage(处理前, SrcImg);IplImage *DestImg cvCreateImage(cvSize(SrcImg-width / 2, SrcImg-height / 2), SrcImg-depth, SrcImg-nChannels);LARGE_INTEGER t1, t2, tc;QueryPerformanceFrequency(tc);QueryPerformanceCounter(t1);for(int i0; i100; i) cvResize(SrcImg, DestImg, CV_INTER_CUBIC);QueryPerformanceCounter(t2);printf(Use Time:%f\n, (t2.QuadPart - t1.QuadPart) * 1000.0f / tc.QuadPart);cvNamedWindow(处理后, CV_WINDOW_AUTOSIZE);cvShowImage(处理后, DestImg);cvReleaseImage(SrcImg);cvReleaseImage(DestImg);即使用彩色图像进行测试运行的结果为Use Time:271.705700 ms。看这个的时间和灰度的82ms相比一猜就知道还是做了特别的处理。但是我们还是多做几个测试我们将输出图像的大小修改为1501*1000、1500*1001、1501*1001时100次的耗时在1367ms如果输入图像修改为长或宽为非偶数时耗时也差不多要1300多ms说明OpenCV对彩色图像的这种情况也有做优化处理。因此这个算法对彩色也是有效的。以上三个条件在一起构成了出现上述异常现象的充分必要条件。下面根据我个人的想法来谈谈OpenCV为什么会出现这个现象(我没有去翻OpenCV的代码)。个人认为出现该现象核心还是由双线性插值算法的本质引起的。双线性插值算法在插值时涉及到周边四个像素当源图像宽度和高度都为2的倍数如果此时的目标图像的长度和高度又恰好是源图像宽度和高度的一半这个时候的双线性插值就退化为对原图像行列方向每隔一个像素求平均值(四个像素)的过程。如果不是双线性插值他涉及到领域范围就不是4个比如三次立方就涉及到16个领域而非2的倍数或非一半的大小则无法规整到0.25的权重(4个像素的平均值)。对于这个特例我们用C语言可以简单的写出其计算过程int IM_ZoomIn_Half_Bilinear(unsigned char *Src, unsigned char *Dest, int SrcW, int SrcH, int StrideS, int DstW, int DstH, int StrideD){ int Channel StrideS / SrcW; if ((Src NULL) || (Dest NULL)) return IM_STATUS_NULLREFRENCE; if ((SrcW 0) || (SrcH 0) || (DstW 0) || (DstH 0)) return IM_STATUS_INVALIDPARAMETER; if ((Channel ! 1) (Channel ! 3) (Channel ! 4)) return IM_STATUS_INVALIDPARAMETER; if ((SrcW % 2 ! 0) || (SrcH % 2 ! 0)) return IM_STATUS_INVALIDPARAMETER; if ((DstW ! SrcW / 2) || (DstH ! SrcH / 2)) return IM_STATUS_INVALIDPARAMETER; if (Channel 1) { for (int Y 0; Y { unsigned char *LinePD Dest Y * StrideD; unsigned char *LineP1 Src Y * 2 * StrideS; unsigned char *LineP2 LineP1 StrideS; for (int X 0; X 2, LineP2 2) { LinePD[X] (LineP1[0] LineP1[1] LineP2[0] LineP2[1] 2) 2; } } } else if (Channel 3) { for (int Y 0; Y { unsigned char *LinePD Dest Y * StrideD; unsigned char *LineP1 Src Y * 2 * StrideS; unsigned char *LineP2 LineP1 StrideS; for (int X 0; X { LinePD[0] (LineP1[0] LineP1[3] LineP2[0] LineP2[3] 2) 2; LinePD[1] (LineP1[1] LineP1[4] LineP2[1] LineP2[4] 2) 2; LinePD[2] (LineP1[2] LineP1[5] LineP2[2] LineP2[5] 2) 2; LineP1 6; LineP2 6; LinePD 3; } } }}SSE尝试代码非常简单注意到计算式里最后的2是为了进行四舍五入。我们先测试下灰度图使用上述代码在同样的环境下可以获得Use Time:225.456300 ms 的成绩使用循环内2路或4路并行的方式大约能将成绩提高到190ms左右但是和OpenCV的速度相比还是有蛮大的差距。这么简答的代码我们可以直接用SIMD指令进行优化我们先使用SSE进行尝试__m128i Zero _mm_setzero_si128();for (int Y 0; Y DstH; Y){unsigned char *LinePD Dest Y * StrideD;unsigned char *LineP1 Src Y * 2 * StrideS;unsigned char *LineP2 LineP1 StrideS;for (int X 0; X Block * BlockSize; X BlockSize, LineP1 BlockSize * 2, LineP2 BlockSize * 2) { __m128i Src1 _mm_loadu_si128((__m128i *)LineP1); __m128i Src2 _mm_loadu_si128((__m128i *)LineP2); // A0B0 A1B1 A2B2 A3B3 A4B4 A5B5 A6B6 A7B7 __m128i Sum_L _mm_add_epi16(_mm_cvtepu8_epi16(Src1), _mm_cvtepu8_epi16(Src2)); // A8B8 A9B9 A10B10 A11B11 A12B12 A13B13 A14B14 A151B15 __m128i Sum_H _mm_add_epi16(_mm_unpackhi_epi8(Src1, Zero), _mm_unpackhi_epi8(Src2, Zero)); // A0A1B0B1 A2A3B2B3 A4A5B4B5 A6A7B6B7 A8A9B8B9 A10A11B10B11 A12A13B12B13 A14A15B141B15 __m128i Sum _mm_hadd_epi16(Sum_L, Sum_H); // (A0A1B0B12)/4 (A2A3B2B3)/4 (A4A5B4B5)/4 (A6A7B6B7)/4 (A8A9B8B9)/4 (A10A11B10B11)/4 (A12A13B12B13)/4 (A14A15B141B15)/4 __m128i Result _mm_srli_epi16(_mm_add_epi16(Sum, _mm_set1_epi16(2)), 2); _mm_storel_epi64((__m128i *)(LinePD X), _mm_packus_epi16(Result, Zero)); } for (int X Block * BlockSize; X DstW; X, LineP1 2, LineP2 2) { LinePD[X] (LineP1[0] LineP1[1] LineP2[0] LineP2[1] 2) 2; }}对SSE优化来说也没啥加载数据将其转换成16位(字节相加肯定会溢出到16位后4个数相加肯定会在16位的范围内)注意上面的最为精华的部分为_mm_hadd_epi16的使用他的水平累加过程恰好可以完成最后的列方向的处理如果我们先用这个函数完成A0A1这样的工作那如果要完成同样的工作后续就要多了一些shuffle过程了这样就降低了速度。这段SIMD指令经过测试100次循环耗时在90-100ms之间徘徊和OpenCV的结果有点差不多了。如果我们使用AVX指令进行优化整体基本和SSE差不多但是局部细节上还是有所差异的如下所示for (int Y 0; Y DstH; Y){unsigned char *LinePD Dest Y * StrideD;unsigned char *LineP1 Src Y * 2 * StrideS;unsigned char *LineP2 LineP1 StrideS;__m256i Zero _mm256_setzero_si256();for (int X 0; X Block * BlockSize; X BlockSize, LineP1 BlockSize * 2, LineP2 BlockSize * 2) { __m256i Src1 _mm256_loadu_si256((__m256i *)LineP1); __m256i Src2 _mm256_loadu_si256((__m256i *)LineP2); // 注意这里使用unpack的方式来实现8位和16位的转换,如果使用_mm256_cvtepu8_epi16则低位部分需要一个__m128i变量而 // 高位使用_mm256_unpackhi_epi8则需要一个__m256i变量这样会存在重复加载现象的。 __m256i Sum_L _mm256_add_epi16(_mm256_unpacklo_epi8(Src1, Zero), _mm256_unpacklo_epi8(Src2, Zero)); __m256i Sum_H _mm256_add_epi16(_mm256_unpackhi_epi8(Src1, Zero), _mm256_unpackhi_epi8(Src2, Zero)); __m256i Sum _mm256_hadd_epi16(Sum_L, Sum_H); __m256i Result _mm256_srli_epi16(_mm256_add_epi16(Sum, _mm256_set1_epi16(2)), 2); // 注意_mm256_packus_epi16 并不是_mm_packus_epi16的线性扩展很恶心的做法 _mm_storeu_si128((__m128i *)(LinePD X), _mm256_castsi256_si128(_mm256_permute4x64_epi64(_mm256_packus_epi16(Result, Zero), _MM_SHUFFLE(3, 1, 2, 0)))); } for (int X Block * BlockSize; X DstW; X,LineP1 2, LineP2 2) { LinePD[X] (LineP1[0] LineP1[1] LineP2[0] LineP2[1] 2) 2; }}特别注意到的是最后_mm256_packus_epi16指令的使用他和_mm256_add_epi16或者 _mm256_srli_epi16不一样并不是对SSE指令简单的从128位扩展到256位我们从其简单的数学解释就可以看到:add指令就是直接从8次一次性计算简单的扩展到16次一次性计算在来看packus指令_mm256_packus_epi16 实际上可以看成是对两个__m128i变量单独进行处理而不是把他们看成一个整体这样同样的算法我们就在AVX中就不能使用同样SSE指令了比如最后的保存的语句我们必须使用一个_mm256_permute4x64_epi64指令来进行一下shuffle调序操作。这种不便利性也是我不愿意将大部分SSE指令扩展到AVX的一个重要障碍之一。使用AVX编写的程序优化后的耗时大约在80ms左右波动这个已经非常接近OpenCV的速度了至此我们有理由相信OpenCV在实现这个的过程中应该也采取了类似我上述的优化方式进行处理(没有仔细的翻OpenCV的代码请有看过的朋友指导下)。那么我们再谈谈为什么这个速度比最近邻插值还要快吧最近邻算法中不存在插值直接在源图像中选择一个坐标位置的点作为新的像素值在放大时其会出现多行像素相同的特性这个特性可以用来加快算法执行速度但是对于缩小只有一个点一个点的计算至多可以用查找表提前计算好坐标经过尝试这算法是不易用多媒体指令进行优化的而且即使用也无明显的速度提升。而对于本文的双线性的特例其并行的特性非常好而且本身的计算量也不是很大因此就出现用SIMD优化后速度还比最近邻还快的结果。对于彩色图像普通的C语言代码也很简单上面也已经贴出代码这段代码执行100次大概耗时在500ms左右注意这个时候对他进行SIMD指令优化就不是一件很直接和很简单的事情了因为BGRBGR这样的排列顺序到底无法直接使用灰度模式的指令扩展必须要将BGR重新排序变为BBB GGG RRR这样的模式然后单独对分量进行处理处理完成后再合成为BGR排列因此这样排列需要一次性加载48个字节(SSE)用3个SSE寄存器保存数据这个时候如果使用AVX指令就显得有点繁琐了而且就是用AVX带来的性能收益也微乎其微。同样的这种计算量不大的算法用SIMD指令优化后的收益并不是特别明显对于彩色图像SSE优化后其时间大概能缩短到300ms这个速度要比OpenCV的稍微慢一点。随着现在的视频显示设备越来越先进采集的图像也越来越大比如现在4K的高清摄像头也不在少数在有些实时要求性很好的场合我们必须考虑处理能力将图像缩小在处理是常用的手段而且我想长宽各一半的这种缩小场合在此情况下也应该是很常见的因此特列的特别优化就显得非常有意义。还有一般情况下图像多次缩小2倍要比直接缩小大于2倍的效果更好或者说通过多次缩放得到的结果一般要比直接一次性缩放得到的结果要更好比如下面左图是直接缩放到原图1/4长宽的结果右图是先缩小一半在缩小一半的结果在风车的边缘可以看到后者更为平滑。在耗时上比如上面这个操作直接缩小到1/4因不是特殊处理而通过2次一半的处理每次都是特殊算法虽然次数多了但是总耗时也就比直接缩小1/4多了0.5倍效果却要好一点对于那些重效果的地方还是非常有意义的特别是如果是处理4K的图这种处理也有很好的借鉴意义。最后说一下进一步测试表面我自行优化的缩放算法和OpenCV的相比灰度图上基本差不多彩色图像大概要快20%左右。本文Demo下载地址http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar云厚者雨必猛弓劲者箭必远 推荐阅读 OpenCV4系统化学习路线图-视频版本OpenCV单应性矩阵发现参数估算方法详解OpenCV4.2 OpenVINO2020安装配置与应用演示OpenVINO深度学习推理框架 开发技术系列文章汇总单应性矩阵应用-基于特征的图像拼接OpenCV图像拼接改进算法之完美拼接OpenCV | 二值图像分析的技巧都在这里OpenCV二值图像分析之形态学应用技巧图像色彩空间与应用转换五分钟学会C高效图表绘制神器调用没想到图像直方图有这么多应用场景基于灰度共生矩阵(GLCM)的图像纹理分析与提取OpenCV中一个最容易搞错的形态学操作