//PlaneCalibration class... /// /// The plane callibration class handles the transformation that map points from the raster space of /// the video feed to NDC coordinates. It accomplishes this transformation by analying an image from /// the video feed to identify and store the corner locations of the screen in the image. A homography is /// found that maps these points to NDC corners. /// public class PlaneCalibration : CallibrationData { /* * CONSTANTS */ public const int GREEN_CORNER_THRESHOLD = 10; public const int BOUNDS_PADDING = 10; /* * CLASS PROPERTIES */ private bool HomFound; private Matrix3 HomMatrix; private List hCircles; private Vector2 cornerTL; private Vector2 cornerTR; private Vector2 cornerBL; private Vector2 cornerBR; private WinDraw.Rectangle bounds; /* * CONSTRUCTOR */ /// /// constructor for a new plane callibration object /// /// the name of the capture device /// the resolution of the video feed /// the form object for displaying the video feed /// the new capture device object /// the calibration form object public PlaneCalibration(String newCaptureDeviceName, WinDraw.Size videoResolution, PictureBox newMonitor, CaptureDevice newCaptureDevice, CalibrationForm newCalibrationForm) : base( newName, videoResolution, newMonitor, newDevice, newParentForm) { HomFound = false; HomMatrix = Matrix3.Identity; hCircles = new List(); } //manual point manipualtion omitted /* * AUTO CALIBRATION */ /// /// Automatically identifies the corners of the screen /// in the video feed image (using the most recent frame). /// The application displays an image with green circles in the four /// corners of the screen so that only a few pixels need to be considered. /// public override void AutoUpdateCorners() { //verify non null values if (displayFrame == null || videoMonitor == null) throw new Exception("null component, cannot proceed with auto calibration"); //get most recent frame as a bitmap in unmanaged memory Bitmap cornerRefBitmap = (Bitmap)displayFrame.Clone(); UnsafeBitmap cornerRef = new UnsafeBitmap(cornerRefBitmap); cornerRef.LockBitmap(); //brightest pixels List brightPoints = new List(); // identify all points bright enough to // be considered part of the screen image for (int y = 0; y < cornerRef.Bitmap.Height; y++) { for (int x = 0; x < cornerRef.Bitmap.Width; x++) { //for each pixel in image checkt to see if pixel is bright enough //to be a corner area pixel if (cornerRef.GetPixel(x, y).green > GREEN_CORNER_THRESHOLD) brightPoints.Add(new Vector2d(x, y)); } } if (brightPoints.Count < 4) throw new Exception("Not enough points found."); //find the hull of the bright pixels double minX = brightPoints[0].X; double maxX = brightPoints[0].X; double minY = brightPoints[0].Y; double maxY = brightPoints[0].Y; for (int i = 1; i < brightPoints.Count; i++) { if (brightPoints[i].X < minX) minX = brightPoints[i].X; if (brightPoints[i].Y < minY) minY = brightPoints[i].Y; if (brightPoints[i].X > maxX) maxX = brightPoints[i].X; if (brightPoints[i].Y > maxY) maxY = brightPoints[i].Y; } //boudning box of the corner points bounds = new System.Drawing.Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY)); //points on the corners of the bounding box Vector2d boundsTL = new Vector2d(minX, minY); Vector2d boundsTR = new Vector2d(maxX, minY); Vector2d boundsBL = new Vector2d(minX, maxY); Vector2d boundsBR = new Vector2d(maxX, maxY); //direction of the bounding box edges Vector2d topDir = boundsTR - boundsTL; Vector2d bottomDir = boundsBR - boundsBL; Vector2d leftDir = boundsTL - boundsBL; Vector2d rightDir = boundsTR - boundsBR; //screen corners Vector2d TL = Vector2d.Zero; Vector2d TR = Vector2d.Zero; Vector2d BL = Vector2d.Zero; Vector2d BR = Vector2d.Zero; //edge lengths double topLength = Vector2d.DistanceSquared(boundsTL, boundsTR); double bottomLength = Vector2d.DistanceSquared(boundsBL, boundsBR); double rightLength = Vector2d.DistanceSquared(boundsTR, boundsBR); double leftLength = Vector2d.DistanceSquared(boundsTL, boundsBL); double curDist = 0; double TLMinDist = 0; double TRMinDist = 0; double BLMinDist = 0; double BRMinDist = 0; // find the positions that minimizes the distance between // two edges of the bounding box to find the screen corner for (int i = 0; i < brightPoints.Count; i++) { //top and left curDist = distanceToLine(boundsTL, topDir, topLength, brightPoints[i]) + distanceToLine(boundsBL, leftDir, leftLength, brightPoints[i]); if (curDist < TLMinDist || i == 0) { TL = brightPoints[i]; TLMinDist = curDist; } //top and right curDist = distanceToLine(boundsTL, topDir, topLength, brightPoints[i]) + distanceToLine(boundsBR, rightDir, rightLength, brightPoints[i]); if (curDist < TRMinDist || i == 0) { TR = brightPoints[i]; TRMinDist = curDist; } //bottom and left curDist = distanceToLine(boundsBL, bottomDir, bottomLength, brightPoints[i]) + distanceToLine(boundsBL, leftDir, leftLength, brightPoints[i]); if (curDist < BLMinDist || i == 0) { BL = brightPoints[i]; BLMinDist = curDist; } //bottom and right curDist = distanceToLine(boundsBL, bottomDir, bottomLength, brightPoints[i]) + distanceToLine(boundsBR, rightDir, rightLength, brightPoints[i]); if (curDist < BRMinDist || i == 0) { BR = brightPoints[i]; BRMinDist = curDist; } } //convert to XNA vectors cornerTL = TL.ToXNAVector(); cornerTR = TR.ToXNAVector(); cornerBL = BL.ToXNAVector(); cornerBR = BR.ToXNAVector(); //output image with identified corners circled for testing purposes cornerRef.UnlockBitmap(); Graphics dc = Graphics.FromImage(cornerRefBitmap); Pen p = new Pen(Color.Red, 1); dc.DrawEllipse(p, (float)cornerTL.X - 2, (float)cornerTL.Y - 2, 4, 4); dc.DrawEllipse(p, (float)cornerTR.X - 2, (float)cornerTR.Y - 2, 4, 4); dc.DrawEllipse(p, (float)cornerBL.X - 2, (float)cornerBL.Y - 2, 4, 4); dc.DrawEllipse(p, (float)cornerBR.X - 2, (float)cornerBR.Y - 2, 4, 4); dc.Dispose(); cornerRefBitmap.Save(curGame.ApplicationDirectory + CallibrationData.CORNER_REF_IMAGE, WinImage.ImageFormat.Jpeg); //update corner point positions (hCircles list) //in video monitor on calibration form updateCorners(); } /// /// crop video feed to the corners of the screen image area /// public override void AutoCrop() { base.autoCrop(); if (bounds != null && bounds.Width > 0 && bounds.Height > 0) { //add padding to bounding box bounds.X -= BOUNDS_PADDING; bounds.Y -= BOUNDS_PADDING; float twicePadding = BOUNDS_PADDING * 2; bounds.Width += twicePadding; bounds.Height += twicePadding; if (bounds.X < 0) bounds.X = 0; if (bounds.Y < 0) bounds.Y = 0; if (bounds.Right > fullResolution.Width - 1) bounds.Width = fullResolution.Width - bounds.X - 1; if (bounds.Bottom > fullResolution.Height - 1) bounds.Height = fullResolution.Height - bounds.Y - 1; //update crop... this.cropRectangle = bounds; } } /* * HOMOGRAPHIC MATRIX CALCULATION */ /// /// put point positions into a list in the correct /// order for homography calculation(TL, TR, BL, BR) /// private void getCornerPoints() { if (hCircles.Count >= 4) { List screenCorners = new List(); for (int i = 0; i < 4; i++) { //correct for video scaling and cropping screenCorners.Add(new Vector2d(hCircles[i].Location)); if (mKfound) screenCorners[i] = tNormLensInv.transform(radialDistort(tNormLens.transform(screenCorners[i]), Kvector)); } //identify each of 4 corners as the top left, top right, bottom left, and bottom right positions double minX = fullResolution.Width; double minX2 = fullResolution.Width; double minY = fullResolution.Height; double minY2 = fullResolution.Height; double minXIndex = 0; double minX2Index = 0; double minYIndex = 0; double minY2Index = 0; //find smallest two X coordinates for (int i = 0; i < 4; i++) { if (screenCorners[i].X < minX) { minX2 = minX; minX2Index = minXIndex; minX = screenCorners[i].X; minXIndex = i; } else if (screenCorners[i].X < minX2) { minX2 = screenCorners[i].X; minX2Index = i; } } //find smallest two Y coordinates for (int i = 0; i < 4; i++) { if (screenCorners[i].Y < minY) { minY2 = minY; minY2Index = minYIndex; minY = screenCorners[i].Y; minYIndex = i; } else if (screenCorners[i].Y < minY2) { minY2 = screenCorners[i].Y; minY2Index = i; } } //find identified screen corners Vector2d TL = Vector2d.Zero; Vector2d TR = Vector2d.Zero; Vector2d BL = Vector2d.Zero; Vector2d BR = Vector2d.Zero; for (int i = 0; i < 4; i++) { if ((i == minXIndex || i == minX2Index) && (i == minYIndex || i == minY2Index)) TL = screenCorners[i]; else if ((!(i == minXIndex || i == minX2Index)) && (i == minYIndex || i == minY2Index)) TR = screenCorners[i]; else if ((i == minXIndex || i == minX2Index) && !(i == minYIndex || i == minY2Index)) BL = screenCorners[i]; else BR = screenCorners[i]; } screenCorners[0] = TL; screenCorners[1] = TR; screenCorners[2] = BL; screenCorners[3] = BR; return screenCorners; } return null; } /// /// updates homographic matrix based on corner circle positions /// private void updateHomography() { HomFound = false; if (hCircles != null && hCircles.Count >= 4) { List screenCorners = getCornerPoints(); //normalized device coordinate corners NDCPoints = new List(); NDCPoints.Add(new Vector2d(-1, -1)); NDCPoints.Add(new Vector2d(1, -1)); NDCPoints.Add(new Vector2d(-1, 1)); NDCPoints.Add(new Vector2d(1, 1)); //find mapping from raster corners in video space to NDC corners HomMatrix = Matrix3.Homography(screenCorners, NDCPoints); HomFound = true; } } /* * LASER POSITION RECTIFICATION */ /// /// rectify point positions from video space to NDC application space /// public override void rectifyLaserPositions() { base.rectifyLaserPositions(); if (HomFound) { foreach (LaserDot laser in lasers) if (laser.onScreen) laser.AdjustedPosition = HomMatrix.transform(laser.AdjustedPosition); } } }