Время в режиме реального времени изменение с opencv и python
Этот блог является частью серии # 7daysofcomputervisionProjects Отказ Ссылки на блоги и видео каждого проекта являются:
- Меня в реальном времени: Видео |. Блог Воздушная мышь: Управляющая мышь с жестами
- Видео |. Блог Играть в игру Trex с жестом
- Видео |. Блог Auto Dino: Play Trex Game автоматически
- видео | Блог Жест на основе письма
- видео | Блог Игра: Убейте муху
- видео | Блог Калькулятор на основе жеста
- Видео |. Блог
Введение
Это будет наш первый проект на сериале # 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 Код.
Установка
- Сделать
PIP Установить MediaPipe
или следовать за Официальные инструкции Отказ
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”