C#Opencvsharp4实现几种图像特效

环境配置: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()注意一下两种方式输入图像要求即可

 几种常见的图像特效基本就这些了,还有其他的欢迎补充,共同学习,一起进步

猜你喜欢

转载自blog.csdn.net/Iawfy_/article/details/124704693
今日推荐