Problems with getting values for face angle age estimation

I’m having problems with values for R1, C1, C2 in step 6:

I don’t want to use dlib or yunet for my solution!

  1. Find the row number R1 with minimum row sum of gray level in UPART. Find the column numbers C1 and C2 with minimum column sum of gray
    level in REYE and LEYE. So, (R1, C1) coordinate represents middle
    point of right eyeball and (R1, C2) coordinate represents middle point
    of left eyeball.

I get the following output:

C:\Users\cam30\OneDrive\Desktop>python ainv.py
x:  359  y:  242
w:  366  h:  366
Row sums in UPART: [13515 15300 16575 16065 17085 18615 19380 19380 19635 20655 21165 21165
 20145 20400 20655 21165 21165 20655 21675 22440 21675 24735 26775 28305
 28305 28815 29835 31365 32895 33660 35445 36975 37230 36975 37230 37995
 38760 39015 39015 39780 39270 39015 39780 40800 41055 42075 43605 43350
 42330 41820 42585 43095 43605 43350 43095 43095 43095 43095 42840 42585
 42585 42330 43095 43605 44115 44625 44880 44625 44880 45390 45390 45390
 45390 45645 45900 46410 47175 47175 47940 47940 47685 47940 48450 48450
 48450 48705 49215 46920 45390 46665 45135 43350 41820 35955 31110 27285
 24480 21420 22950 22950 24480 27540 29835 33915 35955 39525 43095 48450
 48705 49725 50490 49470 44880 45900 43860 39525 38505 35445 31620 29325
 22950 21675 24735 25755 26520 26775 25755 28050 29325 29580 29835 30090
 28050 30855 36210 35955 35700 35190 40035 44625 47685 47940 48705 47685
 47940 49470 54315 55335 55845 56100 58905 59925 59670 58395 59160 59160
 59415 59415 59160 59670 59160 59415 59160 59160 58905 59670 58395 59160
 59670 60945 61200 62985 62985 63750 64005 64005 64515 65790 65790 65280
 66045 66555 65535]
Selected R1: 0
Column sums in REYE: [ 9690  6630  5865  4335  5100  5610  6630  8160  8670  9180  9180  8925
  8670  8415  8415  8415  8415  8160  7905  7140  5355  3825  4590  4590
  4845  4845  4845  3570  3060  1785     0   255     0     0     0     0
     0     0     0     0     0     0     0     0     0     0     0     0
     0  1530  5865  6885  8925 11730 13770 14535 14025 14790 15045 15300
 15300 15555 15555 15555 15555 15555 15555 15555 15810 16065 16065 16320
 16320 16320 17340 16575 17340 17595 17085 17340 18360 18360 20910 23205
 23715 25755 27285 31875 33915 33915 35445 35955 37485 37740 37740 38760
 39015 39780 41055 41310 41565 41565 41565 41055 40290 39525 39525 40035
 40290 39780 40035 39780 39015 38760 39270 40290 40290 39780 39270 38760
 39015 39525 40035 40035 39780 40290 40035 41565 41820 42075 42330 42075
 42585 41565 41565 42585 41820 42330 42330 42585 42840 42840 42075 42840
 44370 44880 45390 45390 45135 43860 42840 42840 43605 44625 44115 42840
 42330 41820 42585 42840 42840 42840 44115 45135 44625 44115 44625 44370
 45135 44625 39780 41055 40800 41055 40290 41055 41055 40035 39270 39525
 39525 39780 39015]
Selected C1: 30
An error occurred: Error: Unable to determine the column for the right eye.

Based on: Age Group Estimation Using Face Angle. IOSR Journal of Computer Engineering (IOSRJCE) ISSN: 2278-0661, ISBN: 2278-8727 Volume 7, Issue 5 (Nov-Dec. 2012), PP 35-39 www.iosrjournals.org

  1. Detect the rectangular face area from input face image using Matlab in-built object function. If number of detected face is more than one,
    an error message will be displayed.
  2. Crop the detected rectangular face area and detect the significance eye-region using Matlab in-built object function.
  3. The cropped rectangular face image is histogram equalized and then converted into binary image.
  4. The binary image of face is divided horizontally into two parts. Upper part that contains two eyes is denoted by UPART and lower part
    that contains mouth is denoted by LPART.
  5. Divide UPART vertically into two parts. One part that contains right eye is denoted by REYE and other part that contains left eye is
    denoted by LEYE.
  6. Find the row number R1 with minimum row sum of gray level in UPART. Find the column numbers C1 and C2 with minimum column sum of gray
    level in REYE and LEYE. So, (R1, C1) coordinate represents middle
    point of right eyeball and (R1, C2) coordinate represents middle point
    of left eyeball.
  7. Find the row number R2 with minimum row sum of gray level in LPART. So, R2 row represents the mouth row.
  8. Calculate the midpoint C3 of two eye ball. So, C3= (C1+ C2) /2 and the coordinate (R2, C3) is middle point of mouth.
  9. Draw a triangle by three coordinate points left eyeball (R1, C1), right eyeball (R1, C2) & mouth point (R2, C3).
  10. Calculate slope (m1) of triangle sides from mouth point (R2, C3) to right eyeball (R1, C1) and slope (m2) of triangle sides from mouth
    point (R2, C3) to left eyeball (R1, C2).
  11. Find the face angle (A) using formula: A = tan-1( (m1- m2) / (1 + m1 * m2) )
  12. Determine age group based on the face angle (A).
import cv2
import numpy as np
import matplotlib.pyplot as plt

def detect_face_angle(image_path):
    try:
        # Step 1: Load image and detect face
        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')

        # Read the input image
        image = cv2.imread(image_path)
        if image is None:
            raise ValueError("Error: Unable to read the input image. Check the file path.")
        
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(100, 100))

        if len(faces) > 1:
            raise ValueError("Error: More than one face detected.")
        elif len(faces) == 0:
            raise ValueError("Error: No face detected.")

        # Extract the face region
        x, y, w, h = faces[0]
        print("x: ", x, " y: ", y)
        print("w: ", w, " h: ", h)
        face_image = gray[y:y+h, x:x+w]
        
        #plt.subplot(2, 3, 1)
        #plt.imshow(face_image, cmap='gray')
        #plt.title("Face")

        # Step 2: Detect eye region
        eyes = eye_cascade.detectMultiScale(face_image)
        if len(eyes) < 2:
            raise ValueError("Error: Less than two eyes detected in the face region.")

        # Step 3: Histogram equalization and binary conversion
        face_hist_eq = cv2.equalizeHist(face_image)
        #plt.subplot(2, 3, 4)
        #plt.imshow(face_hist_eq, cmap='gray')
        #plt.title("Equalization (face_hist_eq)")
        
        _, binary_image = cv2.threshold(face_hist_eq, 128, 255, cv2.THRESH_BINARY)
        
        # Step 4: Divide face into UPART and LPART
        h, w = binary_image.shape
        UPART = binary_image[:h//2, :]
        LPART = binary_image[h//2:, :]
        
        #plt.subplot(2, 3, 2)
        #plt.imshow(UPART, cmap='gray')
        #plt.title("Upper Face (UPART)")
        
        #plt.subplot(2, 3, 3)
        #plt.imshow(LPART, cmap='gray')
        #plt.title("Lower Face (LPART)")

        # Step 5: Divide UPART into REYE and LEYE
        REYE = UPART[:, :w//2]
        LEYE = UPART[:, w//2:]
        
        #plt.subplot(2, 3, 5)
        #plt.imshow(REYE, cmap='gray')
        #plt.title("Right Eye (REYE)")
        
        #plt.subplot(2, 3, 6)
        #plt.imshow(LEYE, cmap='gray')
        #plt.title("Left Eye (LEYE)")
        
        plt.show()

        # Step 6: Find R1, C1, and C2
        R1 = np.argmin(np.sum(UPART, axis=1)) # Row with minimum row sum in UPART
        print("Row sums in UPART:", np.sum(UPART, axis=1))
        print("Selected R1:", R1)
        if np.sum(UPART[R1, :]) == 0:
            raise ValueError("Error: Unable to determine the upper row for eyes.")

        #column_sums = np.sum(REYE, axis=0)
    
        # Check if there are any non-numeric values
        #assert not (np.isnan(column_sums)).any(), "There are NaNs in the data"
    
        #C1 = np.argmin(np.abs(column_sums))  # Find index of column with minimum sum


        C1 = np.argmin(np.sum(REYE, axis=0)) # Column with minimum column sum in REYE
        print("Column sums in REYE:", np.sum(REYE, axis=0))
        print("Selected C1:", C1)
        if np.sum(REYE[:, C1]) == 0:
            raise ValueError("Error: Unable to determine the column for the right eye.")

        C2 = np.argmin(np.sum(LEYE, axis=0)) + w//2  # Adjust C2 relative to full image
        print("Column sums in LEYE:", np.sum(LEYE, axis=0))
        print("Selected C2:", C2)
        if np.sum(LEYE[:, C2 - w//2]) == 0:
            raise ValueError("Error: Unable to determine the column for the left eye.")

        # Step 7: Find R2
        R2 = np.argmin(np.sum(LPART, axis=1)) + h//2  # Adjust R2 relative to full image4
        print("Selected R2:", R2)
        if np.sum(LPART[R2 - h//2, :]) == 0:
            raise ValueError("Error: Unable to determine the row for the mouth.")

        # Step 8: Calculate midpoint C3
        C3 = (C1 + C2) // 2

        # Step 9: Draw triangle
        
        ################## image
        plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
        
        #plt.scatter([x], [y], color='blue', s=50)
        #plt.scatter([w], [h], color='yellow', s=50)
        
        plt.scatter([x + C1, x + C2, x + C3], [y + R1, y + R1, y + R2], color='red', s=50)        
        plt.plot([x + C1, x + C2, x + C3, x + C1], [y + R1, y + R1, y + R2, y + R1], 'g-')
        plt.show()
        
        # Step 10: Calculate slopes
        m1 = (R2 - R1) / (C3 - C1) if C3 != C1 else np.inf
        m2 = (R2 - R1) / (C3 - C2) if C3 != C2 else np.inf

        # Step 11: Calculate face angle
        A = np.degrees(np.arctan(abs((m1 - m2) / (1 + m1 * m2))))
        print("A: ", A)

        # Step 12: Determine age group based on the face angle (A)
        # Display result
        if A < 44: print("< 18")
        elif 44 <= A <= 48: print("18 to 25")
        elif 49 <= A <= 54: print("26 to 35")
        elif 55 <= A <= 60: print("36 to 45")
        elif A > 60: print("> 45")
        else: print("None") # Handle invalid input if needed)
            
    except Exception as e:
        print(f"An error occurred: {e}")

# Usage
detect_face_angle("test.jpg")

also posted on

why not ?
yunet would give you the landmarks for free, and you would not need those dreadful intensity calculations, based on arcane cascades ..
we also have much better age group dnn models than this.
p.s: imo the paper is already bs.

From careful observation we conclude that this face triangle is
unique for every person and this face triangle can be used for face recognition with age.

it’s either unique per person OR per age, you cant have both.
(and i doubt, that it can do either properly)

I have a yunet/onnx version and an new dlib one as well based on the stack exchange post, but I want to compare all 3 in terms of execution time, so would have to tidy up the code a little bit once I get the paper to work in python. The results should be the same in terms of classification if I can get this “arcane” version to work. I’m not going to test it in matlab/octave like the OG paper as i’d probably have to buy yet another matlab toolkit (DLC).

I’m not sure what the problem is with the code, but it might be persons hairline and threshold and I’m still trying to debug the code.

I’ve used other classifiers in the past, but I’m not ready to call BS on the original paper until I have compared it against a couple of age classifiers on hugging face.