bskyvision RSS 태그 관리 글쓰기 방명록
2021-01-07 08:56:06
728x90

저는 오늘 마스크 착용 유무 판별기를 만들어보려고 합니다. 객체 검출(object detection)의 방법으로 만들 수도 있겠지만, 그렇게 하면 데이터셋을 준비하는 데 꽤 큰 노력을 들여야합니다. 이미지들에서 마스크 쓴 얼굴과 마스크를 쓰지 않은 얼굴을 labelImg와 같은 라벨링 툴로 일일이 라벨링을 해줘야하기 때문입니다. 라벨링을 한다는 것은 이미지에서 마스크 쓴 얼굴과 마스크를 쓰지 얼굴을 모두 찾아서 거기에 바운딩 박스(bounding box)를 그려준 후에, 마스크를 쓰지 않은 얼굴에 0, 마스크를 쓴 얼굴에 1 이런식으로 라벨을 부여하는 것을 의미합니다. 이게 꽤 많은 시간을 들여야 하는 작업입니다. 

 

그래서 좀 더 쉽고 간단하게 마스크 착용 유무 판별기를 만들 수 없나 고민하다가 택한 방법은 다음과 같습니다.

 

 

1. 먼저 이미 훈련된 얼굴 검출기로 얼굴들을 검출합니다.

이때 주의해야할 것은 마스크를 쓴 얼굴도 검출할 수 있는 얼굴 검출기여야 한다는 것입니다. cvlib 라이브러리의 얼굴 검출기는 마스크를 쓴 얼굴들도 잘 검출하더라고요. 그래서 저는 그것을 선택했습니다. 

 

2. 마스크를 쓴 얼굴인지, 마스크를 쓰지 않은 얼굴인지 판별할 수 있는 이미지 분류기를 만듭니다. 

이를 위해서는 마스크를 쓴 얼굴 이미지들과 마스크를 쓰지 않은 얼굴 이미지들이 필요합니다. 그래야 이미지 이진 분류기를 훈련시킬 수 있기 때문입니다. 저는 전이학습(transfer learning)을 이용해서 마스크 착용 유무 분류기를 훈련시키겠습니다. 

 

3. 1번째 단계에서 검출된 얼굴들을 마스크를 쓴 얼굴과 마스크를 쓰지 않은 얼굴로 분류합니다. 

 

 

일단 결과물을 보여드리고 한 단계씩 설명드리도록 하겠습니다. 

 

youtu.be/DvnIxNjC8sI

 

여러 명이 동시에 등장해도 잘 판별해냅니다. 프라이버시를 위해 촬영하진 않았지만요.ㅎㅎ 

 

또한 마스크를 안 쓰고 손으로 입주변을 가리는 분들이 계실 수 있을 것 같아서, 손으로 입주변을 가릴 때는 어떻게 판별해내는지 테스트해봤습니다. 이때도 역시 노 마스크로 판별해냅니다. (2021년 1월 27일에 꼬장스카이비전 님께서 남겨주신 좋은 질문 감사합니다.)

 

 

 

 

자, 그럼 이제 마스크 착용 유무 판별기를 함께 만들어보겠습니다. 

 

마스크 착용 유무 판별기 만들기

1. 마스크 착용 얼굴, 마스크 미착용 얼굴 데이터 수집

웹캠을 이용해서 촬영되는 영상에 얼굴 검출기를 적용해서 얻은 얼굴들을 잘라서 저장합니다. 한 번은 마스크를 착용한 채 촬영하고, 한 번은 마스크를 미착용한 상태로 촬영합니다. 마스크를 착용한 상태에서 얻은 얼굴 이미지들은 ./mask 디렉토리에 저장하고, 마스크 미착용 얼굴 이미지들은 ./nomask 디렉토리에 저장합니다. 모든 프레임의 영상을 다루면, 얼굴 이미지에 큰 차이가 없기 때문에 저는 8장마다 한 장의 프레임을 선택해서 얼굴 이미지들을 저장했습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import cv2
import cvlib as cv
 
# open webcam (웹캠 열기)
webcam = cv2.VideoCapture(0)
 
if not webcam.isOpened():
    print("Could not open webcam")
    exit()
    
 
sample_num = 0    
captured_num = 0
    
# loop through frames
while webcam.isOpened():
    
    # read frame from webcam 
    status, frame = webcam.read()
    sample_num = sample_num + 1
    
    if not status:
        break
 
    # 이미지 내 얼굴 검출
    face, confidence = cv.detect_face(frame)
    
    print(face)
    print(confidence)
 
    # loop through detected faces
    for idx, f in enumerate(face):
        
        (startX, startY) = f[0], f[1]
        (endX, endY) = f[2], f[3]
 
 
        if sample_num % 8  == 0:
            captured_num = captured_num + 1
            face_in_img = frame[startY:endY, startX:endX, :]
            cv2.imwrite('./mask/face'+str(captured_num)+'.jpg', face_in_img) # 마스크 미착용 데이터 수집시 주석처리
            # cv2.imwrite('./nomask/face'+str(captured_num)+'.jpg', face_in_img) # 마스크 미착용 데이터 수집시 주석해제
 
 
    # display output
    cv2.imshow("captured frames", frame)        
    
    # press "Q" to stop
    if cv2.waitKey(1& 0xFF == ord('q'):
        break
    
# release resources
webcam.release()
cv2.destroyAllWindows()   
cs

 

./mask 디렉토리를 확인해보면, 마스크를 착용한 얼굴들이 잘 저장되어 있는 것을 확인하실 수 있습니다. ./nomask 디렉토리에는 마찬가지로 마스크를 미착용한 얼굴들이 잘 저장되어 있습니다. 그렇게 많은 장수의 이미지가 필요하지 않습니다. mask 이미지, nomask 이미지 각각 200장 정도면 충분한 것 같습니다. 

 

 

그 다음에 제대로 검출되지 않은 이미지들이 있다면 삭제해줍니다. 별로 많진 않더라고요. 

 

2. 마스크 착용, 미착용 판별기 훈련하기

데이터를 수집했으니 이제 마스크 착용, 미착용 판별기를 훈련시킬 차례입니다. ImageNet에 훈련된 ResNet50을 가지고 와서 전이학습을 이용해서 커스터마이징 해줍니다. 

 

 

ResNet50의 마지막 층을 제거한 것에 flatten, fc 층, batch normalization, fc 층을 추가한 것을 마스크 착용 얼굴 이미지와 마스크 미착용 얼굴 이미지들과 그것에 해당하는 라벨값으로 훈련시켰습니다. 저는 마스크 착용 이미지에 1의 라벨을, 마스크 미착용 이미지에 0의 라벨을 부여했습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import os
import numpy as np
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Flatten, BatchNormalization
from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input
from tensorflow.keras.preprocessing.image import load_img, img_to_array
 
 
path_dir1 = './nomask/'
path_dir2 = './mask/'
 
file_list1 = os.listdir(path_dir1) # path에 존재하는 파일 목록 가져오기
file_list2 = os.listdir(path_dir2)
 
file_list1_num = len(file_list1)
file_list2_num = len(file_list2)
 
file_num = file_list1_num + file_list2_num
 
 
#%% 이미지 전처리
num = 0;
all_img = np.float32(np.zeros((file_num, 2242243))) 
all_label = np.float64(np.zeros((file_num, 1)))
 
for img_name in file_list1:
    img_path = path_dir1+img_name
    img = load_img(img_path, target_size=(224224))
    
    x = img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    all_img[num, :, :, :] = x
    
    all_label[num] = 0 # nomask
    num = num + 1
 
for img_name in file_list2:
    img_path = path_dir2+img_name
    img = load_img(img_path, target_size=(224224))
    
    x = img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    all_img[num, :, :, :] = x
    
    all_label[num] = 1 # mask
    num = num + 1
 
 
# 데이터셋 섞기(적절하게 훈련되게 하기 위함) 
n_elem = all_label.shape[0]
indices = np.random.choice(n_elem, size=n_elem, replace=False)
 
all_label = all_label[indices]
all_img = all_img[indices]
 
 
# 훈련셋 테스트셋 분할
num_train = int(np.round(all_label.shape[0]*0.8))
num_test = int(np.round(all_label.shape[0]*0.2))
 
train_img = all_img[0:num_train, :, :, :]
test_img = all_img[num_train:, :, :, :] 
 
train_label = all_label[0:num_train]
test_label = all_label[num_train:]
 
 
 
#%% 
# create the base pre-trained model
IMG_SHAPE = (2242243)
 
base_model = ResNet50(input_shape=IMG_SHAPE, weights='imagenet', include_top=False)
base_model.trainable = False
base_model.summary()
print("Number of layers in the base model: "len(base_model.layers))
 
flatten_layer = Flatten()
dense_layer1 = Dense(128, activation='relu')
bn_layer1 = BatchNormalization()
dense_layer2 = Dense(1, activation=tf.nn.sigmoid)
 
model = Sequential([
        base_model,
        flatten_layer,
        dense_layer1,
        bn_layer1,
        dense_layer2,
        ])
 
base_learning_rate = 0.001
model.compile(optimizer=tf.keras.optimizers.Adam(lr=base_learning_rate),
              loss='binary_crossentropy',
              metrics=['accuracy'])
model.summary()
 
model.fit(train_img, train_label, epochs=10, batch_size=16, validation_data = (test_img, test_label))
 
 
# save model
model.save("model.h5")
 
print("Saved model to disk")  
cs

 

위 코드를 실행하면 model.h5를 얻게 되는데, 여기에 바로 마스크 착용 유무 판별기가 들어가 있습니다. 

 

3. 영상에서 얼굴 영역 검출 후 마스크 썼는지 안 썼는지 판별

이제 모든 준비를 마쳤습니다. 촬영되는 영상에 나오는 인물들이 마스크를 썼는지 안 썼는지를 잘 판별해내는지 확인해보겠습니다. 마스크를 썼을 때는 초록색 바운딩 박스를 얼굴 주위에 그려줬고, 마스크를 쓰지 않았을 때는 빨간색 바운딩 박스를 그려줬습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# import necessary packages
import cvlib as cv
import cv2
import numpy as np
from tensorflow.keras.models import load_model
from tensorflow.keras.applications.resnet50 import preprocess_input
from tensorflow.keras.preprocessing.image import img_to_array
from PIL import ImageFont, ImageDraw, Image
 
 
model = load_model('model.h5')
model.summary()
 
# open webcam
webcam = cv2.VideoCapture(0)
 
 
if not webcam.isOpened():
    print("Could not open webcam")
    exit()
    
 
# loop through frames
while webcam.isOpened():
 
    # read frame from webcam 
    status, frame = webcam.read()
    
    if not status:
        print("Could not read frame")
        exit()
 
    # apply face detection
    face, confidence = cv.detect_face(frame)
 
    # loop through detected faces
    for idx, f in enumerate(face):
        
        (startX, startY) = f[0], f[1]
        (endX, endY) = f[2], f[3]
        
        if 0 <= startX <= frame.shape[1and 0 <= endX <= frame.shape[1and 0 <= startY <= frame.shape[0and 0 <= endY <= frame.shape[0]:
            
            face_region = frame[startY:endY, startX:endX]
            
            face_region1 = cv2.resize(face_region, (224224), interpolation = cv2.INTER_AREA)
            
            x = img_to_array(face_region1)
            x = np.expand_dims(x, axis=0)
            x = preprocess_input(x)
            
            prediction = model.predict(x)
 
            if prediction < 0.5# 마스크 착용으로 판별되면, 
                cv2.rectangle(frame, (startX,startY), (endX,endY), (0,0,255), 2)
                Y = startY - 10 if startY - 10 > 10 else startY + 10
                text = "No Mask ({:.2f}%)".format((1 - prediction[0][0])*100)
                cv2.putText(frame, text, (startX,Y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)
                
            else# 마스크 착용으로 판별되면
                cv2.rectangle(frame, (startX,startY), (endX,endY), (0,255,0), 2)
                Y = startY - 10 if startY - 10 > 10 else startY + 10
                text = "Mask ({:.2f}%)".format(prediction[0][0]*100)
                cv2.putText(frame, text, (startX,Y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)
                
    # display output
    cv2.imshow("mask nomask classify", frame)
 
    # press "Q" to stop
    if cv2.waitKey(1& 0xFF == ord('q'):
        break
    
# release resources
webcam.release()
cv2.destroyAllWindows() 
cs

 

위 코드를 실행한 결과를 캡쳐한 것이 바로 맨 위에 보여드린 영상입니다. 

 

마무리 인사

오랜만에 영상처리 관련된 글을 쓰는 것 같네요. 앞으로는 다시 영상처리 관련된 글 작성 비중을 높이려고 합니다. 아무래도 그것이 제 블로그를 구독해주시는 분들, 관심을 가져주신 분들에 대한 예의가 아닐까 싶네요. ㅎㅎ

 

내용과 관련된 질의는 언제든 환영하니, 댓글로 남겨주세요.^^

 

bskyvision의 추천글

[Anaconda+python] 전이학습 이용해서 가위, 바위, 보 분류기 만들기

전이학습(transfer learning) 재밌고 쉽게 이해하기

[ubuntu+python] 얼굴 검출

이미지 분류(image classification)와 물체 검출(object detection)의 차이는?  

 

 

(이 글은 2021-1-28에 최종적으로 수정되었습니다.)

댓글

방문해주신 모든 분들을 환영합니다.

* 글을 읽던 중에 궁금했던 부분은 질문해주세요.

* 칭찬, 지적, 의문, 격려, 감사표현 등을 남겨주세요.

* 최대한 답변 드리도록 노력하겠습니다.

* 욕설과 광고를 담은 댓글은 가차없이 삭제합니다.


  1. 이전 댓글 더보기
  2. @2021.05.28 18:50 ~$ 비밀댓글입니다 [댓글주소]  [수정/삭제]  [답글작성]
  3. @2021.05.28 21:13 ~$ 비밀댓글입니다 [댓글주소]  [수정/삭제]  [답글작성]
  4. BlogIcon bskyvision@2021.05.28 21:35 신고 ~$ 혹시 한사람에서만 샘플 추출하셨나요?? [댓글주소]  [수정/삭제]  [답글작성]
  5. @2021.05.28 22:03 ~$ 비밀댓글입니다 [댓글주소]  [수정/삭제]  [답글작성]
  6. ye@2021.05.28 23:40 ~$ Resnet으로 base 모델을 한 뒤에 Dense(128)의 128은 변경하면 안되는 값인가요? 또한, 여러번의 Dense 를 사용할경우도 궁금합니다.!(여러번의 은닉층?) [댓글주소]  [수정/삭제]  [답글작성]
  7. BlogIcon 파이썬..@2021.05.30 16:49 ~$ ---------------------------------------------------------------------------
    PermissionError Traceback (most recent call last)
    <ipython-input-3-17cd79919926> in <module>
    39 for img_name in file_list2:
    40 img_path = path_dir2+img_name
    ---> 41 img = load_img(img_path, target_size=(224, 224))
    42
    43 x = img_to_array(img)

    ~\anaconda3\lib\site-packages\tensorflow\python\keras\preprocessing\image.py in load_img(path, grayscale, color_mode, target_size, interpolation)
    298 ValueError: if interpolation method is not supported.
    299 """
    --> 300 return image.load_img(path, grayscale=grayscale, color_mode=color_mode,
    301 target_size=target_size, interpolation=interpolation)
    302

    ~\anaconda3\lib\site-packages\keras_preprocessing\image\utils.py in load_img(path, grayscale, color_mode, target_size, interpolation)
    111 raise ImportError('Could not import PIL.Image. '
    112 'The use of `load_img` requires PIL.')
    --> 113 with open(path, 'rb') as f:
    114 img = pil_image.open(io.BytesIO(f.read()))
    115 if color_mode == 'grayscale':

    PermissionError: [Errno 13] Permission denied: './mask/.ipynb_checkpoints'

    보기와같이 에러가뜨는데 구글링 해보니 경로 문제라고 하는데 주피터 바탕화면에 nomask, mask 폴더 생성후 실행하였는데도 이와같은 에러가 뜹니다...ㅜ [댓글주소]  [수정/삭제]  [답글작성]
  8. BlogIcon 파이썬..@2021.06.01 21:26 ~$ 혹시 영상 출력결과는 어떻게 도출하나요? [댓글주소]  [수정/삭제]  [답글작성]
  9. BlogIcon yoon1211@2021.06.15 02:15 ~$ 안녕하세요 , 첫번째 부분에서 캠이 켜지고 디렉토리에 저장이 되는건지 잘 모르겠네요. [댓글주소]  [수정/삭제]  [답글작성]
  10. python@2021.06.17 14:43 ~$ 안녕하세요, 코드 따라해보면서 실행까지는 성공했는데 마스크를 인식하지 못합니다 ㅠㅠ
    마스크를 써서 웹캠에 보여줘도 'no mask'라고 뜨고, 정확도는 더 떨어집니다.
    사진 개수를 늘려도 정확도가 나아지지 않는데 개선할 수 있는 방법이 있을까요?

    그리고 모델링할 때 아래와 같은 문구가 뜨는데 이건 무슨 뜻인지 알 수 있을까요? 구글링 해도 잘 안나와서요 ㅠㅠ

    I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2) [댓글주소]  [수정/삭제]  [답글작성]
  11. 강낭콩@2021.06.28 20:00 ~$ 안녕하세요 글 작성해주셔서 감사합니다 ㅜㅜ 도움이 많이 됐습니다ㅜㅜ 이 글에 있는 코드를 수정해서 프로젝트를 하나 만들었는데 제 깃허브 레포지토리에 출처를 기재하고 올려도 될까요? [댓글주소]  [수정/삭제]  [답글작성]
  12. abc@2021.07.05 16:09 ~$ 안녕하세요. 우선 블로그 글 잘 보고 있습니다ㅎㅎ
    현재 이 코드는 마스크를 착용한 얼굴, 착용하지 않은 얼굴을 학습시켜 착용 유무를 판단하게 만드는 코드인데, 저는 얼굴 대신 사람과 자동차를 구별하는 코드를 만들고자 합니다.

    이를 위해 사람 이미지 데이터과 자동차 이미지 데이터를 모아 각각 폴더로 나누어 학습시키고자 하는데, 블로거님께서 공유해주신 현재 코드에 이미지 파일만 바꿔서 적용해도 구별 가능한 것인지 궁금합니다. [댓글주소]  [수정/삭제]  [답글작성]
  13. BlogIcon Elile@2021.08.11 20:24 ~$ 안녕하세요!! 저는 블로거님의 코드로 공부중인 햇병아리 학생입니다!!
    코드를 해석하며 공부하다가 모르는 부분이 있어서 질문 남깁니다.
    if 0 <= startX <= frame.shape[1] and 0 <= endX <= frame.shape[1] and 0 <= startY <= frame.shape[0] and 0 <= endY <= frame.shape[0]:
    이부분 해석을 못하겠습니다.
    Y = startY - 10 if startY - 10 > 10 else startY + 10
    이 부분도 해석 부탁드립니다 ㅠㅠ
    그리고 text = "No Mask ({:.2f}%)".format((1 – prediction[0][0])*100) 이부분에서 [0][0]은 뭘 의미하는 것인가요??
    답변 부탁드립니다 :) [댓글주소]  [수정/삭제]  [답글작성]
  14. @2021.10.09 15:09 ~$ 비밀댓글입니다 [댓글주소]  [수정/삭제]  [답글작성]
  15. 학생@2021.10.18 12:47 ~$ 혹시 라즈베리 파이로도 만들 수 있나요?
    라즈베리파이로 만들려면 위에 rpi.gpio입력하면되나요? [댓글주소]  [수정/삭제]  [답글작성]
  16. 딥러닝 배우는 학생@2021.10.20 15:16 ~$ 안녕하세요 딥러닝을 배우고 있는 학생입니다!
    위에 방식을 따라하며 마스크 착용 유무 판단을 인식하는 모델을 제작하였습니다.
    성능을 판단하기 위해 궁금한 점이 있어 질문 남깁니다!

    Q. 만들어진 모델을 이용하여 성능을 평가하고 싶은데 혹시 방법을 아시나요?
    이미 만들어진 모델을 가지고는 성능을 평가 할 수 있는지 궁금합니다. 찾아 보았는데 잘 나오지 않아서 질문 드립니다!

    [댓글주소]  [수정/삭제]  [답글작성]
  17. @2021.10.22 15:04 ~$ 비밀댓글입니다 [댓글주소]  [수정/삭제]  [답글작성]
  18. 컴맹@2021.11.09 20:14 ~$ 안녕하세요 평소에도 글 잘 보고잇는 컴맹입니다..
    다름이 아니라 1번째를 실행했을때 아래와 같은 오류가 발생하는데 해결책을 알 수 있을까요?..
    cv2도 깔았고 bootstrap도 깔아봤는데 실행이 안되네요..

    Traceback (most recent call last):
    File "mask_2.py", line 1, in <module>
    import cv2
    File "C:\Users\yunharyu\workspace\cv2\__init__.py", line 180, in <module>
    bootstrap()
    File "C:\Users\yunharyu\workspace\cv2\__init__.py", line 152, in bootstrap
    native_module = importlib.import_module("cv2")
    File "C:\ProgramData\Miniconda3\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
    ImportError: DLL load failed: 지정된 모듈을 찾을 수 없습니다. [댓글주소]  [수정/삭제]  [답글작성]
    • BlogIcon 꼬장스카이비전@2021.11.10 10:25 ~$ [답글]: google에서
      "python cv2 import 지정된 모듈" 이라고 검색해보세요.

      그리고 나오는 결과 확인해서 cv2 설치 문제 따라해보시고요.
      그 중에 하나)-> https://skyseven73.tistory.com/2


      지금 보이는 예제 전에 python opencv 예제 찾아서 실행 먼저 해보시길 권장합니다 ^^

      [댓글주소]  [수정/삭제]
  19. @2021.11.11 16:09 ~$ 비밀댓글입니다 [댓글주소]  [수정/삭제]  [답글작성]
  20. rssong@2021.11.25 19:59 ~$ resnet50 과 같은 신경망을 안쓰고도 학습시키는게 가능할까요? [댓글주소]  [수정/삭제]  [답글작성]
  21. @2021.11.27 16:24 ~$ 비밀댓글입니다 [댓글주소]  [수정/삭제]  [답글작성]
guest@이름 ~$
guest@패스워드 ~$
guest@홈페이지주소작성 ~$

guest@댓글작성 ~$




bskyvision. Designed by bskyvision.