Рубрики
Без рубрики

День 1: фон в реальном времени Изменение

Время в режиме реального времени изменение с opencv и python Этот блог является частью серии … Теги с Python, MachineLearning, CodeNewie.

Время в режиме реального времени изменение с opencv и python

Этот блог является частью серии # 7daysofcomputervisionProjects Отказ Ссылки на блоги и видео каждого проекта являются:

  1. Меня в реальном времени: Видео |. Блог Воздушная мышь: Управляющая мышь с жестами
  2. Видео |. Блог Играть в игру Trex с жестом
  3. Видео |. Блог Auto Dino: Play Trex Game автоматически
  4. видео | Блог Жест на основе письма
  5. видео | Блог Игра: Убейте муху
  6. видео | Блог Калькулятор на основе жеста
  7. Видео |. Блог

Введение

Это будет наш первый проект на сериале # 7daysofcomputervisionProject И вся серия ориентирована на вас, если вы начинаете или опытные, но хотите попробовать что-то для удовольствия.

С государством художественных методов фон можно легко и идеально изменить. У нас есть видеозвонки, такие как Zoom и Facebook Messenger, который позволяет нам менять наш фон в реальном времени с некоторым уровнем реалистичного опыта. Моя цель здесь – не сделать что-то вроде тех гигантов, но использовать простую концепцию обработки изображений и достичь некоторых уровней изменения фона.

Я буду пытаться немногие концепции и идеи вместе с некоторыми экспериментами на пути.

Предварительные задачи

Импорт библиотеки

import cv2
import numpy as np
import matplotlib.pyplot as plt

Определите общую функцию

Я не знаю, почему я всегда определяю эту функцию сначала.

def show(img, fsize=(10,10)):
    figure=plt.figure(figsize=fsize)
    plt.imshow(img)
    plt.show()
show(np.random.randint(0, 255, (100, 100)))

Эксперимент 1.: Используйте концепцию предпосылки вычитания

Вычитание фона действительно веселая и сложная задача И это тоже просто. Основная концепция заключается в том, что мы начнем с выбора сцены или изображения, в котором мы хотим, чтобы наш объект был включен. Тогда мы сделаем изображение, где будет объектом и его фон. Тогда, если у нас есть фоновое изображение в качестве отдельного изображения, то мы можем вычитать фоном от исходного изображения и получить маску объекта. Теперь мы изменим пиксели на изображении сцены в тех положении, где лежит маска объекта. Маска будет не ноль, и мы можем легко найти это. Мы изменим значение пикселя к значению объекта на этих ненулевых положении маски.

Давайте попробуем сначала с некоторым фиктивным изображением.

# create one empty image then add some background color
bg = np.zeros((480, 640, 3))
bg[:, :, 0]+=100 # red color increase
bg = bg.astype(np.uint8)

show(bg)

# make copy of bg and then add object on it
img = bg.copy()

# make circle on it :) object!
cv2.circle(img, (360, 240), 100, (25, 80, 55), -1)
show(img)

# read a scene image
scene = cv2.imread("scene.jpg", -1)
scene = cv2.resize(scene, (img.shape[1], img.shape[0]))
rgb_scene = cv2.cvtColor(scene, cv2.COLOR_BGR2RGB)
show(rgb_scene)

# how to add the circle on the scene?
mask = img-bg # subtract background from image
show(mask)

# now apply mask to scene
res = scene.copy()
res[mask!=0] = img[mask!=0]
show(res)

Приведенный выше пример был очень простой, и мы выполним эту концепцию в качестве позвоночника для некоторых предстоящих экспериментов.

Функция, чтобы сделать в среднем

В нашей Live Camera Feed мы не сможем различать фона и цвет переднего плана, потому что цветовая комбинация может быть разными. Следовательно, мы начнем, создавая фоновое изображение до нескольких кадров. Мы возьмем в среднем кадра каждого кадра для некоторых кадров, а затем запускают предпосылку вычитания. И тогда мы нанесем концепцию вычитания фона для вставки сцены на нашем фоне.

def running_average(bg_img, image, aweight):
    if bg_img is None:
        bg_img = image.copy().astype("float")
    else:
        cv2.accumulateWeighted(image, bg_img, aweight)    
    return bg_img

Вычитание фона: только статические объекты на фоне

Пожалуйста, обратитесь к комментарию на каждой строке для объяснения кода.

Мы используем новый фон как ниже изображения.

# read camera feed
cam = cv2.VideoCapture(0)
notify_num = 200 # up to how many frames to take background average.
frame_count=0 # a variable to count current frame

aweight = 0.5 # variable used to take average
bg = None # background image
take_bg=True # 

scene = cv2.imread("scene.jpg") # read the scene image
scene = cv2.resize(scene, (640, 480)) # resize scene to the size of frame

while True: # loop until termination
    ret, frame = cam.read() # read frame
    frame= cv2.flip(frame, 1) # flip the frame to make frame like mirror image
    clone = frame.copy() # make a local copy of frame


    gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY) # convert frame to grayscale
    gray = cv2.medianBlur(gray, 5) # add some median blur to remove Salt and Pepper noise

    key = cv2.waitKey(1) & 0xFF # listen for the key event

    if key == 27: # if hit escape key
        break # break out of the loop


    if take_bg == True and notify_num>frame_count: # condition to take a background average
        txt = f"Taking background, Hold Still: {str(notify_num-frame_count)}"

        cv2.putText(clone, txt, (10, 50),
                                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

        bg=running_average(bg, gray, aweight) # call the running average function to get the average on each frame
    else:
        take_bg= False # don't take background average now!
        frame_count=0 # set frame count to 0

        diff = cv2.absdiff(bg.astype("uint8"), gray) # get the absolute difference of background image and current image
        diff[diff<30]=0 # threshold it little bit
        f = clone.copy() # again make a loval copy 
        f[diff==0] = scene[diff==0] # image masking !!!!!
        cv2.imshow("Subtraction", f) # show the background subtracted image.


    frame_count+=1
    cv2.imshow("Output", clone)
cam.release()
cv2.destroyAllWindows()

Выход

Чтобы правильно запустить этот код, не попадайте перед камерой, пока фона не будет сделана. Таким образом, наш фон будет только статическими объектами, такими как стена и плакаты.

Недостатки текущего кода

  • Во-первых, мы не можем оставаться перед камерой во время принятия в среднем.

Для устранения вышеуказанного недостатка мы можем начать с определения ROI, представляющей интересующую область, которая будет представлять наш фон. Для этой концепции работать, это Необходимо иметь фон в простом цвете.

Вычитание фона: roi для фона

# read camera feed
cam = cv2.VideoCapture(0)
notify_num = 200 # up to how many frames to take background average.
frame_count=0 # a variable to count current frame

aweight = 0.5 # variable used to take average
bg = None # background image
take_bg=True # 

fsize = (520, 720)
scene = cv2.imread("scene.jpg") # read the scene image
scene = cv2.resize(scene, (fsize[1], fsize[0])) # resize scene to the size of frame

left,top,right,bottom=(400, 20, 630, 300)


while True: # loop until termination
    ret, frame = cam.read() # read frame
    frame= cv2.flip(frame, 1) # flip the frame to make frame like mirror image
    frame = cv2.resize(frame, (fsize[1], fsize[0]))

    clone = frame.copy() # make a local copy of frame

    gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY) # convert frame to grayscale
    gray = cv2.medianBlur(gray, 5) # add some median blur to remove Salt and Pepper noise


    key = cv2.waitKey(1) & 0xFF # listen for the key event

    roi = gray[top:bottom, left:right]

    roi = cv2.resize(roi, (fsize[1], fsize[0]))

    if key == 27: # if hit escape key
        break # break out of the loop


    if take_bg == True and notify_num>frame_count: # condition to take a background average
        txt = f"Taking background, Hold Still: {str(notify_num-frame_count)}"

        cv2.putText(clone, txt, (10, 50),
                                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.rectangle(clone, (left, top), (right, bottom), (0, 0, 255), 1)
        bg=running_average(bg, roi, aweight) # call the running average function to get the average on each frame
    else:
        take_bg= False # don't take background average now!
        frame_count=0 # set frame count to 0

        diff = cv2.absdiff(bg.astype("uint8"), gray) # get the absolute difference of background image and current image
        diff[diff<40]=0 # threshold it little bit
        cv2.imshow("diff", diff.astype("uint8"))
        f = clone.copy() # again make a loval copy 
        f[diff==0] = scene[diff==0] # image masking !!!!!
        cv2.imshow("Subtraction", f) # show the background subtracted image.



    frame_count+=1
    cv2.imshow("Output", clone)
cam.release()
cv2.destroyAllWindows()

Вместо того, чтобы использовать весь кадр как фоновое изображение, я выбираю только некоторую часть простой предпосылки. И результат не так уж плохо.

Эксперимент 2: используйте пороговую концепцию

# read camera feed
cam = cv2.VideoCapture(0)

fsize = (520, 720)
scene = cv2.imread("scene.jpg") # read the scene image
scene = cv2.resize(scene, (fsize[1], fsize[0])) # resize scene to the size of frame


while True: # loop until termination
    ret, frame = cam.read() # read frame
    frame= cv2.flip(frame, 1) # flip the frame to make frame like mirror image
    frame = cv2.resize(frame, (fsize[1], fsize[0]))

    clone = frame.copy() # make a local copy of frame

    gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY) # convert frame to grayscale
    gray = cv2.medianBlur(gray, 9) # add some median blur to remove Salt and Pepper noise


    key = cv2.waitKey(1) & 0xFF # listen for the key event


    if key == 27: # if hit escape key
        break # break out of the loop


    kernel = np.ones((7, 7))
    th = cv2.threshold(gray, 40, 255, cv2.THRESH_OTSU)[1]        
    th = cv2.dilate(th, kernel, iterations=1)
    th = cv2.erode(th, kernel, iterations=5)

    f = clone.copy()

    f[th!=0] = scene[th!=0]
    cv2.imshow("Thresh Result", f)

    edges = cv2.Canny(gray, 10, 50)
    kernel = np.ones((3, 3))
    edges = cv2.dilate(edges, kernel, iterations=5)
#     edges = cv2.erode(edges, kernel, iterations=7)
    cv2.imshow("Canny", edges)

    (cnts, _) = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    dm = np.zeros_like(edges)    
    if len(cnts)>0:
        mcnt = max(cnts[:], key=cv2.contourArea)
        dm=cv2.fillConvexPoly(dm, mcnt, (255))
        cv2.imshow("DM", dm)
    c = frame.copy()
    c[dm!=255]=scene[dm!=255]
    cv2.imshow("Canny Result", c)

cam.release()
cv2.destroyAllWindows()

Приведенный выше код быстро, но у него много проблем, таких как она не может работать с динамическим фоном, не существует отчетливая идентификация переднего плана и фонового изображения.

Эксперимент 3: MOG2

Есть хорошие методы вычитания предпосылки, доступные под OpenCV, и те могут справиться с фоновым вычитанием довольно сильно. Один из них – MOG2.

cam = cv2.VideoCapture(0)
mog = cv2.createBackgroundSubtractorMOG2()

fsize = (520, 720)
scene = cv2.imread("scene.jpg") # read the scene image
scene = cv2.resize(scene, (fsize[1], fsize[0])) # resize scene to the size of frame


while True:
    ret, frame = cam.read()
    if ret:
        frame = cv2.flip(frame, 1)
        frame = cv2.resize(frame, (fsize[1], fsize[0]))
        fmask = mog.apply(frame, 0.5)


        kernel = np.ones((3, 3))  
        fmask = cv2.dilate(fmask, kernel, iterations=1)
#         fmask = cv2.erode(fmask, kernel, iterations=1)

        cv2.imshow("mog", fmask)

        key = cv2.waitKey(1) & 0xFF 


        if key == 27: # if hit escape key
            break # break out of the loop

        frame[fmask==0] = scene[fmask==0]

        cv2.imshow("Frame", frame)

cam.release()
cv2.destroyAllWindows()

MOG2 – хороший алгоритм вычитания фона для движущегося объекта на статическом фоне, но в нашем случае он не удается. Теперь мы будем двигаться к продвинутому и лучшему инструменту.

Эксперимент 4: MediaPipe

MediaPipe – это инструмент OpenSource Google для выполнения потрясающих задач компьютерных видажений, таких как обнаружение лица, чтобы обнаружить позе. И в этом примере я собираюсь использовать Сегментация Selfie Код.

Установка

import cv2
import mediapipe as mp
import numpy as np

mp_selfie_segmentation = mp.solutions.selfie_segmentation

cam = cv2.VideoCapture(0)

fsize = (520, 720)
scene = cv2.imread("scene.jpg") # read the scene image
scene = cv2.resize(scene, (fsize[1], fsize[0])) # resize scene to the size of frame


# begin with selfie segmentation model
with mp_selfie_segmentation.SelfieSegmentation(model_selection=1) as selfie_seg:
    bg_image = scene

    while cam.isOpened():
        ret, frame = cam.read()
        if not ret:
            print("Error reading frame...")
            continue
        frame = cv2.resize(frame, (fsize[1], fsize[0]))

        # flip it to look like selfie camera
        frame = cv2.flip(frame, 1)


        # get rgb image to pass that on selfie segmentation
        rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        # process it!
        results = selfie_seg.process(rgb) 

        # get the condition from result's segmentation mask
        condition = np.stack((results.segmentation_mask, ) * 3, axis=-1) > 0.1

        # apply background change if condition matches
        output_image = np.where(condition, frame, bg_image)

        # show the output
        cv2.imshow('Background Change with MP', output_image)
        if cv2.waitKey(5) & 0xFF == 27:
            break
cam.release()
cv2.destroyAllWindows()

Заключение

Это были просто некоторые эксперименты и хитрости обработки изображений, чтобы прохладить, как меняющий фон в режиме реального времени. Моя собственная версия экспериментов была не так хороша, но результат MediaPipe является просто потрясающим. Есть и другие интересные темы и функции, предоставляемые MediaPipe и в следующей части, я буду пытаться их.

Код и видео YouTube находятся на ссылке ниже.

Оригинал: “https://dev.to/qviper/day-1-real-time-background-changing-5e00”