### 一、前言
MS-Celeb-1M 是微软于2016年发布的大规模人脸识别数据集,但由于隐私问题(各州政府发布了相关禁令),微软于2019年6月删除(关闭)了该数据集(外界访问)。美国政府重视人脸隐私的程度似乎比国内高了很多?
不过种子仍然还是存在的,[MS-Celeb-1M: {A} Dataset and Benchmark for Large-Scale Face Recognition - Technical - Academic Torrents](https://academictorrents.com/details/9e67eb7cc23c9417f39778a8e06cca5e26196a97/tech&hit=1&filelist=1)。
Adam Harvey:一旦你发布了它,人们下载了它,那它就会存在于全世界的硬盘里。
请勿滥用该数据集!!!!!
### 二、数据集解析
我只下载了该数据集中的 FaceImageCroppedWithAlignment.tsv 部分,大概 84.8 GB,文件内格式:
```txt
File format: text files, each line is an image record containing 7 columns, delimited by TAB.
Column1: Freebase MID
Column2: ImageSearchRank
Column3: ImageURL
Column4: PageURL
Column5: FaceID
Column6: FaceRectangle_Base64Encoded (four floats, relative coordinates of UpperLeft and BottomRight corner)
Column7: FaceData_Base64Encoded
```
使用以下程序解析成图片存储:
```python
import base64
import csv
import os
filename = './FaceImageCroppedWithAlignment.tsv'
outputDir = 'images'
with open(filename, 'r', encoding = 'UTF-8') as tsvF:
reader = csv.reader(tsvF, delimiter='\t')
i = 0
for row in reader:
MID, imgSearchRank, faceID, data = row[0], row[1], row[4], base64.b64decode(row[-1])
saveDir = os.path.join(outputDir, MID)
savePath = os.path.join(saveDir, "{}-{}.jpg".format(imgSearchRank, faceID))
if not os.path.exists(saveDir):
os.makedirs(saveDir)
# print("makedirs {}".format(saveDir))
with open(savePath, 'wb') as f:
f.write(data)
i += 1
if i % 1000 == 0:
print("Extract {} images".format(i))
```
总共解析出 8456240 张图片,不过这些图片是"pretty dirty"的,有很多标注错误,举个例子如下:

在文件夹 **m.0107_f** 中,很明显出现了不止一个人的图片,甚至还有不同性别的。因此我们如果要使用这个数据集的话,还需要对它进行清洗,也就是重新标注。
统计已解压数据的身份标注数和图片总数:
```python
import os
cleanFolder = './images'
num = 0
f_num = len(os.listdir(cleanFolder))
print("Folder numer:", f_num)
idens = os.listdir(cleanFolder)
for idx, iden in enumerate(idens):
#print(str(idx)+"/"+str(f_num))
num += len(os.listdir(os.path.join(cleanFolder, iden)))
print("All images:", num)
```
### 三、数据清洗
对如此大的一个数据集进行清洗是很费时费力的,人工标注显然不合理,自己写代码标注又不能保证准确性,不过幸好,已有 [前人](https://github.com/EB-Dodo/C-MS-Celeb) 完成了这部分工作。
[clean_list.7z](https://github.com/EB-Dodo/C-MS-Celeb/blob/master/clean_list.7z) 中包含了重新标注的内容:
clean_list_128Vec_WT051_P010.txt 中包含了原先标注正确的图片信息:
```python
...
m.0g82cy m.0g82cy/40-FaceId-0.jpg
m.0g82cy m.0g82cy/41-FaceId-0.jpg
m.0g82cy m.0g82cy/42-FaceId-0.jpg
m.0g82cy m.0g82cy/43-FaceId-1.jpg
m.0g82cy m.0g82cy/44-FaceId-0.jpg
m.0g82cy m.0g82cy/47-FaceId-0.jpg
m.0g82cy m.0g82cy/48-FaceId-1.jpg
m.0g82cy m.0g82cy/49-FaceId-0.jpg
m.0g82cy m.0g82cy/5-FaceId-0.jpg
m.0g82cy m.0g82cy/50-FaceId-0.jpg
...
```
每行中,第一列为标签,第二列为图片。
relabel_list_128Vec_T058.txt 中则包含了原先标注错误但重新标注后的图片信息:
```python
...
m.0n5vkzf m.0107_f/20-FaceId-0.jpg
m.025dmd6 m.0107_f/22-FaceId-0.jpg
m.0h3rxh8 m.0107_f/29-FaceId-0.jpg
m.070096 m.0107_f/30-FaceId-0.jpg
m.070096 m.0107_f/33-FaceId-0.jpg
m.04n5ffj m.0107_f/38-FaceId-0.jpg
m.025dmd6 m.0107_f/50-FaceId-0.jpg
m.070096 m.0107_f/64-FaceId-0.jpg
...
```
每行中,第二列为图片,第一列为正确标签(新标签,我手动验证了一部分,由于数据量太大了,没办法全部验证,这里也就默认它是正确标签了)。
因此,在上述两个文件的指导下,我们只需要把对应图片移动到相应的文件夹下即完成了数据的重新标注任务了。
**需要特别注意的是:** 原数据集在标注错误的情况下,会存在这种情况:属于同一个人的不同图片,由于标注错误,存在于不同的文件夹下,但是它们的文件名正好相同,这时如果简单的进行移动,会存在同名冲突或者同名覆盖,就会丢失一部分数据。我们假设同一个人的不同图片都不完全相同,因此可以把这些图片都保留下来,对于同名的那些图片,我们给个不冲突的随机名称即可。
以下是图片移动的代码:
> 我假设 clean_list_128Vec_WT051_P010.txt 中不存在重复命名的同一图片,也即原数据集中不存在重复命名的同一图片,因此针对它,不采用命名冲突时的随机命名;仅针对 relabel_list_128Vec_T058.txt 采用随机命名来解决命名冲突问题。
```python
import shutil
import random
import os
def generate_random_str(randomlength=10):
random_str = ''
base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
length = len(base_str) -1
for i in range(randomlength):
random_str += base_str[random.randint(0, length)]
return random_str
if __name__ == "__main__":
imgFolder = './images'
cleanFolder = './clean_images'
washLists = ['./clean_list_128Vec_WT051_P010.txt', './relabel_list_128Vec_T058.txt']
for lis in washLists:
print(lis)
with open(lis, 'r') as f:
goodFiles = f.readlines()
goodFiles = [f.strip('\n ') for f in goodFiles]
goodFiles = [f.strip('\r ') for f in goodFiles]
count = 0
for goodFile in goodFiles:
label, filename = goodFile.split(' ')
filepath = os.path.join(imgFolder, filename)
if os.path.exists(filepath):
count += 1
cleanpath = os.path.join(cleanFolder, label)
if not os.path.exists(cleanpath):
os.makedirs(cleanpath)
print("makedirs {}".format(cleanpath))
filename = filename.split('/')[-1]
if lis == washLists[-1]:
while os.path.exists(os.path.join(cleanpath, filename)):
filename = generate_random_str() + "." + filename.split(".")[-1]
if not os.path.exists(os.path.join(cleanpath, filename)):
shutil.move(filepath, os.path.join(cleanpath, filename))
if count>0 and count % 1000 == 0:
print("Relabel {} images".format(count))
```
最终清洗后的数据集拥有 94682 个人脸类别(身份信息),6464016 张图片,和论文数据保持一致。需要注意的是,论文也说了,新数据集也只能保证约 97.3% 的正确标注率,因此这方面还是得注意,等到后面模型在这个数据集上的识别正确率比其他数据集(模型)低,也许不是模型的问题。
> With our method, we obtain a clean version of the original “MS-Celeb-1M” database named “C-MS-Celeb” (containing 6,464,016 images of 94,682 celebrities) where approximately 97.3% of the images are correctly labeled.
上述 [github](https://github.com/EB-Dodo/C-MS-Celeb) 中介绍共得到 6464018 张图片,这个说法是错误的,因为它可能只是统计了那两个 txt 标注文件的内容行数,而这两个文件最后都多了一行回车符,因此会比实际图片数量多 2 。
**ps**: 做科研怎么能不严谨呢?但他论文数据又是对的,这这这......

经过重新标注后,原文件夹还剩下 99891 个人脸类别(身份信息),1992224 张图片,加上我们转移到 clean_images 文件夹中的 6464016 张图片,等于最开始的 8456240 张图片,因此上述步骤应该是正确的。
至此,对 MS-Celeb-1M 数据集的解析和清洗工作就完成了。
### 四、重复
直接假设原数据集中同一个人的不同图片都不完全相同,我越想越觉得不合理,因此重新标注之后我随机检验了几个文件夹,发现这个假设是错误的。
e.g. 在 m.05h2lg1 文件夹中,就会存在两张完全一样的图片。
所以这个数据集还需要进一步的处理,不过我没时间了,暂时就这么滴了。
也许这些完全一样的图片计算 HASH 值得到的结果一样?是不是这样就可以剔除重复项了?
### 五、吐槽
不得不说,机械盘写数据真的是太慢了,即便是移动文件那部分操作也花了很多时间。Linux 下应该会好一些, 感觉 Windows 还是不是很适合开发者啊。

MS-Celeb-1M 解析及清洗