主要对答题卡通用性方面进行了优化(可不限于5*5的答题卡,对图片规格进行统一,可自动计算轮廓间距),并对一些容易出现的bug进行了修复。还需要手动控制统一后的图片大小规格及测量需要检测到的轮廓大小范围。
1.主要代码
Mat src = new Image<Bgr, byte>(ib_original.Image.Bitmap).Mat;
//1.获取当前图像的最大矩形边界
VectorOfVectorOfPoint result_contour = commonUse.GetBoundaryOfPic(src);
//2.对图像进行矫正
Mat mat_Perspective = commonUse.MyWarpPerspective(src, result_contour);
//规范图像大小
CvInvoke.Resize(mat_Perspective, mat_Perspective, new Size(656, 818), 0, 0, Emgu.CV.CvEnum.Inter.Cubic);
//3.二值化处理(大于阈值取0,小于阈值取255。其中白色为0,黑色为255)
Mat mat_threshold = new Mat();
CvInvoke.Threshold(mat_Perspective, mat_threshold, 160, 255, Emgu.CV.CvEnum.ThresholdType.BinaryInv);
//ib_result.Image = mat_threshold;
//4.获取符合标准的轮廓(外接矩形宽、高大于50,需手动调整)
VectorOfVectorOfPoint selected_contours = commonUse.GetContoursAboveGivenSize(mat_threshold, 50, 100, 50, 100);
//5.获取行、列个数信息
Size sizeOfRowAndColumn = commonUse.GetCountOfRowAndColumn(selected_contours);
int countOfRow = sizeOfRowAndColumn.Width;
int countOfColumn = sizeOfRowAndColumn.Height;
if (countOfRow == 0 || countOfColumn == 0)
{
//没有符合要求的轮廓时,显示二值化后的图像
ib_result.Image = mat_threshold;
return;
}
else
{
//6.对轮廓进行分类排序,并获取分类排序后的二维数组
VectorOfVectorOfPoint[,] classed_contours = commonUse.ClassedOfContours(selected_contours, countOfRow, countOfColumn);
//7.检测答题者的选项
int[,] result_count = commonUse.GetResultArray(mat_threshold, classed_contours, countOfRow, countOfColumn);
//8.标示出答题者的选项
Mat temp_mat = commonUse.DrawResultContours(mat_Perspective, classed_contours, result_count);
ib_result.Image = temp_mat;
}
2.调用模块代码
using System;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using System.Drawing;
namespace 圆形答题卡识别
{
class CommonUse
{
/// <summary>
/// 获取给定图像的最大矩形边界
/// </summary>
/// <param name="src"></param>
/// <returns></returns>
public VectorOfVectorOfPoint GetBoundaryOfPic(Mat src)
{
Mat dst = new Mat();
Mat src_gray = new Mat();
CvInvoke.CvtColor(src, src_gray, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);
//边缘检测
CvInvoke.Canny(src_gray, dst, 120, 180);
//寻找答题卡矩形边界(最大的矩形)
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();//创建VectorOfVectorOfPoint数据类型用于存储轮廓
CvInvoke.FindContours(dst, contours, null, Emgu.CV.CvEnum.RetrType.External,
Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);//提取轮廓
VectorOfVectorOfPoint result_contour = new VectorOfVectorOfPoint();//用于存储筛选过后的轮廓
int ksize = contours.Size; //获取连通区域个数
if (ksize == 1)
{
result_contour = contours;
}
else
{
double maxLength = -1;//用于保存轮廓周长的最大值
int index = -1;//轮廓周长的最大值的序号
for (int i = 0; i < ksize; i++)
{
VectorOfPoint contour = contours[i];//获取独立的连通轮廓
double length = CvInvoke.ArcLength(contour, true);//计算连通轮廓的周长
if (length > maxLength)
{
maxLength = length;
index = i;
}
}
result_contour.Push(contours[index]);//筛选后的连通轮廓
}
return result_contour;
}
/// <summary>
/// 进行透视操作,获取矫正后图像
/// </summary>
/// <param name="src"></param>
/// <param name="result_contour"></param>
public Mat MyWarpPerspective