├── .gitignore ├── Eye Tracking.py ├── README.md ├── pyvenv.cfg ├── requirements.txt └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # created by virtualenv automatically 2 | * 3 | -------------------------------------------------------------------------------- /Eye Tracking.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import mediapipe as mp 3 | import numpy as np 4 | import utils 5 | 6 | LEFT_EYE = [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398] 7 | RIGHT_EYE = [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246] 8 | 9 | mp_face_mesh = mp.solutions.face_mesh 10 | 11 | cap = cv2.VideoCapture("eye.mp4") 12 | 13 | def landmark_detect(img, res, draw=False): 14 | height = img.shape[0] 15 | width = img.shape[1] 16 | mesh_coor = [(int(point.x * width), int(point.y * height)) for point in res.multi_face_landmarks[0].landmark] 17 | if draw: 18 | [cv2.circle(img, p, 2, (0, 255, 0), -1) for p in mesh_coor] 19 | return mesh_coor 20 | 21 | def ecul_dis(point, point1): 22 | x, y = point 23 | x1, y1 = point1 24 | dis = np.sqrt((x1 - x) ** 2 + (y1 - y) ** 2) 25 | return dis 26 | 27 | def eyes_extractor(img, right_eye_corr, left_eye_corr): 28 | cv2.polylines(img, [np.array(right_eye_corr, dtype=np.int32)], True, (0, 255, 0), 1) 29 | cv2.polylines(img, [np.array(left_eye_corr, dtype=np.int32)], True, (0, 255, 0), 1) 30 | 31 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 32 | dim = gray.shape 33 | mask = np.zeros(dim, dtype=np.uint8) 34 | 35 | cv2.fillPoly(mask, [np.array(right_eye_corr, dtype=np.int32)], 255) 36 | cv2.fillPoly(mask, [np.array(left_eye_corr, dtype=np.int32)], 255) 37 | 38 | eyes = cv2.bitwise_and(gray, gray, mask=mask) 39 | cv2.imshow("Eye Draw", eyes) 40 | eyes[mask == 0] = 155 41 | 42 | r_min_x = min(right_eye_corr, key=lambda item: item[0])[0] 43 | r_max_x = max(right_eye_corr, key=lambda item: item[0])[0] 44 | r_min_y = min(right_eye_corr, key=lambda item: item[1])[1] 45 | r_max_y = max(right_eye_corr, key=lambda item: item[1])[1] 46 | 47 | l_min_x = min(left_eye_corr, key=lambda item: item[0])[0] 48 | l_max_x = max(left_eye_corr, key=lambda item: item[0])[0] 49 | l_min_y = min(left_eye_corr, key=lambda item: item[1])[1] 50 | l_max_y = max(left_eye_corr, key=lambda item: item[1])[1] 51 | 52 | cropped_right = eyes[r_min_y:r_max_y, r_min_x:r_max_x] 53 | cropped_left = eyes[l_min_y:l_max_y, l_min_x:l_max_x] 54 | 55 | return cropped_right, cropped_left 56 | 57 | def pos_estimation(cropped_eye): 58 | h, w = cropped_eye.shape 59 | 60 | gaussian_blur = cv2.GaussianBlur(cropped_eye, (9, 9), 0) 61 | median_blur = cv2.medianBlur(gaussian_blur, 3) 62 | 63 | thres_eye = cv2.adaptiveThreshold(median_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 64 | cv2.THRESH_BINARY, 33, 0) 65 | 66 | piece = int(w / 3) 67 | 68 | right_piece = thres_eye[0:h, 0:piece] 69 | center_piece = thres_eye[0:h, piece:piece + piece] 70 | left_piece = thres_eye[0:h, piece + piece:w] 71 | 72 | eye_pos, color = pixel_counter(right_piece, center_piece, left_piece) 73 | 74 | if np.sum(thres_eye == 0) > (0.8 * h * w): 75 | eye_pos = "CLOSED" 76 | color = [utils.RED, utils.BLACK] 77 | 78 | return eye_pos, color 79 | 80 | def pixel_counter(first_piece, second_piece, third_piece): 81 | right_part = np.sum(first_piece == 0) 82 | center_part = np.sum(second_piece == 0) 83 | left_part = np.sum(third_piece == 0) 84 | 85 | eye_parts = [right_part, center_part, left_part] 86 | 87 | max_ind = eye_parts.index(max(eye_parts)) 88 | 89 | if max_ind == 0 and (eye_parts[0] > eye_parts[1] + 10): 90 | pos_eye = "RIGHT" 91 | color = [utils.BLACK, utils.RED] 92 | elif max_ind == 2 and (eye_parts[2] > eye_parts[1] + 10): 93 | pos_eye = "LEFT" 94 | color = [utils.BLACK, utils.RED] 95 | else: 96 | pos_eye = "CENTER" 97 | color = [utils.BLACK, utils.GREEN] 98 | 99 | return pos_eye, color 100 | 101 | with mp_face_mesh.FaceMesh(min_detection_confidence=0.5, min_tracking_confidence=0.5) as face_mesh: 102 | while True: 103 | ret, frame = cap.read() 104 | frame = cv2.resize(frame, None, fx=.50, fy=.50, interpolation=cv2.INTER_CUBIC) 105 | 106 | r_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 107 | 108 | res = face_mesh.process(r_frame) 109 | 110 | if res.multi_face_landmarks: 111 | mesh_coor = landmark_detect(frame, res, False) 112 | 113 | right_corr = [mesh_coor[p] for p in RIGHT_EYE] 114 | left_corr = [mesh_coor[p] for p in LEFT_EYE] 115 | 116 | crop_right, crop_left = eyes_extractor(frame, right_corr, left_corr) 117 | 118 | cv2.imshow('Right Eye', crop_right) 119 | cv2.imshow('Left Eye', crop_left) 120 | 121 | eye_pos, color = pos_estimation(crop_right) 122 | cv2.putText(frame, "Real-Time Eye Tracking and Eye Position Estimation ", (57, 50), 123 | cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA) 124 | if eye_pos == "CENTER": 125 | utils.colorBackgroundText(frame, "CENTER", cv2.FONT_HERSHEY_COMPLEX, 1.3, (385, 100), 2, color[0], 126 | color[1], 10, 10) 127 | elif eye_pos == "LEFT": 128 | utils.colorBackgroundText(frame, "LEFT", cv2.FONT_HERSHEY_COMPLEX, 1.3, (200, 100), 2, color[0], 129 | color[1], 10, 10) 130 | elif eye_pos == "RIGHT": 131 | utils.colorBackgroundText(frame, "RIGHT", cv2.FONT_HERSHEY_COMPLEX, 1.3, (600, 100), 2, color[0], 132 | color[1], 10, 10) 133 | 134 | cv2.imshow("Image", frame) 135 | if cv2.waitKey(1) == ord('q'): 136 | break 137 | 138 | cap.release() 139 | cv2.destroyAllWindows() 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Real-Time Eye Tracking and Position Estimation 2 | 3 | ## Overview 4 | This project implements a real-time eye-tracking system using OpenCV and MediaPipe. The code tracks the user's eye position (left, right, or center) and displays it on the screen. The system works by detecting facial landmarks, extracting eye regions, and estimating the eye's position based on pixel analysis. 5 | 6 | ## Files 7 | Eye_Tracking.py: Main script that captures video input, processes each frame to detect eyes, and estimates eye position. 8 | utils.py: Utility functions for drawing colored backgrounds and text on images. 9 | 10 | ## Features 11 | 12 | - **Real-Time Tracking**: Detects eye landmarks using MediaPipe Face Mesh. 13 | - **Eye Region Extraction**: Extracts and displays regions of interest from the eyes. 14 | - **Position Estimation**: Classifies eye position (CENTER, LEFT, RIGHT) based on pupil location. 15 | - **Live Visualization**: Displays eye tracking and position estimation on the video feed. 16 | 17 | ## Dependencies 18 | 19 | Ensure you have Python 3.x installed. The following packages are required: 20 | 21 | - OpenCV 22 | - MediaPipe 23 | - NumPy 24 | 25 | ## Installation 26 | 27 | 1. **Clone the Repository:** 28 | 29 | ```bash 30 | git clone https://github.com/EsraaMeslam/-Real-Time-Eye-Tracking-and-Position-Estimation-Using-OpenCV-and-MediaPipe-.git 31 | cd Real-Time-Eye-Tracking-and-Position-Estimation-Using-OpenCV-and-MediaPipe 32 | ``` 33 | 34 | 35 | 2. **Install Required Packages:** 36 | 37 | ```bash 38 | pip install -r requirements.txt 39 | ``` 40 | 41 | ## Usage 42 | 43 | 1. **Prepare Your Video File:** 44 | 45 | Place your video file in the project directory and name it `eye.mp4`. Alternatively, update the `cv2.VideoCapture("eye.mp4")` line in `Eye_Tracking.py` with the path to your video file. 46 | 47 | 2. **Run the Script:** 48 | 49 | ```bash 50 | python Eye_Tracking.py 51 | ``` 52 | 53 | 3. **View Results:** 54 | 55 | The application will open a window showing the real-time video feed with eye tracking and position estimation. Press `q` to quit the application. 56 | 57 | ## File Descriptions 58 | 59 | - **`Eye_Tracking.py`**: Main script for eye tracking. Captures video, processes frames to detect facial landmarks, extracts eye regions, estimates eye position, and displays results. 60 | - **`utils.py`**: Utility functions for text rendering and color management used in the eye tracking script. 61 | - **`requirements.txt`**: Lists the required Python packages for the project. 62 | 63 | ## Example Output 64 | 65 | - **Real-Time Eye Tracking Window**: Shows the original video with outlined eye regions and estimated eye position. 66 | - **Eye Region Windows**: Displays cropped regions of the left and right eyes. 67 | 68 | ## Notes 69 | 70 | - Ensure that the video file is in a compatible format and accessible from the script. 71 | - Adjust script parameters as needed for better accuracy or different video resolutions. 72 | 73 | 74 | ## Acknowledgments 75 | 76 | - **MediaPipe**: For providing a powerful library for facial landmark detection. 77 | - **OpenCV**: For its robust image processing capabilities. 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = C:\Users\user\AppData\Local\Programs\Python\Python311 2 | implementation = CPython 3 | version_info = 3.11.2.final.0 4 | virtualenv = 20.16.7 5 | include-system-site-packages = false 6 | base-prefix = C:\Users\user\AppData\Local\Programs\Python\Python311 7 | base-exec-prefix = C:\Users\user\AppData\Local\Programs\Python\Python311 8 | base-executable = C:\Users\user\AppData\Local\Programs\Python\Python311\python.exe 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.24.2 2 | opencv-python==4.8.0.76 3 | mediapipe==0.10.0 4 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | 2 | import cv2 as cv 3 | import numpy as np 4 | 5 | 6 | BLACK = (0, 0, 0) 7 | WHITE = (255, 255, 255) 8 | BLUE = (255, 0, 0) 9 | RED = (0, 0, 255) 10 | CYAN = (255, 255, 0) 11 | YELLOW = (0, 255, 255) 12 | MAGENTA = (255, 0, 255) 13 | GRAY = (128, 128, 128) 14 | GREEN = (0, 255, 0) 15 | PURPLE = (128, 0, 128) 16 | ORANGE = (0, 165, 255) 17 | PINK = (147, 20, 255) 18 | points_list = [(200, 300), (150, 150), (400, 200)] 19 | 20 | 21 | def drawColor(img, colors): 22 | x, y = 0, 10 23 | w, h = 20, 30 24 | 25 | for color in colors: 26 | x += w + 5 27 | # y += 10 28 | cv.rectangle(img, (x - 6, y - 5), (x + w + 5, y + h + 5), (10, 50, 10), -1) 29 | cv.rectangle(img, (x, y), (x + w, y + h), color, -1) 30 | 31 | 32 | def colorBackgroundText(img, text, font, fontScale, textPos, textThickness=1, textColor=(0, 255, 0), bgColor=(0, 0, 0), 33 | pad_x=3, pad_y=3): 34 | """ 35 | Draws text with background, with control transparency 36 | @param img:(mat) which you want to draw text 37 | @param text: (string) text you want draw 38 | @param font: fonts face, like FONT_HERSHEY_COMPLEX, FONT_HERSHEY_PLAIN etc. 39 | @param fontScale: (double) the size of text, how big it should be. 40 | @param textPos: tuple(x,y) position where you want to draw text 41 | @param textThickness:(int) fonts weight, how bold it should be 42 | @param textPos: tuple(x,y) position where you want to draw text 43 | @param textThickness:(int) fonts weight, how bold it should be. 44 | @param textColor: tuple(BGR), values -->0 to 255 each 45 | @param bgColor: tuple(BGR), values -->0 to 255 each 46 | @param pad_x: int(pixels) padding of in x direction 47 | @param pad_y: int(pixels) 1 to 1.0 (), controls transparency of text background 48 | @return: img(mat) with draw with background 49 | """ 50 | (t_w, t_h), _ = cv.getTextSize(text, font, fontScale, textThickness) # getting the text size 51 | x, y = textPos 52 | cv.rectangle(img, (x - pad_x, y + pad_y), (x + t_w + pad_x, y - t_h - pad_y), bgColor, -1) # draw rectangle 53 | cv.putText(img, text, textPos, font, fontScale, textColor, textThickness) # draw in text 54 | 55 | return img 56 | 57 | 58 | def textWithBackground(img, text, font, fontScale, textPos, textThickness=1, textColor=(0, 255, 0), bgColor=(0, 0, 0), 59 | pad_x=3, pad_y=3, bgOpacity=0.5): 60 | """ 61 | Draws text with background, with control transparency 62 | @param img:(mat) which you want to draw text 63 | @param text: (string) text you want draw 64 | @param font: fonts face, like FONT_HERSHEY_COMPLEX, FONT_HERSHEY_PLAIN etc. 65 | @param fontScale: (double) the size of text, how big it should be. 66 | @param textPos: tuple(x,y) position where you want to draw text 67 | @param textThickness:(int) fonts weight, how bold it should be 68 | @param textPos: tuple(x,y) position where you want to draw text 69 | @param textThickness:(int) fonts weight, how bold it should be. 70 | @param textColor: tuple(BGR), values -->0 to 255 each 71 | @param bgColor: tuple(BGR), values -->0 to 255 each 72 | @param pad_x: int(pixels) padding of in x direction 73 | @param pad_y: int(pixels) 1 to 1.0 (), controls transparency of text background 74 | @return: img(mat) with draw with background 75 | """ 76 | (t_w, t_h), _ = cv.getTextSize(text, font, fontScale, textThickness) # getting the text size 77 | x, y = textPos 78 | overlay = img.copy() # coping the image 79 | cv.rectangle(overlay, (x - pad_x, y + pad_y), (x + t_w + pad_x, y - t_h - pad_y), bgColor, -1) # draw rectangle 80 | new_img = cv.addWeighted(overlay, bgOpacity, img, 1 - bgOpacity, 0) # overlaying the rectangle on the image. 81 | cv.putText(new_img, text, textPos, font, fontScale, textColor, textThickness) # draw in text 82 | img = new_img 83 | 84 | return img 85 | 86 | 87 | def textBlurBackground(img, text, font, fontScale, textPos, textThickness=1, textColor=(0, 255, 0), kneral=(33, 33), 88 | pad_x=3, pad_y=3): 89 | """ 90 | Draw text with background blured, control the blur value, with kernal(odd, odd) 91 | @param img:(mat) which you want to draw text 92 | @param text: (string) text you want draw 93 | @param font: fonts face, like FONT_HERSHEY_COMPLEX, FONT_HERSHEY_PLAIN etc. 94 | @param fontScale: (double) the size of text, how big it should be. 95 | @param textPos: tuple(x,y) position where you want to draw text 96 | @param textThickness:(int) fonts weight, how bold it should be. 97 | @param textColor: tuple(BGR), values -->0 to 255 each 98 | @param kneral: tuple(3,3) int as odd number: higher the value, more blurry background would be 99 | @param pad_x: int(pixels) padding of in x direction 100 | @param pad_y: int(pixels) padding of in y direction 101 | @return: img mat, with text drawn, with background blured 102 | 103 | call the function: 104 | img =textBlurBackground(img, 'Blured Background Text', cv2.FONT_HERSHEY_COMPLEX, 0.9, (20, 60),2, (0,255, 0), (49,49), 13, 13 ) 105 | """ 106 | 107 | (t_w, t_h), _ = cv.getTextSize(text, font, fontScale, textThickness) # getting the text size 108 | x, y = textPos 109 | blur_roi = img[y - pad_y - t_h: y + pad_y, x - pad_x:x + t_w + pad_x] # croping Text Background 110 | img[y - pad_y - t_h: y + pad_y, x - pad_x:x + t_w + pad_x] = cv.blur(blur_roi, 111 | kneral) # merging the blured background to img 112 | cv.putText(img, text, textPos, font, fontScale, textColor, textThickness) 113 | # cv.imshow('blur roi', blur_roi) 114 | # cv.imshow('blured', img) 115 | 116 | return img 117 | 118 | 119 | def fillPolyTrans(img, points, color, opacity): 120 | """ 121 | @param img: (mat) input image, where shape is drawn. 122 | @param points: list [tuples(int, int) these are the points custom shape,FillPoly 123 | @param color: (tuples (int, int, int) 124 | @param opacity: it is transparency of image. 125 | @return: img(mat) image with rectangle draw. 126 | 127 | """ 128 | list_to_np_array = np.array(points, dtype=np.int32) 129 | overlay = img.copy() # coping the image 130 | cv.fillPoly(overlay, [list_to_np_array], color) 131 | new_img = cv.addWeighted(overlay, opacity, img, 1 - opacity, 0) 132 | # print(points_list) 133 | img = new_img 134 | cv.polylines(img, [list_to_np_array], True, color, 1, cv.LINE_AA) 135 | return img 136 | 137 | 138 | # def pollyLines(img, points, color): 139 | # list_to_np_array = np.array(points, dtype=np.int32) 140 | # cv.polylines(img, [list_to_np_array], True, color,1, cv.LINE_AA) 141 | # return img 142 | 143 | def rectTrans(img, pt1, pt2, color, thickness, opacity): 144 | """ 145 | 146 | @param img: (mat) input image, where shape is drawn. 147 | @param pt1: tuple(int,int) it specifies the starting point(x,y) os rectangle 148 | @param pt2: tuple(int,int) it nothing but width and height of rectangle 149 | @param color: (tuples (int, int, int), it tuples of BGR values 150 | @param thickness: it thickness of board line rectangle, if (-1) passed then rectangle will be fulled with color. 151 | @param opacity: it is transparency of image. 152 | @return: 153 | """ 154 | overlay = img.copy() 155 | cv.rectangle(overlay, pt1, pt2, color, thickness) 156 | new_img = cv.addWeighted(overlay, opacity, img, 1 - opacity, 0) # overlaying the rectangle on the image. 157 | img = new_img 158 | 159 | return img 160 | 161 | 162 | def main(): 163 | cap = cv.VideoCapture('Girl.mp4') 164 | counter = 0 165 | while True: 166 | success, img = cap.read() 167 | # img = np.zeros((1000,1000, 3), dtype=np.uint8) 168 | img = rectTrans(img, pt1=(30, 320), pt2=(160, 260), color=(0, 255, 255), thickness=-1, opacity=0.6) 169 | img = fillPolyTrans(img=img, points=points_list, color=(0, 255, 0), opacity=.5) 170 | drawColor(img, [BLACK, WHITE, BLUE, RED, CYAN, YELLOW, MAGENTA, GRAY, GREEN, PURPLE, ORANGE, PINK]) 171 | textBlurBackground(img, 'Blured Background Text', cv.FONT_HERSHEY_COMPLEX, 0.8, (60, 140), 2, YELLOW, (71, 71), 172 | 13, 13) 173 | img = textWithBackground(img, 'Colored Background Texts', cv.FONT_HERSHEY_SIMPLEX, 0.8, (60, 80), 174 | textThickness=2, bgColor=GREEN, textColor=BLACK, bgOpacity=0.7, pad_x=6, pad_y=6) 175 | imgGray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) 176 | # cv.imwrite('color_image.png', img) 177 | counter += 1 178 | cv.imshow('img', img) 179 | cv.imwrite(f'image/image_{counter}.png', img) 180 | if cv.waitKey(1) == ord('q'): 181 | break 182 | 183 | 184 | if __name__ == "__main__": 185 | main() --------------------------------------------------------------------------------