private void btn_processomrsheet_Click(object sender, EventArgs e) { //empty the resultlist if it is not empty if (resultlist.Count > 0) { resultlist.Clear(); dgv_omrresult.Rows.Clear(); } if (this.resultlist_dupmerge.Count > 0) { resultlist_dupmerge.Clear(); } if (list_queslocation.Count > 0) { list_queslocation.Clear(); } // Scale the image to lower proportions while maintaining aspect ratio (for performance enhancements) Bitmap scaledsheet = ImageProcessor.ScaleImage(picbox_displayomrsheet.Image, 900, 1300); picbox_displayomrsheet.Image = scaledsheet; ts = new TimeSpan(DateTime.Now.Ticks); logOutputTerminal("Initializing OMR Extraction Process"); // Applying Image Pre-Processing logOutputTerminal("Applying Pre-Processing on the image"); Bitmap omrpreprocess_bmp = ImageProcessor.ApplyPreProcessing((Bitmap)scaledsheet, 160); picbox_displayomrsheet.Image = omrpreprocess_bmp; showTimeStamp("Image Pre-Processing Completed"); //Apply OMR Extraction showTimeStamp("Image Flattening Started"); Bitmap extractedsheet = OmrProcessor.ExtractPaperFromFlattened(omrpreprocess_bmp, scaledsheet, 5, 5, true); picbox_displayomrsheet.Image = extractedsheet; showTimeStamp("Image Flattening Completed"); if (extractedsheet == null) { MessageBox.Show("Quadrilateral transformation on the preprocess omr sheet failed due to incorrect blob detection", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } //Apply Resizing on the quadrilateral transformed image and bring it to a fixed proportion and resolution Bitmap resizedsheet = ImageProcessor.ResizeImage(extractedsheet, 600, 849, 72); picbox_displayomrsheet.Image = resizedsheet; //Apply preprocessing once again on the extracted sheet showTimeStamp("Second PreProcessing Started"); Bitmap extracted_preprocess = ImageProcessor.ApplyPreProcessing(resizedsheet, 190); showTimeStamp("Second PreProcessing Completed"); List <AForge.Imaging.Blob> bubblesblobs = OmrProcessor.ExtractBubbleCorrespondingBlobs(extracted_preprocess, 8, 8); showTimeStamp("Bubble Detection Completed"); try { //Generate XML file containing blobs(i.e filled bubbles) information(position, area, center of gravity, fullness etc) XMLReaderWriter.WriteNewBubblesDataXML(bubblesblobs, "BubbleData.xml"); } catch (System.Xml.XmlException) { MessageBox.Show("Failed to Generate a Bubble XML file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } showTimeStamp("Bubble XML Generation Completed"); if (picbox_displayomrsheet.SizeMode == PictureBoxSizeMode.AutoSize) { picbox_displayomrsheet.SizeMode = PictureBoxSizeMode.StretchImage; } /***debug purpose only***/ //Bitmap bm = new Bitmap(resizedsheet.Size.Width, resizedsheet.Size.Height); //Graphics g = Graphics.FromImage(bm); //g.DrawImage(resizedsheet, new Rectangle(0, 0, resizedsheet.Width, resizedsheet.Height)); //Pen redpen = new Pen(Color.Red, 2); //foreach (AForge.Imaging.Blob blob in bubblesblobs) //{ // g.DrawEllipse(redpen, blob.Rectangle.X, blob.Rectangle.Y, blob.Image.Width, blob.Image.Height); //} //picbox_displayomrsheet.Image = bm; /***debug purpose only***/ Rectangle[] Blocks = new Rectangle[] { new Rectangle(52, 384, 99, 454), new Rectangle(196, 384, 99, 454), new Rectangle(340, 384, 99, 454), new Rectangle(484, 384, 99, 454) }; //answer fetching algorithm starts List <AForge.Imaging.Blob>[] blocksfitbubbles = new List <AForge.Imaging.Blob> [Blocks.Length]; for (int i = 0; i < Blocks.Length; i++) { blocksfitbubbles[i] = new List <AForge.Imaging.Blob>(); } //Re-position each blob in corresponding answerblocks array foreach (AForge.Imaging.Blob blob in bubblesblobs) { bool flag = false; for (int i = 0; i < Blocks.Length; i++) { if (Blocks[i].Contains(new Point((int)blob.CenterOfGravity.X, (int)blob.CenterOfGravity.Y))) { blocksfitbubbles[i].Add(blob); flag = true; break; } } if (flag) { continue; } } //identify each bubble in block correspond to which question no and which option(a,b,c,d) //for identification of question no, slice the blocks and identify each bubbles falls in which slice //for identification of option(a,b,c,d) measure the difference in horizontal length from blob.rectangle.X to its corresponding block rectangle.X List <Rectangle>[] blockslices = new List <Rectangle> [Blocks.Length]; int totalquestionsperblock = 25; for (int i = 0; i < Blocks.Length; i++) { blockslices[i] = new List <Rectangle>(); } for (int i = 0; i < Blocks.Length; i++) { Rectangle[] singleblockslices = SliceBlock(Blocks[i], totalquestionsperblock); foreach (Rectangle slice in singleblockslices) { blockslices[i].Add(slice); } } for (int i = 0; i < Blocks.Length; i++) { foreach (AForge.Imaging.Blob blob in blocksfitbubbles[i]) { bool flag = false; foreach (Rectangle slice in blockslices[i]) { if (slice.Contains(new Point((int)blob.CenterOfGravity.X, (int)blob.CenterOfGravity.Y))) { //bubble is present in this slice //get corresponding question no and its checked option int questionno = ((int)(slice.Bottom - Blocks[i].Top) / slice.Height) + (i * totalquestionsperblock); //MessageBox.Show("Ques: " + questionno + Environment.NewLine + "Bubble X: " + blob.CenterOfGravity.X.ToString() + Environment.NewLine + "Bubble Y: " + blob.CenterOfGravity.Y.ToString()); int parallelans_hordiff = (int)(slice.Width / 4); int ans_hordist_fromslice = blob.Rectangle.Left - slice.Left; int answerchecked = 0; for (int k = 1; k <= 4; k++) { if ((ans_hordist_fromslice > (parallelans_hordiff * (k - 1))) && (ans_hordist_fromslice < (parallelans_hordiff * (k)))) { answerchecked = k; } } resultlist.Add(Tuple.Create(questionno, answerchecked)); list_queslocation.Add(Tuple.Create(questionno, slice.Location)); //MessageBox.Show("Ques: " + questionno + Environment.NewLine + "Answer: " + answerchecked); flag = true; break; } } if (flag) { continue; } } } //answer fetching algorithm ends this.resultlist_dupmerge = resultlist.GroupBy(x => x.Item1, x => x.Item2).ToList(); foreach (var group in resultlist_dupmerge) { DataGridViewRow dgrow = new DataGridViewRow(); dgrow.CreateCells(dgv_omrresult); dgrow.Cells[0].Value = group.Key.ToString(); string ans = ""; foreach (var element in group) { if (element == 1) { ans += "A"; } else if (element == 2) { if (ans.Length >= 1) { ans += " "; } ans += "B"; } else if (element == 3) { if (ans.Length >= 1) { ans += " "; } ans += "C"; } else if (element == 4) { if (ans.Length >= 1) { ans += " "; } ans += "D"; } else { ans = "-"; } } dgrow.Cells[1].Value = ans; dgv_omrresult.Rows.Add(dgrow); } }
public static Bitmap ExtractPaperFromFlattened(Bitmap bitmap, Bitmap originalimage, int minblobwidth, int minblobheight, bool applyrotation) { Bitmap bm = new Bitmap(bitmap.Size.Width, bitmap.Size.Height); Graphics g = Graphics.FromImage(bm); g.DrawImage(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height)); Pen redpen = new Pen(Color.Red, 2); AForge.Math.Geometry.SimpleShapeChecker detectshape = new AForge.Math.Geometry.SimpleShapeChecker(); if (applyrotation) { //rotate the image in case it is not properly oriented // lock the image BitmapData bitmapdata_rot = bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat); BlobCounter blobCounter_rot = new BlobCounter(); blobCounter_rot.FilterBlobs = true; blobCounter_rot.MinHeight = minblobheight; blobCounter_rot.MinWidth = minblobwidth; blobCounter_rot.ProcessImage(bitmapdata_rot); bitmap.UnlockBits(bitmapdata_rot); Blob[] blob_objects_rot = blobCounter_rot.GetObjects(bitmap, false); try { foreach (Blob blob in blob_objects_rot) { List <IntPoint> edgePoints = blobCounter_rot.GetBlobsEdgePoints(blob); //detect rotated paper based on left edge rectangular mark List <IntPoint> cornerPoints; if (detectshape.IsQuadrilateral(edgePoints, out cornerPoints)) { if (detectshape.CheckPolygonSubType(cornerPoints) == AForge.Math.Geometry.PolygonSubType.Rectangle) { if ((blob.Fullness > 0.7) && (((double)blob.Image.Width / (double)bitmap.Size.Width > 0.025) && ((double)blob.Image.Width / (double)bitmap.Size.Width < 0.037)) && (((double)blob.Image.Height / (double)bitmap.Size.Height > 0.005) && ((double)blob.Image.Height / (double)bitmap.Size.Height < 0.013))) { // A------p------B Suppose these are the Coordinates of the Rectangle (image) // | 2 | 1 | A,B,C,D are vertices and 1,2,3,4 are quadrants // s------O------q p,q,r,s midpoints // | 3 | 4 | O is the center of the rectangle // D------r------C let the blob be denoted by BL System.Drawing.Point cent_O = new System.Drawing.Point(bitmap.Size.Width / 2, bitmap.Size.Height / 2); GraphicsUnit units = GraphicsUnit.Point; System.Drawing.Point pnt_A = new System.Drawing.Point((int)bitmap.GetBounds(ref units).X, (int)bitmap.GetBounds(ref units).Y); System.Drawing.Point pnt_B = new System.Drawing.Point(bitmap.Size.Width, (int)bitmap.GetBounds(ref units).Y); System.Drawing.Point pnt_C = new System.Drawing.Point(bitmap.Size.Width, bitmap.Size.Height); System.Drawing.Point pnt_D = new System.Drawing.Point((int)bitmap.GetBounds(ref units).X, bitmap.Size.Height); System.Drawing.Point midpnt_P = new System.Drawing.Point((pnt_A.X + pnt_B.X) / 2, (pnt_A.Y + pnt_B.Y) / 2); System.Drawing.Point midpnt_Q = new System.Drawing.Point((pnt_B.X + pnt_C.X) / 2, (pnt_B.Y + pnt_C.Y) / 2); System.Drawing.Point midpnt_R = new System.Drawing.Point((pnt_C.X + pnt_D.X) / 2, (pnt_C.Y + pnt_D.Y) / 2); System.Drawing.Point midpnt_S = new System.Drawing.Point((pnt_A.X + pnt_D.X) / 2, (pnt_A.Y + pnt_D.Y) / 2); System.Drawing.Point blob_CENGRAV = new System.Drawing.Point((int)blob.CenterOfGravity.X, (int)blob.CenterOfGravity.Y); if (ImageProcessor.IsPointInsideRegion(bitmap, pnt_A, midpnt_P, cent_O, midpnt_S, blob_CENGRAV)) { //do nothing...image is properly oriented break; //terminate the loop } else if (ImageProcessor.IsPointInsideRegion(bitmap, midpnt_P, pnt_B, midpnt_Q, cent_O, blob_CENGRAV)) { //blob is present in quadrant 1 //rotate image by three CW bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone); originalimage.RotateFlip(RotateFlipType.Rotate270FlipNone); break; } else if (ImageProcessor.IsPointInsideRegion(bitmap, cent_O, midpnt_Q, pnt_C, midpnt_R, blob_CENGRAV)) { //blob is present in quadrant 4 //rotate image by two CW bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); originalimage.RotateFlip(RotateFlipType.Rotate180FlipNone); break; } else if (ImageProcessor.IsPointInsideRegion(bitmap, cent_O, midpnt_R, pnt_D, midpnt_S, blob_CENGRAV)) { //blob is present in quadrant 3 //rotate image by one CW bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone); originalimage.RotateFlip(RotateFlipType.Rotate90FlipNone); break; } } } } } } catch (ArgumentException) { MessageBox.Show("Bilinear Rotational Transformation Failed"); } } //After applying rotational process, extract the blobs once again to find the circular edge markers // lock the image BitmapData bitmapdata = bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat); // Finding Blobs in the Bitmap image BlobCounter blobCounter = new BlobCounter(); blobCounter.FilterBlobs = true; blobCounter.MinHeight = minblobheight; blobCounter.MinWidth = minblobwidth; blobCounter.ProcessImage(bitmapdata); Blob[] blobs = blobCounter.GetObjectsInformation(); bitmap.UnlockBits(bitmapdata); //Rectangle[] rects = blobCounter.GetObjectsRectangles(); Blob[] blob_objects = blobCounter.GetObjects(bitmap, false); // Paper Detection happens through the detection of the edge-markers placed on all four // edges of the OMR sheet List <IntPoint> quad = new List <IntPoint>(); // Store sheet corner locations (if anyone is detected ) try { foreach (Blob blob in blob_objects) { //detect edge circles if ((double)blob.Rectangle.Width / blob.Rectangle.Height < 1.4 && (double)blob.Rectangle.Width / blob.Rectangle.Height > .6) // filters out blobs having insanely wrong aspect ratio { List <IntPoint> edgePoints = blobCounter.GetBlobsEdgePoints(blob); //detect circles only if (detectshape.IsCircle(edgePoints)) { //detect filled circles only //the ratio of width(blob)/width(bitmap) should be in range of 0.031 - 0.042 if ((blob.Fullness > 0.7) && (((double)blob.Image.Width / (double)bitmap.Width) > 0.031) && (((double)blob.Image.Width / (double)bitmap.Width) < 0.042)) { //g.DrawRectangle(redpen, blob.Rectangle); quad.Add(new IntPoint((int)blob.CenterOfGravity.X, (int)blob.CenterOfGravity.Y)); } } } } // filter out if wrong blobs pretend to be our blobs. if (quad.Count == 4) { if (!((quad[0].DistanceTo(quad[1]) / quad[0].DistanceTo(quad[2]) > 0.5) && (quad[0].DistanceTo(quad[1]) / quad[0].DistanceTo(quad[2]) < 1.5))) { quad.Clear(); } else { //rearrange the edge coordinates, if not in proper manner while ((quad[0].X > quad[1].X) || (quad[1].Y > quad[3].Y) || (quad[2].X > quad[3].X) || (quad[0].Y > quad[2].Y)) { if (quad[0].X > quad[1].X) { IntPoint tmp = quad[0]; quad[0] = quad[1]; quad[1] = tmp; } if (quad[1].Y > quad[3].Y) { IntPoint tmp = quad[1]; quad[1] = quad[3]; quad[3] = tmp; } if (quad[2].X > quad[3].X) { IntPoint tmp = quad[3]; quad[3] = quad[2]; quad[2] = tmp; } if (quad[0].Y > quad[2].Y) { IntPoint tmp = quad[0]; quad[0] = quad[2]; quad[2] = tmp; } } } } if (quad.Count != 4) { //call recursively as the sheet is not detected properly //todo: try altering the blob height and width, threshold etc, when program does not find edge markers } else { //rearrange the edges coordinates // 0----1 // | | // 2----3 //interchange 2 and 3 IntPoint tp2 = quad[3]; quad[3] = quad[2]; quad[2] = tp2; //sort the edges for wrap operation QuadrilateralTransformation wrap = new QuadrilateralTransformation(quad, 2100, 2970); wrap.UseInterpolation = false; wrap.AutomaticSizeCalculaton = true; Bitmap wrappedoriginal = wrap.Apply(originalimage); return(wrappedoriginal); } } catch (ArgumentException) { MessageBox.Show("No Blobs Found"); } return(null); }