Этот пост точно соответствует коду и содержанию оригинального учебника, созданного Садаивал Сингхом, и его можно посетить по следующей ссылке:

Обзор

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

  • 1 палец
  • 2 пальца
  • 3 пальца
  • 4 пальца
  • 5 пальцев
  • Ok
  • Молодец

Цель

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

Как вы увидите ниже, наш жестко запрограммированный подход к распознаванию жестов ограничен методами из пакета OpenCV. И хотя эта программа в этом контексте работает довольно хорошо, следует отметить, что только нейронная сеть допускает больший диапазон классов и абстрактное распознавание. Например; Попытка запрограммировать логику, которая захватывала бы и переводила язык жестов из сырых видеоданных, была бы почти невыполнимой задачей, однако нейронная сеть была бы гораздо лучше приспособлена для решения этой задачи.

Предварительная обработка изображений

Так зачем вообще беспокоиться о предварительной обработке визуальных данных, а не просто передавать нейронной сети нефильтрованное изображение? Ответ заключается в понимании того, как нейронные сети принимают решения; с использованием слоев узлов, каждый со своими рассчитанными весами и смещениями. Чтобы наша модель работала эффективно, узлы должны иметь возможность положительно реагировать на данные, относящиеся к нашей проблеме, и, наоборот, игнорировать то, что не нужно.

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

В частности, в нашем случае мы выполняем следующие шаги предварительной обработки изображения:

  • Выберите границу входного изображения, внутри которой мы будем сканировать на наличие человеческой руки.
  • Создайте маску, выбрав только те пиксели, которые соответствуют указанному цветовому диапазону.
  • Размытие изображения маски, чтобы заполнить недостающие точки данных.
  • Нарисуйте контур руки и определите показанные пальцы с помощью инструментов из Open CV.

Начиная

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

import cv2
import numpy as np
import math
cap = cv2.VideoCapture(0)

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

while(1):
        
    try:  #an error comes if it does not find anything in window as it cannot find contour of max area
          #therefore this try error statement
          
        ret, frame = cap.read()
        frame=cv2.flip(frame,1)
        kernel = np.ones((3,3),np.uint8)
        
        #define region of interest
        roi=frame[100:300, 100:300]
        
        
        cv2.rectangle(frame,(100,100),(300,300),(0,255,0),0)    
        hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
        
        
         
    # define range of skin color in HSV
        lower_skin = np.array([0,20,70], dtype=np.uint8)
        upper_skin = np.array([20,255,255], dtype=np.uint8)
        
     #extract skin colur imagw  
        mask = cv2.inRange(hsv, lower_skin, upper_skin)
        
   
        
    #extrapolate the hand to fill dark spots within
        mask = cv2.dilate(mask,kernel,iterations = 4)
        
    #blur the image
        mask = cv2.GaussianBlur(mask,(5,5),100) 
        
        
        
    #find contours
        _,contours,hierarchy= cv2.findContours(mask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    
   #find contour of max area(hand)
        cnt = max(contours, key = lambda x: cv2.contourArea(x))
        
    #approx the contour a little
        epsilon = 0.0005*cv2.arcLength(cnt,True)
        approx= cv2.approxPolyDP(cnt,epsilon,True)
       
        
    #make convex hull around hand
        hull = cv2.convexHull(cnt)
        
     #define area of hull and area of hand
        areahull = cv2.contourArea(hull)
        areacnt = cv2.contourArea(cnt)
      
    #find the percentage of area not covered by hand in convex hull
        arearatio=((areahull-areacnt)/areacnt)*100
    
     #find the defects in convex hull with respect to hand
        hull = cv2.convexHull(approx, returnPoints=False)
        defects = cv2.convexityDefects(approx, hull)
        
    # l = no. of defects
        l=0
        
    #code for finding no. of defects due to fingers
        for i in range(defects.shape[0]):
            s,e,f,d = defects[i,0]
            start = tuple(approx[s][0])
            end = tuple(approx[e][0])
            far = tuple(approx[f][0])
            pt= (100,180)
            
            
            # find length of all sides of triangle
            a = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2)
            b = math.sqrt((far[0] - start[0])**2 + (far[1] - start[1])**2) 
            c = math.sqrt((end[0] - far[0])**2 + (end[1] - far[1])**2)
            s = (a+b+c)/2
            ar = math.sqrt(s*(s-a)*(s-b)*(s-c))
            
            #distance between point and convex hull
            d=(2*ar)/a
            
            # apply cosine rule here
            angle = math.acos((b**2 + c**2 - a**2)/(2*b*c)) * 57
            
        
            # ignore angles > 90 and ignore points very close to convex hull(they generally come due to noise)
            if angle <= 90 and d>30:
                l += 1
                cv2.circle(roi, far, 3, [255,0,0], -1)
            
            #draw lines around hand
            cv2.line(roi,start, end, [0,255,0], 2)
            
            
        l+=1
        
        #print corresponding gestures which are in their ranges
        font = cv2.FONT_HERSHEY_SIMPLEX
        if l==1:
            if areacnt<2000:
                cv2.putText(frame,'Put hand in the box',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
            else:
                if arearatio<12:
                    cv2.putText(frame,'0',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
                elif arearatio<17.5:
                    cv2.putText(frame,'Good Job',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
                   
                else:
                    cv2.putText(frame,'1',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
                    
        elif l==2:
            cv2.putText(frame,'2',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
            
        elif l==3:
         
              if arearatio<27:
                    cv2.putText(frame,'3',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
              else:
                    cv2.putText(frame,'ok',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
                    
        elif l==4:
            cv2.putText(frame,'4',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
            
        elif l==5:
            cv2.putText(frame,'5',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
            
        elif l==6:
            cv2.putText(frame,'reposition',(0,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
            
        else :
            cv2.putText(frame,'reposition',(10,50), font, 2, (0,0,255), 3, cv2.LINE_AA)
            
        #show the windows
        cv2.imshow('mask',mask)
        cv2.imshow('frame',frame)
    except:
        pass
        
    
    k = cv2.waitKey(5) & 0xFF
    if k == 2:
        break 
    
cv2.destroyAllWindows()
cap.release()

Вывод

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

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

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

Написано Кинаном Джеймсом под руководством профессора Амита Мараджа.