參考:https://blog.csdn.net/qiao_lili/article/details/83176480
成都創(chuàng)新互聯公司是一家專業(yè)提供臨滄企業(yè)網站建設,專注與做網站、網站設計、成都h5網站建設、小程序制作等業(yè)務。10年已為臨滄眾多企業(yè)、政府機構等服務。創(chuàng)新互聯專業(yè)網站制作公司優(yōu)惠進行中。網上關于C#結合EmguCv開發(fā)答題卡識別的資料很少,因為有做答題卡識別方面的需求,個人也只熟悉C#語言,找了半天也只找到一些通過C++、Python或matlab結合OpenCV來實現識別的例子,只好對著別人的例子嘗試著翻譯成EmguCv,還算成功。自己又針對答題卡識別封裝了幾個函數,記錄在這留給需要的人。(得吐槽下,EmguCv在錯誤管理上還欠缺很多,經常出現莫名其妙的錯誤)
1.設置兩個圖片顯示容器ib_original.SizeMode = PictureBoxSizeMode.Zoom;
ib_original.FunctionalMode = Emgu.CV.UI.ImageBox.FunctionalModeOption.Minimum;
ib_result.SizeMode = PictureBoxSizeMode.Zoom;
ib_result.FunctionalMode = Emgu.CV.UI.ImageBox.FunctionalModeOption.Minimum;
2.載入要處理的圖片OpenFileDialog op = new OpenFileDialog();
if (op.ShowDialog() == DialogResult.OK)
{
Mat src = new Mat(op.FileName, Emgu.CV.CvEnum.LoadImageType.AnyColor);
ib_original.Image = src;
}
實例圖片
//獲取當前圖像的大矩形邊界
VectorOfVectorOfPoint result_contour = GetBoundaryOfPic(src);
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();//創(chuàng)建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; //獲取連通區(qū)域個數
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;
}
4.對圖像進行矯正//對圖像進行矯正
Mat mat_Perspective = MyWarpPerspective(src, result_contour);
public Mat MyWarpPerspective(Mat src, VectorOfVectorOfPoint result_contour)
{
//擬合答題卡的幾何輪廓,保存點集pts并順時針排序
VectorOfPoint pts = new VectorOfPoint();//用于存放逼近的結果
VectorOfPoint tempContour = result_contour[0];//臨時用
double result_length = CvInvoke.ArcLength(tempContour, true);
CvInvoke.ApproxPolyDP(tempContour, pts, result_length * 0.02, true); //幾何逼近,獲取矩形4個頂點坐標
//Point[]轉換為PointF[]類型
PointF[] pts_src = Array.ConvertAll(pts.ToArray(), new Converter(PointToPointF));
//確定透視變換的寬度、高度
Size sizeOfRect = CalSizeOfRect(pts_src);
int width= sizeOfRect.Width;
int height=sizeOfRect.Height;
//計算透視變換矩陣
PointF[] pts_target = new PointF[] { new PointF(0, 0), new PointF(width - 1, 0) ,
new PointF(width - 1, height - 1) ,new PointF(0, height - 1)};
//計算透視矩陣
Mat data = CvInvoke.GetPerspectiveTransform(pts_src, pts_target);
//進行透視操作
Mat mat_Perspective = new Mat();
Mat src_gray = new Mat();
CvInvoke.CvtColor(src, src_gray, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);
CvInvoke.WarpPerspective(src_gray, mat_Perspective, data, new Size(width, height));
return mat_Perspective;
}
////// 計算給定四個坐標點四邊形的寬、高
/// ////// public Size CalSizeOfRect(PointF[] pts_src)
{
if (pts_src.Length != 4) return new Size(0,0);//確保為四邊形
if (pts_src[1].X< pts_src[3].X)
{
//說明當前為逆時針存儲,改為順時針存儲(交換第2、4點)
PointF p = new PointF();
p = pts_src[1];
pts_src[1] = pts_src[3];
pts_src[3] = p;
}
//確定透視變換的寬度、高度
int width;
int height;
double width1 = Math.Pow(pts_src[0].X - pts_src[1].X, 2) + Math.Pow(pts_src[0].Y - pts_src[1].Y, 2);
double width2 = Math.Pow(pts_src[2].X - pts_src[3].X, 2) + Math.Pow(pts_src[2].Y - pts_src[3].Y, 2);
width = width1 >width2 ? (int)Math.Sqrt(width1) : (int)Math.Sqrt(width2);//根號下a方+b方,且取寬度大的
double height1 = Math.Pow(pts_src[0].X - pts_src[3].X, 2) + Math.Pow(pts_src[0].Y - pts_src[3].Y, 2);
double height2 = Math.Pow(pts_src[1].X - pts_src[2].X, 2) + Math.Pow(pts_src[1].Y - pts_src[2].Y, 2);
height = height1 >height2 ? (int)Math.Sqrt(height1) : (int)Math.Sqrt(height2);
return new Size(width, height);
}
////// Point轉換為PointF類型
/// ////// public static PointF PointToPointF(Point p)
{
return new PointF(p.X, p.Y);
}
5.閾值分割//閾值分割
Mat mat_threshold = new Mat();
CvInvoke.Threshold(mat_Perspective, mat_threshold, 160, 255, Emgu.CV.CvEnum.ThresholdType.BinaryInv);
6.獲取符合標準的圓形輪廓//獲取符合標準的圓形輪廓
VectorOfVectorOfPoint selected_contours =GetContoursAboveGivenSize(mat_threshold, 20, 20);
////// 提取圖中大于給定寬、高的輪廓
/// ///要提取輪廓的圖片///輪廓外接矩形的寬///輪廓外接矩形的高public VectorOfVectorOfPoint GetContoursAboveGivenSize(Mat mat_threshold, int width, int height)
{
//輪廓篩選
//1.膨脹,改善輪廓
Mat struct_element = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Cross,
new Size(3, 3), new Point(-1, -1));//結構元素
Mat mat_dilate = new Mat();
CvInvoke.MorphologyEx(mat_threshold, mat_dilate, Emgu.CV.CvEnum.MorphOp.Dilate, struct_element, new Point(-1, -1), 1,
Emgu.CV.CvEnum.BorderType.Default, new MCvScalar(0, 0, 0));//形態(tài)學膨脹
//2.篩選輪廓。篩選條件:寬度和高度同時大于20
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();//所有的輪廓
VectorOfVectorOfPoint selected_contours = new VectorOfVectorOfPoint();//用于存儲篩選過后的圓形輪廓
Mat mat_dilate_clone = mat_dilate.Clone();//克隆
CvInvoke.FindContours(mat_dilate_clone, contours, null, Emgu.CV.CvEnum.RetrType.External,
Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);//提取輪廓,操作過程中會對輸入圖像進行修改
//選取外接矩形寬、高要同時大于給定標準的輪廓
for (int i = 0; i< contours.Size; i++)
{
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);//外接矩形
if (rect.Width >width && rect.Height >height)
{
selected_contours.Push(contours[i]);
}
}
return selected_contours;
}
7.對輪廓進行分類排序,獲取分類排序后的二維數組//對輪廓進行分類排序,獲取分類排序后的二維數組
VectorOfVectorOfPoint[,] classed_contours = ClassedOfContours(selected_contours, 5, 5);
////// 對給定的一些輪廓進行分類排序,返回分類排序后的二維數組
/// ///要進行排序的輪廓///一行中輪廓的個數///一列中輪廓的個數/// public VectorOfVectorOfPoint[,] ClassedOfContours(VectorOfVectorOfPoint selected_contours, int countOfRow, int countOfColumn)
{
//依據圓心的位置來確認答題卡輪廓的位置
//1.計算所有外接圓基本數據
float[] radius = new float[selected_contours.Size];
PointF[] center = new PointF[selected_contours.Size];
for (int i = 0; i< selected_contours.Size; i++)
{
CircleF circleF = CvInvoke.MinEnclosingCircle(selected_contours[i]);//最小外接圓
center[i] = circleF.Center;
radius[i] = circleF.Radius;
}
//2.計算x軸、y軸分割間隔
float x_min = 999, y_min = 999;
float x_max = -1, y_max = -1;
float x_interval = 0, y_interval = 0;//相鄰圓心的間距
foreach (PointF pf in center)
{
//獲取所有圓心中的坐標最值
if (pf.X< x_min) x_min = pf.X;
if (pf.X >x_max) x_max = pf.X;
if (pf.Y< y_min) y_min = pf.Y;
if (pf.Y >y_max) y_max = pf.Y;
}
x_interval = (x_max - x_min) / (countOfRow - 1);//答題卡每行5個圓,即4個間隔
y_interval = (y_max - y_min) / (countOfColumn - 1);//答題卡每列5個圓,即4個間隔
//4.分類
VectorOfVectorOfPoint[,] classed_contours = new VectorOfVectorOfPoint[countOfRow, countOfColumn];
//初始化VectorOfVectorOfPoint二維數組
for (int i = 0; i< 5; i++)
{
for (int j = 0; j< 5; j++)
{
classed_contours[i, j] = new VectorOfVectorOfPoint();
}
}
for (int i = 0; i< selected_contours.Size; i++)
{
PointF pf = center[i];
int index_x = (int)Math.Round((pf.X - x_min) / x_interval);
int index_y = (int)Math.Round((pf.Y - y_min) / y_interval);
VectorOfPoint temp = selected_contours[i];
classed_contours[index_x, index_y].Push(temp);
}
return classed_contours;
}
8.檢測答題者的選項//檢測答題者的選項
int[,] result_count = GetResultArray(mat_threshold, classed_contours, 5, 5);
////// 檢測答題者的選項,獲取涂選的結果數組
/// ///經閾值處理后的圖像///經排序分類后的輪廓數組///一行中輪廓的個數///一列中輪廓的個數/// public int[,] GetResultArray(Mat mat_threshold,VectorOfVectorOfPoint[,] classed_contours, int countOfRow, int countOfColumn)
{
int[,] result_count = new int[countOfRow, countOfColumn];//結果數組
//統(tǒng)計所有答題圓圈外接矩形內非零像素個數
Rectangle[,] re_rect = new Rectangle[countOfRow, countOfColumn];//外接矩形數組
int[,] count_roi = new int[countOfRow, countOfColumn];//外接矩形內非零像素個數
int min_count = 999;//非零像素個數大值,作為已涂選的參照
int max_count = -1;//非零像素個數最小值,作為未涂選的參照
for (int i = 0; i< countOfRow; i++)
{
for (int j = 0; j< countOfColumn; j++)
{
VectorOfPoint countour = classed_contours[i, j][0];
re_rect[i, j] = CvInvoke.BoundingRectangle(countour);
Mat temp = new Mat(mat_threshold, re_rect[i, j]);//提取ROI矩形區(qū)域
int count = CvInvoke.CountNonZero(temp);//計算圖像內非零像素個數
count_roi[i, j] = count;
if (count >max_count)max_count = count;
if (count< min_count)min_count = count;
}
}
//比對涂選的答案,以涂滿圓圈一半以上為標準
for (int i = 0; i< countOfRow; i++)
{
for (int j = 0; j< countOfColumn; j++)
{
if (count_roi[i, j] >max_count / 2)
{
result_count[i, j]=1;
}
}
}
return result_count;
}
9.?標示出答題者的選項//標示出答題者的選項
Mat temp_mat = new Mat();
CvInvoke.CvtColor(mat_Perspective, temp_mat, Emgu.CV.CvEnum.ColorConversion.Gray2Bgr);
for (int i = 0; i< 5; i++)
{
for (int j = 0; j< 5; j++)
{
if (result_count[i,j]==1)
{
CvInvoke.DrawContours(temp_mat, classed_contours[i,j], -1, new MCvScalar(255, 0, 0), 2);
}
}
}
ib_result.Image = temp_mat;
結果圖如下:?
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧
網頁標題:基于EmguCv的圓形答題卡識別-創(chuàng)新互聯
網站地址:http://aaarwkj.com/article16/ccghgg.html
成都網站建設公司_創(chuàng)新互聯,為您提供商城網站、網站設計、自適應網站、響應式網站、定制開發(fā)、營銷型網站建設
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯