在图像处理中,局部算法一般来说,在很大程度上会获得比全局算法更为好的效果,因为他考虑到了图像领域像素的信息,而很多局部算法可以借助于直方图获得加速。同时,一些常规的算法,比如中值滤波、最大值滤波、最小值滤波、表面模糊等等都可以通过局部直方图进行加速。而传统的获取局部直方图计算量很大,特别是半径增加时,耗时会成平方关系增加。一些局部算法只有在半径较大时才会获得很好的效果,因此,必须找到一种合适的加速计算局部直方图的方式。

在参考Median Filter in Constant Time.pdf一文附带的C的代码的基础上,本文提出了基于SSE加速的恒长任意半径局部直方图获取技术,可以大大加速算法的计算时间,特别是大半径时的提速更为明显。

主要的优化思路是,沿着列方向一行一行的更行整行的列直方图,新的一行对应的列直方图更新时只需要减去已经不再范围内的那个像素同时加入新进入的像素的直方图信息。之后,对于一行中的第一个像素点,累加半径辐射范围内的列直方图,得到改点的局部直方图,对于行中的其他的像素,则类似于更新行直方图,先减去不在范围内那列的列直方图,然后加上移入范围内的列直方图。由于采用了基于SSE函数的加速过程,直方图想加和相减的速度较普通的加减法有了10倍以上的提速,因此大大的提高了整体的实用性。

具体的过程我用代码加以说明:

  1、一些公用的内存分配过程

    TMatrix *Row = NULL, *Col = NULL;
unsigned char *LinePS, *LinePD;
int X, Y, K, Width = Src->Width, Height = Src->Height;
int *RowOffset, *ColOffSet; unsigned short *ColHist = (unsigned short *)IS_AllocMemory(256 * (Width + 2 * Radius) * sizeof(unsigned short), true);
if (ColHist == NULL) {Ret = IS_RET_ERR_OUTOFMEMORY; goto Done8;}
unsigned short *Hist = (unsigned short *)IS_AllocMemory(256 * sizeof(unsigned short), true);
if (Hist == NULL) {Ret = IS_RET_ERR_OUTOFMEMORY; goto Done8;}
Ret = GetValidCoordinate(Width, Height, Radius, Radius, Radius, Radius, Edge, &Row, &Col); // 获取坐标偏移量
if (Ret != IS_RET_OK) goto Done8;

  其中的ColHist用于保存一行像素对应的列直方图 ,注意这里的行是用的扩展后的行的大小即:Width + 2 * Radius。IS_AllocMemory是个内部使用了_mm_malloc定义的内存分配函数,主要是考虑SSE函数的16字节对齐问题。

Hist变量用于保存每个像素点的局部直方图数据,任何基于局部直方图技术的函数最终都演变为对于该函数进行各种各样的计算。

GetValidCoordinate是一个用于辅助边界处像素点处理的函数,具体可详见附件中给出的代码。

2、更新一行像素的列直方图

for (Y = 0; Y < Height; Y++)
{
if (Y == 0) // 第一行的列直方图,要重头计算
{
for (K = -Radius; K <= Radius; K++)
{
LinePS = Src->Data + ColOffSet[K] * Src->WidthStep;
for (X = -Radius; X < Width + Radius; X++)
{
ColHist[X * 256 + LinePS[RowOffset[X]]]++;
}
}
}
else // 其他行的列直方图,更新就可以了
{
LinePS = Src->Data + ColOffSet[Y - Radius - 1] * Src->WidthStep;
for (X = -Radius; X < Width + Radius; X++) // 删除移出范围内的那一行的直方图数据
{
ColHist[X * 256 + LinePS[RowOffset[X]]]--;
} LinePS = Src->Data + ColOffSet[Y + Radius] * Src->WidthStep;
for (X = -Radius; X < Width + Radius; X++) // 增加进入范围内的那一行的直方图数据
{
ColHist[X * 256 + LinePS[RowOffset[X]]]++;
}
}
  //  依次获取一行每个像素的局部直方图
//  根据局部直方图获的结果
}

  可见,这部分和普通的局部优化方式类似,没有什么特殊的地方。

  3、依次获取一行每个像素的局部直方图

    for (Y = 0; Y < Height; Y++)
{
     //  更新一行像素的列直方图 memset(Hist, 0, 256 * sizeof(unsigned short)); // 每一行直方图数据清零先
LinePS = Src->Data + Y * Src->WidthStep;
LinePD = Dest->Data + Y * Dest->WidthStep;
for (X = 0; X < Width; X++)
{
if (X == 0)
{
for (K = -Radius; K <= Radius; K++) // 行第一个像素,需要重新计算
HistgramAddShort(ColHist + K * 256, Hist);
}
else
{
  /*  HistgramAddShort(ColHist + RowOffset[X + Radius] * 256, Hist);
  HistgramSubShort(ColHist + RowOffset[X - Radius - 1] * 256, Hist);
       */
HistgramSubAddShort(ColHist + RowOffset[X - Radius - 1] * 256, ColHist + RowOffset[X + Radius] * 256, Hist); // 行内其他像素,依次删除和增加就可以了
}
        //  根据局部直方图获的结果
            LinePS++;
LinePD++;
}
}

  上面处理的过程其实和2的过程的优化道理是类似的,只不过一个是行方向,一个是列方向,聪明者自然能明白,稍微愚钝者请自己多多斟酌,自然有豁然开朗的时刻。

  4、 根据局部直方图获的结果

  根据不同的算法需求,结合局部直方图信息来获取结果,比如最大值算法可以用如下方式获得:

    for (K = 255; K >= 0; K--)
{
if (Hist[K] != 0)
{
LinePD[X] = K;
break;
}
}

 关于直方图累加的代码如下:

/// <summary>
/// 无符号短整形直方图数据相加,Y = X + Y, 整理时间2014.12.28;
/// </summary>
/// <param name="X">加数。</param>
/// <param name="Y">被加数,结果保存于该数中。</param>
/// <remarks>使用了SSE优化。</remarks>
void HistgramAddShort(unsigned short *X, unsigned short *Y)
{
*(__m128i*)(Y + 0) = _mm_add_epi16(*(__m128i*)&Y[0], *(__m128i*)&X[0]); // 不要想着用自己写的汇编超过他的速度了,已经试过了
*(__m128i*)(Y + 8) = _mm_add_epi16(*(__m128i*)&Y[8], *(__m128i*)&X[8]);
*(__m128i*)(Y + 16) = _mm_add_epi16(*(__m128i*)&Y[16], *(__m128i*)&X[16]);
*(__m128i*)(Y + 24) = _mm_add_epi16(*(__m128i*)&Y[24], *(__m128i*)&X[24]);
*(__m128i*)(Y + 32) = _mm_add_epi16(*(__m128i*)&Y[32], *(__m128i*)&X[32]);
*(__m128i*)(Y + 40) = _mm_add_epi16(*(__m128i*)&Y[40], *(__m128i*)&X[40]);
*(__m128i*)(Y + 48) = _mm_add_epi16(*(__m128i*)&Y[48], *(__m128i*)&X[48]);
*(__m128i*)(Y + 56) = _mm_add_epi16(*(__m128i*)&Y[56], *(__m128i*)&X[56]);
*(__m128i*)(Y + 64) = _mm_add_epi16(*(__m128i*)&Y[64], *(__m128i*)&X[64]);
*(__m128i*)(Y + 72) = _mm_add_epi16(*(__m128i*)&Y[72], *(__m128i*)&X[72]);
*(__m128i*)(Y + 80) = _mm_add_epi16(*(__m128i*)&Y[80], *(__m128i*)&X[80]);
*(__m128i*)(Y + 88) = _mm_add_epi16(*(__m128i*)&Y[88], *(__m128i*)&X[88]);
*(__m128i*)(Y + 96) = _mm_add_epi16(*(__m128i*)&Y[96], *(__m128i*)&X[96]);
*(__m128i*)(Y + 104) = _mm_add_epi16(*(__m128i*)&Y[104], *(__m128i*)&X[104]);
*(__m128i*)(Y + 112) = _mm_add_epi16(*(__m128i*)&Y[112], *(__m128i*)&X[112]);
*(__m128i*)(Y + 120) = _mm_add_epi16(*(__m128i*)&Y[120], *(__m128i*)&X[120]);
*(__m128i*)(Y + 128) = _mm_add_epi16(*(__m128i*)&Y[128], *(__m128i*)&X[128]);
*(__m128i*)(Y + 136) = _mm_add_epi16(*(__m128i*)&Y[136], *(__m128i*)&X[136]);
*(__m128i*)(Y + 144) = _mm_add_epi16(*(__m128i*)&Y[144], *(__m128i*)&X[144]);
*(__m128i*)(Y + 152) = _mm_add_epi16(*(__m128i*)&Y[152], *(__m128i*)&X[152]);
*(__m128i*)(Y + 160) = _mm_add_epi16(*(__m128i*)&Y[160], *(__m128i*)&X[160]);
*(__m128i*)(Y + 168) = _mm_add_epi16(*(__m128i*)&Y[168], *(__m128i*)&X[168]);
*(__m128i*)(Y + 176) = _mm_add_epi16(*(__m128i*)&Y[176], *(__m128i*)&X[176]);
*(__m128i*)(Y + 184) = _mm_add_epi16(*(__m128i*)&Y[184], *(__m128i*)&X[184]);
*(__m128i*)(Y + 192) = _mm_add_epi16(*(__m128i*)&Y[192], *(__m128i*)&X[192]);
*(__m128i*)(Y + 200) = _mm_add_epi16(*(__m128i*)&Y[200], *(__m128i*)&X[200]);
*(__m128i*)(Y + 208) = _mm_add_epi16(*(__m128i*)&Y[208], *(__m128i*)&X[208]);
*(__m128i*)(Y + 216) = _mm_add_epi16(*(__m128i*)&Y[216], *(__m128i*)&X[216]);
*(__m128i*)(Y + 224) = _mm_add_epi16(*(__m128i*)&Y[224], *(__m128i*)&X[224]);
*(__m128i*)(Y + 232) = _mm_add_epi16(*(__m128i*)&Y[232], *(__m128i*)&X[232]);
*(__m128i*)(Y + 240) = _mm_add_epi16(*(__m128i*)&Y[240], *(__m128i*)&X[240]);
*(__m128i*)(Y + 248) = _mm_add_epi16(*(__m128i*)&Y[248], *(__m128i*)&X[248]);
}

  _mm_add_epi16可以一次性完成16个short类型的数据的加法,比传统的add指令快了很多倍。

由于_mm_add_epi16是这对短整形数据进行的处理,因此,一般情况下改指令所能处理的半径不能大于127,如果需要大于127,则需要修改过程序中的short类型为int,同时需要使用_mm_add_epi32指令,这样程序的速度会有所下降。

  经过测试,在我的I5的台式机中,1024*768图像在直方图更新上所需要的平均之间约为30ms,相比局部算法的核心就算部分时间(比如上述的求最大值),可能大部分耗时并不在这里。

附件的代码中有个完整的测试工程,并有我目前所有的TMatrix结构的完整代码,我以后的文章都将以改结构为依托进行处理。

代码还共享了很多处理的函数,我很自信一定值得朋友去学习的。

这种前后依赖的算法都有一个很致命的缺点,就是不可以并行,把图像分段处理,也会造成过多初始化耗时。

代码下载地址:http://files.cnblogs.com/files/Imageshop/BaseFile.rar

http://www.cnblogs.com/Imageshop/p/4441972.html#undefined

图像处理之优化---任意半径局部直方图类算法在PC中快速实现的框架的更多相关文章

  1. 任意半径局部直方图类算法在PC中快速实现的框架。

    在图像处理中,局部算法一般来说,在很大程度上会获得比全局算法更为好的效果,因为他考虑到了图像领域像素的信息,而很多局部算法可以借助于直方图获得加速.同时,一些常规的算法,比如中值滤波.最大值滤波.最小 ...

  2. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...

  3. 【图像处理】基于OpenCV实现图像直方图的原理

    背景 图像的直方图是衡量图像像素分布的一种方式,可以通过分析像素分布,使用直方图均衡化对图像进行优化,让图像变的清晰. opencv官方对图像直方图的定义如下: 直方图是图像中像素强度分布的图形表达方 ...

  4. JVM性能优化--类加载器,手动实现类的热加载

    一.类加载的机制的层次结构 每个编写的".java"拓展名类文件都存储着需要执行的程序逻辑,这些".java"文件经过Java编译器编译成拓展名为". ...

  5. 在类的成员函数中调用delete this

    最近面试的时候被问到一个问题是,在C++中,能否在类的成员函数中调用delete this,后来网上查了一下资料,关于这个问题说得比较好的有http://blog.sina.com.cn/s/blog ...

  6. 基于FPGA的HDTV视频图像灰度直方图统计算法设计

    随着HDTV的普及,以LCD-TV为主的高清数字电视逐渐进入蓬勃发展时期.与传统CRT电视不同的是,这些高清数字电视需要较复杂的视频处理电路来驱动,比如:模数转换(A/D Converter).去隔行 ...

  7. 迭代硬阈值类算法总结||IHT/NIHT/CGIHT/HTP

    迭代硬阈值类(IHT)算法总结 斜风细雨作小寒,淡烟疏柳媚晴滩.入淮清洛渐漫漫. 雪沫乳花浮午盏,蓼茸蒿笋试春盘.人间有味是清欢. ---- 苏轼 更多精彩内容请关注微信公众号 "优化与算法 ...

  8. [POJ 2821]TN's Kindom III(任意长度循环卷积的Bluestein算法)

    [POJ 2821]TN's Kindom III(任意长度循环卷积的Bluestein算法) 题面 给出两个长度为\(n\)的序列\(B,C\),已知\(A\)和\(B\)的循环卷积为\(C\),求 ...

  9. DWA局部路径规划算法论文阅读:The Dynamic Window Approach to Collision Avoidance。

    DWA(动态窗口)算法是用于局部路径规划的算法,已经在ROS中实现,在move_base堆栈中:http://wiki.ros.org/dwa_local_planner DWA算法第一次提出应该是1 ...

随机推荐

  1. Activity生命周期以及启动模式对生命周期的影响(二)

    前面一篇文章概述了Android四大组件之一的Activity生命周期方法的调用先后顺序,但对于非标准启动模式下Activity被多次调用时的一些生命周期方法并未详细阐述,现在针对该情况着重记录. 现 ...

  2. 算法笔记_075:蓝桥杯练习 最短路(Java)

    目录 1 问题描述 2 解决方案 2.1 floyd算法解决 2.2 spfa算法解决   1 问题描述 问题描述 给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环).请你计算从 ...

  3. 启动ActiveMQ时报端口被占用异常

    配置好ActiveMQ后,前几次都启动成功.有一天启动时发现启动不成功,查看报错日志发现出现如下提示: Failed to start Apache ActiveMQ (localhost, ID:* ...

  4. LoadRunner字符串处理 - 补齐字符串

    有些时候需要在某个字符串的前面用0补齐,以便满足长度的格式要求. 在LoadRunner中可以封装出一个函数来处理这种问题: /* Function to pad a string to x char ...

  5. css新单位 vw , vh

    考虑到未来响应式设计的开发,如果你需要,浏览器的高度也可以基于百分比值调整.但使用基于百分比值并不总是相对于浏览器窗口的大小定义的最佳方式,比如字体大小不会随着你窗口改变而改变,如今css3引入的新单 ...

  6. PyPy与VirtualEnv的安装问题

    PyPy与VirtualEnv的安装问题 说明:本博客由bitpeach原创撰写,请勿商用.转载免费,请注明出处,谢谢. (零)背景 VirtualEnv工具的详细内容是什么,请自行百度.这里大概简介 ...

  7. hibernate 关系映射之 双向外键关联一对一

    在上一篇博客内容的基础上做了以下修改,即可实现.   注解方式:   package com.bjsxt.hibernate; import javax.persistence.Entity; imp ...

  8. 学会Git玩转Github笔记(二)——Git使用

    一.Git基本工作流程 Git工作区域 向仓库中添加文件流程 二. Git初始化及仓库创建和操作 基本信息设置 . 设置用户名 git config --global user.name 'itcas ...

  9. Linux su命令参数及用法详解--Linux切换用户命令

    建议大家切换用户的时候 使用  su -  root  这样,否则可能发现某些命令执行不了 关于su .su - 及 sudo的区别 请往下看 1.命令作用 su的作用是变更为其它使用者的身份,超级用 ...

  10. c# 按位与,按位或

    在工作中遇到按位或组合权限串.一直不是特别明白.今天终于花了半个下午的时间搞明白其中的道理. 首先每一个权限数都是2的N次方数 如:k1=2 ; //添加 k2=4 ; //删除 k3=8; //修改 ...