基于DCT变换图像去噪算法的终极优化(1920*1080灰度图单核约22ms)

相关文章:

优化IPOL网站中基于DCT(离散余弦变换)的图像去噪算法(附源代码)。

SSE图像算法优化系列二十一:基于DCT变换图像去噪算法的进一步优化(100W像素30ms)。

这个算法2015年优化过一版,2018年又优化过一版,2016年初又来回访一下,感觉还有优化空间,继续又折腾了将近一周,速度又有进一步的提升,下面是这个三个版本在同一台电脑上的速度比较:

对于小图,其实2026版和2018年速度差异不是很大,到了大图,新版约有25%左右的提升。

那么2026版优化的渠道主要有两处,第一个还是内部DCT变换本身的优化,以8*8的DCT为例,2015版DCT1D的代码如下所示:

inline void IM_DCT1D_8x1_GT_C(float *In, float *Out) { const float mx00 = In[0] + In[7]; const float mx01 = In[1] + In[6]; const float mx02 = In[2] + In[5]; const float mx03 = In[3] + In[4]; const float mx04 = In[0] - In[7]; const float mx05 = In[1] - In[6]; const float mx06 = In[2] - In[5]; const float mx07 = In[3] - In[4]; const float mx08 = mx00 + mx03; const float mx09 = mx01 + mx02; const float mx0a = mx00 - mx03; const float mx0b = mx01 - mx02; const float mx0c = 1.38703984532215f * mx04 + 0.275899379282943f * mx07; const float mx0d = 1.17587560241936f * mx05 + 0.785694958387102f * mx06; const float mx0e = -0.785694958387102f * mx05 + 1.17587560241936f * mx06; const float mx0f = 0.275899379282943f * mx04 - 1.38703984532215f * mx07; const float mx10 = 0.353553390593274f * (mx0c - mx0d); const float mx11 = 0.353553390593274f * (mx0e - mx0f); Out[0] = 0.353553390593274f * (mx08 + mx09); Out[1] = 0.353553390593274f * (mx0c + mx0d); Out[2] = 0.461939766255643f * mx0a + 0.191341716182545f * mx0b; Out[3] = 0.707106781186547f * (mx10 - mx11); Out[4] = 0.353553390593274f * (mx08 - mx09); Out[5] = 0.707106781186547f * (mx10 + mx11); Out[6] = 0.191341716182545f * mx0a - 0.461939766255643f * mx0b; Out[7] = 0.353553390593274f * (mx0e + mx0f); }

可以看到Out[0]到Out[7]这个8个数据前面都还有很多系数,共有26次加法及22次乘法,实际上可以把他们和前面的系数融合到一起,修改后数据如下所示:

inline void IM_DCT1D_1x8_C(float* In, float* Out) { float V00 = In[0] + In[7]; float V01 = In[0] - In[7]; float V02 = In[1] + In[6]; float V03 = In[1] - In[6]; float V04 = In[2] + In[5]; float V05 = In[2] - In[5]; float V06 = In[3] + In[4]; float V07 = In[3] - In[4]; float V08 = 0.353553390593274f * (V00 + V06); float V09 = 0.353553390593274f * (V02 + V04); float V10 = V00 - V06; float V11 = V02 - V04; float V12 = 0.490392640201616f * V01 + 0.097545161008064f * V07; float V13 = 0.415734806151273f * V03 + 0.277785116509801f * V05; float V14 = 0.415734806151273f * V05 - 0.277785116509801f * V03; float V15 = 0.097545161008064f * V01 - 0.490392640201616f * V07; float V16 = 0.707106781186547f * (V12 - V13); float V17 = 0.707106781186547f * (V14 - V15); Out[0] = V08 + V09; Out[1] = V12 + V13; Out[2] = 0.461939766255643f * V10 + 0.191341716182545f * V11; Out[3] = V16 - V17; Out[4] = V08 - V09; Out[5] = V16 + V17; Out[6] = 0.191341716182545f * V10 - 0.461939766255643f * V11; Out[7] = V14 + V15; }

修改后的代码只有26次加法及16次乘法,减少了6次乘法。

加速的另外一个部分就是,原先的方法是沿着列方向更新权重和累加值,新的算法更改为行方向更新权重和累加值,这里自然的就变为了缓存友好的方式了。当然其中,会增加一部分内存占用。

原先的处理方式如下:

for (int X = 0; X < Width - 7; X += Step) { for (int Y = 0; Y < Height; Y++) { IM_Convert8ucharTo8float_PureC(Src + Y * Stride + X, DctIn + Y * 8); // 把一整列的字节数据转换为浮点数 } for (int Y = 0; Y < Height; Y++) { IM_DCT1D_8x1_GT_C(DctIn + Y * 8, DctIn + Y * 8); } for (int Y = 0; Y < Height - 7; Y += Step) { IM_DCT2D_8x8_With1DRowDCT_GS_PureC(DctIn + Y * 8, DctOut); // DCT变换 IM_DctThreshold8x8_PureC(DctOut, Threshold); // 阈值处理 IM_IDCT2D_8x8_GS_PureC(DctOut, DctOut); // 在反变换回来 IM_UpdateSum8x8_PureC(Sum + Y * Width + X, DctOut, Width); // 更新权重和阈值 } } IM_SumDivWeight2uchar8x8_PureC(Sum, Dest, Width, Height, Stride, FastMode);

更改后的方案为:

// 先把所有的字节数据转换为浮点数 for (int Y = 0; Y < Height; Y++) { IM_ConvertU8To32F_PureC(Src + Y * Stride, SrcF + Y * Width, Width); } // 因为后面进行了以y为起点,连续8个元素的行方向的DCT变换,所以最后7个点也是有取样的 for (int Y = 0; Y < Height - 7; Y += Step) { float* SumL = Sum + Y * Width; // 8行数据的一维列DCT变换,保存到DctIn中 IM_DCT1D_8xW_C_PureC(SrcF + Y * Width, DctIn, Width); // 把列DCT变换的数据转置下 IM_TransposeF_PureC(DctIn, DctInT, Width, 8); // 进行了以X为起点,X方向连续7个列方向的DCT列变换,所以列方向最后7个元素也能取样到 for (int X = 0; X < Width - 7; X += Step) { // 可以重复利用行方向的转置数据 IM_DCT1D_8x8_C_PureC(DctInT + X * 8, DctOut); // 转换后的数据还要转置 IM_Transpose8x8F_W8_PureC(DctOut, DctOut); // 阈值处理 IM_DctThreshold8x8_PureC(DctOut, Threshold); // 在反变换回来 IM_IDCT2D_8x8_PureC(DctOut, DctOut); // 更新权重和阈值 IM_UpdateSum8x8_PureC(SumL + X, DctOut, Width); } } IM_SumDivWeight2uchar8x8_PureC(Sum, Dest, Width, Height, Stride, FastMode);

具体的细节还需要大家自己慢慢悟。

对于16*16的DCT,本身的计算量就特别夸张,原先论文配套的代码也有提供,但是同样的道理那里的代码存在大量的可节省计算,很多乘法部分可以合并的。优化后的DCT16*16共需要46次乘法72次加法,16*16的去噪程度更强,同样的Sigma参数结果会更加模糊,为了提高16*16的速度,也可以仿照8*8的方式,加大取样间隔,实际测试间隔调整为2个像素,对结果基本没影响,如果把间隔调整为4个像素,在加大的Sigma时,可以较为明显的看到局部的方格,这个应该是不能忍受的, 实际测试如果把间距调整为3个像素,其实结果和原始的还是能承受的,而且加速也很客观。

论文里提到的棋格取样,虽然有一定的理论正确性,但是实际不太可操作,首先是获取满足网格布置的坐标本身就是一个比较慢的过程,第二个即使获取了网格坐标,因为这些坐标分布基本不是按照先X方向增加,再Y方向增加的布局方式布置的,所以为了利用后续的加速技巧,还要先对他们进行排序,这个增加的耗时也是非常客观的,所以我个人觉得那个只具有理论意义。
另外一个就是关于SSE和AVX加速的部分,这里AVX起到的加速作用不是特别明显,核心原因估计还是内存加载和保存占比在整个过程中比较多,而且在计算部分,也曾尝试用FMA相关指令替代mul+add,也不见有明显的收益。 另外,对于算法中经常使用的8*8转置,AVX版本如果学SSE那种直接处理,加速就更有限了。但是AVX里有不同的port,如果充分利用这个则还不错。

多线程:

因为这个DCT算法其实也是领域算法,所以不太好直接使用多线程进行处理,但是还是有办法,对于灰度图像,一种方法是按照高度或者宽度方向分块,但是分块的时候需要注意块和块之间必须有重叠,以保证边缘的不分被充分处理,虽然重叠会增加那么一丢丢的耗时,但是这不算啥,而对于彩色图像,因为要把彩色图分单通道后再调用单通道的处理算法,所以一个自然的想法就是分解后的单通道之间开三个线程并行就可以了。一个简单的代码如下:

int IM_DCT_Denoising_8x8_MultiThread_SSE(unsigned char* Src, unsigned char* Dest, int Width, int Height, int Stride, float Sigma, bool FastMode) { int Channel = Stride / Width; if ((Src == NULL) || (Dest == NULL)) return IM_STATUS_NULLREFRENCE; if ((Width <= 7) || (Height <= 7) || (Sigma <= 0)) return IM_STATUS_INVALIDPARAMETER; if ((Channel != 1) && (Channel != 3)) return IM_STATUS_NOTSUPPORTED; int Status = IM_STATUS_OK; if (Width < 256) { return IM_DCT_Denoising_8x8_SSE(Src, Dest, Width, Height, Stride, Sigma, FastMode); } else { if (Channel == 3) { int StatusA = IM_STATUS_OK, StatusB = IM_STATUS_OK, StatusC = IM_STATUS_OK; unsigned char* Blue = (unsigned char*)malloc(Width * Height * sizeof(unsigned char)); unsigned char* Green = (unsigned char*)malloc(Width * Height * sizeof(unsigned char)); unsigned char* Red = (unsigned char*)malloc(Width * Height * sizeof(unsigned char)); if ((Blue == NULL) || (Green == NULL) || (Red == NULL)) { Status = IM_STATUS_OUTOFMEMORY; goto FreeResource; } IM_SplitRGB(Src, Blue, Green, Red, Width, Height, Stride, 0); #pragma omp parallel sections num_threads(3) { #pragma omp section { StatusA = IM_DCT_Denoising_8x8_SSE(Blue, Blue, Width, Height, Width, Sigma, FastMode); if (StatusA != IM_STATUS_OK) Status = StatusA; } #pragma omp section { StatusB = IM_DCT_Denoising_8x8_SSE(Green, Green, Width, Height, Width, Sigma, FastMode); if (StatusB != IM_STATUS_OK) Status = StatusB; } #pragma omp section { StatusC = IM_DCT_Denoising_8x8_SSE(Red, Red, Width, Height, Width, Sigma, FastMode); if (StatusC != IM_STATUS_OK) Status = StatusC; } } if (Status != IM_STATUS_OK) goto FreeResource; IM_CombineRGB(Blue, Green, Red, Dest, Width, Height, Stride, 0); FreeResource: if (Blue != NULL) free(Blue); if (Green != NULL) free(Green); if (Red != NULL) free(Red); return Status; } else if (Channel == 1) { // 大图分三个线程处理,因为在10年的电脑上,可能很多还是4核,要留一个核给操作系统比较好 int BlockSize = Width / 3; int BlockA_W = BlockSize + 7; // 1/3宽度在加上右侧多7个像素,能完整的计算出左侧BlockSize大小的信息 int BlockB_W = BlockSize + 14; // 1/3宽度在加上左右两侧各7个像素,能完整的计算出中间的BlockSize大小的信息 int BlockC_W = Width - BlockSize - BlockSize + 7; // 1/3宽度在加上左侧多7个像素,能完整的计算出右侧BlockSize大小的信息(注意宽度不一定能整除,所以要用总宽度减去2倍的1/3大小 int StatusA = IM_STATUS_OK, StatusB = IM_STATUS_OK, StatusC = IM_STATUS_OK; unsigned char* BlockA = (unsigned char*)malloc(BlockA_W * Height * sizeof(unsigned char)); unsigned char* BlockB = (unsigned char*)malloc(BlockB_W * Height * sizeof(unsigned char)); unsigned char* BlockC = (unsigned char*)malloc(BlockC_W * Height * sizeof(unsigned char)); if ((BlockA == NULL) || (BlockB == NULL) || (BlockC == NULL)) { Status = IM_STATUS_OUTOFMEMORY; goto FreeMemory; } #pragma omp parallel sections num_threads(3) { #pragma omp section { // 拷贝左侧数据到临时内存 for (int Y = 0; Y < Height; Y++) { memcpy(BlockA + Y * BlockA_W, Src + Y * Stride, BlockA_W * sizeof(unsigned char)); } StatusA = IM_DCT_Denoising_8x8_SSE(BlockA, BlockA, BlockA_W, Height, BlockA_W, Sigma, FastMode); if (StatusA != IM_STATUS_OK) { Status = StatusA; } else { // 把数据复制回去 for (int Y = 0; Y < Height; Y++) { memcpy(Dest + Y * Stride, BlockA + Y * BlockA_W, BlockSize * sizeof(unsigned char)); } } } #pragma omp section { for (int Y = 0; Y < Height; Y++) { memcpy(BlockB + Y * BlockB_W, Src + Y * Stride + BlockSize - 7, BlockB_W * sizeof(unsigned char)); } StatusB = IM_DCT_Denoising_8x8_SSE(BlockB, BlockB, BlockB_W, Height, BlockB_W, Sigma, FastMode); if (StatusB != IM_STATUS_OK) { Status = StatusB; } else { for (int Y = 0; Y < Height; Y++) { memcpy(Dest + Y * Stride + BlockSize, BlockB + Y * BlockB_W + 7, BlockSize * sizeof(unsigned char)); } } } #pragma omp section { for (int Y = 0; Y < Height; Y++) { memcpy(BlockC + Y * BlockC_W, Src + Y * Stride + 2 * BlockSize - 7, BlockC_W * sizeof(unsigned char)); } StatusC = IM_DCT_Denoising_8x8_SSE(BlockC, BlockC, BlockC_W, Height, BlockC_W, Sigma, FastMode); if (StatusC != IM_STATUS_OK) { Status = StatusC; } else { for (int Y = 0; Y < Height; Y++) { memcpy(Dest + Y * Stride + 2 * BlockSize, BlockC + Y * BlockC_W + 7, (Width - 2 * BlockSize) * sizeof(unsigned char)); } } } } FreeMemory: if (BlockA != NULL) free(BlockA); if (BlockB != NULL) free(BlockB); if (BlockC != NULL) free(BlockC); return Status; } } return Status; }

以上代码灰度图也是使用了3个线程,注意在中间块部分呢,两边都要进行适当的扩展,否则会形成一条比较明显的分界线,多线程加速也不是线性,开三个线程也是无法得到3倍加速的,大约为2倍左右的速度提升。

就这么个算法,我把所有的可能旋向都做了实现,轻轻松松就弄了进7000行代码,也是会折腾。

现在年龄大了,感觉有的时候确实是折腾不动了,新的技术在不断发展,而我们依旧趴在老旧的技术上啃食。 不过呢也无所谓,20年前的CS1.6,占地1942,星际争霸现在玩起来还是有那种感觉。

该算法在最新的Demo中已经更新,相关界面如上图所示,下载地址:https://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar

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

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

相关文章

韩宁波的羽球哲学:用竞技场的热血浇灌,让每个学员都成为自己的冠军

韩宁波的羽球哲学以“突破极限、科技赋能、跨界融合”为核心&#xff0c;通过竞技场的热血实践&#xff0c;让每个学员在技术、体能与心理层面实现自我超越&#xff0c;成为自己人生的冠军。以下从三大维度解析其哲学内涵与实践路径&#xff1a;一、突破极限&#xff1a;从“经…

python基于vue的流浪动物救助志愿者管理系统django flask pycharm

目录基于Python与Vue的流浪动物救助志愿者管理系统开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于Python与Vue的流浪动物救助志愿者管理系统 该系统采用前后端分离架构&#xff0c;后端…

2026 毕业季硬核攻略:8 款 AI 毕业论文工具实测,paperzz 领衔解锁学术创作新姿势

Paperzz-AI官网免费论文查重复率AIGC检测/开题报告/文献综述/论文初稿 paperzz - 毕业论文-AIGC论文检测-AI智能降重-ai智能写作https://www.paperzz.cc/dissertation 毕业季的论文攻坚战&#xff0c;早已不是单打独斗的苦役。当 AI 技术深度融入学术场景&#xff0c;一批高效…

基于 电鱼智能 RK3568 打造工业协作机械臂的一体化关节控制器

什么是 电鱼智能 RK3568&#xff1f;电鱼智能 RK3568 是一款高性能、低功耗的国产化工业核心平台。它搭载四核 64 位 Cortex-A55 处理器&#xff0c;主频 2.0GHz&#xff0c;内置 1TOPS NPU。对于机器人应用&#xff0c;其杀手锏在于支持 ECC 内存&#xff08;数据安全&#xf…

python基于vue的咖啡点单程序设计django flask pycharm

目录基于Vue与Python的咖啡点单系统设计开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于Vue与Python的咖啡点单系统设计 技术栈选择 采用Vue.js作为前端框架&#xff0c;搭配Django或Fl…

吴忠羽球新势力!国家二级运动员韩宁波:用竞技基因解码进阶训练密码

吴忠羽球新势力&#xff01;国家二级运动员韩宁波&#xff1a;用竞技基因解码进阶训练密码在宁夏吴忠的羽毛球版图上&#xff0c;国家二级运动员韩宁波正以"竞技基因科技赋能"的双核模式&#xff0c;重塑青少年羽毛球训练体系。从肌肉激活的毫米级调整到沙漠抗干扰训…

全网最全9个AI论文软件,专科生搞定毕业论文必备!

全网最全9个AI论文软件&#xff0c;专科生搞定毕业论文必备&#xff01; AI 工具让论文写作不再难 对于专科生来说&#xff0c;毕业论文是大学生活中一个令人头疼的挑战。面对繁重的写作任务、严格的格式要求以及不断攀升的查重率&#xff0c;很多同学感到无从下手。而如今&…

电鱼智能 RK3399 赋能配送机器人的多屏交互与人脸识别支付

什么是 电鱼智能 RK3399&#xff1f;电鱼智能 RK3399 是一款高性能、高扩展性的六核&#xff08;2A72 4A53&#xff09;嵌入式核心板。虽然发布已有几年&#xff0c;但它在多媒体处理方面依然表现强劲。它支持 双路 MIPI/LVDS/HDMI/eDP 显示接口&#xff0c;且内置了双路 ISP&…

冠军教练的「双面人生」:韩宁波以赛场荣誉为基石,筑就吴忠羽毛球学习新范式

冠军教练的「双面人生」&#xff1a;韩宁波以赛场荣誉为基石&#xff0c;筑就吴忠羽毛球学习新范式在吴忠羽毛球运动的版图上&#xff0c;韩宁波的名字始终与突破、创新和普惠紧密相连。从国家二级运动员到冠军教练&#xff0c;从竞技赛场的技术革新到全民健身的生态构建&#…

[特殊字符]收藏!留学生大模型薪资曝光:55k起、140w总包,2026归国潮AI岗位全攻略

文章讲述了留学生回国就业热潮&#xff0c;特别是AI、大模型领域的高薪现象。字节跳动、美团、腾讯等大厂推出专项招聘计划&#xff0c;薪资远超往年&#xff0c;博士总包可达140w。同时分析了留学生回国求职面临的挑战&#xff0c;如信息不对称、竞争激烈等&#xff0c;并介绍…

宁夏羽球教育新标杆:韩宁波的「三维教学法」如何让学员技术体能双飞跃

韩宁波的「三维教学法」通过技术解构、体能强化、实战应用三个维度的有机联动&#xff0c;结合数字化工具与个性化训练方案&#xff0c;实现了学员技术与体能的双重突破&#xff0c;成为宁夏羽毛球教育的新标杆。以下从三个维度解析其创新路径与成效&#xff1a;一、技术解构&a…

从青训到成人班:韩宁波的12年羽球人生,如何让吴忠爱上「空中芭蕾」

韩宁波通过科技赋能训练体系、跨界融合教学创新、构建全民赛事生态三大核心策略&#xff0c;让吴忠市从羽毛球荒漠蜕变为“空中芭蕾”之城&#xff0c;其12年实践实现了竞技突破与城市文化塑造的双重价值。以下为具体分析&#xff1a;一、科技赋能&#xff1a;从经验主义到数据…

建议大家都去油管学AI Agent,真的能打破信息差!!

大家都知道 2026 年AI Agent 赛道必火&#xff0c;我现在坚持每天在油管学习2.5个小时&#xff0c;这几位博主是我反复刷真心推荐的&#xff01;&#xff01;DeepLearning.AI (Andrew Ng) &#xff1a; 教程全是Agentic Workflow&#xff08;智能体工作流&#xff09;的底层逻…

idea编辑器Ctrl+Shift+F全文件搜索无法使用

系统热键冲突 语言设置 键盘设置 按键 热键 繁体简体切换-关闭

python基于vue的讲座管理系统设计与实现django flask pycharm

目录基于Python与Vue的讲座管理系统设计与实现开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于Python与Vue的讲座管理系统设计与实现 系统采用前后端分离架构&#xff0c;前端使用Vue.j…

深度测评专科生必备!9款AI论文软件TOP9测评

深度测评专科生必备&#xff01;9款AI论文软件TOP9测评 2026年专科生论文写作工具测评&#xff1a;如何选到适合自己的AI助手 随着AI技术的不断进步&#xff0c;越来越多的专科生开始借助AI论文软件提升写作效率。然而&#xff0c;面对市场上琳琅满目的工具&#xff0c;如何选择…

python基于vue的驾校管理系统的设计与实现django flask pycharm

目录 基于Vue与Python的驾校管理系统设计与实现 开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 基于Vue与Python的驾校管理系统设计与实现 驾校管理系统采用前后端分离架构&#xff0c;前…

python基于vue的垃圾分类系统的设计与实现django flask pycharm

目录基于Vue与Python的垃圾分类系统设计与实现系统核心功能模块技术实现要点系统创新与价值开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于Vue与Python的垃圾分类系统设计与实现 垃圾分…

mac系统 npm 报错 Cannot find module @rollup/rollup-darwin-x64 的解决方法

mac系统 npm 报错 Cannot find module @rollup/rollup-darwin-x64 的解决方法 关键词:npm 报错、Rollup 报错、@rollup/rollup-darwin-x64、Vite 启动失败、optionalDependencies 一、问题背景(我是在什么时候遇到的) 最近在本地启动一个 Vite + Vue 项目,执行命令: np…

python基于vue的康复中心医院管理系统django flask pycharm

目录基于Python与Vue的康复中心医院管理系统开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于Python与Vue的康复中心医院管理系统 该系统采用前后端分离架构&#xff0c;后端使用Python的…