中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

基于EmguCv的圓形答題卡識(shí)別-創(chuàng)新互聯(lián)

參考:https://blog.csdn.net/qiao_lili/article/details/83176480

成都創(chuàng)新互聯(lián)公司是一家專業(yè)提供臨滄企業(yè)網(wǎng)站建設(shè),專注與做網(wǎng)站、網(wǎng)站設(shè)計(jì)、成都h5網(wǎng)站建設(shè)、小程序制作等業(yè)務(wù)。10年已為臨滄眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站制作公司優(yōu)惠進(jìn)行中。

網(wǎng)上關(guān)于C#結(jié)合EmguCv開發(fā)答題卡識(shí)別的資料很少,因?yàn)橛凶龃痤}卡識(shí)別方面的需求,個(gè)人也只熟悉C#語言,找了半天也只找到一些通過C++、Python或matlab結(jié)合OpenCV來實(shí)現(xiàn)識(shí)別的例子,只好對(duì)著別人的例子嘗試著翻譯成EmguCv,還算成功。自己又針對(duì)答題卡識(shí)別封裝了幾個(gè)函數(shù),記錄在這留給需要的人。(得吐槽下,EmguCv在錯(cuò)誤管理上還欠缺很多,經(jīng)常出現(xiàn)莫名其妙的錯(cuò)誤)

1.設(shè)置兩個(gè)圖片顯示容器
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;
            }

實(shí)例圖片

3.獲取當(dāng)前圖像的大矩形邊界
//獲取當(dāng)前圖像的大矩形邊界
                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);

            //邊緣檢測(cè)
            CvInvoke.Canny(src_gray, dst, 120, 180);

            //尋找答題卡矩形邊界(大的矩形)
            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();//創(chuàng)建VectorOfVectorOfPoint數(shù)據(jù)類型用于存儲(chǔ)輪廓

            CvInvoke.FindContours(dst, contours, null, Emgu.CV.CvEnum.RetrType.External,
                Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);//提取輪廓

            VectorOfVectorOfPoint result_contour = new VectorOfVectorOfPoint();//用于存儲(chǔ)篩選過后的輪廓

            int ksize = contours.Size; //獲取連通區(qū)域個(gè)數(shù)
            if (ksize == 1)
            {
                result_contour = contours;
            }
            else
            {
                double maxLength = -1;//用于保存輪廓周長(zhǎng)的大值
                int index = -1;//輪廓周長(zhǎng)的大值的序號(hào)
                for (int i = 0; i< ksize; i++)
                {
                    VectorOfPoint contour = contours[i];//獲取獨(dú)立的連通輪廓
                    double length = CvInvoke.ArcLength(contour, true);//計(jì)算連通輪廓的周長(zhǎng)

                    if (length >maxLength)
                    {
                        maxLength = length;
                        index = i;
                    }
                }
                result_contour.Push(contours[index]);//篩選后的連通輪廓
            }
            return result_contour;
        }
4.對(duì)圖像進(jìn)行矯正
//對(duì)圖像進(jìn)行矯正
                Mat mat_Perspective = MyWarpPerspective(src, result_contour);

public Mat MyWarpPerspective(Mat src, VectorOfVectorOfPoint result_contour)
        {
            //擬合答題卡的幾何輪廓,保存點(diǎn)集pts并順時(shí)針排序
            VectorOfPoint pts = new VectorOfPoint();//用于存放逼近的結(jié)果
            VectorOfPoint tempContour = result_contour[0];//臨時(shí)用
            double result_length = CvInvoke.ArcLength(tempContour, true);
            CvInvoke.ApproxPolyDP(tempContour, pts, result_length * 0.02, true); //幾何逼近,獲取矩形4個(gè)頂點(diǎn)坐標(biāo)

            //Point[]轉(zhuǎn)換為PointF[]類型
            PointF[] pts_src = Array.ConvertAll(pts.ToArray(), new Converter(PointToPointF));
            
            //確定透視變換的寬度、高度
            Size sizeOfRect = CalSizeOfRect(pts_src);
            int width= sizeOfRect.Width;
            int height=sizeOfRect.Height;

            //計(jì)算透視變換矩陣
            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)};

            //計(jì)算透視矩陣
            Mat data = CvInvoke.GetPerspectiveTransform(pts_src, pts_target);
            //進(jìn)行透視操作
            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;
        }

////// 計(jì)算給定四個(gè)坐標(biāo)點(diǎn)四邊形的寬、高
        /////////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)
            {
                //說明當(dāng)前為逆時(shí)針存儲(chǔ),改為順時(shí)針存儲(chǔ)(交換第2、4點(diǎn))
                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);//根號(hào)下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轉(zhuǎn)換為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.獲取符合標(biāo)準(zhǔn)的圓形輪廓
//獲取符合標(biāo)準(zhǔn)的圓形輪廓
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));//結(jié)構(gòu)元素
            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)學(xué)膨脹

            //2.篩選輪廓。篩選條件:寬度和高度同時(shí)大于20
            VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();//所有的輪廓
            VectorOfVectorOfPoint selected_contours = new VectorOfVectorOfPoint();//用于存儲(chǔ)篩選過后的圓形輪廓
            Mat mat_dilate_clone = mat_dilate.Clone();//克隆
            CvInvoke.FindContours(mat_dilate_clone, contours, null, Emgu.CV.CvEnum.RetrType.External,
                Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);//提取輪廓,操作過程中會(huì)對(duì)輸入圖像進(jìn)行修改

            //選取外接矩形寬、高要同時(shí)大于給定標(biāo)準(zhǔn)的輪廓
            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.對(duì)輪廓進(jìn)行分類排序,獲取分類排序后的二維數(shù)組
//對(duì)輪廓進(jìn)行分類排序,獲取分類排序后的二維數(shù)組
VectorOfVectorOfPoint[,] classed_contours = ClassedOfContours(selected_contours, 5, 5);

////// 對(duì)給定的一些輪廓進(jìn)行分類排序,返回分類排序后的二維數(shù)組
        //////要進(jìn)行排序的輪廓///一行中輪廓的個(gè)數(shù)///一列中輪廓的個(gè)數(shù)///public VectorOfVectorOfPoint[,] ClassedOfContours(VectorOfVectorOfPoint selected_contours, int countOfRow, int countOfColumn)
        {
            //依據(jù)圓心的位置來確認(rèn)答題卡輪廓的位置
            //1.計(jì)算所有外接圓基本數(shù)據(jù)
            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.計(jì)算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)
            {
                //獲取所有圓心中的坐標(biāo)最值
                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個(gè)圓,即4個(gè)間隔
            y_interval = (y_max - y_min) / (countOfColumn - 1);//答題卡每列5個(gè)圓,即4個(gè)間隔
            //4.分類
            VectorOfVectorOfPoint[,] classed_contours = new VectorOfVectorOfPoint[countOfRow, countOfColumn];
            //初始化VectorOfVectorOfPoint二維數(shù)組
            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.檢測(cè)答題者的選項(xiàng)
//檢測(cè)答題者的選項(xiàng)
int[,] result_count = GetResultArray(mat_threshold, classed_contours, 5, 5);

////// 檢測(cè)答題者的選項(xiàng),獲取涂選的結(jié)果數(shù)組
        //////經(jīng)閾值處理后的圖像///經(jīng)排序分類后的輪廓數(shù)組///一行中輪廓的個(gè)數(shù)///一列中輪廓的個(gè)數(shù)///public int[,] GetResultArray(Mat mat_threshold,VectorOfVectorOfPoint[,] classed_contours, int countOfRow, int countOfColumn)
        {
            int[,] result_count = new int[countOfRow, countOfColumn];//結(jié)果數(shù)組
            //統(tǒng)計(jì)所有答題圓圈外接矩形內(nèi)非零像素個(gè)數(shù)
            Rectangle[,] re_rect = new Rectangle[countOfRow, countOfColumn];//外接矩形數(shù)組
            int[,] count_roi = new int[countOfRow, countOfColumn];//外接矩形內(nèi)非零像素個(gè)數(shù)
            int min_count = 999;//非零像素個(gè)數(shù)大值,作為已涂選的參照
            int max_count = -1;//非零像素個(gè)數(shù)最小值,作為未涂選的參照
            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);//計(jì)算圖像內(nèi)非零像素個(gè)數(shù)
                    count_roi[i, j] = count;

                    if (count >max_count)max_count = count;
                    if (count< min_count)min_count = count;
                }
            }

            //比對(duì)涂選的答案,以涂滿圓圈一半以上為標(biāo)準(zhǔn)
            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.?標(biāo)示出答題者的選項(xiàng)
//標(biāo)示出答題者的選項(xiàng)
                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;

結(jié)果圖如下:?

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧

網(wǎng)頁標(biāo)題:基于EmguCv的圓形答題卡識(shí)別-創(chuàng)新互聯(lián)
網(wǎng)站地址:http://www.rwnh.cn/article16/ccghgg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站網(wǎng)站設(shè)計(jì)、自適應(yīng)網(wǎng)站、響應(yīng)式網(wǎng)站、定制開發(fā)營(yíng)銷型網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站
武汉市| 商南县| 商城县| 阳泉市| 城步| 彝良县| 兴义市| 永安市| 诏安县| 舒城县| 吕梁市| 花莲市| 嫩江县| 虹口区| 恩平市| 凌云县| 永康市| 赣州市| 正定县| 永春县| 大竹县| 任丘市| 莲花县| 南皮县| 澜沧| 故城县| 瓦房店市| 宁阳县| 宁都县| 扎赉特旗| 南通市| 册亨县| 叙永县| 搜索| 宝坻区| 铁岭县| 建德市| 肃宁县| 广元市| 黎平县| 南开区|