✨ 背景介绍

CCF大数据与计算智能大赛(CCF Big Data & Computing Intelligence Contest,简称CCF BDCI)是由中国计算机学会大数据专家委员会于2013年创办的国际化智能算法、创新应用和大数据系统大型挑战赛事,是全球大数据与人工智能领域最具影响力的活动之一。2019 CCF大数据与计算智能大赛由教育部高等学校计算机类专业教学指导委员会、国家自然科学基金委员会信息科学部及郑州市人民政府指导,中国计算机学会主办,郑州市郑东新区管理委员会、教育部易班发展中心、CCF大数据专家委员会、大洋洲计算机研究与教育协会、数联众创承办。

人脸识别已经在生活中快速的普及开来, 但是人脸识别技术在实际应用中遇到的一个广为人知的问题是它在不同人种的性能有差异。 如何快速的提升人脸识别系统在不同人种的性能, 是一个实用的人脸识别算法应该考虑的问题。

😄赛题任务:本次比赛目标是提高人脸识别模型在不同人种上面的性能。以人脸1:1 比对为场景, 参赛队需要同时优化人脸识别模型在不同人种上的性能,提高在低误识率情况下不同人种的通过率。

⭐️ Our method

虽然比赛提供了训练数据,但是也是允许参赛队伍使用额外公开数据集和预训练模型的。只是使用的时候要在讨论群里艾特所有管理员告知所使用的数据集和预训练模型,并附上链接。

在此次比赛中,尝试过训练模型,奈何显卡不行,而实验室的服务器也是满负荷在跑实验,所以自然是很难重新训练模型了。

所以我们的主要工作就是使用预训练模型并且对图像进行预处理来提高识别准确率🙈。

预训练模型 我们在比赛中用的预训练模型和大多数队伍使用的预训练模型一样,是insightface,模型的Github仓库 http://insightface.ai/ ,主页 http://insightface.ai/ 。这应该目前人脸识别准确率最高的开源模型了。在主页中也提供了一些简单的Demo。

预处理方法 此次比赛有四种人种,而人种的差别主要在肤色在一块。我首先使用了自动gamma校正,然后使用了图像锐化,最后将图像送了预训练的 Insightface 模型获取特征向量。

🐶 值得注意和参考的小trick:

1️⃣ 优先使用 insightface.app.FaceAnalysis() 的分析人脸方法,这个会将人脸关键点提取校正,还会有一些预处理。而 insightface.model_zoo.get_model('arcface_r100_v1') 则是直接对图像获取特征向量了。比如我就是优先使用前者,而对于测试集中无法识别人脸的图像才使用后者。

2️⃣ 一个在讨论区公开分享的trick。先是获取两张图像比较a-b的相似度sim1,然后或者水平翻转后的图像flipa-flipb的相似度sim2,最后计算sim1和sim2的平均值也可以提高得分。

⚡️具体的代码:

generate_mat.py

# -*- coding: utf-8 -*-
# @Time     :2019/10/31 9:30
# @Author   :AngusCai
import os
import cv2
import numpy as np
import time
import scipy.io as sio
from tqdm import tqdm
import math
import insightface
from PIL import Image, ImageEnhance

model1 = insightface.app.FaceAnalysis()
model1.prepare(ctx_id=0, nms=0.4)

model2 = insightface.model_zoo.get_model('arcface_r100_v1')
model2.prepare(ctx_id=0)


def gamma_trans(img, gamma):  # gamma函数处理
    """
    :param img:  待处理图像
    :param gamma:  伽马变换的幂
    :return:   经过伽马变换的图像
    """
    gamma_table = [np.power(x / 255.0, gamma) * 255.0 for x in range(256)]  # 建立映射表
    gamma_table = np.round(np.array(gamma_table)).astype(np.uint8)  # 颜色值为整数
    return cv2.LUT(img, gamma_table)  # 图片颜色查表。另外可以根据光强(颜色)均匀化原则设计自适应算法。


def get_features(test_list, flip=False):
    """
    :param test_list:  测试图像路径列表
    :param flip:  是否水平翻转图像的flag
    :return:   测试图像的特征矩阵
    """
    progress_bar = tqdm(total=len(test_list))
    for idx, img_path in enumerate(test_list):
        progress_bar.update(1)
        # 自动gamma校正
        img_gray = cv2.imread(img_path, 0)  # 灰度图读取,用于计算gamma值
        img = cv2.imread(img_path)  # 原图读取
        mean = np.mean(img_gray)
        gamma_val = math.log10(0.5) / math.log10(mean / 255)  # 公式计算gamma
        image_gamma_correct = gamma_trans(img, gamma_val)  # gamma变换
        if flip:
            tmp_result = cv2.flip(image_gamma_correct, 1, dst=None)
        else:
            tmp_result = image_gamma_correct
        # 图像锐化
        image = Image.fromarray(cv2.cvtColor(tmp_result, cv2.COLOR_BGR2RGB))
        im_30 = ImageEnhance.Sharpness(image).enhance(3.0)
        result = cv2.cvtColor(np.asarray(im_30), cv2.COLOR_RGB2BGR)

        # 尝试获取预处理图像中的人脸,检测到人脸,用model1获取特征编码向量;没有检测到人脸就用model2 获取特征编码向量
        try:
            face = model1.get(result)  # insightface.app.FaceAnalysis有比较好的人脸对齐实现
            if idx == 0:
                feature = face[0].embedding.reshape(1, 512)
                features = feature
            else:
                feature = face[0].embedding.reshape(1, 512)
                features = np.concatenate((features, feature), axis=0)
        except:
            print(img_path)
            image = cv2.resize(result, (112, 112))
            feature = model2.get_embedding(image)
            features = np.concatenate((features, feature), axis=0)

    return features


def get_feature_dict(test_list, features):
    """
    :param test_list:  测试图像路径列表
    :param features:   测试图像的特征矩阵
    :return:  测试图像路径与特征的字典, key=path, value=feature
    """
    fe_dict = {}
    for i, each in enumerate(test_list):
        fe_dict[each] = features[i]
    return fe_dict


data_dir = '../Baseline/test/'  # test_set dir
name_list = [name for name in os.listdir(data_dir)]
img_paths = [data_dir + name for name in os.listdir(data_dir)]
print('Images number:', len(img_paths))

# 生成没有水平翻转的图像向量mat
s = time.time()
features = get_features(img_paths, flip=False)
t = time.time() - s
print(features.shape)
print('total time is {}, average time is {}'.format(t, t / len(img_paths)))

fe_dict = get_feature_dict(name_list, features)
print('Output number:', len(fe_dict))
sio.savemat('gamma_sharp3_arcface_app_embedding_test.mat', fe_dict)

# 生成水平翻转后图像的向量mat
s = time.time()
features2 = get_features(img_paths, flip=True)
t = time.time() - s
print(features2.shape)
print('total time is {}, average time is {}'.format(t, t / len(img_paths)))

fe_dict2 = get_feature_dict(name_list, features2)
print('Output number:', len(fe_dict2))
sio.savemat('gamma_flip_sharp3_arcface_app_embedding_test.mat', fe_dict2)

generate_csv.py

# -*- coding: utf-8 -*-
# @Time     :2019/10/31 9:30
# @Author   :AngusCai
import numpy as np
import scipy.io as sio
from tqdm import tqdm


def cosine_metric(x1, x2):
    return np.dot(x1, x2) / (np.linalg.norm(x1) * np.linalg.norm(x2))


def get_sim(x1, x2):
    return np.dot(x1, x2.T)


gamma_sharp3_features = sio.loadmat('gamma_sharp3_arcface_app_embedding_test.mat')
print('Loaded gamma_sharp3_features mat')
gamma_flip_sharp3_features = sio.loadmat('gamma_flip_sharp3_arcface_app_embedding_test.mat')
print('Loaded gamma_flip_sharp3_features mat')
sample_sub = open('./submission_template.csv', 'r')  # sample submission file dir
sub = open('[angusaha]_results.csv', 'w', encoding='utf-8')
print('Loaded CSV')

lines = sample_sub.readlines()  # 获取提交示例中的所有行

progress_bar = tqdm(total=len(lines))
for line in lines:
    pair = line.split(',')[0]  # 获取逗号分隔的要比较的图像对

    sub.write(pair + ',')  # 将要比较的图像对写入结果文件
    a, b = pair.split(':')  # 图像对用冒号分隔,获取单独两个的img_name

    sim1 = cosine_metric(gamma_sharp3_features[a][0], gamma_sharp3_features[b][0])  # a和b的特征向量的余弦相似度
    sim2 = cosine_metric(gamma_flip_sharp3_features[a][0], gamma_flip_sharp3_features[b][0])  # flip-a和flip-b的特征向量的余弦相似度
    sim = (sim1 + sim2) / 2.  # 计算两个相似度之间的平均值
    score = '%.5f' % sim    # 结果保留5位小数
    sub.write(score + '\n')
    progress_bar.update(1)
sample_sub.close()
sub.close()

🏆 名次

这是第一次自己完全参与、正规参加算法比赛。能进入复赛拿到现在的名次还是很开心的。初赛A榜24/897,初赛B榜26/897,复赛A榜23/39,复赛B榜26/39。
初赛A榜
初赛B榜
复赛A榜
复赛B榜

🎆 可能有用的思路

在复赛开始后的几天,无意间搜索多人种人脸识别的时候,发现了一篇发表在ICCV 2019的多人种人脸识别方法。作者是北邮的一位老师,他们研究团队还构建了一份用于公平比较各种算法在多人种人脸识别领域的准确度的多人种数据集RFW👍 。 数据集主页 http://www.whdeng.cn/RFW/index.html 。论文 [1] Mei Wang, Weihong Deng, Jiani Hu, Xunqiang Tao, Yaohai Huang. Racial Faces in-the-Wild: Reducing Racial Bias by Information Maximization Adaptation Network. ICCV 2019 。

论文中所使用的迁移学习,在多人种人脸识别这一块确实是表现出色,论文中比较对象也包括了此次比赛被广泛使用的Insightface模型。

或许迁移学习可能就是解决这个赛题的最佳方法,这篇论文的方法值得参考,只是还貌似没有开源代码和训练好的模型。