//Function to cross all informations added to this face and evaluate the best values public void Evaluate() { //Evaluate mouth evaluatedMouth = new Rect(0, 0, 0, 0); //Random randomizer = new Random(); //evaluatedMouth = mouths[randomizer.Next(0, mouths.Count - 1)]; //must work a few on the mouth to choose the best one and proceed to histogram check for try to determinate skin color, eye color, hair color etc.. foreach (Rect mouth in mouths) { if (mouth.y < face.y + face.height / 2) continue; if (evaluatedMouth.width > mouth.width) continue; evaluatedMouth = mouth; } //Evaluate eyes evaluatedEyes = new ArrayList<Rect>(); ArrayList<Rect> rightCandidates = new ArrayList<Rect>(); ArrayList<Rect> leftCandidates = new ArrayList<Rect>(); foreach (Rect eye in eyes) { //Ensure the eyes are in the upper half of the img region if (eye.y + eye.height / 2 > face.y + face.height / 2) continue; if (eye.x + eye.width / 2 < face.x + face.width / 2) rightCandidates.add(eye); else leftCandidates.add(eye); } //get centers for each side weighted by their areas int totalAreas = 0; int totalX = 0; int totalY = 0; if (rightCandidates.size() > 0) { foreach (Rect eye in rightCandidates) { int eyeArea = eye.width * eye.height; totalAreas += eyeArea; totalX += (eye.x + eye.width / 2) * eyeArea; totalY += (eye.y + eye.height / 2) * eyeArea; } Point rightPoint = new Point(totalX / totalAreas, totalY / totalAreas); int rightEyeSide = (int)Math.Sqrt((double)totalAreas / (double)rightCandidates.size()); Rect rightEye = new Rect( rightPoint.x - rightEyeSide / 2, rightPoint.y - rightEyeSide / 2, rightEyeSide, rightEyeSide); //rightEye.Offset(-rightEye.Width / 2, -rightEye.Height / 2); evaluatedEyes.add(rightEye); } if (leftCandidates.size() > 0) { totalAreas = 0; totalX = 0; totalY = 0; foreach (Rect eye in leftCandidates) { int eyeArea = eye.width * eye.height; totalAreas += eyeArea; totalX += (eye.x + eye.width / 2) * eyeArea; totalY += (eye.y + eye.height / 2) * eyeArea; } Point leftPoint = new Point(totalX / totalAreas, totalY / totalAreas); int leftEyeSide = (int)Math.Sqrt((double)totalAreas / (double)leftCandidates.size()); Rect leftEye = new Rect( leftPoint.x - leftEyeSide / 2, leftPoint.y - leftEyeSide / 2, leftEyeSide, leftEyeSide); //leftEye.Offset(-leftEye.Width / 2, -leftEye.Height / 2); evaluatedEyes.add(leftEye); } //Check if it is valid isValid = false; if (evaluatedEyes.size() > 2) throw new Exception("Eyes count must be equal or less than two"); if (evaluatedEyes.size() == 2) { isValid = true; //Get the face line data Point eye1Center = new Point(evaluatedEyes.get(0).x + evaluatedEyes.get(0).width / 2, evaluatedEyes.get(0).y + evaluatedEyes.get(0).height / 2); Point eye2Center = new Point(evaluatedEyes.get(1).x + evaluatedEyes.get(1).width / 2, evaluatedEyes.get(1).y + evaluatedEyes.get(1).height / 2); int xOffset = (eye2Center.x - eye1Center.x) / 2; int yOffset = (eye2Center.y - eye1Center.y) / 2; Point eyeLineCenter = new Point(eye1Center.x + xOffset, eye1Center.y + yOffset); int zeroDivFac = eye1Center.x == eye2Center.x ? 1 : 0; //Generate face line slope and offset double aFact = (double)(eye1Center.y - eye2Center.y) / (double)(eye1Center.x - eye2Center.x + zeroDivFac); aFact = Math.Atan(aFact) + Math.PI / 2; aFact = Math.Tan(aFact); double bFact = eyeLineCenter.y - aFact * eyeLineCenter.x; faceLineSlope = aFact; faceLineOffset = bFact; //If the mouth is invalid, project a new based on the face line if (evaluatedMouth.width == 0) { PointGenerator faceLinePoint = new PointGenerator(aFact, bFact); Point projMouthPos = faceLinePoint.GetFromY(face.y + face.height * 0.8); evaluatedMouth = new Rect( projMouthPos.x - (face.width / 3) / 2, projMouthPos.y - (face.height / 5) / 2, face.width / 3, face.height / 5); //evaluatedMouth.Offset(-evaluatedMouth.Width / 2, -evaluatedMouth.Height / 2); } } if (evaluatedEyes.size() == 1 && evaluatedMouth.width > 0) { isValid = true; //Project the other eye based on the mouth //Get the bottom mouth coords Point mouthBottomCenter = new Point( evaluatedMouth.x + evaluatedMouth.width / 2, evaluatedMouth.y + evaluatedMouth.height); //get the facetop coords Point faceTopCenter = new Point(face.width / 2 + face.x, face.y); //Apply an experimental correct factor to the values int correctFact = mouthBottomCenter.x - faceTopCenter.x; //correctFact = (int)(correctFact * 0.5); mouthBottomCenter.x += correctFact; faceTopCenter.x -= correctFact; //Get the slope of the faceline //In case they are the same value, add a pixel to prevent division by 0 int zeroDivFac = mouthBottomCenter.x == faceTopCenter.x ? 1 : 0; double a = (double)(mouthBottomCenter.y - faceTopCenter.y) / (double)(mouthBottomCenter.x - faceTopCenter.x + zeroDivFac); //Get the offset of the face line double b = mouthBottomCenter.y - a * mouthBottomCenter.x; faceLineSlope = a; faceLineOffset = b; //Get the line function of the face PointGenerator faceLinePoint = new PointGenerator(a, b); //Get the reference of the existing eye and its center point Rect eyeRef = evaluatedEyes.get(0); Point eyeCenter = new Point(eyeRef.x + eyeRef.width / 2, eyeRef.y + eyeRef.height / 2); //Get the slope of the eye line (it must be normal to the face line, so we turn it Pi/2 double aEyeFact = Math.Atan(a) + Math.PI / 2; aEyeFact = Math.Tan(aEyeFact); //Get the eye line offset double bEyeFact = eyeCenter.y - aEyeFact * eyeCenter.x; //Get the line function of the eye PointGenerator eyeLinePoint = new PointGenerator(aEyeFact, bEyeFact); //Get the horizontal difference between the center of the existing eye and the face line int diff = faceLinePoint.GetFromY(eyeCenter.y).x - eyeCenter.x; //Get the project eye coords Point projEyePoint = eyeLinePoint.GetFromX(eyeCenter.x + diff * 2); //Get the project eye rectangle Rect projEyeRect = new Rect( projEyePoint.x - eyeRef.width / 2, projEyePoint.y - eyeRef.height / 2, eyeRef.width, eyeRef.height); //projEyeRect.Offset(-eyeRef.Width / 2, -eyeRef.Height / 2); evaluatedEyes.add(projEyeRect); } //If the face keep invalid, put the face line on the middle of the face square if (!isValid) { faceLineSlope = -face.height / 0.01; faceLineOffset = face.y - faceLineSlope * face.x + face.width / 2; } }
void drawFaceFeatures(PersonFace personFace, Mat illustratedImg) { Rect faceRect = personFace.GetFace(); Rect mouthRect = personFace.GetMouth(); Rect noseRect = personFace.GetNose(); Rect[] eyesRects = personFace.GetEyes(); //Draw face division line double[] faceLineData = personFace.GetFaceLineData(); PointGenerator faceLine = new PointGenerator(faceLineData[0], faceLineData[1]); Point faceTopPoint = faceLine.GetFromY(faceRect.y); Point faceBottomPoint = faceLine.GetFromY(faceRect.y + faceRect.height); //Imgproc.line(illustratedImg, faceTopPoint, faceBottomPoint, new Scalar(255, 0, 0), 1); //Get face feature angle double faceFeatureAngle = Math.Atan(faceLineData[0]); faceFeatureAngle = RadianToDegree(faceFeatureAngle); faceFeatureAngle += faceFeatureAngle > 0 ? -90 : 90; //Draw face lateral boundaries lines //Detect right and left eye Rect rightEye, leftEye; if (eyesRects[0].x > eyesRects[1].x) { rightEye = eyesRects[1]; leftEye = eyesRects[0]; } else { rightEye = eyesRects[0]; leftEye = eyesRects[1]; } //get eye line generator PointGenerator eyeLines = new PointGenerator( getRectCenter(rightEye), getRectCenter(leftEye)); Point leftFacePoint = eyeLines.GetFromX(getRectCenter(leftEye).x + leftEye.width); Point rightFacePoint = eyeLines.GetFromX(getRectCenter(rightEye).x - rightEye.width); /* CvInvoke.Circle(image, leftFacePoint, 20, new Bgr(Color.Green).MCvScalar, -1); CvInvoke.Circle(image, rightFacePoint, 20, new Bgr(Color.Blue).MCvScalar, -1);*/ //Get line generators for each side of the face double faceLineSlope = faceLineData[0]; //Left side double leftFaceSideOffset = leftFacePoint.y - leftFacePoint.x * faceLineSlope; PointGenerator leftFaceLine = new PointGenerator(faceLineSlope, leftFaceSideOffset); Point startPointL = leftFaceLine.GetFromY(0); Point endPointL = leftFaceLine.GetFromY(illustratedImg.Height); //Right side double rightFaceSideOffset = rightFacePoint.y - rightFacePoint.x * faceLineSlope; PointGenerator rightFaceLine = new PointGenerator(faceLineSlope, rightFaceSideOffset); Point startPointR = rightFaceLine.GetFromY(0); Point endPointR = rightFaceLine.GetFromY(illustratedImg.Height); //Imgproc.line(illustratedImg, startPointL, endPointL, new Scalar(0,255,0), 5); //Imgproc.line(illustratedImg, startPointR, endPointR,new Scalar(255,0,0), 3); //Draw mouth line //Put center on the top for the mouth stay in the middle of the mouth square Point mouthCenter = new Point(mouthRect.x + mouthRect.width / 2, mouthRect.y); Size mouthSize = new Size(mouthRect.width / 2, mouthRect.height / 2); Point mCenter = getRectCenter(mouthRect); //Get mouth line generator double aFactMouth = Math.Tan(Math.Atan(faceLineSlope) + Math.PI / 2); double bfactMouth = mCenter.y - mCenter.x * aFactMouth; PointGenerator mouthLine = new PointGenerator(aFactMouth, bfactMouth); double leftFaceMouthCrossX = (bfactMouth - leftFaceSideOffset) / (faceLineSlope - aFactMouth); double rightFaceMouthCrossX = (bfactMouth - rightFaceSideOffset) / (faceLineSlope - aFactMouth); Point leftFaceMouthCross = mouthLine.GetFromX(leftFaceMouthCrossX); Point rightFaceMouthCross = mouthLine.GetFromX(rightFaceMouthCrossX); //Get face top line double afactTopFace = aFactMouth; //use the mouth line since this uses the same slope double bfactTopFace = faceTopPoint.y - faceTopPoint.x * afactTopFace; PointGenerator faceTopLine = new PointGenerator(afactTopFace, bfactTopFace); double leftTopFaceCrossX = (bfactTopFace - leftFaceSideOffset) / (faceLineSlope - afactTopFace); double rightTopFaceCrossX = (bfactTopFace - rightFaceSideOffset) / (faceLineSlope - afactTopFace); Point leftTopFaceCross = faceTopLine.GetFromX(leftTopFaceCrossX); Point rightTopFaceCross = faceTopLine.GetFromX(rightTopFaceCrossX); /*CvInvoke.Circle(illustratedImg, leftTopFaceCross, 5, new MCvScalar(), -1); CvInvoke.Circle(illustratedImg, rightTopFaceCross, 5, new MCvScalar(), -1); CvInvoke.Circle(illustratedImg, leftFaceMouthCross, 5, new MCvScalar(), -1); CvInvoke.Circle(illustratedImg, rightFaceMouthCross, 5, new MCvScalar(), -1); CvInvoke.Circle(illustratedImg, faceBottomPoint, 5, new MCvScalar(), -1);*/ MatOfPoint facePointsMat = new MatOfPoint(leftTopFaceCross, rightTopFaceCross, rightFaceMouthCross, faceBottomPoint, leftFaceMouthCross); //CvInvoke.Polylines(image, facePointsVector, true, new Bgr(172, 203, 227).MCvScalar, 1); Imgproc.fillConvexPoly(illustratedImg, facePointsMat, new Scalar(255,255,255)); Imgproc.ellipse(illustratedImg, mouthCenter, mouthSize, faceFeatureAngle, 0, 180, new Scalar(0,0,0), 2); Point p1 = faceTopLine.GetFromX(0); Point p2 = faceTopLine.GetFromX(illustratedImg.Width); //Imgproc.line(illustratedImg, p1, p2, new Scalar(0, 0, 0), 3); //Draw nose line Point noseCenter = new Point(noseRect.x + noseRect.width / 2, noseRect.y + noseRect.height / 2); Size noseSize = new Size(noseRect.width / 2, noseRect.height / 2); double noseAngle = Math.Atan(faceLineData[0]); noseAngle = RadianToDegree(noseAngle); Imgproc.ellipse(illustratedImg, noseCenter, noseSize, noseAngle, 0, 180, new Scalar(0, 0, 0), 2); //Draw eyes ellipses foreach (Rect eye in personFace.GetEyes()) { Point eyeCenter = new Point(eye.x + eye.width / 2, eye.y + eye.height / 2); Size ellipseSize = new Size(eye.width / 5, eye.height / 2); Imgproc.ellipse(illustratedImg, eyeCenter, ellipseSize, faceFeatureAngle, 0, 360, new Scalar(0, 0, 0), -1); } Imgproc.line(illustratedImg, faceBottomPoint, new Point(illustratedImg.width() / 2, illustratedImg.height()), new Scalar(0, 0, 0)); }