OpenCv案例(五):基于OpenCvSharp对原图像进行透视变换处理

1:需求:在图像拍摄或是扫描过程中,获取到不规则的矩形,这样的图像若不预处理,对后期的处理过程中会造成较大的难度,需要通过透视变换来校正图像,得到正确的形状;

2:先看原图,如下所示:

3:处理后结果,如下所示:

 4:详细代码如下所示:

public Mat PerspectiveCorrection(Mat src, out string printLines)
        {
            printLines = string.Empty;
            //灰度处理
            Mat grayMat = new Mat();
            Cv2.CvtColor(src, grayMat, ColorConversionCodes.BGR2GRAY);
            Cv2.ImShow("grayMat", grayMat);

            //模糊处理 降低噪音
            Mat blurMat = new Mat();
            //Cv2.MedianBlur(grayMat, blurMat, 3);
            Cv2.GaussianBlur(grayMat, blurMat, new Size(3, 3), 9, 9);
            //Cv2.BilateralFilter(grayMat, blurMat, 5, 70, 100);
            Cv2.ImShow("blurMat", blurMat);


            //Mat edgesMat = new Mat();
            //Cv2.Canny(blurMat, edgesMat, 50, 50 * 2, 3);
            Cv2.Laplacian(blurMat, edgesMat, MatType.CV_8UC3, 3);
            //Cv2.ImShow("edgesMat", edgesMat);

            //二值处理
            Mat binaryMat = new Mat();
            Cv2.Threshold(blurMat, binaryMat, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
            Cv2.ImShow("binaryMat", binaryMat);

            //形态学操作
            Mat morphMat = new Mat();
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(9, 9), new Point(-1, -1));
            Cv2.MorphologyEx(binaryMat, morphMat, MorphTypes.Close, kernel, new Point(-1, -1), 3);
            Cv2.ImShow("morphMat", morphMat);

            //轮廓发现
            Cv2.BitwiseNot(morphMat, morphMat, new Mat());
            Cv2.ImShow("BitwiseNot", morphMat);

            Point[][] contours;
            HierarchyIndex[] hierarchy;
            Cv2.FindContours(morphMat, out contours, out hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, new Point());


            #region 寻找最大轮廓下标
            //double area = -1;
            //double tempArea = 0;
            //int index = -1;
            //for (int i = 0; i < contours.Length; i++)
            //{
            //    area = Cv2.ContourArea(contours[i]);
            //    if (tempArea < area)
            //    {
            //        tempArea = area;
            //        index = i;
            //    }
            //} 
            #endregion

            //轮廓绘制
            int width = src.Cols;
            int height = src.Rows;
            Mat drawImg = Mat.Zeros(src.Size(), MatType.CV_8UC3);
            for (int i = 0; i < contours.Length; i++)
            {
                Rect rect = Cv2.BoundingRect(contours[i]);

                if (rect.Width > width / 4 && rect.Width < width - 10)
                {
                    Cv2.DrawContours(drawImg, contours, i, new Scalar(0, 0, 255), 2, LineTypes.Link8, hierarchy);
                }
            }
            Cv2.ImShow("contours", drawImg);

            //查找直线
            Mat contoursImg = new Mat();
            int accu = Math.Min(width / 2, height / 2);
            Cv2.CvtColor(drawImg, contoursImg, ColorConversionCodes.BGR2GRAY);
            LineSegmentPoint[] lines = Cv2.HoughLinesP(contoursImg, 1, Math.PI / 180.0, accu / 5, accu / 5);
            Mat linesImg = Mat.Zeros(src.Size(), MatType.CV_8UC3);
            for (int i = 0; i < lines.Length; i++)
            {
                Cv2.Line(linesImg, lines[i].P1, lines[i].P2, new Scalar(0, 0, 255), 1, LineTypes.Link8);
            }
            Cv2.ImShow("LinesImg", linesImg);

            #region 查找坐标点
            寻找与定位上下左右4条线 差异最大化 横线两条线的Y值差别大,竖线X防线差值大
            int deltah = 0, deltaW = 0;
            LineSegmentPoint topLine = new LineSegmentPoint();
            LineSegmentPoint bottomLine = new LineSegmentPoint();
            LineSegmentPoint leftLine = new LineSegmentPoint();
            LineSegmentPoint rightLine = new LineSegmentPoint();

            string str = string.Empty;
            for (int i = 0; i < lines.Length; i++)
            {
                //上下横线 判断两点位置坐标差值 并且长度大于最小边的1/10;
                deltah = Math.Abs(lines[i].P2.X - lines[i].P1.X);
                if (lines[i].P2.Y < (height / 5 * 2) && lines[i].P1.Y < (height / 5 * 2) && deltah > accu / 10)
                {
                    topLine = lines[i];
                    str += "t";
                }

                if (lines[i].P2.Y > height / 5 * 3 && lines[i].P1.Y > height / 5 * 3 && deltah > accu / 10)
                {
                    bottomLine = lines[i];
                    str += "b";
                }

                //宽度 x方向
                deltaW = Math.Abs(lines[i].P2.Y - lines[i].P1.Y);
                if (lines[i].P1.X < (width / 5 ) && lines[i].P2.X < (width / 5) && deltaW > accu / 10)
                {
                    leftLine = lines[i];
                    str += "l";
                }

                //在x五分之四的位置查找最右边线上的点
                if (lines[i].P1.X > (width / 5 * 4) && lines[i].P2.X > (width / 5 * 4) && deltaW > accu / 10)
                {
                    rightLine = lines[i];
                    str += "r";
                }
            }

            if (!(str.Contains("t") && str.Contains("b") && str.Contains("l") && str.Contains("r")))
            {
                str = "RecFailed";
            }

            //打印坐标点显示
            printLines = "topLine:p1(x,y)=" + topLine.P1.X + "," + topLine.P1.Y + "  p2(x,y)=" + topLine.P2.X + "," + topLine.P2.Y + "\r\n";
            printLines += "bottomLine:p1(x,y)=" + bottomLine.P1.X + "," + bottomLine.P1.Y + "  p2(x,y)=" + bottomLine.P2.X + "," + bottomLine.P2.Y + "\r\n";
            printLines += "leftLine:p1(x,y)=" + leftLine.P1.X + "," + leftLine.P1.Y + "  p2(x,y)=" + leftLine.P2.X + "," + leftLine.P2.Y + "\r\n";
            printLines += "rightLine:p1(x,y)=" + rightLine.P1.X + "," + rightLine.P1.Y + "  p2(x,y)=" + rightLine.P2.X + "," + rightLine.P2.Y + "\r\n";


            绘制圆点位置
            Cv2.Circle(linesImg, topLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, topLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, bottomLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, bottomLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, leftLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, leftLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, rightLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, rightLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);

            Cv2.ImShow("linesImg", linesImg);

            #region 计算交点
            //y=kx+b 求出两条直线的交点 从而求出四个点
            double k1, c1;
            //两点法 (y1-y2)/(x1-x2) = k  //横线X方向差值大 防止Y方向值相同 分母为零
            k1 = Convert.ToDouble(topLine.P2.Y - topLine.P1.Y) / Convert.ToDouble(topLine.P2.X - topLine.P1.X);
            c1 = topLine.P1.Y - k1 * topLine.P1.X;


            double k2, c2;
            k2 = Convert.ToDouble(bottomLine.P2.Y - bottomLine.P1.Y) / Convert.ToDouble(bottomLine.P2.X - bottomLine.P1.X);
            c2 = bottomLine.P1.Y - k2 * bottomLine.P1.X;

            //竖线Y方向差值大 防止X方向值相同 分母为零
            double k3, c3;
            if (Convert.ToDouble(leftLine.P2.X - leftLine.P1.X) == 0)//防止分母为0
            {
                leftLine.P2.X = leftLine.P2.X + 1;
            }
            k3 = Convert.ToDouble(leftLine.P2.Y - leftLine.P1.Y) / Convert.ToDouble(leftLine.P2.X - leftLine.P1.X);
            c3 = leftLine.P1.Y - k3 * leftLine.P1.X;


            double k4, c4; //考虑p1点和p2点x或y坐标相同
            if (Convert.ToDouble(rightLine.P2.X - rightLine.P1.X) == 0)//防止分母为0
            {
                rightLine.P2.X = rightLine.P2.X + 1;
            }
            k4 = Convert.ToDouble(rightLine.P2.Y - rightLine.P1.Y) / Convert.ToDouble(rightLine.P2.X - rightLine.P1.X);
            c4 = rightLine.P1.Y - k4 * rightLine.P1.X;


            //四条直线的焦点
            //左上角
            Point p0 = new Point();
            p0.X = Convert.ToInt32((c1 - c3) / (k3 - k1));
            p0.Y = Convert.ToInt32(k1 * p0.X + c1);

            //右上角
            Point p1 = new Point();
            p1.X = Convert.ToInt32((c1 - c4) / (k4 - k1));
            p1.Y = Convert.ToInt32(k1 * p1.X + c1);

            //左下角
            Point p2 = new Point();
            p2.X = Convert.ToInt32((c2 - c3) / (k3 - k2));
            p2.Y = Convert.ToInt32(k2 * p2.X + c2);

            //右下角
            Point p3 = new Point();
            p3.X = Convert.ToInt32((c2 - c4) / (k4 - k2));
            p3.Y = Convert.ToInt32(k2 * p3.X + c2);

            printLines += "p1(x,y)=" + p0.X + "," + p0.Y + "\r\n";
            printLines += "p2(x,y)=" + p1.X + "," + p1.Y + "\r\n";
            printLines += "p3(x,y)=" + p2.X + "," + p2.Y + "\r\n";
            printLines += "p4(x,y)=" + p3.X + "," + p3.Y + "\r\n";

            //绘制圆点位置
            Cv2.Circle(linesImg, p0, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, p1, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, p2, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, p3, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);

            Cv2.Line(linesImg, topLine.P1, topLine.P2, new Scalar(255, 0, 0), 1, LineTypes.Link8, 0);
            Cv2.ImShow("fourCorner", linesImg);

            //透视变换
            List<Point2f> srcContoursPoints = new List<Point2f>();
            srcContoursPoints.Add(p0);
            srcContoursPoints.Add(p1);
            srcContoursPoints.Add(p2);
            srcContoursPoints.Add(p3);

            List<Point2f> dstContoursPoints = new List<Point2f>();
            dstContoursPoints.Add(new Point2f(0, 0));
            dstContoursPoints.Add(new Point2f(width, 0));
            dstContoursPoints.Add(new Point2f(0, height));
            dstContoursPoints.Add(new Point2f(width, height));

            //透视变换矩阵
            Mat retImg = new Mat();
            Mat warpMat = Cv2.GetPerspectiveTransform(srcContoursPoints, dstContoursPoints);
            Cv2.WarpPerspective(src, retImg, warpMat, retImg.Size(), InterpolationFlags.Linear);
            Cv2.ImShow("retImg", retImg);
            #endregion
            #endregion

            return linesImg;
        }

基本处理过程图像如下:

        二值图像

         轮廓查找

        线段提取

         线段交点查找

 图像透视变换 

        

该案例透视变换的基本处理步骤如上,主要难点是如何噪声去除和线段交点查找部分的操作;不同图像需要对上述算法中一些参数进行调整(若不理解部分算法参数含义,查找相关资料学习);

猜你喜欢

转载自blog.csdn.net/qq_37835727/article/details/123642234