स्मार्ट वीडियो प्लेयर या सिर्फ इशारा पहचान

परिचय


यह लेख इशारा मान्यता पर केंद्रित होगा। मेरा मानना ​​है कि यह विषय आज बहुत प्रासंगिक है, क्योंकि जानकारी दर्ज करने का यह तरीका मनुष्यों के लिए अधिक सुविधाजनक है। आप मान्यता, ऑब्जेक्ट ट्रैकिंग के बारे में YouTube पर बहुत सारे वीडियो देख सकते हैं, हब में इस विषय पर लेख भी हैं, और इसलिए, मैंने अपने स्वयं के, उपयोगी और आवश्यक कुछ प्रयोग करने और करने का फैसला किया। मैंने एक वीडियो प्लेयर बनाने का फैसला किया, जिसका उपयोग इशारों को नियंत्रित करने के लिए किया जा सकता है, क्योंकि कभी-कभी मैं माउस को उठाने में बहुत आलसी होता हूं, इस स्लाइडर को ढूंढता हूं और थोड़ा आगे या थोड़ा पीछे हटता हूं, खासकर जब मैं एक विदेशी भाषा में फिल्में देखता हूं (मुझे इसे अक्सर रिवाइंड करना पड़ता है) ।

लेख मुख्य रूप से इस बात से निपटेगा कि मैंने हावभाव पहचान को कैसे लागू किया, और मैं केवल सामान्य तौर पर वीडियो प्लेयर के बारे में बात करूंगा।

तो हम क्या चाहते हैं?


हम इशारों का उपयोग करते हुए निम्नलिखित आदेश देने में सक्षम होना चाहते हैं:
  1. फास्ट फॉरवर्ड / रिवाइंड
  2. रोकें / जारी रखें
  3. ध्वनि जोड़ें / घटाएं
  4. अगला / पिछला ट्रैक।

इशारों के साथ निम्नलिखित क्रियाओं की तुलना करें:
  1. बाएं से दाएं / बाएं से दाएं
  2. केंद्र से ऊपर-नीचे करने के लिए आंदोलन
  3. नीचे से ऊपर / ऊपर से नीचे की ओर गति
  4. केंद्र से नीचे-दाएं / केंद्र से नीचे-बाएं से आंदोलन

हम एक वेबकैम के माध्यम से इशारों का पालन करेंगे। मेरे लैपटॉप पर 0.3 मेगापिक्सेल कैमरा है। यह कमजोर है, लेकिन अच्छी रोशनी के साथ, इसका उपयोग ऐसे उद्देश्यों के लिए भी किया जा सकता है। लेकिन मैं एक बाहरी USB वर्म कैमरे का उपयोग करूंगा। इशारों को कुछ एक-रंग की छड़ी के साथ दिखाया जाएगा, क्योंकि हम इसे रंग से फ़िल्टर करके पृष्ठभूमि से चुनेंगे। उदाहरण के लिए, हम इस तरह के ऑब्जेक्ट के रूप में एक साधारण पेंसिल का उपयोग करेंगे। बेशक, यह किसी वस्तु को पहचानने का सबसे अच्छा तरीका नहीं है, लेकिन मैं तस्वीर में एक वस्तु के बजाय एक इशारा (आंदोलन) को पहचानने पर ध्यान केंद्रित करना चाहता था।

उपकरण


मैं Qt ढांचे 5.2 का उपयोग विकास के माहौल के रूप में करूंगा। वेबकैम से वीडियो स्ट्रीम को संसाधित करने के लिए मैं OpenCV 4.6 का उपयोग करूंगा। GUI पूरी तरह से QML में होगा, और मान्यता इकाई C ++ में होगी।

दोनों ओपन सोर्स टूल हैं, दोनों क्रॉस-प्लेटफॉर्म हैं। मैं लिनक्स के लिए एक खिलाड़ी विकसित कर रहा था, लेकिन इसे किसी अन्य प्लेटफ़ॉर्म पर स्थानांतरित किया जा सकता है, आपको केवल वांछित प्लेटफ़ॉर्म के लिए क्यूटी समर्थन के साथ ओपनसीवी को संकलित करना होगा और खिलाड़ी को फिर से बनाना होगा। मैंने खिलाड़ी को विंडोज में स्थानांतरित करने की कोशिश की, लेकिन मैं इसके लिए क्यूटी समर्थन के साथ ओपनसीवी संकलित नहीं कर सका। कौन प्रयास करेगा और कौन सफल होगा, कृपया मैनुअल या बायनेरिज़ साझा करें।

खिलाड़ी संरचना


नीचे दिया गया आंकड़ा खिलाड़ी की संरचना को दर्शाता है। खिलाड़ी दो धाराओं में काम करता है। मुख्य धारा जीयूआई और वीडियो प्लेयर है। इंटरफ़ेस को हैंग और वीडियो प्लेबैक से रोकने के लिए एक मान्यता इकाई को एक अलग स्ट्रीम में रखा गया है। मैंने QML में इंटरफ़ेस लिखा था, खिलाड़ी तर्क मैंने जेएस में लिखा था, और सी ++ में मान्यता ब्लॉक (हर कोई जानता है कि क्यों)। खिलाड़ी सिग्नल और स्लॉट का उपयोग करके मान्यता इकाई के साथ "संचार" करता है। मैंने एप्लिकेशन को 2 थ्रेड में अलग करने की सुविधा के लिए मान्यता वर्ग के लिए एक आवरण बनाया। वास्तव में, आवरण मुख्य धागे में है (यानी जैसा दिखाया नहीं गया है)। रैपर मान्यता वर्ग का एक उदाहरण बनाता है और इसे एक नए, अतिरिक्त स्ट्रीम में रखता है। वास्तव में, खिलाड़ी के बारे में सब कुछ, मैं मान्यता के बारे में बात करना और कोड देना जारी रखूंगा।



मान्यता


पहचानने के लिए, हम फ़्रेम एकत्र करेंगे और संभाव्यता सिद्धांत के तरीकों का उपयोग करके उन्हें संसाधित करेंगे। हम प्रति सेकंड बीस फ्रेम इकट्ठा करेंगे (वेबकैम अधिक अनुमति नहीं देता है)। हम प्रत्येक को दस फ्रेम प्रोसेस करेंगे।

एल्गोरिथ्म:
  1. हम वेब कैमरा से फ्रेम प्राप्त करते हैं और इसे फ़िल्टर पर भेजते हैं;
  2. फ़िल्टर एक द्विआधारी छवि देता है, जो एक काली पृष्ठभूमि पर एक सफेद आयत के रूप में केवल एक पेंसिल दिखाता है;
  3. बाइनरी इमेज को एनालाइज़र में भेजा जाता है, जहाँ पेन्सिल के कोने की गणना की जाती है। शीर्ष चोटी को सरणी में दर्ज किया गया है।
  4. यदि सरणी 10 तत्वों के आकार तक पहुंचती है, तो यह सरणी संभाव्य विश्लेषक को भेजी जाती है, जहां संख्याओं के जोड़े के अनुक्रम का विश्लेषण सबसे कम वर्गों द्वारा किया जाता है।
  5. यदि विश्लेषण ने किसी कमांड को मान्यता दी है, तो यह कमांड वीडियो प्लेयर को भेज दी जाती है।


मैं केवल 3 मूल मान्यता कार्य दूंगा।

निम्नलिखित कार्य कैमरे की निगरानी करता है यदि इशारा नियंत्रण सक्षम है:

void MotionDetector::observCam() { m_cap >> m_frame; //     filterIm(); //    detectStick(); // ,      drawStick(m_binIm); //    showIms(); //    } 


यहाँ मान्यता समारोह है:

 void MotionDetector::detectStick() { m_contours.clear(); cv::findContours(m_binIm.clone(), m_contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); if (m_contours.empty()) return; bool stickFound = false; for(int i = 0; i < m_contours.size(); ++i) { //    ,    if(cv::contourArea(m_contours[i]) < m_arAtThreshold) continue; //    m_stickRect = cv::minAreaRect(m_contours[i]); cv::Point2f vertices[4]; cv::Point top; cv::Point bottom; m_stickRect.points(vertices); if (lineLength(vertices[0], vertices[1]) > lineLength(vertices[1], vertices[2])){ top = cv::Point((vertices[1].x + vertices[2].x) / 2., (vertices[1].y + vertices[2].y) / 2.); bottom = cv::Point((vertices[0].x + vertices[3].x) / 2., (vertices[0].y + vertices[3].y) / 2.); } else{ top = cv::Point((vertices[0].x + vertices[1].x) / 2., (vertices[0].y + vertices[1].y) / 2.); bottom = cv::Point((vertices[2].x + vertices[3].x) / 2., (vertices[2].y + vertices[3].y) / 2.); } if (top.y > bottom.y) qSwap(top, bottom); m_stick.setTop(top); m_stick.setBottom(bottom); stickFound = true; } //   switch (m_state){ case ST_OBSERVING: if (!stickFound){ m_state = ST_WAITING; m_pointSeries.clear(); break; } m_pointSeries.append(QPair<double, double>(m_stick.top().x, m_stick.top().y)); if (m_pointSeries.size() >= 10){ m_actionPack = m_pSeriesAnaliser.analize(m_pointSeries); if (!m_actionPack.isEmpty()){ emit sendAction(m_actionPack); } m_pointSeries.clear(); } break; case ST_WAITING: m_state = ST_OBSERVING; break; } } 


आप यहां सबसे कम वर्ग विधि के बारे में पढ़ सकते हैं। अब मैं दिखाऊंगा कि मैंने इसे कैसे महसूस किया। निम्नलिखित एक संभावित श्रृंखला विश्लेषक है।

 bool SeriesAnaliser::linerCheck(const QVector<QPair<double, double> > &source) { int count = source.size(); //    2  ,   . QVector<double> x(count); QVector<double> y(count); for (int i = 0; i < count; ++i){ x[i] = source[i].first; y[i] = source[i].second; } double zX, zY, zX2, zXY; // z -   . zX -  x-  .. QVector<double> yT(count); //   zX = 0; for (int i = 0; i < count; ++i) zX += x[i]; zY = 0; for (int i = 0; i < count; ++i) zY += y[i]; zX2 = 0; for (int i = 0; i < count; ++i) zX2 += x[i] * x[i]; zXY = 0; for (int i = 0; i < count; ++i) zXY += x[i] * y[i]; //    double a = (count * zXY - zX * zY) / (count * zX2 - zX * zX); double b = (zX2 * zY - zX * zXY) / (count * zX2 - zX * zX); //   y for (int i = 0; i < count; ++i) yT[i] = x[i] * a + b; double dif = 0; for (int i = 0; i < count; ++i){ dif += qAbs(yT[i] - y[i]); } if (a == 0) a = 10; #ifdef QT_DEBUG qDebug() << QString("%1x+%2").arg(a).arg(b); qDebug() << dif; #endif //   > vBorder,  ,  ,   //    epsilan,  ,  ,   //  oblMovMin < a < oblMovMax,  ,  ,   //    0.6,  ,  ,   //  a < horMov,  ,  ,  . int vBorder = 3; int epsilan = 50; double oblMovMin = 0.5; double oblMovMax = 1.5; double horMov = 0.2; //    ,   if (qAbs(a) < vBorder && dif > epsilan) return false; //   double msInFrame = 1000 / s_fps; double dTime = msInFrame * count; // ms double dDistance; // px double speed = 0; /*px per ser*/ if (qAbs(a) < vBorder) dDistance = x[count - 1] - x[0]; //    else dDistance = y[count -1] - y[0]; speed = dDistance / dTime; //px per //    ,  if (qSqrt(qPow(x[0] - x[count - 1], 2) + qPow(y[0] - y[count - 1], 2)) < 15){ return false; } //    . if (speed > 0.6) return false; //   if (qAbs(a) > oblMovMin && qAbs(a) < oblMovMax){ //  if (a < 0){ //   s_actionPack = "next"; } else{ if (speed < 0) s_actionPack = "play"; else //   s_actionPack = "previous"; } } else if (qAbs(a) < horMov) { s_actionPack = QString("rewind %1").arg(speed * -30000); } else if (qAbs(a) > vBorder){ s_actionPack = QString("volume %1").arg(speed * -1); } else return false; return true; } 


निम्नलिखित कोड स्निपेट मान्यता प्राप्त कार्रवाई करता है (वीडियो प्लेयर पक्ष पर):

  function executeComand(comand){ var comandList = comand.split(' '); console.log(comand); switch (comandList[0]) { case "next": nextMedia(); break; case "previous": previousMedia(); break; case "play": playMedia(); break; case "rewind": mediaPlayer.seek(mediaPlayer.position + Number(comandList[1])); break; case "volume": mediaPlayer.volume += Number(comandList[1]); break; default: break; } } 


हां, मैं लगभग भूल गया था, पेंसिल रंग से पृष्ठभूमि से बाहर खड़ा है, और इसे कहीं स्थापित करने की आवश्यकता है। मैंने समस्या को इस प्रकार हल किया:



यही है, हम किसी भी सांत्वना के लिए किसी भी रंग को समायोजित कर सकते हैं, और इस रंग को मेनू में सहेज सकते हैं ताकि हम इसे बिना किसी समायोजन के तुरंत उपयोग कर सकें।

यहां मुझे मिले परिणाम हैं:


निष्कर्ष


अब वीडियो प्लेयर बहुत ही सरल इशारों को पहचान सकता है। मेरी राय में, खिलाड़ी की सबसे सफल और सुविधाजनक चीज इशारों को आगे / पीछे करना है। और यह यह टीम है जो सबसे अच्छी और पूरी तरह से काम करती है। हालाँकि आपको मूवी देखने के लिए जेस्चर कंट्रोल को ट्वीक करने की आवश्यकता है, फिर आप इसे थोड़ा पीछे हटाने के लिए माउस को नहीं देख सकते हैं।

पुनश्च: कौन परवाह करता है , यहां स्मार्टवीपी के स्रोत हैं।
PPS: मैंने कई रंगों को रिप्रजेंट किया, ऑरेंज सबसे अच्छा निकला (फास्ट मूवमेंट के लिए प्रतिरोधी)।

Source: https://habr.com/ru/post/In213417/


All Articles