MinFilter(MaxFilter)快速算法C++实现
参考资料:
- MinFilter - Wolfram 语言与系统参考资料中心
- ImageFilter - Wolfram 语言与系统参考资料中心
- Streaming Maximum-Minimum Filter Using No More than Three Comparisons per Element
- [SSE图像算法优化系列七:基于SSE实现的极速的矩形核腐蚀和膨胀(最大值和最小值)算法。]
1、算法简述
1.1、MinFilter(MaxFilter) 算法简述
MinFilter(MaxFilter)算法是用于对一维或多维数据进行滤波的算法,滤波的结果为原数据中对应位置领域r
内的最小(最大)值。在数据的边界处,使用较小(较大)的邻域.。
1.2、MinFilter(MaxFilter) 快速算法简述
对于MinFilter(MaxFilter)的快速算法,思想来自于这篇论文Streaming Maximum-Minimum Filter Using No More than Three Comparisons per Element。在网上找到了这张图,但这个图也没有什么文字说明,并不是很清楚。
下面按照我实现的时候的思路,来说一下我的理解。
首先,对于一个多维的数据,都可以逐个维度进行处理。比如说一个图片,也就是二维数据,可以先对每一行进行处理,然后再对每一列进行处理,这样得到的结果与行列同时处理是一样的。
假设r=1
原始数据 --> 逐行处理 --> 逐列处理
5 2 1 3 4 2 1 1 1 3 2 1 1 1 3
6 9 8 4 7 6 6 4 4 4 2 1 1 0 0
7 3 8 2 0 3 3 2 0 0 0 0 0 0 0
9 0 1 5 6 0 0 0 1 5 0 0 0 0 0
原始数据 --> 逐行列处理
5 2 1 3 4 2 1 1 1 3
6 9 8 4 7 2 1 1 0 0
7 3 8 2 0 0 0 0 0 0
9 0 1 5 6 0 0 0 0 0
因此算法的关键在于提高一行数据处理的效率。
这个算法的过程大概是这样的:
1、首先遍历一行数据中最左边的r*2+1
个数据,获取最小值和最小值的位置。然后对左边边界部分的处理,直接赋最小值。
2、从r+1
位置开始向后遍历,一直到右边界部分。
3、遍历的时候,判断上一次获取的最小值索引minIndex
,是否在当前位置的领域r
以内。
如果不在,则遍历当前位置的领域r
范围,找出最小值的位置。也可以先与当前位置领域r
内最右边的比较,如果最右边的小于minIndex
位置的值,则minIndex
就是这最右边的这个,否则就需要遍历当前位置领域r
范围内。
如果在,则说明当前位置领域r
内,除了最右边的元素,肯定都小于minIndex
处的值。因为minIndex
是当前位置上一个的领域r
内的最小值,而上一个位置的领域r
范围与当前位置的领域r
范围只偏移了一个位置。
2、实现代码
我这里实现了对一行数据的过滤,然后在一行数据过滤的基础上实现对二维矩阵进行过滤。
对于MaxFilter的相关实现,只需要将下面对应的>=
改为<=
即可。
2.1、MinFilterOneRow 单行滤波代码
/**********************************************************************//**
* @brief 对一行数据进行滤波,每个值用邻域 r 内的最小值替换.
* @author solym@sohu.com/ymwh@foxmail.com
* @date 2019/4/23
* @param srcData 待滤波数据地址.
* @param srcChanelCount 待滤波数据每个像素的通道数.
* @param srcChanelIndex 待滤波数据要进行滤波的通道[0,srcChanelCount).
* @param dstData 滤波后输出数据地址.
* @param dstChanelCount 滤波后输出数据每个像素的通道数.
* @param dstChanelIndex 滤波后数据要输出的通道索引[0,srcChanelCount).
* @param colnumCount 该行数据要滤波的像素数.
* @param radius 滤波的半径大小.
*************************************************************************/
template<typename PixelDataType>
void MinFilterOneRow(
PixelDataType* srcData, const size_t srcChanelCount, const size_t srcChanelIndex,
PixelDataType* dstData, const size_t dstChanelCount, const size_t dstChanelIndex,
const size_t colnumCount, const size_t radius)
{
PixelDataType* pSrc = srcData;
PixelDataType* pDst = dstData;
size_t minIndex = 0; // 记录最小值的下标
size_t blockSize = radius * 2 + 1; // 块大小,以当前点为中心,左右各radius的宽度
PixelDataType minValue = pSrc[srcChanelIndex]; // 比较中获取最小值进行记录
// 对第一个块进行处理(i从1开始,比较(i-1,i)位置像素值)
// 找出最小值(第一个块内的最小值,就是r位置(块中心)处的输出值)
for(size_t iPixel=1; iPixel < blockSize; ++iPixel){
PixelDataType value = pSrc[iPixel*srcChanelCount + srcChanelIndex];
// 使用 >= 比 > 更快推进minIndex向前走
if(minValue >= value){
minValue = value;
minIndex = iPixel;
}
}
// 输出到第一个块中心(r)位置处的值。
// 它已经是第一个块内的最小值,也就是该块左边都只能是这个值
for(size_t i=0;i<=radius;++i){
pDst[i*dstChanelCount + dstChanelIndex] = minValue;
}
// 开始处理r+1位置之后的值
for (size_t iPixel = radius + 1; iPixel < colnumCount - radius; ++iPixel) {
/* i-r i i+r
* |____________|___________|_
* └min
* 当前最小的索引在当前位置为中心的块的内(一定位于当前块内或前一个)
* iPixel是当前块的中心,下面说的当前位置都指iPixel
*/
if(minIndex >= (iPixel - radius)) {
// 当前最小索引位置值与当前位置为中心的块的最后一个值比较
// 根据下面的代码可知,如果mIndex在块的内部,它所在位置的值一定是最小的
// 进入本次循环时,minIndex是上次比较的值,而上一个块与当前块等长,位置差一位
// 所以可以直接和当前块最后一个像素值进行比较了,当前块也就完全比较完了
size_t nextBlockFirstIndex = iPixel + radius;
if(pSrc[minIndex*srcChanelCount + srcChanelIndex] >
pSrc[nextBlockFirstIndex*srcChanelCount + srcChanelIndex]){
// 赋值当前最小值索引和值
minIndex = nextBlockFirstIndex;
minValue = pSrc[nextBlockFirstIndex*srcChanelCount + srcChanelIndex];
}
}else{
// 如果不在当前位置为中心的块内,则对当前块进行查找最小值
// 则将minIndex设置该块的最左边位置
minIndex = iPixel - radius;
// 获取当前位置为中心的块的最小值和索引
minValue = pSrc[minIndex*srcChanelCount + srcChanelIndex];
size_t blockEnd = minIndex + blockSize;
for (size_t iBPixel = minIndex; iBPixel < blockEnd; ++iBPixel) {
PixelDataType value = pSrc[iBPixel*srcChanelCount + srcChanelIndex];
if(minValue >= value){
minIndex = iBPixel;
minValue = value;
}
}
} // end if minIndex > ...
pDst[iPixel*dstChanelCount + dstChanelIndex] = minValue;
} // end for iPixel
// 最后一个块中心位置的右边,一定都是和它中心位置的值是一样的
for (size_t i = colnumCount-radius; i < colnumCount; ++i) {
pDst[i*dstChanelCount + dstChanelIndex] = minValue;
}
}
2.2、MinFilterOneMatrix 单个二维矩阵滤波代码
对于二维矩阵进行滤波,实际上是先进行行滤波,然后结果进行行列转置,对转置的结果再次进行行滤波,然后再行列转置输出。
/**********************************************************************//**
* @brief 对一个矩阵数据进行滤波,每个值用邻域 r 内的最小值替换.
* @author solym@sohu.com/ymwh@foxmail.com
* @date 2019/4/23
* @param srcData 待滤波数据地址.
* @param srcBytePerRow 待滤波数据每行的字节数.
* @param srcChanelCount 待滤波数据每个像素的通道数.
* @param srcChanelIndex 待滤波数据要进行滤波的通道[0,srcChanelCount).
* @param dstData 滤波后输出数据地址.
* @param dstBytePerRow 滤波后输出数据每行的字节数.
* @param dstChanelCount 滤波后输出数据每个像素的通道数.
* @param dstChanelIndex 滤波后数据要输出的通道索引[0,srcChanelCount).
* @param rowCount 矩阵的行数.
* @param colCount 矩阵的列数.
* @param radius 滤波的半径大小.
*************************************************************************/
template<typename PixelDataType>
void MinFilterOneMatrix(
PixelDataType* srcData, const size_t srcBytePerRow,
const size_t srcChanelCount, const size_t srcChanelIndex,
PixelDataType* dstData, const size_t dstBytePerRow,
const size_t dstChanelCount, const size_t dstChanelIndex,
const size_t rowCount, const size_t colCount,
const size_t radius)
{
unsigned char* pSrc = reinterpret_cast<unsigned char*>(srcData);
unsigned char* pDst = reinterpret_cast<unsigned char*>(dstData);
// 保存中间结果
std::vector<PixelDataType> tmpData(rowCount * colCount);
// 逐行进行滤波
for (size_t row = 0; row < rowCount; ++row) {
// 获取输入和输出每行的行首位置
PixelDataType* pSrcRowFirst = (PixelDataType*)(pSrc + row * srcBytePerRow);
PixelDataType* pDstRowFirst = tmpData.data() + row * colCount;
// 对当前行进行滤波
MinFilterOneRow<PixelDataType>(pSrcRowFirst,srcChanelCount,srcChanelIndex,
pDstRowFirst,1,0,
colCount,radius);
}
// 将行滤波后的结果进行 行列转置(进行列滤波)
std::vector<PixelDataType> tmpDataT(rowCount * colCount);
for (size_t row = 0; row < rowCount; ++row) {
for(size_t col = 0; col < colCount; ++col){
tmpDataT[col*rowCount + row] = tmpData[row*colCount + col];
}
}
// 对转置后的矩阵进行 逐行滤波(就是原行滤波后结果进行列滤波)
for (size_t col = 0; col < colCount; ++col) {
PixelDataType* pSrcColFirst = tmpDataT.data() + col * rowCount;
PixelDataType* pDstColFirst = tmpData.data() + col * rowCount;
// 对当前行进行滤波
MinFilterOneRow<PixelDataType>(pSrcColFirst,1,0,
pDstColFirst,1,0,
rowCount,radius);
}
// 将行列滤波后的结果输出
for (size_t row = 0; row < rowCount; ++row) {
PixelDataType* pDstRowFirst = (PixelDataType*)(pDst + row * dstBytePerRow);
for(size_t col = 0; col < colCount; ++col){
pDstRowFirst[col*dstChanelCount+dstChanelIndex] = tmpData[col*rowCount + row];
}
}
}
3、测试
3.1 测试截图
使用Qt写了一个简单的测试程序进行测试,测试结果如下:
3.2 测试代码
#include "filter.hpp"
#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QComboBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QWebEngineView>
#include <QXmlStreamWriter>
#include <QBuffer>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QImage srcImage,dstImage;
// 创建窗口
QWidget widget;
// 添加控件
QWebEngineView *wevView = new QWebEngineView(&widget);
QLineEdit* leSrcImagePath = new QLineEdit(&widget);
QPushButton* pbSelectSrcFile = new QPushButton(QStringLiteral("选择图片"),&widget);
QComboBox* cbSelectFilterAlg = new QComboBox(&widget);
cbSelectFilterAlg->addItems(
{ QStringLiteral("MinFilter"),QStringLiteral("MaxFilter")});
QPushButton* pbRunFilter = new QPushButton(QStringLiteral("执行滤波"),&widget);
//pbRunDetect->setEnabled(false);
QHBoxLayout* hbLayout = new QHBoxLayout;
// 设置布局
hbLayout->addWidget(leSrcImagePath);
hbLayout->addWidget(pbSelectSrcFile);
hbLayout->addWidget(cbSelectFilterAlg);
hbLayout->addWidget(pbRunFilter);
QVBoxLayout* vbLayout = new QVBoxLayout(&widget);
vbLayout->addLayout(hbLayout);
vbLayout->addWidget(wevView);
// 添加处理操作
std::function<void(QString,const QImage&,const QImage&)>
updateHtmlView =
[wevView,leSrcImagePath](QString filterName,const QImage& srcImage,const QImage& resultimage)
{
QString tmpPath;
QByteArray html;
{
QXmlStreamWriter writer(&html);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement("html");
writer.writeStartElement("body");
writer.writeAttribute("bgcolor","gray");
if(!srcImage.isNull()){
writer.writeTextElement("h2",QStringLiteral("原图"));
writer.writeStartElement("img");
QBuffer buffer;
srcImage.save(&buffer,"PNG");
writer.writeAttribute("src","data:image/png;base64," + buffer.data().toBase64());
// writer.writeAttribute("src",QUrl(leSrcImagePath->text()).toString());
writer.writeEndElement();
}
if(!resultimage.isNull()){
writer.writeTextElement("h2",filterName + QStringLiteral("滤波结果图"));
writer.writeStartElement("img");
QBuffer buffer;
resultimage.save(&buffer,"PNG");
writer.writeAttribute("src","data:image/png;base64," + buffer.data().toBase64());
// tmpPath = QDir::tempPath() + QString::fromUtf8(".png");
// resultimage.save(tmpPath,"PNG");
// writer.writeAttribute("src",QUrl(tmpPath).toString());
writer.writeEndElement();
}
writer.writeEndElement();
writer.writeEndElement();
}
wevView->setHtml(QString::fromUtf8(html));
// if(!resultimage.isNull()){
// qDebug()<<tmpPath;
// QFile::remove(tmpPath);
// }
};
// 文件选择按钮单击信号处理
QObject::connect(pbSelectSrcFile,&QPushButton::clicked,
[leSrcImagePath,&srcImage,&widget,&updateHtmlView]()
{
static QString path(".");
path = QFileDialog::getOpenFileName(&widget,
QStringLiteral("选择待滤波图片"),
path,
QString("Images (*.png *.jpg *.jpeg *.jfif)"));
if(path.isEmpty()){return;}
QImage image;
if(!image.load(path)){return;}
srcImage = image.convertToFormat(QImage::Format_RGBA8888);
leSrcImagePath->setText(path);
updateHtmlView(QString(),srcImage,QImage());
});
// 执行滤波按钮单击信号处理
QObject::connect(pbRunFilter,&QPushButton::clicked,
[&cbSelectFilterAlg,&srcImage,&dstImage,&updateHtmlView]
{
// 获取滤波算法名称
QString filterName = cbSelectFilterAlg->currentText();
// 最小值滤波 https://reference.wolfram.com/language/ref/MinFilter.html
if(filterName == QStringLiteral("MinFilter")){
dstImage = srcImage;
unsigned int raduis = 2;
uchar* pSrc = srcImage.bits();
uchar* pDst = dstImage.bits();
unsigned int colCount = srcImage.width();
unsigned int rowCount = srcImage.height();
unsigned int chanel = 4;
// 分别对RGB通道进行滤波
MinFilterOneMatrix<uchar>(pSrc,srcImage.bytesPerLine(),chanel,0,
pDst,dstImage.bytesPerLine(),chanel,0,
rowCount,colCount,raduis);
MinFilterOneMatrix<uchar>(pSrc,srcImage.bytesPerLine(),chanel,1,
pDst,dstImage.bytesPerLine(),chanel,1,
rowCount,colCount,raduis);
MinFilterOneMatrix<uchar>(pSrc,srcImage.bytesPerLine(),chanel,2,
pDst,dstImage.bytesPerLine(),chanel,2,
rowCount,colCount,raduis);
}
else if(filterName == QStringLiteral("MaxFilter")){
dstImage = srcImage;
unsigned int raduis = 2;
uchar* pSrc = srcImage.bits();
uchar* pDst = dstImage.bits();
unsigned int colCount = srcImage.width();
unsigned int rowCount = srcImage.height();
unsigned int chanel = 4;
// 分别对RGB通道进行滤波
MaxFilterOneMatrix<uchar>(pSrc,srcImage.bytesPerLine(),chanel,0,
pDst,dstImage.bytesPerLine(),chanel,0,
rowCount,colCount,raduis);
MaxFilterOneMatrix<uchar>(pSrc,srcImage.bytesPerLine(),chanel,1,
pDst,dstImage.bytesPerLine(),chanel,1,
rowCount,colCount,raduis);
MaxFilterOneMatrix<uchar>(pSrc,srcImage.bytesPerLine(),chanel,2,
pDst,dstImage.bytesPerLine(),chanel,2,
rowCount,colCount,raduis);
}
updateHtmlView(filterName,srcImage,dstImage);
});
widget.resize(1024,768);
widget.show();
return a.exec();
}
MinFilter(MaxFilter)快速算法C++实现的更多相关文章
- Op-level的快速算法
十岁的小男孩 本文为终端移植的一个小章节. 目录 引言 FFT Conv2d (7x7, 9x9) Winograd Conv2d (3x3, 5x5) 引言 本节针对CNN进行加速计算的,主要有以下 ...
- 从大整数乘法的实现到 Karatsuba 快速算法
Karatsuba 快速乘积算法是具有独特合并过程(combine/merge)的分治算法(Karatsuba 是俄罗斯人).此算法主要是对两个整数进行相乘,并不适用于低位数(如 int 的 32 位 ...
- 自动色彩均衡(ACE)快速算法
ACE算法源自retinex算法,可以调整图像的对比度,实现人眼色彩恒常性和亮度恒常性,通过差分来计算目标点与周围像素点的相对明暗关系来校正最终像素值,有很好的增强效果.但是计算复杂度非常高,本文提出 ...
- 产生N个不重复的随机数的快速算法
//seed array ,,,,,,,,,}; //随机数个数 ; //结果存放在里面 ]; ; i < N; i++) { //从剩下的随机数里生成 , startArray.length ...
- 多项式相乘快速算法原理及相应C代码实现---用到fft
最近认真研究了一下算法导论里面的多项式乘法的快速计算问题,主要是用到了FFT,自己也实现了一下,总结如下. 1.多项式乘法 两个多项式相乘即为多项式乘法,例如:3*x^7+4*x^5+1*x^2+5与 ...
- Layer-level的快速算法
十岁的小男孩 本文为终端移植的一个小章节. Sparse Block Net 本节为优化加速的第二章节,主要介绍Sparse-block net.上章节为OP算子层的加速,本节为层级间的加速,主要针对 ...
- 求素数的一个快速算法 Python 快速输出素数算法
思想 以100以内为例. 生成一个全是True的101大小的数组 2开始,遇到2的倍数(4,6,8,10...)都赋值为False 因为这些数字都有因子 2 3开始,遇到3的倍数(6,9,12...) ...
- 图像处理之基础---卷积及其快速算法的C++实现
头文件: /* * Copyright (c) 2008-2011 Zhang Ming (M. Zhang), zmjerry@163.com * * This program is free so ...
- YUV / RGB 格式及快速转换算法
1 前言 自然界的颜色千变万化,为了给颜色一个量化的衡量标准,就需要建立色彩空间模型来描述各种各样的颜色,由于人对色彩的感知是一个复杂的生理和心理联合作用 的过程,所以在不同的应用领域中为了更好更准确 ...
随机推荐
- Jenkins的介绍
在公司看见有的部门在使用这个工具,好奇一下,今天就开始学习一番. 1.官网 https://jenkins.io/ 2.介绍 Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目,无论用 ...
- Codeforces 1118F1 Tree Cutting (Easy Version) (简单树形DP)
<题目链接> 题目大意: 给定一棵树,树上的点有0,1,2三中情况,0代表该点无色.现在需要你将这棵树割掉一些边,使得割掉每条边分割成的两部分均最多只含有一种颜色的点,即分割后的两部分不能 ...
- iOS9 中 alertView 的使用
"UIAlertView is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerSty ...
- Linux系统开发之路-上
本节内容主要介绍Linux操作系统的主要特性,包括Linux与Windows操作系统的主要区别:Linux系统的分类:开发环境的推荐:Linux操作系统的安装:Linux系统下开发环境的安装和配置. ...
- Jmeter实现Basic Auth方式登录
背景 在实际测试日常中,产品有两种登录方式,一种是普通登录方式,另一种则是Basic Auth方式登录.两种登录对应着产品中不同的操作,对权限进行了一个划分. Postman登录 使用Postman登 ...
- python简单名片管理系统
- fdisk
fdisk管理分区 参数 作用 m ...
- 声明式调用---Feign
Feign:Feign是一种声明式.模板化的HTTP客户端. 用我的理解来说,Feign的功能类似dubbo暴露服务,但是与dubbo稍有不同的是Feign是HTTP REST接口的形式暴露的. 这一 ...
- SpringBoot+Jpa+MySql学习
上一篇介绍了springboot简单整合mybatis的教程.这一篇是介绍springboot简单整合jpa的教程. 由于jpa的功能强大,后续会继续写关于jpa的介绍已经使用,本文只是简单介绍一下它 ...
- 2018-7-17-随笔-params和ref、out用法、事件访问器
**************************************************************************************************** ...