Logo
Section 1 / 19

识别眼睛颜色

让我们学习如何识别面部图像中眼睛的颜色。

导入

识别眼睛颜色

让我们学习如何识别面部图像中眼睛的颜色。

简介

人们常说,眼睛是心灵的窗户,是灵魂的入口,我们的眼睛反映了内在的自我与情绪。正如每个人的指纹都是独一无二的,每个人的眼睛颜色也各不相同。

但究竟是什么决定了眼睛的颜色?虹膜中的黑色素(melanin)含量决定了眼睛的颜色。色素越多,虹膜颜色就越深;蓝色、灰色和绿色的眼睛较浅,是因为虹膜内的黑色素较少。

目标

本课程旨在通过基于 Python 的模块开发一个轻量级工具,能够自动检测数字图像中的人脸,并识别眼睛的颜色。

...
眼睛颜色识别器

依赖库

我们将使用以下 Python 外部库:

库版本
MediaPipe0.8.9
opencv-python4.4.0.46
scikit-learn1.0.1
NumPy1.19.4
webcolors1.11.1
filetype1.0.7

编写函数代码!##

让我们来看一看这个工具的核心函数。

extract_dominant_colors 函数

该函数用于提取指定图像中的主要颜色。它利用机器学习中的无监督聚类算法(K 均值聚类)对图像像素数据按其 RGB 值进行聚类。该函数执行以下步骤:

  • 第 14 行:保留图像副本以供后续处理。
  • 第 17 行:使用 OpenCV 库将图像从 BGR 格式转换为 RGB 格式。
  • 第 20 行:调用 reshape 函数,根据图像形状创建一个二维数组。
  • 第 23 行:实例化无监督机器学习算法 k-means。K-means 算法会在未标记的多维数据集中寻找预设数量的聚类簇。在颜色聚类中,每个像素是一个包含红、绿、蓝三通道值的三维坐标点。参数 number_of_colors 表示我们要提取的颜色总数。
  • 第 26 行:调用 fit 方法生成聚类结果。
  • 第 29 行:提取指定图像中最主要的颜色列表。

以下是 extract_dominant_colors 函数的完整代码:

1def extract_dominant_colors(image, number_of_colors=5, hasThresholding=False): 2 """ 3 输入参数: 4 image -> 输入图像(BGR 格式,8位3通道) 5 number_of_colors -> 要提取的颜色数量 6 hasThresholding -> 是否使用了阈值掩码 7 使用无监督聚类算法(K-means 聚类)根据像素的 RGB 值对图像像素进行聚类。 8 """ 9 # 快速修复:增加聚类数以忽略黑色(参见文章说明) 10 if hasThresholding == True: 11 number_of_colors += 1 12 13 # 复制图像 14 img = image.copy() 15 16 # 将图像转换为 RGB 颜色空间 17 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 18 19 # 重塑图像为二维数组 20 img = img.reshape((img.shape[0] * img.shape[1]), 3) 21 22 # 初始化 KMeans 对象 23 estimator = KMeans(n_clusters=number_of_colors, random_state=0) 24 25 # 拟合图像数据 26 estimator.fit(img) 27 28 # 获取颜色信息 29 colorInformation = get_color_information(estimator.labels_, estimator.cluster_centers_, hasThresholding) 30 return colorInformation

get_top_dominant_color 函数

该函数用于从返回的主要颜色列表中提取最占主导地位的颜色。根据提取出的颜色值,查找与其最接近的颜色名称。该函数执行以下操作:

  • 第 22–26 行:获取颜色的 BGR 格式值。
  • 第 28–33 行:查找与该颜色值最接近的颜色名称。
  • 第 35 行:获取该颜色出现的百分比。

以下是 get_top_dominant_color 函数的完整代码:

1def get_top_dominant_color(dominant_colors): 2 """ 3 从主要颜色列表中返回占比最高的颜色 4 """ 5 def find_closest_color(req_color): 6 # 将 RGB 像素值转换为颜色名称 7 min_colors = {} 8 for name, key in webcolors.CSS3_HEX_TO_NAMES.items(): 9 r_c, g_c, b_c = webcolors.hex_to_rgb(name) 10 rd = (r_c - req_color[0]) ** 2 11 gd = (g_c - req_color[1]) ** 2 12 bd = (b_c - req_color[2]) ** 2 13 min_colors[(rd + gd + bd)] = key 14 closest_name = min_colors[min(min_colors.keys())] 15 return closest_name 16 17 # 提取颜色值(BGR格式) 18 color_value = ( 19 int(dominant_colors[0].get('color')[2]) 20 , int(dominant_colors[0].get('color')[1]) 21 , int(dominant_colors[0].get('color')[0]) 22 ) 23 24 # 查找最接近的颜色名称(输入为RGB格式) 25 closest_color_name = find_closest_color( 26 ( 27 int(dominant_colors[0].get('color')[0]) 28 ,int(dominant_colors[0].get('color')[1]) 29 ,int(dominant_colors[0].get('color')[2]) 30 ) 31 ) 32 # 计算颜色占比(百分比) 33 color_score = round(dominant_colors[0].get('color_percentage') * 100, 2) 34 return color_value, closest_color_name, color_score

find_eye_center 函数

该函数用于提取眼睛中心点的坐标。具体步骤如下:

  • 第 5 行:将眼部区域的预定义关键点坐标转换为 Numpy 数组。
  • 第 7 行:围绕这些关键点构建一个矩形边界框。
  • 第 13 行:计算并返回眼睛的中心点坐标。

以下是 find_eye_center 函数的完整代码:

1def find_eye_center(frame, eye_box_coordinates): 2 """ 3 定位眼睛中心点 4 """ 5 np_eye_landmarks = np.array(eye_box_coordinates) 6 # 围绕关键点生成边界矩形 7 (x, y, w, h) = cv2.boundingRect(np_eye_landmarks) 8 9 # 计算矩形中心点 10 eye_center = [int(x + w / 2), int(y + h / 2)] 11 return eye_center

segment_right_eye_area 函数

该函数用于处理右眼区域并提取其主色调。具体步骤如下:

  • 第 6–7 行:根据图像尺寸计算绘图时使用的线条粗细系数。
  • 第 10–11 行:基于特定坐标确定右眼的兴趣区域(ROI)。
  • 第 14 行:定位眼睛中心点。
  • 第 18 行:计算眼角之间的距离,用于绘制环绕眼睛的圆圈。
  • 第 20–32 行:围绕眼部关键点绘制凸包(convex hull),裁剪固定兴趣区域,用于识别眼睛颜色。
  • 第 37 行:调用 extract_dominant_colors 函数,提取该区域内前五种主色调。
  • 第 39 行:调用 get_top_dominant_color 函数,从五种主色中提取占比最高的颜色。
  • 第 42 行:使用 OpenCV 的 fillConvexPoly 函数将该区域填充为蓝色,便于用户观察。
  • 第 45 行:绘制一个以最高占比颜色为轮廓的圆圈,围绕眼睛。

以下是 segment_right_eye_area 函数的完整代码:

1def segment_right_eye_area(img, face_coordinates): 2 """ 3 根据人脸坐标分割右眼区域 4 """ 5 # 计算绘图粗细系数 6 img_height, img_width, img_channels = img.shape 7 thickness_factor = math.floor(img_width * img_height / ((img_width + img_height) * 100)) 8 9 # 右眼关键点坐标 10 r_eye_coordinates = [face_coordinates[385], face_coordinates[387], 11 face_coordinates[380], face_coordinates[373]] 12 13 # 定位眼睛中心 14 eye_center = find_eye_center(img, r_eye_coordinates) 15 16 # 计算眼角间距(用于画圆半径) 17 dst = int((face_coordinates[374][1] - face_coordinates[386][1])) 18 19 landmarks_points = [] 20 for n in (385, 386, 374, 380): 21 x = face_coordinates[n][0] 22 y = face_coordinates[n][1] 23 landmarks_points.append((x, y)) 24 landmarks_points.append((eye_center[0], eye_center[1])) 25 26 points = np.array(landmarks_points, np.int32) 27 convexhull = cv2.convexHull(points) 28 (x, y, w, h) = cv2.boundingRect(convexhull) 29 30 # 裁剪兴趣区域 31 roi = img[y:y + h, x:x + w].copy() 32 33 # 提取主色调 34 dominant_colors = extract_dominant_colors(roi, hasThresholding=True) 35 # 获取最主导颜色 36 top_color_value, top_color_name, top_color_score = get_top_dominant_color(dominant_colors) 37 print(f'右眼 - 距离 {dst} - 主色值 {top_color_value}, 主色名 {top_color_name}, 占比 {top_color_score}%') 38 39 # 填充凸包区域(蓝色) 40 cv2.fillConvexPoly(img, points, color=(255, 0, 0)) 41 # 绘制代表眼睛颜色的圆圈 42 cv2.circle(img, (eye_center[0], eye_center[1]), radius=dst, color=top_color_value, thickness=thickness_factor) 43 44 return roi, img, top_color_value, top_color_name, top_color_score

recognize_eyes_color 函数

这是本工具的核心函数,用于识别检测到的人脸中眼睛的主色调,并分析眼睛特定区域的颜色。功能包括:

  • 第 6 行:初始化 MediaPipe 的人脸检测与面部网格(Face Mesh)子模块。
  • 第 9 行:使用 OpenCV 读取输入图像。
  • 第 12 行:保留原始图像副本用于后续处理。
  • 第 15 行:将图像从 BGR 转换为 RGB 格式(OpenCV 默认加载为 BGR)。
  • 第 18 行:使用 MediaPipe 人脸检测模块处理图像。
  • 第 24–25 行:输出检测到的人脸数量信息。
  • 第 27–33 行:实例化 FaceMesh 类对象,用于估计面部关键点。
  • 第 36 行:遍历每一张检测到的人脸,依次执行后续步骤。
  • 第 38–41 行:输出当前人脸的 ID 和检测置信度。
  • 第 47–52 行:获取人脸的相对边界框。
  • 第 55–56 行:计算绝对坐标。
  • 第 58 行:裁剪出人脸区域。
  • 第 61 行:获取面部关键点坐标。
  • 第 64–66 行:处理右眼并识别其颜色。
  • 第 68–69 行:输出右眼主色及其占比。
  • 第 79–82 行:以类似方式处理左眼。
  • 第 92–98 行:保存并输出处理后的图像。

以下是 recognize_eyes_color 函数的完整代码:

1def recognize_eyes_color(input_path:str, display_output:bool = False): 2 """ 3 识别图像中人脸的眼睛颜色 4 """ 5 # 初始化 MediaPipe 子模块 6 mpFaceDetection, mpFaceModule = initialize_mediapipe() 7 8 # 读取输入图像 9 img = cv2.imread(input_path) 10 11 # 保留原始图像副本 12 frame = img.copy() 13 14 # 转换为 RGB 格式 15 rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 16 17 # 检测人脸 18 faces = mpFaceDetection.process(rgb_frame) 19 20 output = [] 21 output_info = [] 22 23 # 输出检测到的人脸数量 24 output_msg = {'msg': f"{len(faces.detections)} 张人脸被检测到。", 'category': "info"} 25 output_info.append(output_msg) 26 27 # 初始化 FaceMesh 模型 28 mpFaceMesh = mpFaceModule.FaceMesh( 29 static_image_mode=True, 30 max_num_faces=len(faces.detections), 31 refine_landmarks=True, 32 min_detection_confidence=MIN_CONFIDENCE_LEVEL 33 ) 34 landmarks = mpFaceMesh.process(rgb_frame).multi_face_landmarks 35 36 # 遍历每张检测到的人脸 37 for idx, (face_detected, face_landmarks) in enumerate(zip(faces.detections, landmarks)): 38 # 输出人脸信息 39 label = f"人脸 ID = {idx + 1} - 检测置信度 {int(face_detected.score[0] * 100)}%" 40 output_msg = {'msg': label, 'category': "info"} 41 output_info.append(output_msg) 42 print(output_msg.get('category'), output_msg.get('msg')) 43 44 frame = img.copy() 45 46 # 获取人脸边界框 47 relativeBoundingBox = face_detected.location_data.relative_bounding_box 48 frameHeight, frameWidth, frameChannels = frame.shape 49 faceBoundingBox = ( 50 int(relativeBoundingBox.xmin * frameWidth), 51 int(relativeBoundingBox.ymin * frameHeight), 52 int(relativeBoundingBox.width * frameWidth), 53 int(relativeBoundingBox.height * frameHeight) 54 ) 55 56 # 绘制人脸框 57 cv2.rectangle(frame, faceBoundingBox, (0, 255, 0), 3) 58 x, y, w, h = faceBoundingBox 59 roi_face_color = frame[y:y+h, x:x+w] 60 61 # 获取面部关键点坐标 62 face_coordinates = grab_face_coordinates(frame, face_landmarks) 63 64 # 处理右眼 65 roi, img, top_color_value, top_color_name, top_color_score = segment_right_eye_area(frame, face_coordinates) 66 rightLabel = f"右眼 {top_color_name} {top_color_score:.0f}%" 67 print(rightLabel) 68 69 # 处理左眼 70 roi, img, top_color_value, top_color_name, top_color_score = segment_left_eye_area(frame, face_coordinates) 71 leftLabel = f"左眼 {top_color_name} {top_color_score:.0f}%" 72 print(leftLabel) 73 74 # 保存处理后的图像 75 output_filepath = os.path.join(config.PROCESSED_PATH, 76 str(uuid.uuid4().hex) + os.path.splitext(input_path)[1]) 77 cv2.imwrite(output_filepath, img) 78 output_item = { 79 'id': 3, 80 'folder': config.PROCESSED_FOLDER, 81 'name': os.path.basename(output_filepath), 82 'msg': rightLabel + " - " + leftLabel 83 } 84 output.append(output_item) 85 86 # 显示结果图像(可选) 87 if display_output: 88 label = "眼睛颜色识别器" 89 cv2.imshow(label, frame) 90 cv2.waitKey(0) 91 cv2.destroyAllWindows() 92 93 # 关闭 MediaPipe 模块 94 mpFaceMesh.close() 95 mpFaceDetection.close() 96 return output_info, output

测试场景

让我们看看这个工具的实际运行效果:

No configuration found for ID: 77753566e5271a02e61d88763368937081d9b91364e7cec40f713a1b4cb4a55c

你可以尝试修改上述代码片段,开发自定义的测试用例。

结论

我们已经看到,这个工具能非常方便地识别出人眼的主色调。你只需拍摄朋友或家人的照片,剩下的就交给工具来完成!

预测分析

计算睁眼比例