灰度变换
将颜色的RGB设置为相同的值即可使得图片为灰色,一般处理方法有:
1、取三种颜色的平均值
2、取三种颜色的最大值(最小值) 3、加权平均值:0.3R + 0.59G + 0.11*B
人眼对绿色最为敏感
黑白滤镜
计算RGB的平均值arg,arg>=100,r=g=b=255,否则均为0
去色滤镜
RGB取三种颜色的最大值和最小值的平均值
单色滤镜
只保留一个通道,其他设为0
高斯模糊
根据正态分布为每个像素点周围的像素点分配权重,将各个权重(各个权重值和为1)与对应的色值相乘,所得结果求和作为中心像素点新的色值
function gaussBlur(imgData, radius, sigma) {
var pixes = imgData.data,
height = imgData.height,
width = imgData.width,
radius = radius || 5;
sigma = sigma || radius / 3;
var gaussEdge = radius * 2 + 1;
var gaussMatrix = [],
gaussSum = 0,
a = 1 / (2 * sigma * sigma * Math.PI),
b = -a * Math.PI;
for(var i = -radius; i <= radius; i++) {
for(var j = -radius; j <= radius; j++) {
var gxy = a * Math.exp((i * i + j * j) * b);
gaussMatrix.push(gxy);
gaussSum += gxy;
}
}
var gaussNum = (radius + 1) * (radius + 1);
for(var i = 0; i < gaussNum; i++) {
gaussMatrix[i] /= gaussSum;
}
for(var x = 0; x < width; x++) {
for(var y = 0; y < height; y++) {
var r = g = b = 0;
for(var i = -radius; i<=radius; i++) {
var m = handleEdge(i, x, width);
for(var j = -radius; j <= radius; j++) {
var mm = handleEdge(j, y, height);
var currentPixId = (mm * width + m) * 4;
var jj = j + radius;
var ii = i + radius;
r += pixes[currentPixId] * gaussMatrix[jj * gaussEdge + ii];
g += pixes[currentPixId + 1] * gaussMatrix[jj * gaussEdge + ii];
b += pixes[currentPixId + 2] * gaussMatrix[jj * gaussEdge + ii];
}
}
var pixId = (y * width + x) * 4;
pixes[pixId] = ~~r;
pixes[pixId + 1] = ~~g;
pixes[pixId + 2] = ~~b;
}
}
imgData.data = pixes;
return imgData;
}
function handleEdge(i, x, w) {
var m = x + I;
if(m < 0) {
m = -m;
} else if(m >= w) {
m = w + i -x;
}
return m;
}
怀旧滤镜
注意更新RGB值需要对0~255范围之外的值进行判断处理
连环画滤镜
对比度增强和亮度增强
private static byte ContrastModify(int degree, byte basePixel)
{
if (degree < -100) degree = -100;
if (degree > 100) degree = 100;
double contrast = (100.0 + degree) / 100.0;
contrast *= contrast;
double pixel = ((basePixel / 255.0 - 0.5) * contrast + 0.5) * 255;
if (pixel < 0) pixel = 0;
if (pixel > 255) pixel = 255;
return (byte)pixel;
}
private static byte BrightModify(int degree, byte basePixel)
{
if (degree < -255) degree = -255;
if (degree > 255) degree = 255;
int pixel = basePixel + degree;
if (pixel < 0) pixel = 0;
if (pixel > 255) pixel = 255;
return (byte)pixel;
}
LOMO滤镜
特点:色彩浓郁、高饱和度、可能伴随晃动、照片暗角
- 将原图与原图进行“柔光”图层混合得到图B
柔光图层混合
根据混合色的通道数值选择不同公式计算:数值大于128的时候,结果色就比基色稍亮;数值小于或等于128,结果色就比基色稍暗。
柔光模式是以基色为主导,混合色只相应改变局部明暗。其中混合色为黑色,结果色不会为黑色,只比结果色稍暗,混合色为中性色,结果色跟基色一样。
计算公式:
- 图B与一种自己设定的风格色(比如蓝色:R-200,G-37,B-11)进行“排除”图层混合,设定40%透明度,得到图C
差值&排除图层混合
差值图层混合用基色减去混合色或用混合色减去基色从基色亮度中减去混合色,如果产生负值取正进行反相,白色与任何颜色混合得到反相色,黑色与任何颜色混合颜色不变。
排除图层混合与差值类似,但对比度更低,白色与基色混合得到基色补色,黑色与任何颜色混合颜色不变。
计算公式:
- 选择一种暗角模板,与图C进行“叠加”图层混合
叠加图层混合
根据基色通道的数值选择不同公式计算,对颜色进行正片叠加或滤色混合,结果色保留基色的明暗对比,以基色为主导。
计算公式:
暗角生成
暗角特效在Vignette类中实现,主要的算法实现原理是首先建立两个以图像中心为中心的椭圆边界将图像划分为Zone A,Zone B和Zone C三个区域,然后根据图像中各像素点在椭圆边界内外的判断对像素点进行操作:Zone A保持原始图像的像素值,Zone C完全使用纯黑色的边角颜色,Zone B则进行暗角和原图的融合叠加。为了实现融合区域的渐变效果,Zone B建立了一系列以图像中心为中心的椭圆,各个相邻椭圆间的区域根据其边界的坐标距离使用不同的融合权重系数以实现中间亮到四个角渐暗的暗角效果,具体的融合公式如下所示:
关于渐变暗角的融合权重系数的代码具体实现如下:
List aVals; //融合区域系列椭圆的宽向坐标
List bVals; //融合区域系列椭圆的高向坐标
List aValsMidPoints;
List bValsMidPoints;
List imageWeight; // 原图融合权重系数
List vignetteWeight; // 暗角融合权重系数
double a0 = vignetteWidthHalf - bandPixelsHalf; //融合区域系列椭圆宽向起始坐标
double b0 = vignetteHeightHalf - bandPixelsHalf; //融合区域系列椭圆高向起始坐标
//计算融合区域系列椭圆的坐标数组
for (int i = 0; i <= numberSteps; ++i)
{
aEll = a0 + stepSize * i;
bEll = b0 + stepSize * i;
aVals.Add(aEll);
bVals.Add(bEll);
}
for (int i = 0; i < numberSteps; ++i)
{
aEll = a0 + stepSize * (i + 0.5);
bEll = b0 + stepSize * (i + 0.5);
aValsMidPoints.Add(aEll);
bValsMidPoints.Add(bEll);
}
// 计算融合权重
double weight1, weight2, arg, argCosVal;
double arguFactor = Math.PI / bandPixels;
for (int i = 0; i < numberSteps; ++i)
{
arg = arguFactor * (aValsMidPoints[i] - a0);
argCosVal = Math.Cos(arg);
weight1 = 0.5 * (1.0 + argCosVal);
weight2 = 0.5 * (1.0 - argCosVal);
imageWeight.Add(weight1);
vignetteWeight.Add(weight2);
}
效果如下:
晶格化滤镜
超像素分割SLIC(simple linear iterative clustering)+块内平均
SLIC算法使用k-means聚类实现,首先根据用户界面设置的聚类中心个数的超参数初始化k个聚类中心,也即超像素中心,将其均匀分布到图像的像素点上。
初始化label数组保存每一个像素点所属的超像素标签,初始化lengths数组保存各个像素点到所属的超像素中心的距离。
如果图片包含N个像素,要分割成K个超像素,那么每个超像素的大小是N/K ,超像素之间的距离。为了将算法复杂度降低为O(n),在聚类时将搜索区域限制在2S*2S范围内。聚类的目标是使各个像素到所属的超像素中心的距离之和最小。实现时首先将图像的RGB颜色转换为LAB颜色空间,使得计算距离时能够同时考虑LAB颜色信息和XY距离信息,并可以通过用户界面设置的M值调整颜色和距离的比重。
对每一个超像素中心x,搜索它2S*2S范围内的点:如果点到超像素中心x的5维信息的距离小于这个点到它原来所属的超像素中心的距离,那么说明这个点属于超像素x,更新lengths数组和label数组,然后对每个聚类中心,找到所有label值为该聚类中心的点,求他们的平均值从而更新得到k个新的聚类中心。上述过程迭代十次,该部分核心实现代码如下所示:
//计算每个超像素分割区域的面积
double S = Math.Sqrt((w * h) / numberOfCenters);
//生成聚类中心
Center[] centers = createCenters(w, h, lband, aband, labbband, numberOfCenters, S);
//生成聚类标签
double[,] labels = new double[h, w];
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
labels[i, j] = -1;
}
}
for (int iteration = 0; iteration < 10; iteration++)
{
double[,] lengths = new double[h, w];
for (int ii = 0; ii < h; ii++)
{
for (int j = 0; j < w; j++)
{
lengths[ii, j] = Double.MaxValue;
}
}
int i = 0;
foreach (Center center in centers)
{
for (int k = (int)Math.Round(center.X - S); k < (int)Math.Round(center.X + S); k++)
for (int l = (int)Math.Round(center.Y - S); l < (int)Math.Round(center.Y + S); l++)
if (k >= 0 && k < h && l >= 0 && l < w)
{
double L = lband[k, l];
double A = aband[k, l];
double B = labbband[k, l];
double Dc = Math.Sqrt(Math.Pow(L - center.L, 2) + Math.Pow(A - center.A, 2) + Math.Pow(B - center.B, 2));
double Ds = Math.Sqrt(Math.Pow(l - center.Y, 2) + Math.Pow(k - center.X, 2));
double length = Math.Sqrt(Math.Pow(Dc, 2) + Math.Pow(Ds / 2, 2) * Math.Pow(m, 2));
if (length < lengths[k, l])
{
lengths[k, l] = length;
labels[k, l] = i;
}
}
i++;
}
centers = calculateNewCenters(lband, aband, labbband, w, h, centers, labels);
}
效果如下:
参考:
- 图像处理的简单滤镜算法
- 看云文档图像滤镜汇总
- 微像素
- 图层混合模式计算公式