파이톤으로 얼굴 인식을 하는 이야기

질문 소개


최근에 나는 1700장의 사진에서 자신의 사진을 찾아야 하는 위치에 있다는 것을 발견했다.나는 앉아서 그렇게 많은 사진을 볼 성격이 없기 때문에 인코딩을 통해 그것을 벗어나는 것이 유일한 해결 방안이라고 결정했다.이것 또한 나로 하여금python을 사용하여 웹 개발자로서 반드시 깊이 있게 공부하지 말아야 할 일을 할 수 있게 한다.주말에 나는 작은 프로그램을 써서 얼굴 검사와 식별을 이용하여 빅데이터에서 나의 이미지를 집중적으로 찾아서 다른 디렉터리에 붙였다.

선결 조건


이 실험을 다시 만들려면 다음 라이브러리를 알아야 합니다.
  • face_recognition: 아주 훌륭하고 매우 간단하며 사용하기 쉬운 라이브러리로 얼굴 검측과 얼굴 식별에 관련된 모든 복잡성을 추상화한다.
  • image_to_numpy: 또 하나의 훌륭한 도서관입니다. 작가는 윗글과 같습니다.NumPy 배열에 이미지 파일을 로드하는 데 사용됩니다.(추가 세부 정보는 나중에 제공됩니다.)
  • opencv-python: OpenCV 라이브러리의 파이톤 귀속
  • 설치 프로그램


    프로젝트 디렉토리


    ./
    │   main.py
    │   logger.py    
    │
    └───face_found
    │   │   image.JPG
    │   │   _image.JPG
    │   │   ...
    │
    └───images
    │   │   image.JPG
    │   │   ...
    │
    └───source_dataset
    │   │   image.JPG
    │   │   ...
    │   
    
  • main.py는 이 프로젝트의 핵심을 포함한다.
  • logger.py는 메시지의 심각성에 따라 컬러 로그를 출력하는 간단한 로그 서비스입니다.You can check it out here!
  • images 디렉토리에는 .JPG 형식의 모든 이미지가 포함됩니다.
  • source_dataset 디렉터리는 .JPG 형식으로 얼굴을 검색해야 하는 사람의 샘플 이미지를 포함한다.
  • face_found 디렉토리는 검색 결과의 이미지 드롭다운 상자입니다.
  • 코드 흐름


    프로그램이 먼저 source_dataset 디렉터리를 훑어보았습니다.디렉터리의 모든 이미지에 대해 얼굴 인코딩이 추출되어 하나의 그룹에 저장됩니다.이것들을 '이미 알고 있는 얼굴' 이라고 부르자.그리고 코드는 images 디렉터리를 계속 훑어보았다.모든 이미지에 대해 모든 사람의 얼굴 위치를 추출한다.그리고 이 위치로 모든 사람의 얼굴 코드를 추출합니다.이것들을 미지의 얼굴이라고 부르자.현재 모든 알 수 없는 얼굴과 모든 알 수 있는 얼굴을 비교하여 어떤 유사성이 있는지 확인합니다.유사성이 있는 경우 이미지는 face_found 디렉토리에 저장됩니다.

    코드 해석


    """INIT"""
    SOURCE_DATA_DIR = "source_dataset/"
    IMAGE_SET_DIR = "images/"
    FACE_FOUND_DIR = "face_found/"
    TOLERANCE = 0.5
    FRAME_THICKNESS = 4
    COLOR = [0, 0, 255]
    DETECTION_MODEL = "hog"
    
    log = Logger(os.path.basename(__file__))
    
    source_faces = []  # known faces
    
    start = time.perf_counter()
    
    이 부분의 코드는 스크립트에 기본 조건을 설정했다.여기서 주의해야 할 것은 TOLERANCE,DETECTION_MODELtime.perf_counter()이다.
  • TOLERANCE는 비교가 얼마나 엄격해야 하는지(이미 알고 있는 얼굴과 미지의 얼굴 사이의 거리)를 평가하는 것이다.낮은 값은 더욱 엄격하고, 높은 값은 더욱 관용적이다.face_recognition의 문서에서 0.6은 최상의 성능의 전형적인 값이라고 언급했다.나는 처음 0.7을 시험해 본 적이 있는데, 그 다음은 0.6이다.이것은 일부 일치하지 않는 일치를 초래했다.결국 나는 0.5점만 받았다.
  • DETECTION_MODEL는 우리가 얼굴 검사를 실시하는 방법(미리 훈련한 모형을 읽는다)이다.이 프로젝트에서 나는 처음에 CNN(권적신경망) 모델을 고려했는데 이것은 주로 컴퓨터 시각에 사용되는 신경망이다.이를 실현하기 위해서는 CUDA를 지원하는 GPU가 있고 dlibface_recognition기계학습과 데이터 분석의 밑바닥 핵심)이 이 하드웨어를 식별할 수 있도록 확보해야 한다.
    import dlib
    print(dlib.cuda.get_num_devices())
    
    만약 위의 코드 세션이 우리에게 >=1의 값을 주었다면, 우리는 CNN을 계속 사용할 수 있다.우리는 print(dlib.DLIB_USE_CUDA)를 사용하여 dlib가 GPU를 사용하고 있는지 검사할 수 있다.이것은 이상적인 반환True이어야 하지만 그렇지 않으면 우리는 간단하게 dlib를 CUDA를 사용하도록 설정할 수 있다. dlib.DLIB_USE_CUDA = True 모든 것이 정상적으로 작동해야 한다.그러나 내 예에서 CNN을 이용해 얼굴 위치를 찾으려고 했을 때MemoryError: std::bad_alloc를 얻었다.From what I understand, 이것은 내가 불러온 이미지 해상도가 매우 높기 때문이다. (최대: 6016×4016 픽셀)내가 사용할 수 있는 해결 방안은 모든 이미지의 해상도를 낮추거나 CNN에서 멀리 떨어지는 것이다.현재 나는 HOG(방향 사다리 직사각형)를 사용하기로 결정했다.HOG는 SVM과 같은 분류 알고리즘을 이용해 얼굴의 존재를 확인한다.HOG와 CNN의 비교는 HOG가 시간을 계산하는 데는 더 빠르지만 정확성 면에서는 더 신뢰할 수 없다는 것을 보여준다.CNN은 왕왕 가장 정확하다.너는 Maël Fabien's work의 얼굴 검측에 관한 글을 읽을 수 있는데, 그 중에서 그는 이 두 가지 모델에 대해 깊이 있게 소개했다.
  • start = time.perf_counter() 시동 계수기.이 값은 마지막에 사용되며 코드 성능의 기본 시간 도량으로 사용됩니다.
  • FRAME_THICKNESSCOLOR는 선택 사항이며 무시할 수 있습니다.
    """LOADING SOURCE IMAGES"""
    log.info(f"loading source images")
    for index, filename in enumerate(os.listdir(SOURCE_DATA_DIR)):
        if filename.endswith("JPG"):
            log.info(f"processing source image {filename}")
            img_path = SOURCE_DATA_DIR + filename
            # using image_to_numpy to load img file -> fixes image orientation -> face encoding is found
            img = image_to_numpy.load_image_file(img_path)
            try:
                source_img_encoding = face_recognition.face_encodings(img)[0]
                log.success("face encoding found")
                source_faces.append(source_img_encoding)
            except IndexError:
                log.error("no face detected")
    
    if (len(os.listdir(SOURCE_DATA_DIR)) - 1) - len(source_faces) != 0:  # -1 for .gitignore
        log.warn(f"{str(len(source_faces))} faces found in {str(len(os.listdir(SOURCE_DATA_DIR)))} images")
    
    그리고 우리는 디렉터리의 모든 그림을 계속 훑어보았다.대부분이 표준python 내용입니다.만약 조건source_dataset이 좀 거칠지만 그 존재는 무시하기 위한 것이다.gitignore 파일입니다.내 모든 사진이 가짜니까.JPG, 나는 100% 실패하지 않을 것을 보증한다.
    다음에 우리는 이미지 경로를 구성하여 그것을 매개 변수로 전달한다if filename.endswith("JPG"):.나에게 있어서 이것은 이 코드에서 가장 재미있는 점이다. 만약 네가 image_to_numpy.load_image_file() 을 사용한다면, 너는 그것이 이미 자신의 face_recognition 함수를 가지고 있다는 것을 알고 있고, 그림의 NumPy 수조를 되돌려 주기 때문이다.그림을 불러올 수 있는 다른 라이브러리를 사용해야 하는 이유는 그림의 방향 때문입니다.이 코드의 최초 버전에서, 나는 몇몇 사람들의 얼굴이 검출되지 못해 곤혹스러웠다.After some research 이미지가 옆으로 돌면 얼굴 검출이 완전히 실패할 것이라는 것을 알게 되었다.load_image_file()의 로드 이미지 기능은 EXIF 방향 표시자를 읽고 필요에 따라 이미지 파일을 회전합니다.
    마지막으로, 불러온 이미지 파일을 image_to_numpy 함수에 전달합니다.이것은 그림의 모든 사람의 얼굴 인코딩 목록을 되돌려줍니다. 그러나 face_recognition.face_encodings() 내 단일 스냅샷만 포함하는 것을 알고 있기 때문에, 나는 그룹의 첫 번째 요소에 간단하게 접근할 수 있습니다.따라서 이 선의 끝은source_dataset이다.이 비트는 [0] 블록에 봉인되어 있어 얼굴 검사에 실패하면 이상이 붕괴되지 않습니다. (옆면에 그림을 불러올 때처럼)그렇지 않으면, 이 인코딩은 코드의 try-except 부분에서 실례화된 source_faces (기지면) 대상에 추가됩니다.
    마지막으로, 인코딩된faces 그룹의 길이가 디렉터리의 전체 항목과 같은지 확인합니다. (.gitignore 빼기) 그렇지 않으면 로그를 출력합니다.이것은 나로 하여금 그중의 괴벽을 이해하게 했다.
    """MAIN PROCESS"""
    log.info(f"Processing dataset")
    for index, filename in enumerate(os.listdir(IMAGE_SET_DIR)):
        if filename.endswith("JPG"):
            log.info(f"processing dataset image {filename} ({index + 1}/{len(os.listdir(IMAGE_SET_DIR))})")
            img_path = IMAGE_SET_DIR + filename
            img = image_to_numpy.load_image_file(img_path)
            try:
                locations = face_recognition.face_locations(img, model=DETECTION_MODEL)
                encodings = face_recognition.face_encodings(img, locations)
                for face_encoding, face_location in zip(encodings, locations):
                    results = face_recognition.compare_faces(source_faces, face_encoding, TOLERANCE)
                    if True in results:
                        log.success("match found!")
                        # optional start
                        top_left = (face_location[3], face_location[0])
                        bottom_right = (face_location[1], face_location[2])
                        cv2.rectangle(img, top_left, bottom_right, COLOR, FRAME_THICKNESS)
                        cv2.imwrite(FACE_FOUND_DIR + "_" + filename, img)
                        # optional end
                        copy(img_path, FACE_FOUND_DIR)
                        break
                    else:
                        log.warn("no match found")
            except IndexError:
                log.error("no face detected")
            except Exception as err:
                log.error(f"error encountered: {err}")
    
    stop = time.perf_counter()
    
    log.info(f"Total time elapsed {stop - start:0.4f} seconds")
    
    현재, 우리는 INIT 디렉터리의 모든 그림을 읽고, 이를 이미 알고 있는 사람의 얼굴 목록과 비교하려고 시도합니다.코드의 시작 방식은 앞의 코드 블록과 같다.우리가 images 블록에 들어갔을 때 상황은 약간 변했다.이전에 우리는 모든 이미지가 try-except 디렉터리에서 나온 것을 알고 있었다. 그 중에는 m의 사진 한 장만 포함되어 있었다. 우리는 우리의 얼굴이 아닌 얼굴이 그가 찾은 얼굴 디렉터리에 나타날 때 공차를 조정해야 한다는 것을 알고 있었다.현재 디렉터리에는 그룹 스냅샷도 포함되어 있습니다.따라서 우리는 먼저 그림에 있는 모든 사람의 얼굴 위치를 찾을 것이다. source_dataset 그리고 이 위치를 사용하여 사람의 얼굴 인코딩을 얻을 것이다. locations = face_recognition.face_locations(img, model=DETECTION_MODEL)그림에서 찾은 모든 사람의 얼굴 인코딩 목록을 되돌려줍니다.그리고 우리는 모든 알고 있는/원본/내 얼굴encodings = face_recognition.face_encodings(img, locations)에 따라 이 인코딩을 반복해서 테스트했다.일치하는 경우, 우리는 그림을 results = face_recognition.compare_faces(source_faces, face_encoding, TOLERANCE) 디렉터리에 복사합니다: face_found.그렇지 않으면 다음 사람의 얼굴 인코딩을 계속하고 다음 그림을 보여 드리겠습니다.
    처음에 공차가 0.7과 0.6으로 설정되었을 때, 나는 copy(img_path, FACE_FOUND_DIR) 디렉터리에 사진이 있었고, 내 얼굴은 존재하지 않았다.선택할 수 있는 자리는 이곳에서 매우 편리하다.코드가 어느 면이 일치한다고 생각하든지 간에 우리가 추출한 면 위치face_found를 사용하여 그 위에 COLORFRAME_THICKENSS의 경계 상자를 그립니다.이 버전의 이미지는 이름 앞에 밑줄이 그어져 있습니다.이런 방식을 통해 우리는 언제든지 우리의 얼굴에 경계선이 없으면 공차를 조정해야 한다는 것을 안다.
    마지막 카운터를 멈추고 전체 과정을 인쇄하는 데 걸리는 총 시간

    결론과 앞으로의 일



    이 연습이 끝났을 때, 나는 cv2.rectangle(img, top_left, bottom_right, COLOR, FRAME_THICKNESS) 디렉터리에 804개의 그림을 가지고 있었다.만약 저장된 그림의 절반에 테두리가 있다면 402장의 그림이 일치할 것이다.사전에 몇 장의 사진이 나의 것인지 몰랐기 때문에 이 코드의 정확성을 판단하기가 매우 어렵다.이상적인 상황에서 코드를 수정하고 더 포함된 데이터 집합에 따라 실제 결과를 평가하기 위해 테스트를 해야 한다.이 코드는 1천700장의 사진을 처리하는 데 약 3.7시간(1천3312.6160초), 더 빠른 얼굴 검사 모델을 사용하는 데도 3.7시간(1천3312.6160초)이 소요된다.분포식 처리 모델에서 이 코드를 실현하면 더욱 시간을 절약할 수 있다.마지막으로, 이 프로그램은 낮은 해상도 이미지 변체를 만들고 CNN 모델을 사용하여 더 정확한 결과를 얻을 수 있는 추가된 코드 블록을 얻을 수 있다.
    그러나 현재로서는 1700장의 사진에서 약 400장을 추출할 수 있어 많은 노력을 기울일 필요가 없다.나에게 있어서 이것은 성공적인 이야기다.이 연습은python을 다시 복습하고 얼굴 인식을 할 수 있게 해 주었는데 그 자체가 재미있었다.전체 code repo here를 볼 수 있습니다.

    좋은 웹페이지 즐겨찾기