C#+OpenCV实战(一)_图片简易角度矫正

/// <summary>
/// 图像角度矫正-纸张
/// </summary>
/// <param name="src">图片</param>
/// <returns>结果图片</returns>
/// <exception cref="Exception"></exception>
public static Mat ImageAngle_Correction(Mat src)
{
    Mat src2 = new Mat();
    src.CopyTo(src2);

    InputArray kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
    Cv2.MorphologyEx(src, src, MorphTypes.Close, kernel, new Point(-1, -1), 3);

    // 高斯滤波
    Cv2.GaussianBlur(src, src, new Size(11, 11), 2, 2);

    // Canny边缘检测
    Mat canny_Image = new Mat();
    Cv2.Canny(src, canny_Image, 10, 30, 3, false);

    // 找轮廓
    /*
     * 找轮廓(输入图像,out 轮廓集合,out 级别,轮廓检索模式,近似法,偏移量)
     * 输入图像:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;
     * 轮廓集合:contours 
     * 历史轮廓:hierarchy:0:后一个轮廓,1:前一个轮廓,2:父轮廓,3:内嵌轮廓
     * 轮廓检索模式:轮廓的检索模式
     *         取值一:CV_RETR_EXTERNAL 只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
     *         取值二:CV_RETR_LIST     检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到
     *         取值三:CV_RETR_CCOMP    检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
     *         取值四:CV_RETR_TREE     检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
     * 近似方法:轮廓的近似方法
     *         取值一:CV_CHAIN_APPROX_NONE   保存物体边界上所有连续的轮廓点到contours向量内
     *         取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留
     *         取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
     * 偏移量:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,且Point可以是负值。不填为默认不偏移Point()
    */
    Cv2.FindContours(canny_Image, out Point[][] contours, out HierarchyIndex[] hierarchly,
        RetrievalModes.External,
        ContourApproximationModes.ApproxSimple,
        new Point(0, 0));

    if (contours.Length == 0)
    {
        throw new Exception("边缘检测失败");
    }

    Random rnd = new Random();
    Scalar color;
    color = new Scalar(0, 255, 0);
    for (int i = 0; i < contours.Length; i++)
    {
        color = new Scalar(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));
        Cv2.DrawContours(src, contours, i, color, 2, LineTypes.Link4);
    }
    //Cv2.ImShow("contours", src);

    //求出面积最大的轮廓
    double max_area = 0.0;
    double currentArea = 0.0;
    Point[] max_contour = null;
    for (int i = 0; i < contours.Length; i++)
    {
        currentArea = Cv2.ContourArea(contours[i]);
        if (currentArea > max_area)
        {
            max_area = currentArea;
            max_contour = contours[i];
        }
    }

    //多边形拟合凸包的四个顶点
    Point[] hull = Cv2.ConvexHull(max_contour);
    double epsilon = 0.02 * Cv2.ArcLength(max_contour, true);
    Point[] approx = Cv2.ApproxPolyDP(hull, epsilon, true);
    if (approx.Length != 4)
    {
        throw new Exception("拟合凸包的四个顶点失败");
    }

    Scalar scalar2 = new Scalar(0, 255, 255);
    Cv2.Line(src, approx[0], approx[1], scalar2, 1, LineTypes.Link4);
    Cv2.Line(src, approx[1], approx[2], scalar2, 1, LineTypes.Link4);
    Cv2.Line(src, approx[2], approx[3], scalar2, 1, LineTypes.Link4);
    Cv2.Line(src, approx[3], approx[0], scalar2, 1, LineTypes.Link4);

    //排序
    Array.Sort(approx, (cs1, cs2) =>
    {
        if (cs1.Y > cs2.Y)
        {
            return 1;
        }
        else if (cs1.Y == cs2.Y)
        {
            return cs1.X < cs2.X ? 1 : -1;
        }
        else
        {
            return -1;
        }
    });

    //算法找出的角点
    Point2f[] srcPt = [approx[0], approx[1], approx[3], approx[2]];

    //最小外接矩形
    RotatedRect rect = Cv2.MinAreaRect(srcPt);
    Rect box = rect.BoundingRect();
    Point2f[] dstPt = new Point2f[4];

    dstPt[0].X = box.X;
    dstPt[0].Y = box.Y;

    dstPt[1].X = box.X + box.Width;
    dstPt[1].Y = box.Y;

    dstPt[2].X = box.X + box.Width;
    dstPt[2].Y = box.Y + box.Height;

    dstPt[3].X = box.X;
    dstPt[3].Y = box.Y + box.Height;

    Mat final = new Mat();
    Mat warpmatrix = Cv2.GetPerspectiveTransform(srcPt, dstPt);  // 获得变换矩阵
    Cv2.WarpPerspective(src2, final, warpmatrix, src.Size());    // 投射变换,将结果赋给final

    return final;
}
posted @ 2024-07-11 11:44  ꧁执笔小白꧂  阅读(161)  评论(0编辑  收藏  举报