अब जब कि हम में से कई कर रहे हैं को निगरानी में होने के कारण COVID -19 , वीडियो कॉल से पहले की तुलना में काफी अधिक लगातार घटना बन गए हैं। विशेष रूप से, ZOOM सेवा अचानक बहुत लोकप्रिय हो गई। संभवतः वर्चुअल बैकग्राउंड के लिए सबसे दिलचस्प ज़ूम फीचर सपोर्ट है । यह उपयोगकर्ताओं को किसी भी छवि या वीडियो के साथ उनके पीछे की पृष्ठभूमि को बदलने की अनुमति देता है।मैं लंबे समय से काम पर ज़ूम का उपयोग कर रहा हूं, कुबेरनेट्स पर खुले स्रोत की बैठकों में, आमतौर पर एक कॉर्पोरेट लैपटॉप से ऐसा कर रहा हूं। अब, जब मैं घर से काम कर रहा हूं, तो मैं अपने कुछ खुले स्रोत कार्यों को हल करने के लिए अधिक शक्तिशाली और सुविधाजनक व्यक्तिगत डेस्कटॉप कंप्यूटर का उपयोग करने के लिए इच्छुक हूं।दुर्भाग्य से, ज़ूम केवल " क्रोमा कुंजी " या " ग्रीन स्क्रीन " के रूप में जाना जाने वाला पृष्ठभूमि हटाने का समर्थन करता है । इस पद्धति का उपयोग करने के लिए, यह आवश्यक है कि पृष्ठभूमि को कुछ ठोस रंग द्वारा दर्शाया जाए, आदर्श रूप से हरा, और समान रूप से जलाया जाए।चूंकि मेरे पास हरी स्क्रीन नहीं है, इसलिए मैंने बस अपनी खुद की पृष्ठभूमि हटाने की प्रणाली को लागू करने का फैसला किया। और यह, ज़ाहिर है, अपार्टमेंट में चीजों को क्रम में रखने या काम लैपटॉप के निरंतर उपयोग से बहुत बेहतर है।जैसा कि यह पता चला है, तैयार किए गए खुले स्रोत घटकों का उपयोग करके और अपने स्वयं के कोड की बस कुछ पंक्तियां लिखकर, आप बहुत ही सभ्य परिणाम प्राप्त कर सकते हैं।कैमरा डेटा पढ़ना
आइए शुरुआत से शुरू करें और निम्नलिखित प्रश्न का उत्तर दें: "वेब कैमरा से वीडियो कैसे प्राप्त करें जिसे हम संसाधित करेंगे?"चूंकि मैं अपने घर के कंप्यूटर पर लिनक्स का उपयोग करता हूं (जब मैं गेम नहीं खेल रहा हूं), मैंने ओपन सीवी पायथन बाइंडिंग का उपयोग करने का फैसला किया, जो मैं पहले से ही परिचित हूं। एक वेब कैमरा से डेटा पढ़ने के लिए V4L2 -bindings के अलावा , उनमें उपयोगी बुनियादी वीडियो प्रसंस्करण कार्य शामिल हैं। अजगर-ऑपनेंव में एक वेबकैम से एक फ्रेम पढ़ना बहुत सरल है:import cv2
cap = cv2.VideoCapture('/dev/video0')
success, frame = cap.read()
अपने कैमरे के साथ काम करते समय परिणामों को बेहतर बनाने के लिए, मैंने इससे वीडियो कैप्चर करने से पहले निम्नलिखित सेटिंग्स लागू कीं:
height, width = 720, 1280
cap.set(cv2.CAP_PROP_FRAME_WIDTH ,width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,height)
cap.set(cv2.CAP_PROP_FPS, 60)
एक भावना है कि अधिकांश वीडियो कॉन्फ्रेंसिंग कार्यक्रम वीडियो को 720p @ 30 एफपीएस या उससे कम पर सीमित करते हैं। लेकिन हम, किसी भी मामले में, हर फ्रेम को नहीं पढ़ सकते हैं। ऐसी सेटिंग्स ऊपरी सीमा निर्धारित करती हैं।लूप में फ़्रेम कैप्चर तंत्र को रखें। अब हमारे पास कैमरे से वीडियो स्ट्रीम तक पहुंच है!while True:
success, frame = cap.read()
आप निम्नानुसार परीक्षण के उद्देश्यों के लिए फ्रेम को बचा सकते हैं:cv2.imwrite("test.jpg", frame)
उसके बाद, हम यह सुनिश्चित कर सकते हैं कि कैमरा काम कर रहा है। महान!मुझे आशा है कि आप मेरी दाढ़ी के खिलाफ नहीं हैंपृष्ठभूमि का पता लगाने
अब जब हमारे पास वीडियो स्ट्रीम तक पहुंच है, तो हम इस बारे में सोचेंगे कि पृष्ठभूमि का पता कैसे लगाया जाए और इसे खोजकर इसे कैसे बदला जाए। लेकिन यह पहले से ही मुश्किल काम है।यद्यपि एक भावना है कि ज़ूम के निर्माता कभी भी इस बारे में बात नहीं करते हैं कि कार्यक्रम पृष्ठभूमि को कैसे हटाता है, जिस तरह से सिस्टम व्यवहार करता है वह मुझे लगता है कि तंत्रिका नेटवर्क के बिना क्या हो सकता है। यह समझाना कठिन है, लेकिन परिणाम बिल्कुल उसी तरह दिखते हैं। इसके अलावा, मैंने एक लेख पाया कि कैसे Microsoft टीमें एक दृढ़ तंत्रिका नेटवर्क का उपयोग करके पृष्ठभूमि के धब्बा को लागू करती हैं । सिद्धांत रूप में, अपना स्वयं का तंत्रिका नेटवर्क बनाना इतना मुश्किल नहीं है। छवि विभाजन पर कई लेख और वैज्ञानिक पत्र हैं ।। बहुत सारे खुले स्रोत पुस्तकालय और उपकरण हैं। लेकिन हमें अच्छे परिणाम प्राप्त करने के लिए एक बहुत विशिष्ट डेटासेट की आवश्यकता है।विशेष रूप से, हमें एक वेब कैमरा से प्राप्त छवियों की बहुत आवश्यकता है, जिसमें अग्रभूमि में एक व्यक्ति की सही तस्वीर है। ऐसी तस्वीर के प्रत्येक पिक्सेल को पृष्ठभूमि से अलग के रूप में चिह्नित किया जाना चाहिए।तंत्रिका नेटवर्क के प्रशिक्षण की तैयारी में इस तरह के डेटासेट का निर्माण करने के लिए अधिक प्रयास की आवश्यकता नहीं हो सकती है। यह इस तथ्य के कारण है कि Google के शोधकर्ताओं की टीम ने पहले से ही सबसे कठिन काम किया है और लोगों को विभाजित करने के लिए खुले स्रोत को एक पूर्व-प्रशिक्षित तंत्रिका नेटवर्क में डाल दिया है। इस नेटवर्क को बॉडीपिक्स कहा जाता है । ये अच्छी तरह काम करता है!बॉडीपिक्स अब केवल TensorFlow.js के लिए उपयुक्त रूप में उपलब्ध है। नतीजतन, बॉडी-पिक्स-नोड लाइब्रेरी का उपयोग करना आसान है । ब्राउज़र में नेटवर्क आउटपुट (पूर्वानुमान)को तेज करने के लिए, WebGL बैकएंड का उपयोग करना बेहतर होता है , लेकिन Node.js वातावरण में आप Tensorflow GPU backend (ध्यान दें कि इसके लिए NVIDIA से वीडियो कार्ड की आवश्यकता होगी , जो मेरे पास है)। परियोजना के सेटअप को सरल बनाने के लिए, हम एक छोटे कंटेनरीकृत वातावरण का उपयोग करेंगे जो TensorFlow GPU और Node.js. प्रदान करता है। यह सब nvidia-docker के साथ उपयोग करना- अपने कंप्यूटर पर आवश्यक निर्भरता स्वयं एकत्रित करने से बहुत आसान है। ऐसा करने के लिए, आपको केवल अपने कंप्यूटर पर डॉकर और नवीनतम ग्राफिक्स ड्राइवरों की आवश्यकता है।यहाँ फ़ाइल की सामग्री है bodypix/package.json
:{
"name": "bodypix",
"version": "0.0.1",
"dependencies": {
"@tensorflow-models/body-pix": "^2.0.5",
"@tensorflow/tfjs-node-gpu": "^1.7.1"
}
}
यहाँ फ़ाइल है bodypix/Dockerfile
:
FROM nvcr.io/nvidia/cuda:10.0-cudnn7-runtime-ubuntu18.04
RUN apt update && apt install -y curl make build-essential \
&& curl -sL https://deb.nodesource.com/setup_12.x | bash - \
&& apt-get -y install nodejs \
&& mkdir /.npm \
&& chmod 777 /.npm
ENV TF_FORCE_GPU_ALLOW_GROWTH=true
WORKDIR /src
COPY package.json /src/
RUN npm install
COPY app.js /src/
ENTRYPOINT node /src/app.js
अब बात करते हैं परिणाम प्राप्त करने की। लेकिन मैं आपको तुरंत चेतावनी देता हूं: मैं एक Node.js विशेषज्ञ नहीं हूं! यह सिर्फ मेरे शाम के प्रयोगों का नतीजा है, इसलिए मेरे लिए दीवाने रहो :-)।निम्नलिखित सरल स्क्रिप्ट एक HTTP POST अनुरोध का उपयोग करके सर्वर पर भेजे गए बाइनरी मास्क छवि को संसाधित करने में व्यस्त है। एक मुखौटा पिक्सल के दो आयामी सरणी है। शून्य द्वारा प्रस्तुत पिक्सेल पृष्ठभूमि हैं।यहाँ फ़ाइल कोड है app.js
:const tf = require('@tensorflow/tfjs-node-gpu');
const bodyPix = require('@tensorflow-models/body-pix');
const http = require('http');
(async () => {
const net = await bodyPix.load({
architecture: 'MobileNetV1',
outputStride: 16,
multiplier: 0.75,
quantBytes: 2,
});
const server = http.createServer();
server.on('request', async (req, res) => {
var chunks = [];
req.on('data', (chunk) => {
chunks.push(chunk);
});
req.on('end', async () => {
const image = tf.node.decodeImage(Buffer.concat(chunks));
segmentation = await net.segmentPerson(image, {
flipHorizontal: false,
internalResolution: 'medium',
segmentationThreshold: 0.7,
});
res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
res.write(Buffer.from(segmentation.data));
res.end();
tf.dispose(image);
});
});
server.listen(9000);
})();
फ़्रेम को मास्क में बदलने के लिए, हम, पायथन स्क्रिप्ट में, खस्ता और अनुरोध पैकेज का उपयोग कर सकते हैं :def get_mask(frame, bodypix_url='http://localhost:9000'):
_, data = cv2.imencode(".jpg", frame)
r = requests.post(
url=bodypix_url,
data=data.tobytes(),
headers={'Content-Type': 'application/octet-stream'})
mask = np.frombuffer(r.content, dtype=np.uint8)
mask = mask.reshape((frame.shape[0], frame.shape[1]))
return mask
परिणाम लगभग निम्नलिखित है।मास्कजबकि मैं यह सब कर रहा था, मैं अगले ट्वीट परआया।यह निश्चित रूप से वीडियो कॉल के लिए सबसे अच्छी पृष्ठभूमि है।अब जब हमारे पास पृष्ठभूमि से अग्रभूमि को अलग करने के लिए एक मुखौटा है, तो पृष्ठभूमि को कुछ और के साथ बदलना बहुत सरल होगा।मैंने ट्वीट शाखा से पृष्ठभूमि की छवि ली और इसे काट दिया ताकि मुझे 16x9 चित्र मिलें।पृष्ठभूमि छविइसके बाद मैंने निम्नलिखित कार्य किया:
replacement_bg_raw = cv2.imread("background.jpg")
width, height = 720, 1280
replacement_bg = cv2.resize(replacement_bg_raw, (width, height))
inv_mask = 1-mask
for c in range(frame.shape[2]):
frame[:,:,c] = frame[:,:,c]*mask + replacement_bg[:,:,c]*inv_mask
उसके बाद मुझे यही मिला।पृष्ठभूमि को बदलने का परिणामयह मुखौटा स्पष्ट रूप से पर्याप्त सटीक नहीं है, इसका कारण प्रदर्शन व्यापार-बंदियां हैं जो हमने बॉडीपिक्स की स्थापना करते समय किए थे। सामान्य तौर पर, जबकि सब कुछ कम या ज्यादा सहिष्णु दिखता है।लेकिन, जब मैंने इस पृष्ठभूमि को देखा, तो मुझे एक विचार आया।दिलचस्प प्रयोग
अब जब हमें पता चल गया है कि मुखौटा कैसे बनाया जाता है, तो हम पूछेंगे कि परिणाम कैसे सुधारें।पहला स्पष्ट कदम मुखौटा के किनारों को नरम करना है। उदाहरण के लिए, इसे इस तरह किया जा सकता है:def post_process_mask(mask):
mask = cv2.dilate(mask, np.ones((10,10), np.uint8) , iterations=1)
mask = cv2.erode(mask, np.ones((10,10), np.uint8) , iterations=1)
return mask
इससे स्थिति में थोड़ा सुधार होगा, लेकिन बहुत प्रगति नहीं हुई है। और एक साधारण प्रतिस्थापन काफी उबाऊ है। लेकिन, जब से हम खुद को यह सब मिला है, इसका मतलब है कि हम तस्वीर के साथ कुछ भी कर सकते हैं, और न केवल पृष्ठभूमि को हटा दें।यह देखते हुए कि हम स्टार वार्स से एक आभासी पृष्ठभूमि का उपयोग कर रहे हैं, मैंने चित्र को अधिक रोचक बनाने के लिए होलोग्राम प्रभाव बनाने का फैसला किया। इसके अलावा, यह आपको मास्क के धुंधलेपन को दूर करने की अनुमति देता है।सबसे पहले, पोस्ट-प्रोसेसिंग कोड अपडेट करें:def post_process_mask(mask):
mask = cv2.dilate(mask, np.ones((10,10), np.uint8) , iterations=1)
mask = cv2.blur(mask.astype(float), (30,30))
return mask
किनारे अब धुंधले हैं। यह अच्छा है, लेकिन हमें अभी भी एक होलोग्राम प्रभाव बनाने की आवश्यकता है।हॉलीवुड होलोग्राम में आमतौर पर निम्नलिखित गुण होते हैं:- एक पीला रंग या मोनोक्रोम चित्र - जैसे कि एक उज्ज्वल लेजर द्वारा खींचा गया हो।
- एक प्रभाव स्कैन लाइनों या ग्रिड की तरह कुछ की याद दिलाता है - जैसे कि छवि कई किरणों में प्रदर्शित होती है।
- "भूत प्रभाव" - जैसे कि प्रक्षेपण परतों में किया जाता है या यदि सही दूरी जिस पर इसे प्रदर्शित किया जाना चाहिए, तो प्रक्षेपण बनाते समय इसे बनाए नहीं रखा जाएगा।
इन सभी प्रभावों को चरण दर चरण लागू किया जा सकता है।सबसे पहले, छवि को नीले रंग की छाया में रंगने के लिए, हम विधि का उपयोग कर सकते हैं applyColorMap
:
holo = cv2.applyColorMap(frame, cv2.COLORMAP_WINTER)
अगला - हलफ़टोन में छोड़ने की याद दिलाने वाले प्रभाव के साथ एक स्वीप लाइन जोड़ें:
bandLength, bandGap = 2, 3
for y in range(holo.shape[0]):
if y % (bandLength+bandGap) < bandLength:
holo[y,:,:] = holo[y,:,:] * np.random.uniform(0.1, 0.3)
अगला, हम छवि पर वर्तमान प्रभाव की स्थानांतरित भारित प्रतियों को जोड़कर "भूत प्रभाव" को लागू करते हैं:
def shift_img(img, dx, dy):
img = np.roll(img, dy, axis=0)
img = np.roll(img, dx, axis=1)
if dy>0:
img[:dy, :] = 0
elif dy<0:
img[dy:, :] = 0
if dx>0:
img[:, :dx] = 0
elif dx<0:
img[:, dx:] = 0
return img
holo2 = cv2.addWeighted(holo, 0.2, shift_img(holo1.copy(), 5, 5), 0.8, 0)
holo2 = cv2.addWeighted(holo2, 0.4, shift_img(holo1.copy(), -5, -5), 0.6, 0)
और अंत में, हम कुछ मूल रंगों को रखना चाहते हैं, इसलिए हम मूल फ्रेम के साथ होलोग्राफिक प्रभाव को जोड़ते हैं, जो "भूत प्रभाव" को जोड़ने के समान है।holo_done = cv2.addWeighted(img, 0.5, holo2, 0.6, 0)
यहाँ एक होलोग्राम प्रभाव वाला एक फ्रेम कैसा दिखता है:होलोग्राम प्रभाव वाला एक फ्रेमयह फ्रेम अपने आप में बहुत अच्छा लगता है।अब इसे पृष्ठभूमि के साथ संयोजित करने का प्रयास करते हैं।पृष्ठभूमि पर छवि मढ़ा।हो गया! (मैं वादा करता हूं - इस तरह का वीडियो अधिक दिलचस्प लगेगा)।वीडियो आउटपुट
और अब मुझे कहना होगा कि हम यहाँ कुछ याद किया है। तथ्य यह है कि हम अभी भी इन सभी का उपयोग वीडियो कॉल करने के लिए नहीं कर सकते हैं।इसे ठीक करने के लिए, हम डमी वेबकैम बनाने के लिए pyfakewebcam और v4l2loopback का उपयोग करेंगे ।इसके अलावा, हम इस कैमरे को डॉकर से जोड़ने की योजना बनाते हैं।सबसे पहले, एक fakecam/requirements.txt
निर्भरता विवरण फ़ाइल बनाएँ :numpy==1.18.2
opencv-python==4.2.0.32
requests==2.23.0
pyfakewebcam==0.1.0
अब fakecam/Dockerfile
उस एप्लिकेशन के लिए एक फ़ाइल बनाएं जो एक डमी कैमरे की क्षमताओं को लागू करता है:FROM python:3-buster
RUN pip install --upgrade pip
RUN apt-get update && \
apt-get install -y \
`
libsm6 libxext6 libxrender-dev \
`
libv4l-dev
WORKDIR /src
COPY requirements.txt /src/
RUN pip install --no-cache-dir -r /src/requirements.txt
COPY background.jpg /data/
COPY fake.py /src/
ENTRYPOINT python -u fake.py
अब, कमांड लाइन से, v4l2loopback स्थापित करें:sudo apt install v4l2loopback-dkms
डमी कैमरा सेट करें:sudo modprobe -r v4l2loopback
sudo modprobe v4l2loopback devices=1 video_nr=20 card_label="v4l2loopback" exclusive_caps=1
कुछ अनुप्रयोगों (क्रोम, ज़ूम) की कार्यक्षमता सुनिश्चित करने के लिए, हमें एक सेटिंग की आवश्यकता है exclusive_caps
। चिह्न card_label
केवल अनुप्रयोगों में एक कैमरा चुनने की सुविधा सुनिश्चित करने के लिए सेट किया गया है। संख्या का संकेत video_nr=20
डिवाइस के निर्माण की ओर जाता है /dev/video20
यदि संगत संख्या व्यस्त नहीं है, और यह व्यस्त होने की संभावना नहीं है।अब हम डमी कैमरा बनाने के लिए स्क्रिप्ट में बदलाव करेंगे:
fake = pyfakewebcam.FakeWebcam('/dev/video20', width, height)
यह ध्यान दिया जाना चाहिए कि pyfakewebcam RGB चैनल (रेड, ग्रीन, ब्लू - रेड, ग्रीन, ब्लू) के साथ छवियों की अपेक्षा करता है, और ओपन CV BGR चैनल (ब्लू, ग्रीन, रेड) के आदेश के साथ काम करता है।फ़्रेम को आउटपुट करने से पहले आप इसे ठीक कर सकते हैं, और फिर फ़्रेम को इस तरह भेज सकते हैं:frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
fake.schedule_frame(frame)
यहाँ पूरी स्क्रिप्ट कोड है fakecam/fake.py
:import os
import cv2
import numpy as np
import requests
import pyfakewebcam
def get_mask(frame, bodypix_url='http://localhost:9000'):
_, data = cv2.imencode(".jpg", frame)
r = requests.post(
url=bodypix_url,
data=data.tobytes(),
headers={'Content-Type': 'application/octet-stream'})
mask = np.frombuffer(r.content, dtype=np.uint8)
mask = mask.reshape((frame.shape[0], frame.shape[1]))
return mask
def post_process_mask(mask):
mask = cv2.dilate(mask, np.ones((10,10), np.uint8) , iterations=1)
mask = cv2.blur(mask.astype(float), (30,30))
return mask
def shift_image(img, dx, dy):
img = np.roll(img, dy, axis=0)
img = np.roll(img, dx, axis=1)
if dy>0:
img[:dy, :] = 0
elif dy<0:
img[dy:, :] = 0
if dx>0:
img[:, :dx] = 0
elif dx<0:
img[:, dx:] = 0
return img
def hologram_effect(img):
holo = cv2.applyColorMap(img, cv2.COLORMAP_WINTER)
bandLength, bandGap = 2, 3
for y in range(holo.shape[0]):
if y % (bandLength+bandGap) < bandLength:
holo[y,:,:] = holo[y,:,:] * np.random.uniform(0.1, 0.3)
holo_blur = cv2.addWeighted(holo, 0.2, shift_image(holo.copy(), 5, 5), 0.8, 0)
holo_blur = cv2.addWeighted(holo_blur, 0.4, shift_image(holo.copy(), -5, -5), 0.6, 0)
out = cv2.addWeighted(img, 0.5, holo_blur, 0.6, 0)
return out
def get_frame(cap, background_scaled):
_, frame = cap.read()
mask = None
while mask is None:
try:
mask = get_mask(frame)
except requests.RequestException:
print("mask request failed, retrying")
mask = post_process_mask(mask)
frame = hologram_effect(frame)
inv_mask = 1-mask
for c in range(frame.shape[2]):
frame[:,:,c] = frame[:,:,c]*mask + background_scaled[:,:,c]*inv_mask
return frame
cap = cv2.VideoCapture('/dev/video0')
height, width = 720, 1280
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
cap.set(cv2.CAP_PROP_FPS, 60)
fake = pyfakewebcam.FakeWebcam('/dev/video20', width, height)
background = cv2.imread("/data/background.jpg")
background_scaled = cv2.resize(background, (width, height))
while True:
frame = get_frame(cap, background_scaled)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
fake.schedule_frame(frame)
अब चित्र एकत्र करें:docker build -t bodypix ./bodypix
docker build -t fakecam ./fakecam
उन्हें चलाएं:
docker network create --driver bridge fakecam
docker run -d \
--name=bodypix \
--network=fakecam \
--gpus=all --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 \
bodypix
docker run -d \
--name=fakecam \
--network=fakecam \
-p 8080:8080 \
-u "$$(id -u):$$(getent group video | cut -d: -f3)" \
$$(find /dev -name 'video*' -printf "--device %p ") \
fakecam
यह केवल इस बात पर विचार करने के लिए रहता है कि किसी भी एप्लिकेशन के साथ काम करते समय कैमरा खोलने से पहले इसे शुरू किया जाना चाहिए। और ज़ूम में या कहीं और आपको एक कैमरा v4l2loopback
/ चयन करने की आवश्यकता है /dev/video20
।सारांश
यहां एक क्लिप है जो मेरे काम के परिणामों को प्रदर्शित करता है। पृष्ठभूमि परिवर्तन का परिणामदेखें! मैं मिलेनियम फाल्कन से कैमरा के साथ काम करने के लिए ओपन सोर्स टेक्नोलॉजी स्टैक का उपयोग कर रहा हूं!मैंने जो किया, वह मुझे बहुत पसंद आया। और मैं निश्चित रूप से अगले वीडियो कॉन्फ्रेंस में इस सब का लाभ उठाऊंगा।प्रिय पाठकों! क्या आप किसी अन्य चीज़ के लिए वीडियो कॉल के दौरान दिखाई देने वाली चीज़ों को बदलने की योजना बना रहे हैं?