융무의 기술블로그
article thumbnail

CNN 영상 자동 모자이크 프로젝트를 진행했었습니다.

OpenCV를 통해 모든 얼굴을 모자이크 한 영상에 CNN 분류기를 적용시켜  특정 인물 모자이크 제거했습니다.

학습시킬 사람 1명의 190장 얼굴 이미지와  학습시킬 사람을 제외한 나머지 550장 얼굴 이미지

128×128 픽셀로 변환시킨 다음에 7:3 으로 train_test_split하였습니다.

import tensorflow as tf
import random
import glob
import numpy as np
import os.path as path
import cv2
import matplotlib.pyplot as plt

tf.random.set_seed(123)

def load_img (img_path):
    image_path=img_path
    file_path=glob.glob(path.join(image_path, '*.png'))

    #파일 이름에 맞는 이미지 불러오기
    images=[cv2.imread(path, cv2.IMREAD_ANYCOLOR) for path in file_path]
    images=np.asarray(images, dtype=np.float32)

    # 이미지 값이 0~1이 되도록 이미지 크기를 변경
    images=images/255
    
    # 이미지이름중 맨 앞 글자를 가져와서 라벨을 만든다
    n_images=images.shape[0]
    labels=[]
    for i in range(n_images):
        filenames=path.basename(file_path[i])[0]
        filenames=int(filenames)
        if filenames == 0:
            filename=[1.,0.]
        else:
            filename=[0.,1.]
        labels.append(filename)
    labels=np.asarray(labels)
    return(images, labels)
    
d_images, d_labels = load_img('uijoong_face')
none_images, none_labels = load_img('another_face')

 데이터를 불러온 다음에 살펴보았습니다.

# train 데이터 만들기
def seperate_train_test (images, labels, rate):
    train_test_split = rate
    n_images = images.shape[0]
    split_index = int(train_test_split*n_images)
    shuffled_indices=np.random.permutation(n_images) #array 셔플
    train_indices=shuffled_indices[0:split_index]
    test_indices=shuffled_indices[split_index:]

    x_train = images[train_indices,:,:]
    y_train = labels[train_indices]
    x_test = images[test_indices,:,:]
    y_test = labels[test_indices]
    
    return(x_train, y_train, x_test, y_test)
    
d_x_train, d_y_train, d_x_test, d_y_test = seperate_train_test(d_images, d_labels, 0.7)
none_x_train, none_y_train, none_x_test, none_y_test = seperate_train_test(none_images, none_labels, 0.7)

x_train = np.r_[d_x_train, none_x_train]
y_train = np.r_[d_y_train, none_y_train]
x_test = np.r_[d_x_test, none_x_test]
y_test = np.r_[d_y_test, none_y_test]

plt.figure(figsize=(10,10))
for i in range(20):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(x_train[i], cmap=plt.cm.binary)
    # The CIFAR labels happen to be arrays, 
    # which is why you need the extra index
plt.show()

간단한 분류 모델을 만든다음에 adam optimizer를 사용하였고 로스는 binary_crossentropy를 이용하였습니다. 모델의 정확도 확인결과 약 98%가 나왔습니다. 그래프를 보면 과적합이 일어나지 않는것을 볼 수 있습니다.

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)),
    tf.keras.layers.MaxPooling2D((2,2)),
    tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D((2, 2)),
    tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(64,activation='relu'),
    tf.keras.layers.Dense(2,activation='softmax')
])

model.summary()

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

hist = model.fit(x_train, y_train, batch_size=128, 
                  epochs=10, verbose=1, validation_data=(x_test, y_test))

score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

acc = hist.history['accuracy']
val_acc = hist.history['val_accuracy']

loss = hist.history['loss']
val_loss = hist.history['val_loss']

epochs_range = range(10)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

모자이크 처리

먼저 이미지의 색을 흑백으로 전환한 뒤 흑백 상태에서 얼굴을 탐지합니다

얼굴 추출 후 추출한 얼굴을 지정한 배율로 축소 한 뒤 모자이크 처리합니다.

그 후에 모자이크 처리한 이미지를 원래 크기로 복구한 뒤 기존 이미지에 적합시킵니다.

image1 = cv2.imread('0 (250).png')
detector = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
gray = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
faces = detector.detectMultiScale(gray, 1.3, 5)
for (x, y, w, h) in faces:
    roi = image1[y:y+h, x:x+w]
    dst = cv2.resize(roi, dsize=(128, 128), interpolation=cv2.INTER_AREA)
    dst_1 = dst.reshape(1, 49152)

mosaic_rate = 30

face_img = cv2.resize(roi, (w//mosaic_rate, h//mosaic_rate))
# 확대/축소한 그림을 원래 크기로 돌리기
face_img = cv2.resize(face_img, (w, h), interpolation=cv2.INTER_AREA)
# 원래 이미지에 붙이기
image1[y:y+h, x:x+w] = face_img
plt.imshow(image1)

웹카메라

실제 만들어진 영상 데이터를 가지고 학습시킨 CNN분류기를 통과시키면 학습자를 제외하고 모자이크 처리가 잘된 상태로 영상이 저장된걸 알 수 있습니다.

import numpy as np
import cv2

mosaic_rate = 30
detector = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

videofile1 = 'final_test.mp4'
video = cv2.VideoCapture(videofile1)

fourcc = cv2.VideoWriter_fourcc(*'XVID')
writer = cv2.VideoWriter('face_mosaic.avi', fourcc, 30.0, (640, 480))

i=0
ok=0
no=0
while (True):
    ret, img = video.read()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = detector.detectMultiScale(gray, 1.3, 5)
    for (x, y, w, h) in faces:
        roi=img[y:y+h, x:x+w]
        dst=cv2.resize(roi, dsize=(128,128),interpolation=cv2.INTER_AREA)
        dst_1=np.expand_dims(dst,0)
        dst_2 = tf.image.convert_image_dtype(dst_2, tf.float32)
        
        predict = model.predict(dst_2)
        
        if tf.argmax(predict,1) == 0 :
            no=no+1
            #print('no:',no)

            face_img = cv2.resize(roi, (w//mosaic_rate, h//mosaic_rate))
        # 확대/축소한 그림을 원래 크기로 돌리기
            face_img = cv2.resize(face_img, (w, h), interpolation=cv2.INTER_AREA)
        # 원래 이미지에 붙이기
            img[y:y+h, x:x+w] = face_img
        
        else :
            ok=ok+1
            #print('ok:', ok)
        

    cv2.imshow('frame', img)
    writer.write(img)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

        
video.release()
writer.release()
cv2.destroyAllWindows()
profile

융무의 기술블로그

@융무

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!