环境配置:VS2019、 Winform、Opencvsharp4 4.5.5.20211231 、 .Net Framework 4.8
实验原图:
1、毛玻璃特效 。
原理: 用该像素点领域内的随机一点的像素值来代替该点像素。
实验结果:
主要代码:
public static bool ImgGroundGlass(Mat src, out Mat dstImg, int ksize = 5)
{
dstImg = src.Clone();
if (ksize < 3) return false;
if (ksize % 2 != 1) return false;
int a = ksize / 2;
Random r = new Random((int)DateTime.Now.Ticks);
int x = 0, y = 0;
// 将图像划分成九个区域
/*
// 左上角 -- 上边 -- 右上角
// | | |
// 左边 -- 中间 -- 右边
// | | |
// 左下角 -- 下边 -- 右下角
*/
// 每个区域进行分块讨论
// 左上角
for (int i = 0; i < a; i++)
{
for (int j = 0; j < a; j++)
{
x = r.Next(0, a);
y = r.Next(0, a);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x, y);
}
}
// 上边
for (int i = 0; i < a; i++)
{
for (int j = a; j < src.Cols - a; j++)
{
x = r.Next(0, a);
y = r.Next(-a, a);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x, y + j);
}
}
// 右上角
for (int i = 0; i < a; i++)
{
for (int j = src.Cols - a; j < src.Cols; j++)
{
x = r.Next(0, a);
y = r.Next(src.Cols - a, src.Cols);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x, y);
}
}
// 左边
for (int i = a; i < src.Rows - a; i++)
{
for (int j = 0; j < a; j++)
{
x = r.Next(-a, a);
y = r.Next(0, a);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x + i, y);
}
}
// 中间
for (int i = a; i < src.Rows - a; i++)
{
for (int j = a; j < src.Cols - a; j++)
{
x = r.Next(-a, a);
y = r.Next(-a, a);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x + i, y + j);
}
}
// 右边
for (int i = a; i < src.Rows - a; i++)
{
for (int j = src.Cols - a; j < src.Cols; j++)
{
x = r.Next(-a, a);
y = r.Next(src.Cols - a, src.Cols);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x + i, y);
}
}
// 左下角
for (int i = src.Rows - a; i < src.Rows; i++)
{
for (int j = src.Cols - a; j < src.Cols; j++)
{
x = r.Next(src.Rows - a, src.Rows);
y = r.Next(0, a);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x, y);
}
}
// 下边
for (int i = src.Rows - a; i < src.Rows; i++)
{
for (int j = a; j < src.Cols - a; j++)
{
x = r.Next(src.Rows - a, src.Rows);
y = r.Next(-a, a);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x, y + j);
}
}
// 右下角
for (int i = src.Rows - a; i < src.Rows; i++)
{
for (int j = src.Cols - a; j < src.Cols; j++)
{
x = r.Next(src.Rows - a, src.Rows);
y = r.Next(src.Cols - a, src.Cols);
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(x, y);
}
}
return true;
}
涉及到像素领域就要考虑到边界的问题,即数组索引超界的问题。可以跟我一样各种的边界问题分别讨论,或者在遍历点时设置循环的上下限,但这样会导致有些边界的点处理不到,或者更改图像大小,只取能够遍历到的中间部分,这样就不需要考虑边界了,但是输出图像变小了。
2、怀旧特效
原理: BGR通道图像使用下面的变化公式即可
实现效果:
主要代码:
public static Mat ImgRetro(Mat src)
{
src.ConvertTo(src, MatType.CV_8UC3);
Mat dstImg = new Mat(src.Size(), MatType.CV_8UC3);
Vec3b vec = new Vec3b();
float bb = 0, bg = 0, br = 0;
for (int i = 0; i < src.Height; i++)
{
for (int j = 0; j < src.Width; j++)
{
vec = src.At<Vec3b>(i, j);
bb = vec.Item2 * 0.272f + vec.Item1 * 0.534f + vec.Item0 * 0.131f;
bg = vec.Item2 * 0.349f + vec.Item1 * 0.686f + vec.Item0 * 0.168f;
br = vec.Item2 * 0.393f + vec.Item1 * 0.769f + vec.Item0 * 0.189f;
dstImg.At<Vec3b>(i, j) = new Vec3b(SetCorrectPix(bb), SetCorrectPix(bg), SetCorrectPix(br));
}
}
return dstImg;
byte SetCorrectPix(float byte1)
{
if (byte1 > 255)
return 255;
else if (byte1 < 0)
return 0;
return (byte)byte1;
}
}
注意在进行每个用到对应像素值得变化的时候,要将其byte值转化成 int double等其他类型,变换后再量化到0到255之间,最后再转化成byte。如果直接使用byte类型进行变化,可能会有溢出的问题,导致最后结果错误。
3、素描画
原理: 可以根据PS素描的步骤: 1 、将BGR图像转成灰度 2 、图像取反 3、高斯滤波 4、 减淡加深图像
实验效果:
主要代码:
public static Mat ImgSketch(Mat src, int meanV = 127)
{
Mat gray = new Mat();
Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
Mat negGray = new Mat();
Cv2.BitwiseNot(gray, negGray);
Mat blurMat = new Mat();
Cv2.GaussianBlur(negGray, blurMat, new OpenCvSharp.Size(3, 3), 0);
Mat negBlurMat = new Mat();
Cv2.BitwiseNot(blurMat, negBlurMat);
Mat dstImg = new Mat();
Cv2.Divide(gray, negBlurMat, dstImg, meanV);
return dstImg;
}
我的理解就是,图像经过高斯滤波后,就滤掉了其中的高频部分,即图像的边缘部分,也就说滤波后的图像只有边缘部分发生了改变,再用Cv2.Divide() , 得到结果就只有改变的部分,即边缘部分结果会比较大,其他部分基本都是1,经过适当的放大后就能显示出只有边缘部分了。我试过不要两次取反得到的结果也差不多。
当然 我们还可以使用自带的 Cv2.PencilSketch() 也能实现素描结果,但就是参数比较难调整。
4、浮雕特效
原理:每个像素点与周围像素点的差值,再加上某一像素。当点位于平滑部分时,像素差值很小,而在边界时像素差值就比较大,这样得到的结果就会时边界部分更加突出,从而到达浮雕的效果.
实验结果:
主要代码:
public static Mat ImgEmboss(Mat src, int kvalue = 150)
{
Mat dstImg = Mat.Zeros(src.Size(), MatType.CV_8UC1);
Mat grayMat = new Mat();
Cv2.CvtColor(src, grayMat, ColorConversionCodes.BGR2GRAY);
int bb = 0;
for (int i = 0; i < src.Height; i++)
{
for (int j = 0; j < src.Width - 1; j++)
{
bb = grayMat.At<byte>(i, j) - grayMat.At<byte>(i, j + 1) + kvalue;
if (bb > 255)
bb = 255;
else if (bb < 0)
bb = 0;
dstImg.At<byte>(i, j) = (byte)bb;
}
}
return dstImg;
}
同样注意索引超限问题和byte值的转化。
5、光圈效果
原理:分别计算每个像素点与光圈中心的距离,并按照相应距离,增加相应亮度。
实验效果:
主要代码:
/// <summary>
/// 光圈效果
/// </summary>
/// <param name="src">输入图像</param>
/// <param name="radious">光圈半径</param>
/// <param name="strength">光圈亮度</param>
/// <param name="center">光圈中心点</param>
/// <returns></returns>
public static Mat ImgAperture(Mat src, int radious, int strength, OpenCvSharp.Point? center = null)
{
Mat dstImg = src.Clone();
OpenCvSharp.Point cc = center ?? new OpenCvSharp.Point(src.Width / 2, src.Height / 2);
bool en = true;
en = en && cc.X > radious && cc.X + radious < src.Width;
en = en && cc.Y > radious && cc.Y + radious < src.Height;
en = en && cc.X > 0 && cc.X < src.Width;
en = en && cc.Y > 0 && cc.Y < src.Height;
if (en == false) return dstImg;
float bb, bg, br;
double dis = 0;
int result = 0;
for (int i = 0; i < src.Height; i++)
{
for (int j = 0; j < src.Width; j++)
{
dis = Math.Pow(cc.X - j, 2) + Math.Pow(cc.Y - i, 2);
bb = src.At<Vec3b>(i, j).Item0;
bg = src.At<Vec3b>(i, j).Item1;
br = src.At<Vec3b>(i, j).Item2;
if (dis < radious * radious)
{
result = (int)(strength * (1 - Math.Sqrt(dis) / radious));
bb += result;
bg += result;
br += result;
}
dstImg.At<Vec3b>(i, j) = new Vec3b(SetCorrectPix(bb), SetCorrectPix(bg), SetCorrectPix(br));
}
}
return dstImg;
byte SetCorrectPix(float byte1)
{
if (byte1 > 255)
return 255;
else if (byte1 < 0)
return 0;
return (byte)byte1;
}
}
6、图像挤压和哈哈镜特效
原理:本质都是图像坐标的非线性变换,具体的步骤可以参考: OpenCV3入门(十四)图像特效—挤压、哈哈镜、扭曲 - 啊哈彭 - 博客园
结果展示:
图像挤压
哈哈镜
代码:
// 挤压
public static void ImgPinch(Mat src, out Mat dstImg, int degree = 5, OpenCvSharp.Point? center = null)
{
if (degree < 1) degree = 1;
if (degree > 32) degree = 32;
src.ConvertTo(src, MatType.CV_8UC3);
dstImg = Mat.Zeros(src.Size(), MatType.CV_8UC3);
OpenCvSharp.Point cc = center ?? new OpenCvSharp.Point(src.Width / 2, src.Height / 2);
int X, Y, offsetX, offsetY;
double radian, radius; //弧和半径
for (int i = 0; i < src.Rows; i++)
{
for (int j = 0; j < src.Cols; j++)
{
offsetX = j - cc.X;
offsetY = i - cc.Y;
radian = Math.Atan2((double)offsetY, (double)offsetX);
// 半径
radius = Math.Sqrt((float)(offsetX * offsetX + offsetY * offsetY));
radius = Math.Sqrt(radius) * degree;
X = (int)(radius * Math.Cos(radian)) + cc.X;
Y = (int)(radius * Math.Sin(radian)) + cc.Y;
if (X < 0) X = 0;
if (X >= src.Cols) X = src.Cols - 1;
if (Y < 0) Y = 0;
if (Y >= src.Rows) Y = src.Rows - 1;
dstImg.At<Vec3b>(i, j) = src.At<Vec3b>(Y, X);
}
}
}
// 哈哈镜
public static void ImgDistoringMirror(Mat src, out Mat dst, int degree = 100, OpenCvSharp.Point? center = null, int R = 100)
{
dst = Mat.Zeros(src.Size(), MatType.CV_8UC3);
OpenCvSharp.Point cc = center ?? new OpenCvSharp.Point(src.Width / 2, src.Height / 2);
bool en = true;
en = en && cc.X > R && cc.X + R < src.Width;
en = en && cc.Y > R && cc.Y + R < src.Height;
en = en && cc.X > 0 && cc.X < src.Width;
en = en && cc.Y > 0 && cc.Y < src.Height;
if (en == false) return;
int i, j;
int X, Y, offsetX, offsetY;
double radian, radius; //弧和半径
for (i = 0; i < src.Rows; i++)
{
for (j = 0; j < src.Cols; j++)
{
offsetX = j - cc.X;
offsetY = i - cc.Y;
radian = Math.Atan2((double)offsetY, (double)offsetX);
// 半径
radius = Math.Sqrt((float)(offsetX * offsetX + offsetY * offsetY));
if (radius <= R && radius > 1)
{
float k = (float)(Math.Sqrt(radius / R) * radius / R * degree);
X = (int)(Math.Cos(radian) * k) + cc.X;
Y = (int)(Math.Sin(radian) * k) + cc.Y;
if (X < 0) X = 0;
if (X >= src.Cols) X = src.Cols - 1;
if (Y < 0) Y = 0;
if (Y >= src.Rows) Y = src.Rows - 1;
dst.At<Vec3b>(i, j) = src.At<Vec3b>(Y, X);
}
else
{
dst.At<Vec3b>(i, j) = src.At<Vec3b>(i, j);
}
}
}
}
7、彩虹特效
原理:就是将画好的彩虹图像融合到图像中
结果:
主要代码:
// 在指定矩形内生成彩虹
public static Mat DrawRainBow(OpenCvSharp.Size size, OpenCvSharp.Rect rect)
{
Mat dstImg = Mat.Zeros(size, MatType.CV_8UC3);
Cv2.Ellipse(dstImg, new OpenCvSharp.Point(rect.Width / 2, rect.Height * 4 / 5), new OpenCvSharp.Size(rect.Width / 2 + 100, rect.Height * 4 / 5), 180, 0, 180, new Scalar(255, 0, 128), 5);
Cv2.Ellipse(dstImg, new OpenCvSharp.Point(rect.Width / 2, rect.Height * 4 / 5), new OpenCvSharp.Size(rect.Width / 2 - 8 + 100, rect.Height * 4 / 5 - 8), 180, 0, 180, new Scalar(255, 0, 0), 5);
Cv2.Ellipse(dstImg, new OpenCvSharp.Point(rect.Width / 2, rect.Height * 4 / 5), new OpenCvSharp.Size(rect.Width / 2 - 8 * 2 + 100, rect.Height * 4 / 5 - 8 * 2), 180, 0, 180, new Scalar(255, 255, 0), 5);
Cv2.Ellipse(dstImg, new OpenCvSharp.Point(rect.Width / 2, rect.Height * 4 / 5), new OpenCvSharp.Size(rect.Width / 2 - 8 * 3 + 100, rect.Height * 4 / 5 - 8 * 3), 180, 0, 180, new Scalar(0, 255, 0), 5);
Cv2.Ellipse(dstImg, new OpenCvSharp.Point(rect.Width / 2, rect.Height * 4 / 5), new OpenCvSharp.Size(rect.Width / 2 - 8 * 4 + 100, rect.Height * 4 / 5 - 8 * 4), 180, 0, 180, new Scalar(0, 255, 255), 5);
Cv2.Ellipse(dstImg, new OpenCvSharp.Point(rect.Width / 2, rect.Height * 4 / 5), new OpenCvSharp.Size(rect.Width / 2 - 8 * 5 + 100, rect.Height * 4 / 5 - 8 * 5), 180, 0, 180, new Scalar(0, 128, 255), 5);
Cv2.Ellipse(dstImg, new OpenCvSharp.Point(rect.Width / 2, rect.Height * 4 / 5), new OpenCvSharp.Size(rect.Width / 2 - 8 * 6 + 100, rect.Height * 4 / 5 - 8 * 6), 180, 0, 180, new Scalar(0, 0, 255), 5);
Cv2.GaussianBlur(dstImg, dstImg, new OpenCvSharp.Size(7, 7), 0, 0);
return dstImg;
}
public static void ImgRainBow(Mat src, out Mat dst, Rect rect)
{
src.ConvertTo(src, MatType.CV_8UC3);
dst = src.Clone();
Mat rainbow = DrawRainBow(rect.Size, rect);
int x = (rect.Left + rect.Right) / 2;
int y = (rect.Top + rect.Bottom) / 2;
OpenCvSharp.Point p = new OpenCvSharp.Point(x, y);
Cv2.SeamlessClone(rainbow, src, null, p, dst, SeamlessCloneMethods.MixedClone);
}
/// <summary>
///
/// </summary>
/// <param name="src"></param>
/// <param name="dst"></param>
/// <param name="rect"></param>
/// <param name="vv">彩虹亮度</param>
public static void ImgRainBow_AddWeight(Mat src, out Mat dst, Rect rect, double vv = 0.2)
{
src.ConvertTo(src, MatType.CV_8UC3);
dst = src.Clone();
Mat rainbow = DrawRainBow(src.Size(), rect);
Cv2.AddWeighted(src, 1.0, rainbow, vv, 0, dst);
}
不同的融合方式实现和结果也不一样,我写了两种 Cv2.SeamlessClone() 和 Cv2.AddWeighted()注意一下两种方式输入图像要求即可
几种常见的图像特效基本就这些了,还有其他的欢迎补充,共同学习,一起进步