作者:爱干球的RD
图像处理中,有几种常见的基础算法,比如“模糊”、“灰度”、“浮雕”、“黑白”、“底片”、“锐化”。这篇文章讲述采用“均值滤波”的算法实现“模糊”。
一、均值滤波原理
原理非常简单,相信你看完,也能很快实现
1)设定一个均值区域,一般定义滤波半径R,半径越大越模糊
2)逐次移动坐标,求该区域内的所有像素的平均值
二、标准均值滤波
逻辑实在是太简单,直接贴代码
如果对Bitmap的RGB解析不了解,可以参考我之前写的:理解Bitmap的ARGB格式,实现颜色选择器
//std mean filter/*** srcData:原图数据* destData:存放处理结果的图片数据* width:图片宽* height:图片高度* stride:图片一行的步幅(>= width)* radius:模糊半径*/#define MIN2(a, b) ((a) < (b) ? (a) : (b))#define MAX2(a, b) ((a) > (b) ? (a) : (b))#define CLIP3(x, a, b) MIN2(MAX2(a,x), b)int MeanFilter(unsigned char *srcData, unsigned char* destData, int width, int height, int stride, int radius){ int ret = 0; if(radius == 0) return ret; int offset = stride - width * 4; unsigned char* destData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride); int M = (radius * 2 + 1) * (radius * 2 + 1); int sumr = 0, sumg = 0, sumb = 0; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { sumr = sumg = sumb = 0; // 对(radius+1) * (radius +1) 的矩形范围求像素的平均值 for(int n = -radius; n <=radius; n++) { for(int m = -radius; m <= radius; m++) { // 注意图片的边界处,坐标会溢出,需要校正 int ny = CLIP3(j + n, 0, height - 1); int nx = CLIP3(i + m, 0, width - 1); int pos = nx * 4 + ny * stride; sumb += srcData[pos]; sumg += srcData[pos + 1]; sumr += srcData[pos + 2]; } } destData[0] = sumb / M; destData[1] = sumg / M; destData[2] = sumr / M; destData += 4; } destData += offset; } return ret;};
不同的模糊半径处理的效果:
三、快速均值滤波
标准均值滤波算法,有大量的重复值的计算,如果图片计算量比较大,可以考虑采用“快速均值滤波”
当计算的点沿x轴移动一个像素,diff只有最左侧的一列和最右侧的一列发生变化,如下图所示。可以将上一次计算的结果减去最左侧,再加上最右侧,可以大幅度提升效率。
y轴移动原理相同。
代码稍微有点绕,逻辑不复杂,有兴趣的同学可以读一读
//Fast mean filter based histagram computationint FastMeanFilter(unsigned char* srcData, unsigned char* dstData, int width, int height ,int stride, int radius){ int ret = 0; if(radius == 0) return ret; if(radius > MIN2(width,height) / 2) radius = (MIN2(width, height) / 2-0.5); memset(dstData, 255, sizeof(unsigned char) * height * stride); int unit = 4, t = 0, t1 = 0; int i,j,k,len = width * height * unit; int block = (radius << 1) + 1; int winSize = block * block; long sumB = 0, sumG = 0,sumR = 0; unsigned char* pSrc = srcData; int* temp = (int*)malloc(sizeof(int)* width * unit); memset(temp,0,sizeof(int) * width * unit); // 一次性求出第一行像素周边的和,存在temp中, // 后面随着y值移动,不断更新temp值 // 设radius = 2, 求出 -2 -1 0 1 2 五行颜色的和,存储在temp中 for(k = -radius; k <= radius; k++) { for(j = 0; j< width; j++) { t = j * unit; // 小于0在图片外面,没有值,对称映射到图片里面来取值 t1 = abs(k) * stride; temp[t] += pSrc[t + t1]; temp[t + 1] += pSrc[t + 1 + t1]; temp[t + 2] += pSrc[t + 2 + t1]; } } // 开始从第一行扫描,沿y方向迭代 for (i = 0; i < height; i++) { sumB = sumG = sumR = 0; // 求出坐标每一行第一个点的卷积 // -2 -1处的值不存在,取绝对值,映射成2 和 1出的值 for (j = -radius; j <= radius; j++) { // j < 0时,图片的左边没有值,映射到图片的右边来取值,这里也可以取(0,y)处的值,不一定要abs(j) t = abs(j) * unit; sumB += temp[t]; sumG += temp[t + 1]; sumR += temp[t + 2]; } // 计算每一行的卷积平均值 for (j = 0; j < width; j++) { // 计算i行j列处的平均值 t = j * unit + i * stride; dstData[t] = (sumB / winSize); dstData[t + 1] = (sumG / winSize); dstData[t + 2] = (sumR / winSize); // sumRGB用完一次,往前推进一个像素,这个if是为了减少最后一次计算,最后一次不用算了 if (j < width - 1) { t = abs(j - radius) * unit; t1 = (j + radius + 1) % width * unit; sumB = sumB - temp[t] + temp[t1]; sumG = sumG - temp[t + 1] + temp[t1 + 1]; sumR = sumR - temp[t + 2] + temp[t1 + 2]; } } // 这个if是为了减少最后一次计算,减去卷积核 if (i < height - 1) { for (k = 0; k < width; k++) { t = k * unit + abs(i - radius) * stride; // 卷积核第一行(注意abs绝对值,是应对边缘处的像素,映射到正值处的位置,增加容错) t1 = k * unit + (i + radius + 1) % height * stride; // 紧邻卷积核的下一行 temp[k * unit] = temp[k * unit] - pSrc[t] + pSrc[t1]; temp[k * unit + 1] = temp[k * unit + 1] - pSrc[t + 1] + pSrc[t1 + 1]; temp[k * unit + 2] = temp[k * unit + 2] - pSrc[t + 2] + pSrc[t1 + 2]; } } } free(temp); return ret;};
第一篇图像处理先到这,对图像处理感兴趣的朋友,欢迎交流