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. BlogIcon 꼬장스카이비젼@2021.01.27 08:26 ~$ 손으로 가린거랑 마스크로 가린거랑 구별 되나요?

    그리고 유튜브에 여기 페이지 링크 걸어둔거 마지막에 '를' 은 하이퍼링크에서 빼야할듯요

    https://www.youtube.com/watch?v=DvnIxNjC8sI
    여기 [댓글주소]  [수정/삭제]  [답글작성]
  2. BlogIcon 꼬장스카이비젼@2021.01.28 13:43 ~$ 오.. 제 댓글에도 드디어 긍정적인 반응이...
    회개하신건가요? [댓글주소]  [수정/삭제]  [답글작성]
  3. ㅎㅎㅎㅎ@2021.03.15 14:46 ~$ 혹시 webcam 영상은 어떠한 방식으로 가져오는지 알수있을까요??? 코딩초보입니다ㅜ [댓글주소]  [수정/삭제]  [답글작성]
  4. ㅎㅎㅎㅎ@2021.03.15 14:46 ~$ 혹시 webcam 영상을 가져오는 방식 알수있을까요. 코딩초보입니다 [댓글주소]  [수정/삭제]  [답글작성]
  5. jjwani@2021.03.23 03:21 ~$ 학습하는 코드에서 자꾸
    Traceback (most recent call last):
    File "/Users/jjwani/Tensor/learning.py", line 27, in <module>
    img = load_img(img_path, target_size=(224, 224))
    File "/Users/jjwani/.conda/envs/Tensor/lib/python3.8/site-packages/tensorflow/python/keras/preprocessing/image.py", line 299, in load_img
    return image.load_img(path, grayscale=grayscale, color_mode=color_mode,
    File "/Users/jjwani/.conda/envs/Tensor/lib/python3.8/site-packages/keras_preprocessing/image/utils.py", line 114, in load_img
    img = pil_image.open(io.BytesIO(f.read()))
    File "/Users/jjwani/.conda/envs/Tensor/lib/python3.8/site-packages/PIL/Image.py", line 2958, in open
    raise UnidentifiedImageError(
    PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7fd169a12180>
    이런 오류가 뜨는데 머가 잘못된건지 모르겠습니다... [댓글주소]  [수정/삭제]  [답글작성]
    • BlogIcon bskyvision@2021.03.23 20:42 신고 ~$ [답글]: https://medium.com/@psubin/cannot-identify-image-file-io-bytesio-object-at-0x7f901ee4ef68-137763049fa1

      저도 이유를 잘 모르겠네요ㅜ ㅎㅎ 한번 위 링크 글 참고해보시겠어요?? [댓글주소]  [수정/삭제]
  6. BlogIcon INU@2021.04.10 17:49 ~$ 대표하여 최고의 코드로 선정되었습니다. [댓글주소]  [수정/삭제]  [답글작성]
  7. 궁금해요!@2021.04.22 22:30 ~$ 먼저 코드와 자료 정말 감사합니다!!
    제가 따라 해보는 중인데 이미지 전처리 load_img 에서 오류가 나더라구요.. 혹시 왜 그러는지 알수있을까요??

    PermissionError Traceback (most recent call last)
    <ipython-input-1-22de63883dcd> in <module>
    28 for img_name in file_list1:
    29 img_path = path_dir1+img_name
    ---> 30 img = load_img(path=img_path, target_size=(224, 224))
    31
    32 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)
    297 ValueError: if interpolation method is not supported.
    298 """
    --> 299 return image.load_img(path, grayscale=grayscale, color_mode=color_mode,
    300 target_size=target_size, interpolation=interpolation) [댓글주소]  [수정/삭제]  [답글작성]
    • BlogIcon bskyvision@2021.04.22 23:08 신고 ~$ [답글]: 질문 감사합니다. 이미지 파일 경로가 잘못되진 않았는지 체크해보시겠어요? ㅎㅎ [댓글주소]  [수정/삭제]
    • BlogIcon 궁금해요!@2021.04.23 16:33 ~$ [답글]: path_dir1 = 'Desktop/img_file/nomask/'
      path_dir2 = 'Desktop/img_file/mask/'
      제가 경로만 변경했는데 이것 자체가 문제가 되나요?.. [댓글주소]  [수정/삭제]
    • BlogIcon bskyvision@2021.04.23 16:39 신고 ~$ [답글]: 혹시 nomask , mask 폴더를 만들어놓으셨나요?? [댓글주소]  [수정/삭제]
    • BlogIcon 궁금해요!@2021.04.24 11:05 ~$ [답글]: 폴더는 만들었구요! 오류가 난게 경로부분이랑 pil import 오류인데 재설치 해도 저 오류가 나네요..ㅜㅜ
      PermissionError Traceback (most recent call last)
      <ipython-input-1-30f95dbc0c43> in <module>
      28 for img_name in file_list1:
      29 img_path = path_dir1+img_name
      ---> 30 img = load_img(path=img_path, target_size=(224, 224))
      31
      32 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)
      297 ValueError: if interpolation method is not supported.
      298 """
      --> 299 return image.load_img(path, grayscale=grayscale, color_mode=color_mode,
      300 target_size=target_size, interpolation=interpolation)
      301

      ~\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: 'Desktop/img_file/nomask/.ipynb_checkpoints'
      [댓글주소]  [수정/삭제]
    • BlogIcon bskyvision@2021.04.24 13:16 신고 ~$ [답글]: 운영체제가 리눅스인가요? 아님 윈도우10이신가요?? [댓글주소]  [수정/삭제]
    • BlogIcon 궁금해요!@2021.04.24 22:04 ~$ [답글]: windows 8.1 입니다! [댓글주소]  [수정/삭제]
  8. potter@2021.04.29 14:21 ~$ 저는 손으로 입과 코를 가리면 마스크라고 인식하는데 어떻게 된걸까요 ㅜㅜ [댓글주소]  [수정/삭제]  [답글작성]
  9. BlogIcon 꼬장스카이비전@2021.04.30 10:02 ~$ 여기 함 들가봐

    https://nacl1119.tistory.com/69 [댓글주소]  [수정/삭제]  [답글작성]
  10. thman@2021.05.09 12:38 ~$ 안녕하세요 이걸로 공부중인데 혹시 Exception has occurred: error
    OpenCV(4.5.1) C:\Users\appveyor\AppData\Local\Temp\1\pip-req-build-wvn_it83\opencv\modules\dnn\src\caffe\caffe_io.cpp:1121: error: (-2:Unspecified error) FAILED: fs.is_open(). Can't open "c:\users\태호\appdata\local\programs\python\python39\lib\site-packages\cvlib\data\deploy.prototxt" in function 'cv::dnn::ReadProtoFromTextFile'
    이 오류가 왜 발생하는지 알수있을까요?
    face, confidence = cv.detect_face(frame) 이 코드에서 발생하는 오류 입니다.
    [댓글주소]  [수정/삭제]  [답글작성]
guest@이름 ~$
guest@패스워드 ~$
guest@홈페이지주소작성 ~$

guest@댓글작성 ~$




bskyvision. Designed by bskyvision.