前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C#使用OpenCV进行答题卡识别

C#使用OpenCV进行答题卡识别

作者头像
码客说
发布2022-09-09 09:10:00
2.8K1
发布2022-09-09 09:10:00
举报
文章被收录于专栏:码客

前言

安装

需要安装两个依赖:

  • OpenCvSharp4
  • OpenCvSharp4.runtime.win

添加引用

代码语言:javascript
复制
using OpenCvSharp;
using Point = OpenCvSharp.Point;
using Rect = OpenCvSharp.Rect;

依赖扩展

  • OpenCvSharp4.Extensions

其中

OpenCvSharp4.Extensions 主要是一些辅助的工具 比如Mat和Bitmap的互转。

操作步骤

常用操作

Mat和Bitmap互转

代码语言:javascript
复制
//Bitmap转Mat
Mat mat = OpenCvSharp.Extensions.BitmapConverter.ToMat(image); 
//Mat转Bitmap
Bitmap bitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(img8);

读取图片

代码语言:javascript
复制
private void readImg()
{
  Mat img1 = new Mat("D:\\Pic\\0.jpg", ImreadModes.Color);
  Cv2.ImShow("win1", img1);
  Cv2.WaitKey(0);
}

保存

代码语言:javascript
复制
Mat img1 = new Mat("D:\\Pic\\0.jpg", ImreadModes.Color);
img1.ImWrite("D:\\Pic\\2.jpg");

查看效果

方式1

本地保存图片

代码语言:javascript
复制
Cv2.ImWrite("D:\\Pic\\3.jpg", img3);

方式2

窗口打开图片

代码语言:javascript
复制
private void showImg(Mat img)
{
  Cv2.ImShow("win1", img);
  Cv2.WaitKey(0);
}

图片模式转换

代码语言:javascript
复制
Mat img10 = new Mat();
Cv2.CvtColor(img7, img10, ColorConversionCodes.GRAY2RGB);

复制

代码语言:javascript
复制
Mat img2 = new Mat();
img1.CopyTo(img2);

图片拼接

type表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为CV_(位数)+(数据类型)+(通道数),由type()返回,但是返回值是int型,不是OpenCV预定义的宏(CV_8UC1, CV_64FC1…),也就是说你用type函数得到的只是一个int型的数值,比如CV_8UC1返回的值是0,而不是CV_8UC1。

数据类型

  • U(unsigned integer)表示的是无符号整数,
  • S(signed integer)是有符号整数,
  • F(float)是浮点数

方式1

代码语言:javascript
复制
/// <summary>
/// Mat拼接
/// </summary>
/// <param name="matList"></param>
/// <returns></returns>
public static Mat jointMat(List<Mat> matList)
{
  if (matList.Count == 0)
  {
    return new Mat();
  }
  int rows = 0;
  int cols = 0;

  for (int j = 0; j < matList.Count; j++)
  {
    Mat img_temp = matList[j];
    rows += img_temp.Rows;
    cols = Math.Max(cols, img_temp.Cols);
  }
  Mat result = new Mat(rows, cols, matList[0].Type(), new Scalar(255, 255, 255));

  int tempRows = 0;
  foreach (Mat itemMat in matList)
  {
    Mat roi = result[new Rect(0, tempRows, itemMat.Cols, itemMat.Rows)];
    itemMat.CopyTo(roi);
    tempRows += itemMat.Rows;
  }
  return result;
}

调用

代码语言:javascript
复制
List<Mat> mats = new List<Mat>();
mats.Add(new Mat("D:\\Pic\\0.jpg", ImreadModes.Color));
mats.Add(new Mat("D:\\Pic\\1.jpg", ImreadModes.Color));
var result = CvCommonUtils.jointMat(mats);
result.ImWrite("D:\\Pic\\2.jpg");

注意

不同色彩模式的图片不能正常合并,和目标图片的色彩模式也要保持一致,这里使用matList[0].Type()设置目标图的模式。 默认背景是纯黑色,这里new Scalar(255, 255, 255)使图片默认为纯白色。

方式2(不推荐)

使用VConcat()HConcat()拼接则要求待拼接图像有相同的宽度或高度

代码语言:javascript
复制
/// <summary>
/// Mat拼接
/// </summary>
/// <param name="matList"></param>
/// <returns></returns>
public static Mat jointMat2(List<Mat> matList)
{
  int rows = 0;
  int cols = 0;

  foreach (Mat itemMat in matList)
  {
    cols = Math.Max(cols, itemMat.Cols);
  }
  List<Mat> matListNew = new List<Mat>();
  foreach (Mat itemMat in matList)
  {
    if (itemMat.Cols == cols)
    {
      matListNew.Add(itemMat);
      rows += itemMat.Rows;
    }
    else
    {
      int rowsNew = cols * itemMat.Rows / itemMat.Cols;
      Mat resultMat = new Mat();
      Cv2.Resize(itemMat, resultMat, new Size(cols, rowsNew));
      matListNew.Add(resultMat);
      rows += resultMat.Rows;
    }
  }
  Mat result = new Mat(rows, cols, MatType.CV_8UC3, new Scalar(255, 255, 255));

  Cv2.VConcat(matListNew, result);
  return result;
}

调用方式

代码语言:javascript
复制
List<Mat> mats = new List<Mat>();
mats.Add(new Mat("D:\\Pic\\0.jpg", ImreadModes.Color));
mats.Add(new Mat("D:\\Pic\\1.jpg", ImreadModes.Color));
var result = CvCommonUtils.jointMat2(mats);
result.ImWrite("D:\\Pic\\2.jpg");

灰度

代码语言:javascript
复制
/// <summary>
/// 灰度
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat gray(Mat source)
{
  Mat resultMat = new Mat();
  Cv2.CvtColor(source, resultMat, ColorConversionCodes.BGR2GRAY);
  return resultMat;
}

二值化

代码语言:javascript
复制
/// <summary>
/// 二值化
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat binary(Mat source)
{
  Mat resultMat = new Mat();
  Cv2.Threshold(source, resultMat, 200, 255, ThresholdTypes.Binary);
  return resultMat;
}

腐蚀与膨胀

腐蚀与膨胀都是针对白色区域的

  • 腐蚀 白色变少 黑色变多
  • 膨胀 白色变多 黑色减少

示例

代码语言:javascript
复制
/// <summary>
/// 膨胀
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat dilation(Mat source)
{
  Mat resultMat = new Mat(source.Rows, source.Cols, source.Type());
  Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
  Cv2.Dilate(source, resultMat, element);
  return resultMat;
}

/// <summary>
/// 腐蚀
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat eroding(Mat source)
{
  Mat resultMat = new Mat(source.Rows, source.Cols, source.Type());
  Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
  Cv2.Erode(source, resultMat, element);
  return resultMat;
}

高斯模糊

代码语言:javascript
复制
/// <summary>
/// 高斯模糊
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat gaussianBlur(Mat source)
{
  Mat resultMat = new Mat();
  Cv2.GaussianBlur(source, resultMat, new OpenCvSharp.Size(11, 11), 4, 4);
  return resultMat;
}

缩放

代码语言:javascript
复制
/// <summary>
/// 图片缩放
/// </summary>
/// <param name="source"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public static Mat resize(Mat source, int width, int height)
{
  Mat resultMat = new Mat();
  Cv2.Resize(source, resultMat, new Size(width, height));
  return resultMat;
}

旋转

其中方式1和方式2都一样,都只能旋转90的倍数。

方式3可以旋转任意角度,但是如果是长方形就会部分无法显示。

所以

  • 旋转90的倍数推荐方式1
  • 旋转其他角度推荐方式3

方式1

代码语言:javascript
复制
public static Mat rotate90Counter(Mat source)
{
  Mat resultMat = new Mat();
  Cv2.Rotate(source, resultMat, RotateFlags.Rotate90Counterclockwise);
  return resultMat;
}

public static Mat rotate90(Mat source)
{
  Mat resultMat = new Mat();
  Cv2.Rotate(source, resultMat, RotateFlags.Rotate90Clockwise);
  return resultMat;
}

public static Mat rotate180(Mat source)
{
  Mat resultMat = new Mat();
  Cv2.Rotate(source, resultMat, RotateFlags.Rotate180);
  return resultMat;
}

其中方向

代码语言:javascript
复制
public enum RotateFlags
{
  Rotate90Clockwise,//顺时针90
  Rotate180,//180
  Rotate90Counterclockwise//逆时针90
}

方式2

逆时针90

代码语言:javascript
复制
/// <summary>
/// 旋转
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat rotate90Counter(Mat source)
{
  Mat resultMat = new Mat();
  Mat tempMat = new Mat();
  Cv2.Transpose(source, tempMat);
  Cv2.Flip(tempMat, resultMat, FlipMode.X);
  return resultMat;
}

顺时针90

代码语言:javascript
复制
/// <summary>
/// 旋转
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat rotate90(Mat source)
{
  Mat resultMat = new Mat();
  Mat tempMat = new Mat();
  Cv2.Transpose(source, tempMat);
  Cv2.Flip(tempMat, resultMat, FlipMode.Y);
  return resultMat;
}

旋转180

代码语言:javascript
复制
/// <summary>
/// 旋转
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Mat rotate180(Mat source)
{
  Mat resultMat = new Mat();
  Cv2.Flip(source, resultMat, FlipMode.XY);
  return resultMat;
}

总结一下:

  • 需逆时针90°旋转时Transpose(src,tmp) + Flip(tmp,dst,0)
  • 需顺时针90°旋转时Transpose(src,tmp) + Flip(tmp,dst,1)
  • 需180°旋转时Flip(src,dst,-1)

Transpose()简单来说,就相当于数学中的转置,在矩阵中,转置就是把行与列相互调换位置;

相当于将图像逆时针旋转90度,然后再关于x轴对称

枚举

代码语言:javascript
复制
public enum FlipMode
{
  //
  // 摘要:
  //     means flipping around x-axis
  X = 0,
  //
  // 摘要:
  //     means flipping around y-axis
  Y = 1,
  //
  // 摘要:
  //     means flipping around both axises
  XY = -1
}

方式3

旋转任意角度

这种方式如果是长方向旋转90度会导致黑边和遮挡。

代码语言:javascript
复制
/// <summary>
/// 旋转
/// </summary>
/// <param name="source"></param>
/// <param name="angle">角度</param>
/// <returns></returns>
public static Mat rotate(Mat source, double angle = 90)
{
  Mat resultMat = new Mat();
  Point center = new Point(source.Cols / 2, source.Rows / 2); //旋转中心
  double scale = 1.0;  //缩放系数
  Mat rotMat = Cv2.GetRotationMatrix2D(center, angle, scale);
  Cv2.WarpAffine(source, resultMat, rotMat, source.Size());

  return resultMat;
}

透视变形

获取黑块顶点

代码语言:javascript
复制
using OpenCvSharp;

using System;

namespace card_scanner.util
{
  public class CvContoursUtils
  {
    /// <summary>
    /// 获取四个顶点
    /// </summary>
    /// <param name="img"></param>
    /// <returns></returns>
    public static Point[] getAllPoints(Mat img)
    {
      Point[] potArr = new Point[4];
      for (int i = 0; i < 4; i++)
      {
        potArr[i] = new Point(-1, -1);
      }
      // 距离四个角的距离
      int[] spaceArr = new int[] { -1, -1, -1, -1 };
      int cols = img.Cols;
      int rows = img.Rows;
      int x1 = cols / 3;
      int x2 = cols * 2 / 3;
      int y1 = rows / 3;
      int y2 = rows * 2 / 3;
      for (int x = 0; x < cols; x++)
      {
        for (int y = 0; y < rows; y++)
        {
          if (x > x1 && x < x2 && y > y1 && y < y2)
          {
            continue;
          }

          Vec3b color = img.Get<Vec3b>(y, x);

          if (color != null && color.Item0 == 0)
          {
            if (spaceArr[0] == -1)
            {
              potArr[0].X = x;
              potArr[0].Y = y;
              potArr[1].X = x;
              potArr[1].Y = y;
              potArr[2].X = x;
              potArr[2].Y = y;
              potArr[3].X = x;
              potArr[3].Y = y;
              spaceArr[0] = getSpace(0, 0, x, y);
              spaceArr[1] = getSpace(cols, 0, x, y);
              spaceArr[2] = getSpace(cols, rows, x, y);
              spaceArr[3] = getSpace(0, rows, x, y);
            }
            else
            {
              int s0 = getSpace(0, 0, x, y);
              int s1 = getSpace(cols, 0, x, y);
              int s2 = getSpace(cols, rows, x, y);
              int s3 = getSpace(0, rows, x, y);
              if (s0 < spaceArr[0])
              {
                spaceArr[0] = s0;
                potArr[0].X = x;
                potArr[0].Y = y;
              }
              if (s1 < spaceArr[1])
              {
                spaceArr[1] = s1;
                potArr[1].X = x;
                potArr[1].Y = y;
              }
              if (s2 < spaceArr[2])
              {
                spaceArr[2] = s2;
                potArr[2].X = x;
                potArr[2].Y = y;
              }
              if (s3 < spaceArr[3])
              {
                spaceArr[3] = s3;
                potArr[3].X = x;
                potArr[3].Y = y;
              }
            }
          }
        }
      }
      return potArr;
    }

    /// <summary>
    /// 计算两点之间的距离
    /// </summary>
    /// <param name="x1"></param>
    /// <param name="y1"></param>
    /// <param name="x2"></param>
    /// <param name="y2"></param>
    /// <returns></returns>
    private static int getSpace(int x1, int y1, int x2, int y2)
    {
      int xspace = Math.Abs(x1 - x2);
      int yspace = Math.Abs(y1 - y2);
      return (int)Math.Sqrt(Math.Pow(xspace, 2) + Math.Pow(yspace, 2));
    }
  }
}

透视变形

代码语言:javascript
复制
using OpenCvSharp;

using System.Collections.Generic;

namespace card_scanner.util
{
  public class CvPerspectiveUtils
  {
    /// <summary>
    /// 透视变换/顶点变换
    /// </summary>
    /// <param name="src"></param>
    /// <param name="points"></param>
    /// <returns></returns>
    public static Mat warpPerspective(Mat src, Point[] points)
    {
      //设置原图变换顶点
      List<Point2f> AffinePoints0 = new List<Point2f>() {
        points[0],
        points[1],
        points[2],
        points[3]
      };
      //设置目标图像变换顶点
      List<Point2f> AffinePoints1 = new List<Point2f>() {
        new Point(0, 0),
        new Point(src.Width, 0),
        new Point(src.Width, src.Height),
        new Point(0, src.Height)
      };
      //计算变换矩阵
      Mat Trans = Cv2.GetAffineTransform(AffinePoints0, AffinePoints1);
      //矩阵仿射变换
      Mat dst = new Mat();
      Cv2.WarpAffine(src, dst, Trans, new OpenCvSharp.Size() { Height = src.Rows, Width = src.Cols });
      return dst;
    }
  }
}

调用

代码语言:javascript
复制
//透视变形
var points = CvContoursUtils.getAllPoints(img5);
Mat img6 = CvPerspectiveUtils.warpPerspective(img5, points);
Cv2.ImWrite("D:\\Pic\\6_透视变形.jpg", img6);

剪裁

代码语言:javascript
复制
// 截取左上角四分之一区域
OpenCvSharp.Rect rect = new OpenCvSharp.Rect(0, 0, img2.Cols / 2, img2.Rows / 2);
Mat img4 = new Mat(img3, rect);
Cv2.ImWrite("D:\\Pic\\4.png", img4);

文件名

代码语言:javascript
复制
public class ZPathUtil
{
  private static int temp = 100;

  public static string GetPathJpeg(string basePath)
  {
    temp += 1;
    if (temp > 1000)
    {
      temp = 101;
    }

    string filename = DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss_ffff") + "_" + temp + ".jpg";
    string pathAll = Path.Combine(basePath, filename);
    return pathAll;
  }
}

是否涂卡

代码语言:javascript
复制
/// <summary>
///区域是否涂卡
/// </summary>
/// <param name="source"></param>
/// <param name="rect"></param>
/// <returns></returns>
public static bool isSmearCard(Mat source, Rect rect)
{
  Mat matTemp = new Mat(source, rect);
  int count = Cv2.CountNonZero(matTemp);
  int total = rect.Width * rect.Height;
  double rate = 1.0f * (total - count) / total;
  return rate > 0.3;
}

注意传入的原图一定要二值化。

绘制边框

代码语言:javascript
复制
Mat img10 = new Mat();
Cv2.CvtColor(img7, img10, ColorConversionCodes.GRAY2RGB);
Cv2.Rectangle(img10, posModel.cantronqrcode, new Scalar(0, 0, 255), 1);
Cv2.Rectangle(img10, posModel.pageRects[0], new Scalar(0, 0, 255), 1);
Cv2.ImWrite("D:\\Pic\\10_边框.png", img10);

注意

黑白图片转为彩色

查找轮廓

实现框选用户选择的选项

代码语言:javascript
复制
/// <summary>
/// 轮廓识别,使用最外轮廓发抽取轮廓RETR_EXTERNAL,轮廓识别方法为CHAIN_APPROX_SIMPLE
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static Point[][] findContours(Mat source)
{
  Point[][] contours;
  HierarchyIndex[] hierarchy;

  Cv2.FindContours(
    source,
    out contours,
    out hierarchy,
    RetrievalModes.List,
    ContourApproximationModes.ApproxSimple
  );
  return contours;
}

调用方式

代码语言:javascript
复制
int rows = mat4.Rows;
int cols = mat4.Cols;
int space = mat4.Rows * 5 / 100;
//获取定位块
OpenCvSharp.Point[][] pointAll = CvContoursUtils.findContours(mat4);
List<OpenCvSharp.Point[]> rectVec2 = new List<OpenCvSharp.Point[]>();
for (int i = 0; i < pointAll.Length; i++)
{
  OpenCvSharp.Point[] pointArr = pointAll[i];
  Rect rect2 = Cv2.BoundingRect(pointArr);

  if (rect2.Width > 10 && rect2.Height > 10 && rect2.Width < 60 && rect2.Height < 60 && (rect2.Left < space || rect2.Top < space || (cols - rect2.Right) < space || (rows - rect2.Bottom) < space))
  {
    rectVec2.Add(pointArr);
  }
}

Mat img7 = new Mat(mat4.Rows, mat4.Cols, MatType.CV_8UC3, new Scalar(255, 255, 255));
Cv2.DrawContours(img7, rectVec2, -1, new Scalar(0, 0, 255), 1);
Cv2.ImWrite("D:\\Pic\\5_边框.jpg", img7);

获取面积

代码语言:javascript
复制
//获取涂写区域
Point[][] pointAll = CvContoursUtils.findContours(img6);
List<Point[]> rectVec2 = new List<Point[]>();
for (int i = 0; i < pointAll.Length; i++)
{
  Point[] pointArr = pointAll[i];
  double image_area = Cv2.ContourArea(pointArr);
  Console.WriteLine(image_area);
}

其中Cv2.CountNonZero(matTemp)是获取非0的像素点个素数,所以在二值化的图片中,用户涂的区域都是0,我们只需要获取涂的百分比就能判断用户是否涂卡。

获取答题卡涂的选项

其中每个选项的坐标区域是在制作答题卡的时候,后台要保存的。

代码语言:javascript
复制
int[][][] ques_select = new int[][][] {
  new int[][]{
    new int[] { 67,6,109,30},
    new int[] { 134,6,169,30},
    new int[] { 199,6,237,30},
  },
  new int[][]{
    new int[] { 67,50,109,72},
    new int[] { 134,50,169,72},
    new int[] { 199,50,237,72},
  },
  new int[][]{
    new int[] { 67,92,109,114},
    new int[] { 134,92,169,114},
    new int[] { 199,92,237,114},
  },
  new int[][]{
    new int[] { 67,132,109,154},
    new int[] { 134,132, 169,154},
    new int[] { 199,132, 237,154},
  },
  new int[][]{
    new int[] { 67,176,109,198},
    new int[] { 134,176, 169,198},
    new int[] { 199,176, 237,198},
  },
};
string[] opts = new string[] { "A", "B", "C" };
for (int i = 0; i < ques_select.Length; i++)
{
  int[][] ques = ques_select[i];
  for (int j = 0; j < ques.Length; j++)
  {
    int[] opt = ques[j];
    int width = opt[2] - opt[0];
    int height = opt[3] - opt[1];
    Mat matTemp = new Mat(img6, new Rect(opt[0], opt[1], width, height));
    int count = Cv2.CountNonZero(matTemp);
    int total = width * height;
    double rate = 1.0f * (total - count) / total;
    if (rate > 0.6)
    {
      Console.WriteLine("题号:" + (i + 1));
      Console.WriteLine("选项:" + opts[j]);
      Console.WriteLine("rate:" + rate);
    }
  }
}

页码识别

页面我们可以转换为二进制然后进行黑块渲染,识别的时候后在转成数字即可。这里是页码从1开始,所以要减1。

代码语言:javascript
复制
/// <summary>
/// 获取页码数据
/// </summary>
/// <param name="pageMat"></param>
/// <returns></returns>
private int getPageNum(Mat pageMat)
{
  string pagestr = "";
  var cantronpage = posModel.cantronpage;
  foreach (var page in cantronpage)
  {
    if (CvCommonUtils.isSmearCard(pageMat, page))
    {
      pagestr += "1";
    }
    else
    {
      pagestr += "0";
    }
  }

  return Convert.ToInt32(pagestr, 2)-1;
}

工具类

基本操作

CvCommonUtils

代码语言:javascript
复制
using OpenCvSharp;

using System;
using System.Collections.Generic;

namespace Z.OpenCV
{
  public class CvCommonUtils
  {
    /// <summary>
    /// Mat拼接
    /// </summary>
    /// <param name="matList"></param>
    /// <returns></returns>
    public static Mat jointMat(List<Mat> matList)
    {
      if (matList.Count == 0)
      {
        return new Mat();
      }
      int rows = 0;
      int cols = 0;

      for (int j = 0; j < matList.Count; j++)
      {
        Mat img_temp = matList[j];
        rows += img_temp.Rows;
        cols = Math.Max(cols, img_temp.Cols);
      }
      Mat result = new Mat(rows, cols, matList[0].Type(), new Scalar(255, 255, 255));

      int tempRows = 0;
      foreach (Mat itemMat in matList)
      {
        Mat roi = result[new Rect(0, tempRows, itemMat.Cols, itemMat.Rows)];
        itemMat.CopyTo(roi);
        tempRows += itemMat.Rows;
      }
      return result;
    }

    /// <summary>
    /// Mat拼接
    /// </summary>
    /// <param name="matList"></param>
    /// <returns></returns>
    public static Mat jointMat2(List<Mat> matList)
    {
      int rows = 0;
      int cols = 0;

      foreach (Mat itemMat in matList)
      {
        cols = Math.Max(cols, itemMat.Cols);
      }
      List<Mat> matListNew = new List<Mat>();
      foreach (Mat itemMat in matList)
      {
        if (itemMat.Cols == cols)
        {
          matListNew.Add(itemMat);
          rows += itemMat.Rows;
        }
        else
        {
          int rowsNew = cols * itemMat.Rows / itemMat.Cols;
          Mat resultMat = new Mat();
          Cv2.Resize(itemMat, resultMat, new Size(cols, rowsNew));
          matListNew.Add(resultMat);
          rows += resultMat.Rows;
        }
      }
      Mat result = new Mat(rows, cols, MatType.CV_8UC3, new Scalar(255, 255, 255));

      Cv2.VConcat(matListNew, result);
      return result;
    }

    /// <summary>
    /// 图片缩放
    /// </summary>
    /// <param name="source"></param>
    /// <param name="width"></param>
    /// <param name="height"></param>
    /// <returns></returns>
    public static Mat resize(Mat source, int width, int height)
    {
      Mat resultMat = new Mat();
      Cv2.Resize(source, resultMat, new Size(width, height));
      return resultMat;
    }

    /// <summary>
    /// 灰度
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat gray(Mat source)
    {
      Mat resultMat = new Mat();
      Cv2.CvtColor(source, resultMat, ColorConversionCodes.BGR2GRAY);
      return resultMat;
    }

    /// <summary>
    /// 二值化
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat binary(Mat source)
    {
      Mat resultMat = new Mat();
      Cv2.Threshold(source, resultMat, 200, 255, ThresholdTypes.Binary);
      return resultMat;
    }

    /// <summary>
    /// 膨胀
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat dilation(Mat source)
    {
      Mat resultMat = new Mat(source.Rows, source.Cols, source.Type());
      Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
      Cv2.Dilate(source, resultMat, element);
      return resultMat;
    }

    /// <summary>
    /// 腐蚀
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat eroding(Mat source)
    {
      Mat resultMat = new Mat(source.Rows, source.Cols, source.Type());
      Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3));
      Cv2.Erode(source, resultMat, element);
      return resultMat;
    }

    /// <summary>
    /// 高斯模糊
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat gaussianBlur(Mat source)
    {
      Mat resultMat = new Mat();
      Cv2.GaussianBlur(source, resultMat, new OpenCvSharp.Size(11, 11), 4, 4);
      return resultMat;
    }

    /// <summary>
    /// 反转
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat bitwiseNot(Mat source)
    {
      Mat resultMat = new Mat();
      Cv2.BitwiseNot(source, resultMat, new Mat());
      return resultMat;
    }

    /// <summary>
    /// 美颜磨皮 双边滤波
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat bilateralFilter(Mat source)
    {
      Mat resultMat = new Mat();
      Cv2.BilateralFilter(source, resultMat, 15, 35d, 35d);
      return resultMat;
    }

    /// <summary>
    /// 逆时针旋转90
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat rotate90Counter(Mat source)
    {
      Mat resultMat = new Mat();
      Cv2.Rotate(source, resultMat, RotateFlags.Rotate90Counterclockwise);
      return resultMat;
    }

    /// <summary>
    /// 顺时针旋转90
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Mat rotate90(Mat source)
    {
      Mat resultMat = new Mat();
      Cv2.Rotate(source, resultMat, RotateFlags.Rotate90Clockwise);
      return resultMat;
    }

    /// <summary>
    ///区域是否涂卡
    /// </summary>
    /// <param name="source"></param>
    /// <param name="rect"></param>
    /// <returns></returns>
    public static bool isSmearCard(Mat source, Rect rect)
    {
      Mat matTemp = new Mat(source, rect);
      int count = Cv2.CountNonZero(matTemp);
      int total = rect.Width * rect.Height;
      double rate = 1.0f * (total - count) / total;
      return rate > 0.3;
    }
  }
}

获取边界

CvContoursUtils

代码语言:javascript
复制
using OpenCvSharp;

using System;
using System.Collections.Generic;

namespace Z.OpenCV
{
  public class CvContoursUtils
  {
    /// <summary>
    /// 获取四个顶点
    /// </summary>
    /// <param name="img"></param>
    /// <returns></returns>
    public static Point[] getAllPoints(Mat img, int space = 20)
    {
      // 忽略周围的像素

      Point[] potArr = new Point[4];
      for (int i = 0; i < 4; i++)
      {
        potArr[i] = new Point(-1, -1);
      }
      // 距离四个角的距离
      int[] spaceArr = new int[] { -1, -1, -1, -1 };
      int cols = img.Cols;
      int rows = img.Rows;
      int x1 = cols / 3;
      int x2 = cols * 2 / 3;
      int y1 = rows / 3;
      int y2 = rows * 2 / 3;
      for (int x = 0; x < cols; x++)
      {
        for (int y = 0; y < rows; y++)
        {
          if (x > x1 && x < x2 && y > y1 && y < y2)
          {
            continue;
          }

          if (x < space || y < space || x > cols - space || y > rows - space)
          {
            continue;
          }

          Vec3b color = img.Get<Vec3b>(y, x);

          if (color != null && color.Item0 == 0)
          {
            if (spaceArr[0] == -1)
            {
              potArr[0].X = x;
              potArr[0].Y = y;
              potArr[1].X = x;
              potArr[1].Y = y;
              potArr[2].X = x;
              potArr[2].Y = y;
              potArr[3].X = x;
              potArr[3].Y = y;
              spaceArr[0] = getSpace(0, 0, x, y);
              spaceArr[1] = getSpace(cols, 0, x, y);
              spaceArr[2] = getSpace(cols, rows, x, y);
              spaceArr[3] = getSpace(0, rows, x, y);
            }
            else
            {
              int s0 = getSpace(0, 0, x, y);
              int s1 = getSpace(cols, 0, x, y);
              int s2 = getSpace(cols, rows, x, y);
              int s3 = getSpace(0, rows, x, y);
              if (s0 < spaceArr[0])
              {
                spaceArr[0] = s0;
                potArr[0].X = x;
                potArr[0].Y = y;
              }
              if (s1 < spaceArr[1])
              {
                spaceArr[1] = s1;
                potArr[1].X = x;
                potArr[1].Y = y;
              }
              if (s2 < spaceArr[2])
              {
                spaceArr[2] = s2;
                potArr[2].X = x;
                potArr[2].Y = y;
              }
              if (s3 < spaceArr[3])
              {
                spaceArr[3] = s3;
                potArr[3].X = x;
                potArr[3].Y = y;
              }
            }
          }
        }
      }
      return potArr;
    }

    /// <summary>
    /// 获取四个顶点(优化版本)
    /// </summary>
    /// <param name="mat"></param>
    /// <returns></returns>
    public static Point[] getAllPoints2(Mat mat)
    {
      // 忽略周围的像素

      Point[] potArr = new Point[4];
      for (int i = 0; i < 4; i++)
      {
        potArr[i] = new Point(-1, -1);
      }
      // 距离四个角的距离
      int[] spaceArr = new int[] { -1, -1, -1, -1 };

      int rows = mat.Rows;
      int cols = mat.Cols;
      int space = mat.Rows * 5 / 100;
      //获取定位块
      Point[][] pointAll = findContours(mat);
      List<Point[]> rectVec2 = new List<Point[]>();
      for (int i = 0; i < pointAll.Length; i++)
      {
        Point[] pointArr = pointAll[i];
        Rect rect2 = Cv2.BoundingRect(pointArr);

        if (rect2.Width > 10 && rect2.Height > 10 && rect2.Width < 60 && rect2.Height < 60 && (rect2.Left < space || rect2.Top < space || (cols - rect2.Right) < space || (rows - rect2.Bottom) < space))
        {
          rectVec2.Add(pointArr);
        }
      }

      foreach (Point[] points in rectVec2)
      {
        foreach (Point point in points)
        {
          int x = point.X;
          int y = point.Y;
          if (spaceArr[0] == -1)
          {
            potArr[0].X = x;
            potArr[0].Y = y;
            potArr[1].X = x;
            potArr[1].Y = y;
            potArr[2].X = x;
            potArr[2].Y = y;
            potArr[3].X = x;
            potArr[3].Y = y;
            spaceArr[0] = getSpace(0, 0, x, y);
            spaceArr[1] = getSpace(cols, 0, x, y);
            spaceArr[2] = getSpace(cols, rows, x, y);
            spaceArr[3] = getSpace(0, rows, x, y);
          }
          else
          {
            int s0 = getSpace(0, 0, x, y);
            int s1 = getSpace(cols, 0, x, y);
            int s2 = getSpace(cols, rows, x, y);
            int s3 = getSpace(0, rows, x, y);
            if (s0 < spaceArr[0])
            {
              spaceArr[0] = s0;
              potArr[0].X = x;
              potArr[0].Y = y;
            }
            if (s1 < spaceArr[1])
            {
              spaceArr[1] = s1;
              potArr[1].X = x;
              potArr[1].Y = y;
            }
            if (s2 < spaceArr[2])
            {
              spaceArr[2] = s2;
              potArr[2].X = x;
              potArr[2].Y = y;
            }
            if (s3 < spaceArr[3])
            {
              spaceArr[3] = s3;
              potArr[3].X = x;
              potArr[3].Y = y;
            }
          }
        }
      }

      return potArr;
    }

    /// <summary>
    /// 计算两点之间的距离
    /// </summary>
    /// <param name="x1"></param>
    /// <param name="y1"></param>
    /// <param name="x2"></param>
    /// <param name="y2"></param>
    /// <returns></returns>
    private static int getSpace(int x1, int y1, int x2, int y2)
    {
      int xspace = Math.Abs(x1 - x2);
      int yspace = Math.Abs(y1 - y2);
      return (int)Math.Sqrt(Math.Pow(xspace, 2) + Math.Pow(yspace, 2));
    }

    /// <summary>
    /// 轮廓识别,使用最外轮廓发抽取轮廓RETR_EXTERNAL,轮廓识别方法为CHAIN_APPROX_SIMPLE
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static Point[][] findContours(Mat source)
    {
      Point[][] contours;
      HierarchyIndex[] hierarchy;

      Cv2.FindContours(
        source,
        out contours,
        out hierarchy,
        RetrievalModes.List,
        ContourApproximationModes.ApproxSimple
      );
      return contours;
    }
  }
}

透视变形

CvPerspectiveUtils

代码语言:javascript
复制
using OpenCvSharp;

using System.Collections.Generic;

namespace card_scanner.util
{
    public class CvPerspectiveUtils
    {
        /// <summary>
        /// 透视变换/顶点变换
        /// </summary>
        /// <param name="src"></param>
        /// <param name="points"></param>
        /// <returns></returns>
        public static Mat warpPerspective(Mat src, Point[] points)
        {
            //设置原图变换顶点
            List<Point2f> AffinePoints0 = new List<Point2f>() {
                points[0],
                points[1],
                points[2],
                points[3]
            };
            //设置目标图像变换顶点
            List<Point2f> AffinePoints1 = new List<Point2f>() {
                new Point(0, 0),
                new Point(src.Width, 0),
                new Point(src.Width, src.Height),
                new Point(0, src.Height)
            };
            //计算变换矩阵
            Mat Trans = Cv2.GetAffineTransform(AffinePoints0, AffinePoints1);
            //矩阵仿射变换
            Mat dst = new Mat();
            Cv2.WarpAffine(src, dst, Trans, new OpenCvSharp.Size() { Height = src.Rows, Width = src.Cols });
            return dst;
        }
    }
}

查看代码执行时间

代码语言:javascript
复制
using System.Diagnostics;

//定义一个计时对象 
System.Diagnostics.Stopwatch oTime = new System.Diagnostics.Stopwatch();    
 //开始计时 
oTime.Start();

//测试的代码
// ...

//结束计时
oTime.Stop();                          

//输出运行时间。  
Console.WriteLine("程序的运行时间:{0} 秒", oTime.Elapsed.TotalSeconds);
Console.WriteLine("程序的运行时间:{0} 毫秒", oTime.Elapsed.TotalMilliseconds);

计时实例可以使用多次

代码语言:javascript
复制
System.Diagnostics.Stopwatch oTime = new System.Diagnostics.Stopwatch();
oTime.Start();
var result = CvContoursUtils.getAllPoints(mat4);
foreach (var point in result)
{
  Console.WriteLine(point);
}
oTime.Stop();
Console.WriteLine("程序的运行时间:{0} 毫秒", oTime.Elapsed.TotalMilliseconds);

oTime.Start();
var result2 = CvContoursUtils.getAllPoints2(mat4);
foreach (var point in result2)
{
  Console.WriteLine(point);
}
oTime.Stop();
Console.WriteLine("程序的运行时间:{0} 毫秒", oTime.Elapsed.TotalMilliseconds);
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 安装
  • 常用操作
    • Mat和Bitmap互转
      • 读取图片
        • 保存
          • 查看效果
            • 图片模式转换
              • 复制
                • 图片拼接
                  • 方式1
                  • 方式2(不推荐)
                • 灰度
                  • 二值化
                    • 腐蚀与膨胀
                      • 高斯模糊
                        • 缩放
                          • 旋转
                            • 方式1
                            • 方式2
                            • 方式3
                          • 透视变形
                            • 剪裁
                              • 是否涂卡
                                • 绘制边框
                                  • 查找轮廓
                                    • 获取面积
                                      • 获取答题卡涂的选项
                                        • 页码识别
                                        • 工具类
                                          • 基本操作
                                            • 获取边界
                                              • 透视变形
                                              • 查看代码执行时间
                                              领券
                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档