需要安装两个依赖:
添加引用
using OpenCvSharp;
using Point = OpenCvSharp.Point;
using Rect = OpenCvSharp.Rect;
依赖扩展
其中
OpenCvSharp4.Extensions 主要是一些辅助的工具 比如Mat和Bitmap的互转。
操作步骤
//Bitmap转Mat
Mat mat = OpenCvSharp.Extensions.BitmapConverter.ToMat(image);
//Mat转Bitmap
Bitmap bitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(img8);
private void readImg()
{
Mat img1 = new Mat("D:\\Pic\\0.jpg", ImreadModes.Color);
Cv2.ImShow("win1", img1);
Cv2.WaitKey(0);
}
Mat img1 = new Mat("D:\\Pic\\0.jpg", ImreadModes.Color);
img1.ImWrite("D:\\Pic\\2.jpg");
方式1
本地保存图片
Cv2.ImWrite("D:\\Pic\\3.jpg", img3);
方式2
窗口打开图片
private void showImg(Mat img)
{
Cv2.ImShow("win1", img);
Cv2.WaitKey(0);
}
Mat img10 = new Mat();
Cv2.CvtColor(img7, img10, ColorConversionCodes.GRAY2RGB);
Mat img2 = new Mat();
img1.CopyTo(img2);
type表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为CV_(位数)+(数据类型)+(通道数),由type()返回,但是返回值是int型,不是OpenCV预定义的宏(CV_8UC1, CV_64FC1…),也就是说你用type函数得到的只是一个int型的数值,比如CV_8UC1返回的值是0,而不是CV_8UC1。
数据类型
/// <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;
}
调用
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)
使图片默认为纯白色。
使用VConcat()
或HConcat()
拼接则要求待拼接图像有相同的宽度或高度
/// <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;
}
调用方式
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");
/// <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>
/// <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
。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;
}
其中方向
public enum RotateFlags
{
Rotate90Clockwise,//顺时针90
Rotate180,//180
Rotate90Counterclockwise//逆时针90
}
逆时针90
/// <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
/// <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
/// <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;
}
总结一下:
Transpose(src,tmp) + Flip(tmp,dst,0)
Transpose(src,tmp) + Flip(tmp,dst,1)
Flip(src,dst,-1)
Transpose()简单来说,就相当于数学中的转置,在矩阵中,转置就是把行与列相互调换位置;
相当于将图像逆时针旋转90度,然后再关于x轴对称
枚举
public enum FlipMode
{
//
// 摘要:
// means flipping around x-axis
X = 0,
//
// 摘要:
// means flipping around y-axis
Y = 1,
//
// 摘要:
// means flipping around both axises
XY = -1
}
旋转任意角度
这种方式如果是长方向旋转90度会导致黑边和遮挡。
/// <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;
}
获取黑块顶点
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));
}
}
}
透视变形
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;
}
}
}
调用
//透视变形
var points = CvContoursUtils.getAllPoints(img5);
Mat img6 = CvPerspectiveUtils.warpPerspective(img5, points);
Cv2.ImWrite("D:\\Pic\\6_透视变形.jpg", img6);
// 截取左上角四分之一区域
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);
文件名
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;
}
}
/// <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;
}
注意传入的原图一定要二值化。
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);
注意
黑白图片转为彩色
实现框选用户选择的选项
/// <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;
}
调用方式
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);
//获取涂写区域
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,我们只需要获取涂的百分比就能判断用户是否涂卡。
其中每个选项的坐标区域是在制作答题卡的时候,后台要保存的。
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。
/// <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
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
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
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;
}
}
}
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);
计时实例可以使用多次
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);