👤 Face recognition, and its limits · การรู้จำใบหน้า และขีดจำกัดของมัน · 人脸识别,以及它的极限

Find a face in a photo. Teach a program two faces — Jay Chou and Michelle Yeoh. Then hand it a new picture, point it at your laptop camera, and measure how often it is wrong. · หาใบหน้าในรูป สอนโปรแกรมให้รู้จักสองใบหน้า เจย โจว และมิชเสล เยอห์ แล้วยื่นรูปใหม่ให้ ลองกับกล้องโน้ตบุก และวัดว่ามันผิดบ่อยแค่ไหน · 在照片里找到脸。教程序认识两张脸 —— 周杰伦和杨紫琼。然后给它一张新照片,把它对准笔记本摄像头,并且量一量它错得有多频繁。

1 🧑‍🤝‍🧑 Two different jobs · สองงานที่ต่างกัน · 两件不同的事

🇬🇧 English

People say “face recognition” for two jobs that are not the same, and the second is much harder than the first.

  1. Finding a face. Is there a face in this picture, and where? Old, fast, and it works.
  2. Naming a face. Is this Jay Chou, or Michelle Yeoh, or somebody we have never seen? This is the hard one, and most of this lesson is about how it fails.

You will build both, plus a version that finds faces in your laptop camera, in well under two hundred lines of Python. No model download, nothing sent to the internet.

🇹🇭 ไทย

คนเรียก “การรู้จำใบหน้า” รวมกันสองงานที่ไม่เหมือนกัน และงานที่สองยากกว่างานแรกมาก

  1. หาใบหน้า ในรูปนี้มีหน้าคนไหม อยู่ตรงไหน เป็นเทคนิคเก่า เร็ว และใช้ได้จริง
  2. บอกว่าเป็นใคร นี่คือเจย โจว หรือมิชเสล เยอห์ หรือคนที่เราไม่เคยเห็น นี่คืองานที่ยาก และบทเรียนนี้ส่วนใหญ่พูดถึงว่ามันพังอย่างไร

คุณจะสร้างทั้งสองอย่าง บวกรุ่นที่ดูกล้องโน้ตบุกสด ด้วย Python ไม่ถึงสองร้อยบรรทัด ไม่ต้องดาวน์โหลดโมเดล และไม่มีอะไรถูกส่งขึ้นอินเทอร์เน็ต

🇨🇳 中文

大家把两件不一样的事都叫“人脸识别”,而第二件比第一件难得多。

  1. 找到一张脸。这张图里有没有人脸?在哪里?技术很老、很快,而且真的好用。
  2. 说出这是谁。这是周杰伦、是杨紫琼、还是一个我们没见过的人?这一件很难,本课大部分内容讲的是它怎么出错。

你会把两件都做出来,再加上一个看笔记本摄像头的版本,总共不到两百行 Python。不下载模型,也没有任何东西被送上网。

READ THIS FIRST A photo of a face is biometric personal data. Under Thailand’s PDPA that is a sensitive category, and a school needs a clear reason and written consent before collecting it — and a camera pointed at a room collects it from everyone who walks past, not just the person you meant. This lesson uses freely-licensed photos of two public figures, never students. If you later use your class’s photos: ask first, in writing; keep the photos and the model file on one machine; delete both when the activity ends; and do not build a face database of children because it turned out to be easy to build. ภาพใบหน้าคือข้อมูลชีวมิติส่วนบุคคล ตาม PDPA ของไทยถือเป็นข้อมูลอ่อนไหว โรงเรียนต้องมีเหตุผลชัดเจนและได้รับความยินยอมเป็นลายลักษณ์อักษรก่อนเก็บ และกล้องที่เปิดไว้ในห้องก็เก็บข้อมูลจากทุกคนที่เดินผ่าน ไม่ใช่เฉพาะคนที่คุณตั้งใจ บทเรียนนี้ใช้ภาพบุคคลสาธารณะสองคนที่เปิดให้ใช้ได้อย่างเสรี ไม่ใช่รูปนักเรียน ถ้าภายหลังคุณจะใช้รูปของนักเรียนในห้อง ให้ขออนุญาตเป็นลายลักษณ์อักษรก่อน เก็บรูปและไฟล์โมเดลไว้ในเครื่องเดียว ลบทั้งคู่เมื่อกิจกรรมจบ และอย่าสร้างฐานข้อมูลใบหน้าของเด็กเพียงเพราะมันสร้างได้ง่าย 人脸照片是生物特征个人数据。在泰国 PDPA 下属于敏感类别,学校收集前需要明确理由和书面同意 —— 而一台对着房间的摄像头,收集的是每一个走过的人,不只是你想拍的那个人。本课用的是两位公众人物的自由许可照片,不是学生的照片。如果以后要用班上学生的照片:先书面征得同意;照片和模型文件只留在一台机器上;活动结束就把两者删掉;不要因为发现很容易就建一个儿童人脸数据库。

2 📦 One install line · ติดตั้งบรรทัดเดียว · 一行安装命令

🇬🇧 English

OpenCV is the library. The contrib build is the one you want: it carries both the face finder and the face namer.

Pin the version. We tested on 4.12.

🇹🇭 ไทย

ไลบรารีคือ OpenCV ให้ใช้รุ่น contrib เพราะมีทั้งตัวหาใบหน้าและตัวบอกชื่อใบหน้ามาให้ครบ

ระบุเวอร์ชันให้ชัด เราทดสอบบน 4.12

🇨🇳 中文

库就是 OpenCV。要装 contrib 版:找脸和认脸的功能都在里面。

把版本钉死。我们测的是 4.12。

terminal

pip install "opencv-contrib-python==4.12.0.88"
CAREFUL Do not install OpenCV 5 for this lesson. We checked: 5.0.0 ships cv2/data/ empty, so cv2.data.haarcascades + "haarcascade_frontalface_default.xml" — the line in nearly every face-detection tutorial online — points at a file that is not there. Version 4.12 ships 17 cascade files and cv2.face for the naming half. อย่าติดตั้ง OpenCV 5 สำหรับบทเรียนนี้ เราตรวจแล้ว รุ่น 5.0.0 ส่ง cv2/data/ มาแบบว่างเปล่า บรรทัด cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ซึ่งมีอยู่ในบทเรียนการหาใบหน้าแทบทุกที่บนอินเทอร์เน็ต จะชี้ไปยังไฟล์ที่ไม่มีอยู่ ส่วนรุ่น 4.12 มีไฟล์ cascade 17 ไฟล์ และมี cv2.face สำหรับงานบอกชื่อ 这一课不要装 OpenCV 5。我们验证过:5.0.0 发出来的 cv2/data/空的,所以网上几乎每篇人脸检测教程里的那行 cv2.data.haarcascades + "haarcascade_frontalface_default.xml" 指向的文件根本不存在。4.12 带了 17 个 cascade 文件,也带了负责认脸的 cv2.face

3 🔍 Find a face · หาใบหน้า · 找到一张脸

🇬🇧 English

The finder is not AI in the modern sense. It slides a set of simple light-and-dark patterns over the picture, at many sizes, and reports the places that look face-shaped. The rules ship inside OpenCV as an XML file.

minNeighbors is the fussiness dial: raise it for fewer false alarms, lower it to catch more faces.

🇹🇭 ไทย

ตัวหาใบหน้าไม่ใช่ AI แบบสมัยใหม่ มันเลื่อนชุดลวดลายสว่าง-มืดง่าย ๆ ไปทั่วรูปในหลายขนาด แล้วรายงานจุดที่รูปทรงเหมือนใบหน้า กฏพวกนี้มากับ OpenCV เป็นไฟล์ XML

minNeighbors คือปุ่มความเข้มงวด เพิ่มค่าเพื่อลดการแจ้งผิด ลดค่าเพื่อจับใบหน้าให้ได้มากขึ้น

🇨🇳 中文

这个找脸器不是现代意义上的 AI。它把一组简单的明暗图案在图片上按多种尺寸滑动,报告哪些位置长得像脸。这些规则作为一个 XML 文件随 OpenCV 一起发布。

minNeighbors 是挑剔程度的旋钮:调高,误报更少;调低,能抓到更多脸。

find_faces.py

"""find_faces.py — is there a face in this picture, and where?

    python find_faces.py photo.jpg

It draws a box around every face it finds and saves the picture as found_photo.jpg.
No AI model is downloaded: the rules for what a face looks like ship with OpenCV.
"""
import sys
from pathlib import Path

import cv2

CASCADE = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"


def find_faces(path):
    """Returns the picture, and a box (x, y, width, height) for every face."""
    picture = cv2.imread(str(path))
    if picture is None:
        raise SystemExit(f"cannot open {path}")
    grey = cv2.cvtColor(picture, cv2.COLOR_BGR2GRAY)      # the detector ignores colour
    detector = cv2.CascadeClassifier(CASCADE)
    faces = detector.detectMultiScale(grey,
                                      scaleFactor=1.1,    # look for faces 10% bigger each pass
                                      minNeighbors=5,     # higher = fewer false alarms
                                      minSize=(30, 30))   # ignore anything tiny
    return picture, faces


if __name__ == "__main__":
    path = Path(sys.argv[1])
    picture, faces = find_faces(path)
    print(f"{path.name}: {len(faces)} face(s)")
    for (x, y, w, h) in faces:
        print(f"   a face at x={x} y={y}, {w} by {h} pixels")
        cv2.rectangle(picture, (x, y), (x + w, y + h), (0, 255, 0), 2)
    out = path.with_name("found_" + path.name)
    cv2.imwrite(str(out), picture)
    print("saved", out.name)

four real photos, one run each

jay_chou_headshot.jpg: 1 face(s)
   a face at x=5 y=89, 167 by 167 pixels

michelle_yeoh_2015.jpg: 1 face(s)
   a face at x=138 y=273, 537 by 537 pixels

jay_chou_the_era_singapore_2010_concert.jpg: 0 face(s)

kungfudunkmoviepromo1.jpg: 8 face(s)
   a face at x=56 y=158, 141 by 141 pixels
   a face at x=757 y=240, 127 by 127 pixels
   a face at x=382 y=450, 142 by 142 pixels
   ... and five more
A photo of Michelle Yeoh with a green box drawn round her face
One face, one box. This is the easy case, and the part that works. หน้าเดียว กล่องเดียว นี่คือกรณีง่าย และเป็นส่วนที่ใช้ได้จริง 一张脸,一个框。这是简单的情况,也是真正好用的那一半。
A film promo photo where the detector has boxed three Motorola logos as faces
The same program, same settings, on a film promo. It found the real faces — and boxed three Motorola logos, a man’s chin, and a pair of trousers. A round shape with two dark marks is, to this detector, a face. โปรแกรมเดียวกัน ค่าเดียวกัน บนภาพโปรโมตหนัง มันเจอใบหน้าจริง และยังตีกรอบโลโก้ Motorola สามอัน คางของผู้ชาย และกางเกง รูปทรงกลมที่มีรอยมืดสองรอย สำหรับตัวตรวจจับนี้คือใบหน้า 同一个程序,同样的参数,用在一张电影宣传照上。它找到了真人的脸 —— 也把三个摩托罗拉标志、一个男人的下巴、和一条裤子都框了起来。一个圆形里有两道暗痕,在这个检测器看来就是一张脸。
CAREFUL Three lessons in four lines. The dark 2010 concert photo gives zero faces — a stage light behind someone defeats it completely. The film poster gives eight, and three of them are the Motorola logo printed on the poster: a round shape with two dark marks is, to this detector, a face. And a concert photo of one singer gave us fifteen, because the audience was in shot. A face finder that never makes a mistake does not exist; this is why the naming program below keeps only the largest face, and why you should look at what it found before you trust it. สามบทเรียนในสี่บรรทัด รูปคอนเสิร์ตมืด ปี 2010 ให้ผลศูนย์ใบหน้า แสงไฟบนเวทีที่อยู่ด้านหลังทำให้มันพ่ายสิ้นเชิง ส่วนโปสเตอร์หนังให้แปดใบหน้า และสามในนั้นคือโลโก้ Motorola ที่พิมพ์อยู่บนโปสเตอร์ รูปทรงกลมที่มีรอยมืดสองรอย สำหรับตัวตรวจจับนี้คือใบหน้า และรูปคอนเสิร์ตของนักร้องคนเดียวให้ผลสิบห้าใบหน้า เพราะมีคนดูอยู่ในภาพ ตัวหาใบหน้าที่ไม่เคยพลาดนั้นไม่มีจริง จึงเป็นเหตุผลที่โปรแกรมบอกชื่อข้างล่างเก็บเฉพาะใบหน้าที่ใหญ่ที่สุด และทำไมควรดูผลที่มันหาเจอก่อนจะเชื่อ 四行里有三个教训。2010 年那张昏暗的演唱会照片找到张脸 —— 人后方的舞台灯就能让它彻底失效。电影海报找到张,其中三张是海报上印着的摩托罗拉标志:一个圆形里有两道暗痕,在这个检测器看来就是一张脸。而一张单人演唱照给了我们十五张,因为观众也在画面里。不会出错的找脸器并不存在;这就是下面那个认脸程序只保留最大那张脸的原因,也是你在相信它之前应该先看一眼它找到了什么的原因。

4 🙋 Give it photos, then a new photo · ให้รูป แล้วให้รูปใหม่ · 先给照片,再给一张新的

🇬🇧 English

Now the second job. Put a few photos of each person in a folder named after them, and the program learns the pattern of light and dark across each face (LBPH). Then hand it a new photo and it answers with a name and a distance.

Low distance means a close match. Above the threshold it must say I do not know — that answer is the whole point.

🇹🇭 ไทย

ทีนี้มาถึงงานที่สอง วางรูปของแต่ละคนไว้ในโฟลเดอร์ที่ตั้งชื่อตามคนนั้น โปรแกรมจะเรียนรูปแบบความสว่าง-มืดบนใบหน้าแต่ละหน้า (LBPH) จากนั้นยื่นรูปใหม่ให้ มันจะตอบเป็นชื่อพร้อมระยะห่าง

ระยะห่างน้อยแปลว่าใกล้เคียงมาก ถ้าเกินเกณฑ์ มันต้องตอบว่า ไม่รู้จัก คำตอบนี้แหละคือหัวใจ

🇨🇳 中文

现在是第二件事。把每个人的几张照片放进以他名字命名的文件夹,程序就会学习每张脸上明暗的分布(LBPH)。然后给它一张新照片,它会回答一个名字和一个距离

距离小表示很接近。超过阈值时它必须说我不知道 —— 这个答案才是重点。

the folders

faces/
  jay_chou/       00.jpg 01.jpg ... 07.jpg
  michelle_yeoh/  00.jpg 01.jpg ... 07.jpg
Sixteen cropped faces: eight of Jay Chou on the top row, eight of Michelle Yeoh below
This is the whole training set: eight photos each, across different years, lighting and hair. Nothing else is learned. นี่คือชุดรูปฝึกทั้งหมด คนละแปดรูป ต่างปี ต่างแสง ต่างทรงผม ไม่มีอย่างอื่นที่มันเรียน 这就是全部的训练集:每人八张,跨不同年份、光线和发型。它学到的只有这些。

who_is_this.py

"""who_is_this.py — learn a few faces, then name the person in a new photo.

    faces/
      ploy/    1.jpg 2.jpg 3.jpg ...     a few photos of each person
      nok/     1.jpg 2.jpg 3.jpg ...

    python who_is_this.py faces test.jpg

It prints a name and a distance. A LOW distance means a good match. Above the
threshold it says "I do not know", which is the answer you want for a stranger.
"""
import sys
from pathlib import Path

import cv2
import numpy as np

CASCADE = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
SIZE = (200, 200)          # every face is cut out and resized to this before learning
THRESHOLD = 56             # distance above this = not sure enough to give a name.
                           # Measured on 101 photos, not guessed. Read the page:
                           # this number is a compromise, not a solution.


def face_from(path):
    """The largest face in a photo, in grey, cut out and resized. None if there is none."""
    picture = cv2.imread(str(path), cv2.IMREAD_GRAYSCALE)
    if picture is None:
        return None
    faces = cv2.CascadeClassifier(CASCADE).detectMultiScale(picture, 1.1, 5, minSize=(30, 30))
    if len(faces) == 0:
        return None
    x, y, w, h = max(faces, key=lambda box: box[2] * box[3])
    return cv2.resize(picture[y:y + h, x:x + w], SIZE)


def learn(folder):
    """Train on faces/<name>/*.jpg. Returns the model and the list of names."""
    faces, labels, names = [], [], []
    for person in sorted(p for p in Path(folder).iterdir() if p.is_dir()):
        photos = sorted(list(person.glob("*.jpg")) + list(person.glob("*.png")))
        found = [face_from(photo) for photo in photos]
        found = [f for f in found if f is not None]
        if not found:
            print(f"  {person.name}: no face found in any photo, skipping")
            continue
        names.append(person.name)
        faces += found
        labels += [len(names) - 1] * len(found)
        print(f"  {person.name}: learned {len(found)} of {len(photos)} photos")
    model = cv2.face.LBPHFaceRecognizer_create()
    model.train(faces, np.array(labels))
    return model, names


def who(model, names, path):
    """Name the face in one photo, or say it does not know."""
    face = face_from(path)
    if face is None:
        return "no face found", None
    label, distance = model.predict(face)
    if distance > THRESHOLD:
        return "I do not know", distance
    return names[label], distance


if __name__ == "__main__":
    folder, test = sys.argv[1], sys.argv[2]
    print("learning:")
    model, names = learn(folder)
    print("knows:", ", ".join(names))
    name, distance = who(model, names, test)
    if distance is None:
        print(f"{Path(test).name}: {name}")
    else:
        print(f"{Path(test).name}: {name}  (distance {distance:.1f}, threshold {THRESHOLD})")

terminal

python who_is_this.py faces test_jay_chou.jpg

five photos it had never been trained on

test_jay_chou.jpg:        jay_chou       (distance 54.6, threshold 56)
test_michelle_yeoh.jpg:   michelle_yeoh  (distance 34.6, threshold 56)
hard_jay_chou.jpg:        I do not know  (distance 64.3, threshold 56)
stranger_zhang_ziyi.jpg:  I do not know  (distance 59.2, threshold 56)
stranger_jackie_chan.jpg: michelle_yeoh  (distance 54.0, threshold 56)
Three test photos with the answer the program gave under each one
The first three answers, on photos it had never trained on. The third is a man it had learned, refused because that photo is lit from the side. The two strangers are not shown here: those photos come from a research dataset that is not ours to republish. สามคำตอบแรก บนรูปที่มันไม่เคยเรียนมา รูปที่สามคือคนที่มันเคยเรียนมาแล้ว แต่ถูกปฏิเสธเพราะรูปนั้นแสงเข้าด้านข้าง ส่วนคนแปลกหน้าสองคนไม่ได้แสดงที่นี่ เพราะมาจากชุดข้อมูลวิจัยที่เราไม่มีสิทธิ์เผยแพร่ซ้ำ 前三个答案,用的是它没训练过的照片。第三张是它确实学过的人,被拒答是因为那张照片是侧光。两个陈生人没有放在这里:那些照片来自一个研究数据集,我们没有转发的权利。
YOU GET Two right answers, one honest refusal about a stranger — and then two failures, in opposite directions. Line three refuses a man it had learned, because that concert photo is lit from the side. Line five is worse: it looked at Jackie Chan and said Michelle Yeoh, with more confidence (54.0) than it had about her own photo on line one. Those last two lines are the reason for the next section. ตอบถูกสองครั้ง ปฏิเสธอย่างซื่อสัตย์หนึ่งครั้งกับคนแปลกหน้า แล้วตามด้วยความผิดพลาดสองแบบที่ตรงกันข้าม บรรทัดที่สามปฏิเสธคนที่มันเคยเรียนมาแล้ว เพราะรูปคอนเสิร์ตนั้นแสงเข้าด้านข้าง บรรทัดที่ห้าแย่กว่านั้น มันมองรูปเชื่อวง เสินหลง แล้วตอบว่ามิชเสล เยอห์ ด้วยความมั่นใจ (54.0) มากกว่าตอนที่เจอรูปของเธอเองในบรรทัดแรก สองบรรทัดท้ายนี้คือเหตุผลของหัวข้อถัดไป 两次答对,一次对陈生人诚实地拒答 —— 然后是两次失败,方向相反。第三行拒答的是它确实学过的人,因为那张演唱会照片是侧光。第五行更糟:它看着成龙,说这是杨紫琼,而且比第一行对她本人的照片还更“有把握”(54.0)。最后这两行,就是下一节存在的理由。

5 📏 How do you know it works? · จะรู้ได้อย่างไรว่ามันใช้ได้ · 你怎么知道它好用?

🇬🇧 English

One demo photo proves nothing: pick a lucky photo and it looks brilliant, pick an awkward one and it looks broken. So we measured. Eight training photos each, then 25 new photos of the same two people and 76 photos of people it had never seen — including Jackie Chan, Zhang Ziyi, Gong Li and Michael Chang, so that “stranger” was not made easy by every stranger looking nothing like them.

Read the first row, then the two rows under it.

🇹🇭 ไทย

รูปเดโมรูปเดียวพิสูจน์อะไรไม่ได้ เลือกรูปที่เข้าทางมันก็ดูเก่ง เลือกรูปที่ยากก็ดูพัง เราจึงวัดจริง ใช้รูปฝึกคนละแปดรูป แล้วทดสอบด้วยรูปใหม่ของสองคนนั้น 25 รูป และรูปของคนที่ไม่เคยเห็น 76 รูป รวมถึงเชื่อวง จางอี้โจว กงลี่ และไมเคิล จาง เพื่อไม่ให้งานแยกคนแปลกหน้าง่ายเกินไป

อ่านแถวแรก แล้วจึงอ่านสองแถวถัดมา

🇨🇳 中文

一张演示照片证明不了什么:挑一张顺的它就像天才,挑一张别扭的它就像坏了。所以我们做了测量。每人八张训练照,然后用这两人的 25 张新照片它没见过的 76 张照片来测 —— 里面包括成龙、章子怡、巩俐和张德培,这样“陈生人”这一关就不会因为每个陈生人都长得不像他们而变简单。

先读第一行,再读它下面那两行。

resultผล结果
Which of the two is it? (no threshold)เป็นคนไหนในสองคน (ไม่ใช้เกณฑ์)是两人中的哪一个?(不用阀值)23 / 25
Same person, distance rangeคนเดียวกัน ช่วงระยะห่าง同一个人,距离范围34.6 – 77.8
Stranger, distance rangeคนแปลกหน้า ช่วงระยะห่าง陈生人,距离范围52.1 – 88.7
Best threshold, and how often it is rightเกณฑ์ที่ดีที่สุด และความถูกต้อง最佳阀值,以及正确率56 → 85.1%
At that threshold: known people recognisedที่เกณฑ์นั้น คนที่รู้จักที่ถูกจำได้在该阀值下:认识的人被认出13 / 25
At that threshold: strangers refusedที่เกณฑ์นั้น คนแปลกหน้าที่ถูกปฏิเสธ在该阀值下:陈生人被拒绝73 / 76
THE REAL LESSON The two ranges overlap: one of the two people scored 77.8, a stranger scored 52.1. No single threshold can separate them. The best one still turned away 12 of 25 photos of people it knew, and still let 3 strangers in. Here is how bad the overlap is: while checking whether two old photos really were Michelle Yeoh, we trained on her six clearest photos and scored them — 54.6 and 57.6. Jackie Chan, scored by the same model, got 54.0. The program could not tell us whether her own photos were her, and was more sure about a man. We had to settle it from the photo descriptions instead. So: the program is good at the closed question, “which of these two is it” — 23 out of 25. It is bad at the open question, “is this anyone I know”. ช่วงทั้งสองทับกัน หนึ่งในสองคนนั้นได้ 77.8 ส่วนคนแปลกหน้าได้ 52.1 ไม่มีเกณฑ์ค่าเดียวที่แยกสองกลุ่มนี้ได้ เกณฑ์ที่ดีที่สุดยังปฏิเสธรูปของคนที่มันรู้จักไป 12 จาก 25 รูป และยังปล่อยคนแปลกหน้าผ่ามา 3 คน ตัวอย่างว่าการทับซ้อนนี้หนักแค่ไหน ตอนตรวจสอบว่ารูปเก่าสองรูปเป็นมิชเสล เยอห์ จริงหรือไม่ เราฝึกด้วยรูปที่ชัดที่สุดหกรูปของเธอแล้วตรวจสอบ ได้ 54.6 และ 57.6 ส่วนเชื่อวง จากโมเดลเดียวกัน ได้ 54.0 โปรแกรมบอกไม่ได้ว่ารูปของเธอเป็นเธอหรือไม่ และมั่นใจกับผู้ชายมากกว่า เราจึงต้องตัดสินจากคำอธิบายภาพแทน สรุปคือ โปรแกรมทำได้ดีกับคำถามปลายปิดว่า “เป็นคนไหนในสองคนนี้” ได้ 23 จาก 25 แต่ทำได้แย่กับคำถามปลายเปิดว่า “คนนี้เป็นคนที่เรารู้จักหรือไม่” 两个范围是重叠的:这两人中有人得 77.8,而某个陈生人得 52.1。没有任何单一阀值能把它们分开。最好的那个仍然把 25 张认识的人的照片挡掉了 12 张,同时还放进了 3 个陈生人。重叠有多严重?我们核对两张旧照片是不是杨紫琼时,用她最清晰的六张训练后打分 —— 54.6 和 57.6。同一个模型给成龙打出的是54.0。程序无法告诉我们她自己的照片是不是她,却对一位男演员更有把握。最后我们是靠照片说明才确认的。所以:它擅长封闭问题 —— “这是两人中的哪一个”,25 次对 23 次。它不擅长开放问题 —— “这个人我认识吗”。

6 📷 The camera: find faces, name nobody · กล้อง: หาใบหน้า ไม่บอกชื่อ · 摄像头:只找脸,不认人

🇬🇧 English

Back to the first job, on a live picture. Read a frame, find every face in it, draw a box, repeat. A camera is just a lot of photos, quickly.

Every face it finds gets a green box labelled with its size in pixels, and a running count sits in the corner. Point it at an empty room and the count stays at nought.

This one deliberately stops there. It does not try to say who anyone is — after section 5 you know that naming is the half that gets it wrong, and a program that quietly puts a name on a stranger is worse than one that says nothing at all.

MIN_FACE is the dial worth playing with: raise it and distant faces are ignored, lower it and the detector starts finding faces in the furniture.

🇹🇭 ไทย

กลับมาที่งานแรก แต่ทำกับภาพสด อ่านหนึ่งเฟรม หาทุกใบหน้าในนั้น วาดกล่อง แล้ววนซ้ำ กล้องก็คือภาพจำนวนมากที่มาเร็ว ๆ เท่านั้น

ทุกใบหน้าที่เจอจะได้กล่องสีเขียวพร้อมป้ายบอกขนาดเป็นพิกเซล และมีตัวนับอยู่ที่มุมภาพ ถ้าหันไปที่ห้องว่าง ตัวนับก็จะอยู่ที่ศูนย์

โปรแกรมนี้ตั้งใจหยุดแค่นั้น มันไม่พยายามบอกว่าใครเป็นใคร หลังอ่านหัวข้อ 5 แล้วคุณรู้ว่าการบอกชื่อคือส่วนที่ผิดพลาด และโปรแกรมที่เงียบ ๆ เอาชื่อไปแปะให้คนแปลกหน้านั้นแย่กว่าโปรแกรมที่ไม่พูดอะไรเลย

MIN_FACE คือปุ่มที่ควรลองปรับ เพิ่มค่าแล้วใบหน้าที่อยู่ไกลจะถูกมองข้าม ลดค่าแล้วตัวตรวจจับจะเริ่มเจอใบหน้าในเฟอร์นิเจอร์

🇨🇳 中文

回到第一件事,只不过对象是实时画面。读一帧,找出里面每一张脸,画个框,重复。摄像头不过是很多张快速到来的照片。

它找到的每一张脸都会得到一个绿框,框上标着以像素为单位的尺寸,画面角落还有一个计数。对着空房间,计数就一直是零。

这个程序特意就停在这里。它试图说出谁是谁 —— 读过第五节你就知道,认名字才是会出错的那一半,而一个悄悄把名字安在陌生人头上的程序,比什么都不说的程序更糟。

MIN_FACE 是值得调着玩的旋钮:调高,远处的脸会被忽略;调低,检测器就会开始在家具上找到人脸。

watch_camera.py

"""watch_camera.py - find faces in the laptop camera picture.

    python watch_camera.py

It draws a box around every face it can see, and counts them. It does not try to
say who anyone is. That is the other half of the job, and section 5 shows how
often it gets that wrong.

Press q to stop. Nothing is recorded: each frame is looked at and thrown away.
"""
import cv2

CAMERA = 0             # 0 is the built-in laptop camera. Try 1 for a plugged-in one.
MIN_FACE = 80          # ignore anything smaller than this, in pixels
CASCADE = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
detector = cv2.CascadeClassifier(CASCADE)


def find_faces(frame):
    """Every face in one camera frame, as a box (x, y, width, height)."""
    grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)      # the detector ignores colour
    return detector.detectMultiScale(grey,
                                     scaleFactor=1.1,
                                     minNeighbors=5,
                                     minSize=(MIN_FACE, MIN_FACE))


if __name__ == "__main__":
    camera = cv2.VideoCapture(CAMERA)
    if not camera.isOpened():
        raise SystemExit("cannot open the camera - is another program using it?")
    print("camera on. Press q to stop.")
    while True:
        ok, frame = camera.read()
        if not ok:
            break
        faces = find_faces(frame)
        for (x, y, w, h) in faces:
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 200, 0), 2)
            cv2.putText(frame, f"{w} by {h}", (x, y - 8),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 200, 0), 2)
        cv2.putText(frame, f"faces: {len(faces)}", (10, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 200, 0), 2)
        cv2.imshow("find a face - press q to quit", frame)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            break
    camera.release()
    cv2.destroyAllWindows()

terminal

python watch_camera.py

a real run on this laptop, with nobody in front of the camera

camera on. Press q to stop.
   faces: 0
(camera 640x480, 320 frames read, nothing recorded)
THINK BEFORE YOU RUN THIS A camera collects biometric data from everyone in the room, including people who never walked up and volunteered. That is true even though this program names nobody. Run it on your own face, or with classmates who said yes in writing; do not leave it running in a classroom, and do not add frame-saving to it. What makes this version safe to run is exactly what was left out: it finds faces and forgets them. If you put naming back in, everything in section 5 applies again — and it still must not decide who enters a building or who is marked absent. กล้องเก็บข้อมูลชีวมิติจากทุกคนในห้อง รวมถึงคนที่ไม่ได้เดินมาอาสา ข้อนี้จริงแม้ว่าโปรแกรมนี้จะไม่บอกชื่อใครก็ตาม ให้รันกับหน้าตัวเอง หรือกับเพื่อนที่ยินยอมเป็นลายลักษณ์อักษร อย่าปล่อยทิ้งไว้ในห้องเรียน และอย่าเพิ่มการบันทึกเฟรมเข้าไป สิ่งที่ทำให้รุ่นนี้ปลอดภัยคือสิ่งที่ไม่ได้ใส่เข้าไปนั่นเอง มันหาใบหน้าแล้วก็ลืมทันที ถ้าคุณใส่การบอกชื่อกลับเข้าไป ทุกอย่างในหัวข้อ 5 จะกลับมาใช้อีกครั้ง และมันก็ยังต้องไม่เป็นผู้ตัดสินว่าใครเข้าอาคารได้ หรือใครถูกบันทึกว่าขาดเรียน 摄像头会收集房间里所有人的生物特征数据,包括那些从没主动走上前来的人。即使这个程序不说出任何人的名字,这一点依然成立。用你自己的脸跑,或者和书面同意过的同学一起跑;不要把它放在教室里一直开着,也不要给它加上保存画面的功能。让这个版本可以放心运行的,恰恰是没有被加进去的那部分:它找到脸,然后就忘掉。如果你把认名字加回去,第五节讲的一切就又都适用了 —— 而且它依然不该决定谁能进楼、谁被记为缺勤。