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

Система визуального письма на основе жестов: веб -приложение

Введение, если мне нужно написать о части, то это 4 -я часть или 4 -я версия GE … Tagged с Python, Machinelearning, Codenewbie.

Введение

Если мне нужно написать о части, то это 4 -я часть или 4 -я версия Жестная система визуальной письменной системы Анкет Если вы прочитали мои предыдущие блоги по той же теме, то вы, вероятно, знаете, сколько у меня проги

Введение

Если мне нужно написать о части, то это 5 -я часть или 4 -я версия Жестная система визуальной письменной системы Анкет Если вы прочитали мои предыдущие блоги по той же теме, то вы, вероятно, знаете, сколько я перешел из ранней версии. Как только мой работодатель предложил мне на собеседовании, вы должны знать, как сделать ваш код пригодным для использования не кодировщиком/публикой, тогда ваш навык может быть полезным. Я применяю ту же концепцию. Он имел в виду, что я должен научиться развернуть мою систему в не кодировщик. Я не профессионал здесь, но я пишу этот код развертывания во время обучения, поэтому я ожидаю отзывов, предложений от читателей. На 4 -й версии системы я написал код, который был более организован концепцией ООП. Теперь я хочу, чтобы эта система была развернута. Все начинается с того, что, если и если вы пытаетесь работать над тем, что если, вы всегда будете изучать новые вещи. В этой версии я написал немного кода Flask для развертывания этого проекта в веб -приложении. Я постараюсь сделать это просто здесь как можно больше. Для лучшего понимания этого проекта требует Основная предпосылка для понимания концепций и алгоритмов, используемых в этом блоге, – это просмотреть мои предыдущие блоги . Потому что я мало объясняю на этот раз. Следуйте Прежде чем что -либо Раздел для получения дополнительной информации.

Что теперь?

  • Я напишу код, используя колбу для развертывания предыдущей системы в веб -приложении.

Простой рабочее время будет чем -то вроде ниже.

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

Прежде чем что -либо

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

Почему?

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

Кредиты

Я хочу отдать должное этим блогам всем на LinkedIn Кто отреагировал, поделился и прокомментировал мой предыдущий блог и на Twitter Также (большинство ретвитов были от ботов LOL). Я очень благодарен, что мой Этот пост LinkedIn о предыдущей версии Получил более 4K -реакции и почти 60 тыс. Просмотров. Я никогда не думал, что это привлечет столько внимания, и здесь я снова его улучшаю. Также мой друг Dip продолжает спрашивать меня о моей следующей работе, потому что он всегда поддерживает меня.

Мотивация

Я очень мотивирован поддержкой, которую люди дали мне LinkedIn .

Цитировать

А почему мы падаем, Брюс?

Чтобы мы могли научиться подбирать себя. – Томас Уэйн

Письменность контура: модификации

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

Инициализация

        ##########################
        ##########################
        self.final_window = cv2.imread("static/taking_avg.png")
        self.detected_text = "Nothing" 
  • final_window : Изображение, которое сообщит пользователю, что среднее значение принимается.
  • DETED_TEXT : Что было обнаружено путем передачи детектора? Последнее окно выглядит ниже:

Метод: __del__

Есть конструктор по умолчанию, созданный вместе с созданием объектов на Python и __del__ это деструктор, призванный, когда все ссылки объекта удаляются.

    def __del__(self):
        self.cam.release()
        cv2.destroyAllWindows()

Когда объект нашего контурного написания удален, мы хотим закрыть нашу камеру и уничтожить все открытые окна OpenCV (если есть доступны).

Метод: детектор

    def detector(self):
        img = self.canvas.canvas.astype(np.uint8)
        op = pytesseract.image_to_string(img, lang="eng", nice="1")
        self.detected_text = op

Здесь ничего странного не произошло, только обнаруженный текст назначается атрибуту.

Метод: Main

Я изменил этот метод только по некоторым линиям. Вы также можете увидеть коды здесь. Чтобы заставить нашу систему работать без каких -либо возможных ошибок, я попытался обернуть коды внутри Try/кроме блока. Основное внимание должно быть уделено на Try/кроме блока в нижней части ниже кода.

    def main(self): 
        try:
            while True:
                (ret, frame) = self.cam.read()
                if ret:
                    self.key = cv2.waitKey(1) & 0xFF
                    frame = imutils.resize(frame, width=self.size[1])
                    frame = cv2.flip(frame, 1)
                    clone = frame.copy()
                    gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY)
                    self.set_grays(gray)
                    self.size = frame.shape

                    # if to take average and num frames on average taking is lesser than 
                    if self.num_frames self.roi_counts["vroi"] and fmode is not None:
                                    self.running_mode = fmode
                                else:
                                    self.running_mode = self.vui.running_mode       
                            else:
                                self.running_mode = fmode
                        self.perform_mode()
                        self.vui.running_mode=self.running_mode
                        canvas = self.canvas.update_window(mode=self.running_mode, 
                                                        pointer=self.roi_pointer["droi"]).astype(np.uint8)
                        self.final_window = self.get_window(canvas=canvas, vui=vui)

                        self.roi_pointer["vroi"] = (-1, -1)

                    clone = self.make_rectangles(clone)
                    self.clone = clone
                    if self.key==27:
                        self.cam.release()
                        cv2.destroyAllWindows()

                    try:
                        ret, clone_jpeg = cv2.imencode('.jpeg', self.clone)
                        ret1, draw_jpeg =  cv2.imencode('.jpeg', self.final_window)
                        if ret:
                            self.clone = clone_jpeg.tobytes()
                            self.vui_frame=draw_jpeg.tobytes()
                            return clone_jpeg.tobytes(), draw_jpeg.tobytes(), self.detected_text
                    except:
                        pass
        except:
            try:
                ret, clone_jpeg = cv2.imencode('.jpeg', self.clone)
                ret1, draw_jpeg =  cv2.imencode('.jpeg', self.final_window)
                if ret:
                    self.clone = clone_jpeg.tobytes()
                    self.vui_frame=draw_jpeg.tobytes()
                    return clone_jpeg.tobytes(), draw_jpeg.tobytes(), self.detected_text
            except:
                pass
  • Возьмите изображение клона, где мы нарисовали прямоугольники, контуры, указатель и тексты. Кодируйте это JPEG.
  • Возьмите окончательное окно Vui/Canvas и кодируйте его JPEG.
  • Преобразуйте оба кодированных изображения в байты.
  • Верните оба байтовых изображения и обнаруженный текст.

Первоначально наш Final_window будет настроен на наше изображение, говорящее на среднем. И когда средние значения были взяты, мы можем отправить его на наши маршруты колбы. Я также установил некоторые атрибуты тоже. Все коды внутри main.py приведен ниже.

Коды: main.py

Пожалуйста, попробуйте игнорировать комментарии и длину кода.

import numpy as np
import os
import matplotlib.pyplot as plt
import cv2
import imutils
import pytesseract
import time

class VUI:
    """
        A class for visual user interface. Recommended to use default parameters.
    """
    def __init__(self, icons_dir="icons/", window_size=(525, 700, 3), vui_part=20, max_count=15):
        """
            icons_dir: directory to use icons from.
            window_size: size of an vui window. Recommended to use equal of final window.
            vui_part: How much % of rows from top will be used to stack icons?

        """
        self.idir = icons_dir
        self.window = np.zeros(window_size).astype(np.uint8)
        self.size = window_size
        self.vui_part = int(vui_part/100 * self.size[0])
        self.dd_part = (self.vui_part, int(50/100 * self.size[0]))

        self.modes = [fname.split(".")[0] for fname in os.listdir(self.idir)]
        self.icon_size = (int(self.size[1]/len(self.modes)), self.vui_part) # c, r

        self.current_icons = []
        self.anim_scale = 0.5
        self.anim_color = [5, 15, 2]
        self.prev_mode = "move"
        self.current_mode = "move"
        self.running_mode = None 
        self.hover=None

        self.mode_count = 1
        self.max_count = max_count
        self.color_count = 1
        self.max_color = 5

        self.current_pointer = (100, 100)
        self.canvas_pointer = None

        self.draw_color = (0, 0, 255)
        self.previous_color = (0, 0, 255)
        self.current_color = (0, 0, 255)
        self.pointer_color = (100, 200, 200)
        self.point = (10, -3)
        self.colors=None

        self.icons = self.prepare_icons()
        self.get_window()

    def prepare_icons(self):
        """
            A method to prepare icons on initial frame.
            Method sets 4 new attributes.
            cols: List to store (y1, y2) of icon.
            icon_position: Dictionary to store (y1, y2) as key and corresponding image as value
            current_icons: A dictionary initialized with initial icons. Changed on every frame when cursor lies above it.
            mode_pos: Mode as key and its icon's (y1, y2) as value.
        """
        icons = []
        cols = np.linspace(0, self.size[1]-1, len(self.modes)+1).astype(np.int64)
        cols = [(cols[i], cols[i+1]) for i in range(len(cols)-1)]

        icon_pos = {}
        mode_pos = {}
        for i, image_name in enumerate(os.listdir(self.idir)):
            img = cv2.imread(self.idir+image_name)
            img = cv2.resize(img, (cols[i][1]-cols[i][0], self.vui_part))
            icon_pos[cols[i]] = img
            mode_pos[self.modes[i]] = cols[i]
        self.cols = cols   
        self.icon_position = icon_pos
        self.current_icons = icon_pos
        self.mode_pos = mode_pos

    def set_colors(self, col=None, new_colors=None):
        """
            A method to set colors when pointer lies above color icon.
            Initially used subset of {Red, Green, Blue}
            col:- column where current pointer lies.
            new_colors:- If to use other colors.

            Method returns list of available colors on dropdown menu. 
            Changes the draw color, pointer color upon condition meet.
        """
        # earlier pointer was clipped within the vui
        pointer = self.canvas_pointer
        pointer = (pointer[1], self.vui_part+ pointer[0])
        if new_colors is None:
            r = np.array([0, 0, 255])
            g = np.array([0, 255, 0])
            b = np.array([255, 0, 0])
            colors = [r, g, b]
            colors_new = [colors[i]+colors[i+1] for i in range(len(colors)-1)]
            colors.extend(colors_new)
            self.colors = colors
        else:
            self.colors = new_colors
        rows = np.linspace(self.dd_part[0], self.dd_part[1], len(self.colors)+1).astype(np.int64)
        rows = [(rows[i], rows[i+1]) for i in range(len(rows)-1)]
        self.color_pos = {}
        for row, color in zip(rows, colors):
            self.color_pos[row] = color
            if row[0]<=pointer[1]=self.max_color:
                    self.draw_color=self.current_color
                self.pointer_color = (np.abs(np.array([200, 200, 100])-color).tolist())
            self.current_window[row[0]:row[1], col[0]:col[1]] = color

        return self.colors  
    def get_window(self):
        """
            A method to return a VUI window upon called. Sets pointer on VUI canvas.
        """
        self.current_window = np.zeros_like(self.window).astype(np.uint8)
        for col, img in self.current_icons.items():
            self.current_window[:self.vui_part, col[0]:col[1]] = img
        if self.running_mode == "color":
            self.set_colors(col=self.cols[self.modes.index("color")])
        if self.current_pointer is not None and self.current_pointer[0]>0:
            cv2.circle(self.current_window, (self.current_pointer[1], self.current_pointer[0]), self.point[0], self.pointer_color, self.point[1])

        return self.current_window
    def update_vui(self, pointer=(100, 100), cpointer=(10, 100)):
        """
            A method to update the entire VUI properties and state.
            pointer: Current pointer on VUI part.
            cpointer: Current pointer on Canvas.

            cpointer is useful when working with color mode.
        """
        self.current_pointer = pointer
        self.canvas_pointer = cpointer
        #print(pointer, canvas_pointer)
        current_icons = {}
        self.hover=None
        if pointer[0]<=self.vui_part:
            for col, mode in zip(self.cols, self.modes):
                icon = self.icon_position[col].copy()
                ishape = icon.shape

                #print(mode)
                if col[0] 1:
                        rd = int((r - ishape[0])/2)
                        cd = int((c - ishape[1])/2)

                        zeros_icon[:, :] = icon[rd:ishape[0]+rd, cd:ishape[1]+cd] 
                    else:
                        rd = int((ishape[0] - r)/2)
                        cd = int((ishape[1] - c)/2)
                        rdd, cdd = 0, 0
                        if ishape[0]-rd-rd > r:
                            rdd=1
                        if ishape[1]-cd-cd > c:
                            cdd=1
                        #print(icon.shape, ishape, rd, abs(r-rd), cd, abs(c-cd))
                        zeros_icon[rd:ishape[0]-rd-rdd, cd:ishape[1]-cd-cdd] = icon[::] 

                    current_icons[col] = zeros_icon.astype(np.uint8) + np.uint8(np.array(self.anim_color)*self.mode_count)


                    if self.prev_mode == self.current_mode:
                        self.mode_count += 1
                    else:
                        self.prev_mode = self.current_mode
                        self.mode_count = 1
                    if self.mode_count >= self.max_count:
                        self.running_mode = self.current_mode
                        self.mode_count = 1
                        self.hover = True

                else:
                    current_icons[col] = icon

            self.current_icons = current_icons
        else:
            self.mode_count = 1

        return self.get_window()

# vui = VUI()
# #show(vui.window)
# vui.update_vui()
# vui.update_vui()
# vui.update_vui(pointer=(200, 100))
# vui.update_vui(pointer=(200, 100))

class Canvas:
    def __init__(self, window_size=(525, 700, 3), draw_color=(100, 100, 100), 
                 pointer_color=(0, 0, 0), bg_color=(25, 25, 25), mode="move", 
                 point=(10, -3), vui=None, ssize=(300, 50, 3)):
        """
            A method to initialize canvas.
            window_size: size of a canvas window.
            draw_color: drawing color in RGB.
            pointer_color: pointer color in RGB.
            bg_color: background color in RGB.
            mode: running mode.
            point: tuple of (pointer radius, thickness)
            vui: VUI object.
            ssize: Slider's size.

        """
        self.size=window_size
        self.draw_color=draw_color
        self.pointer_color = pointer_color
        self.bg_color = bg_color
        self.window = np.zeros(self.size, dtype=np.uint8)
        self.canvas= self.window.copy()+bg_color
        self.mode = mode
        self.pointer = None
        self.point = point
        self.current_window = self.window+self.canvas
        self.vui = vui
        self.ssize = ssize
        self.sregion = ()

    def slider(self, size=(300, 30, 3), spoint=50, scolor=(100, 55, 100)):
        """
            A method to change the pointer size by moving a slider.
            size: size of slider region.
            spoint: slider point, generally row position of pointer.
            scolor: slider color
        """
        swidth=10
        #swidth=int(5/50*spoint)
        #swidth = np.clip(swidth, 5, spoint)
        swindow=np.zeros(self.size).astype(np.uint8)
        swindow[:self.ssize[0], 0:self.ssize[1]] += np.uint8([255, 255, 255])  
        r1 = np.clip(spoint-swidth, swidth, self.ssize[0]-swidth)
        r2 = np.clip(spoint+swidth, swidth, self.ssize[0]-swidth)
        spoint = int(10/50 * spoint)
        #print(r1, r2, spoint)


        swindow[r1:r2, :self.ssize[1]] = scolor
        self.point=(spoint, self.point[1])
        #cv2.imshow("slider", swindow.astype(np.uint8))
        return swindow.astype(np.uint8)   
    def clear(self):
        self.window = np.zeros(self.size, dtype=np.uint8)
        self.canvas= self.window.copy()+self.bg_color
    def update_window(self, mode, pointer=(400, 100)):
        """
            mode: running mode
            pointer: where is pointer now?
        """
        self.mode = mode
        self.vui.mode=mode
        self.pointer = pointer
        self.draw_color=self.vui.draw_color
        self.pointer_color = self.vui.pointer_color
        #self.pointer = (np.clip(self.vui.vui_part, pointer[0], self.size[0]), pointer[1])
        #print("c", self.draw_color)
        swindow = np.zeros(self.size).astype(np.uint8)
        #print(pointer)
        if 010:    

                if rname!="mroi":
                    pshape = self.size
                    if rname=="vroi":
                        pshape = (self.vui.vui_part, self.vui.size[1])
                    if rname=="droi":
                        # make it self.canvas.shape
                        #pshape = (self.canvas_shape[0], self.canvas_shape[1]-self.canvas.ssize[1]) 
                        pshape=self.canvas_shape
                    h = bottom - top
                    l = left - right


                    m = (int((m[0]/l)*pshape[1]), int((m[1]/h)*pshape[0]))    
                else:
                    m = (right+m[0], top+m[1])
                        #print(m)
                #else:
                        #m=(-1,-1)
                 #       pass
                #if rname=="droi":
                 #   m = (m[0]+self.canvas.ssize[1], m[1])
                self.roi_pointer[rname]=(m[1], m[0])

        return clone    

    def get_window(self, canvas, vui):
        final_window = vui.copy()
        canvas_cpy = canvas.copy()
        vshape = vui.shape
        cshape = canvas.shape
        #print(self.running_mode)
        if self.running_mode == "color":
            # get part where color lies and make those part of canvas_bg black
            cp = self.vui.mode_pos[self.running_mode]
            canvas[:self.vui.dd_part[1]-self.vui.vui_part, cp[0]:cp[1]] = vui[self.vui.vui_part:self.vui.dd_part[1], cp[0]:cp[1]]

            #show(canvas)
        #else:#
        final_window[self.vui.vui_part:, :] = canvas
        cp = self.roi_pointer["droi"]
        cp = (cp[1], cp[0]+self.vui.vui_part)
        point = self.canvas.point
        cv2.circle(final_window,  cp, point[0], self.canvas.pointer_color, point[1])
        return final_window

    def check_force_mode(self):
        top, right, bottom, left = self.roi_boxes["mroi"]
        if self.force_modes is None:
            x=np.linspace(right, left, 4).astype(np.int64)
            x=[(x[i],x[i+1]) for i in range(len(x)-1)]
            force_modes = ["move", "draw", "erase"]
            force_modes = {x[i]:force_modes[i] for i in range(len(x))}
            #print(force_modes)
            self.force_modes = force_modes
        elif self.roi_pointer["mroi"][0]>0:
            mpointer = self.roi_pointer["mroi"]

            for col, mode in self.force_modes.items():

                if col[0]<=mpointer[1]=self.fcount_mode:
                        #print("f ", mode)
                        #self.fcurrent_count=0

                        return mode
    def detector(self):
        img = self.canvas.canvas.astype(np.uint8)
        op = pytesseract.image_to_string(img, lang="eng", nice="1")
        self.detected_text = op
        #print("Detected: ", op)
    def perform_mode(self):
        if self.running_mode=="clear":
            self.canvas.clear()
            self.running_mode="move"
        if self.running_mode=="restart":
            self.take_average =True
            self.num_frames=0
            self.running_mode="move"
            self.canvas.clear()
        if self.running_mode=="save":
            #cv2.imshow("canvas", self.canvas.canvas.astype(np.uint8))
            cv2.imwrite(f"canvas {time.time()}.png", self.canvas.canvas.astype(np.uint8))
            #cv2.destroyWindow("canvas")
            self.running_mode="move"
        if self.running_mode=="exit":
            self.key=27
        if self.running_mode=="detect":
            self.running_mode="move"
            self.detector()
    def main(self): 
        try:
            while True:
                (ret, frame) = self.cam.read()
                if ret:
                    self.key = cv2.waitKey(1) & 0xFF
                    frame = imutils.resize(frame, width=self.size[1])
                    frame = cv2.flip(frame, 1)
                    clone = frame.copy()
                    gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY)
                    self.set_grays(gray)
                    self.size = frame.shape
                    #print(self.num_frames)

                    # if to take average and num frames on average taking is lesser than 
                    if self.num_frames self.roi_counts["vroi"] and fmode is not None:
                                    self.running_mode = fmode
                                else:
                                    self.running_mode = self.vui.running_mode       
                            else:
                                self.running_mode = fmode
                        self.perform_mode()
                        self.vui.running_mode=self.running_mode
                        canvas = self.canvas.update_window(mode=self.running_mode, 
                                                        pointer=self.roi_pointer["droi"]).astype(np.uint8)
                        self.final_window = self.get_window(canvas=canvas, vui=vui)
                        #cv2.imshow("CW", final_window)

                        self.roi_pointer["vroi"] = (-1, -1)
                        #print(vui.shape)
                        #print(canvas.shape)
                        #cv2.imshow("Canvas", canvas)
                        #cv2.imshow("VUI", vui)
                    clone = self.make_rectangles(clone)
                    self.clone = clone
                    #cv2.imshow("Feed", clone)
                    if self.key==27:
                        self.cam.release()
                        cv2.destroyAllWindows()
                        #self.__del__()
                        #break
                    try:
                        ret, clone_jpeg = cv2.imencode('.jpeg', self.clone)
                        ret1, draw_jpeg =  cv2.imencode('.jpeg', self.final_window)
                        #print(ret, ret1)
                        if ret:
                            self.clone = clone_jpeg.tobytes()
                            self.vui_frame=draw_jpeg.tobytes()
                            return clone_jpeg.tobytes(), draw_jpeg.tobytes(), self.detected_text
                    except:
                        pass
        except:
            try:
                ret, clone_jpeg = cv2.imencode('.jpeg', self.clone)
                ret1, draw_jpeg =  cv2.imencode('.jpeg', self.final_window)
                #print(ret, ret1)
                if ret:
                    self.clone = clone_jpeg.tobytes()
                    self.vui_frame=draw_jpeg.tobytes()
                    return clone_jpeg.tobytes(), draw_jpeg.tobytes(), self.detected_text
            except:
                pass




#gw = ContourWriting(avg_frames=150, aweight=0.5)
#gw.main()

Приложение

Я не знаю, как начать с Флбой, потому что я сделал лишь несколько проектов на колбе. Если вы не установили колбу, установите ее, используя свой установщик пакета Conda/PIP. Я постараюсь объяснить небольшие коды колбы, но если вы новичок в колбе, постарайтесь узнать некоторые основы об этом. Я изучаю колбу из электронных книг.

Структура проекта

  • значки
    • Изображения значков
  • статический
    • CSS
      • CSS -файлы
    • Ошибка/Среднее взятие изображений
  • шаблоны
    • index.html
    • Обнаружение.html
  • app.py
  • main.py

Импорт зависимости

from flask import Flask, render_template, Response
import cv2
import numpy as np
import matplotlib.pyplot as plt
from main import *
import time


err_img=cv2.imread("static\error_frame.png")
_, err_img_byte = cv2.imencode(".jpg", err_img)
err_img_byte = err_img_byte.tobytes()
draw=None
  • Колба используется для создания объекта приложения.
  • render_template – это отображать HTML -файл по разным маршруту.
  • Ответ должен отправить объект ответа в HTTP -запрос.
  • От нашего main.py Файл, мы импортируем все, но импортируем только класс Контурное написание будет достаточно.

Изображение error_frame.png Похоже, ниже.

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

Класс: GestureWeb

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

  • Установить DETED_TEXT к Ничего изначально.
  • Создать объект Контурное написание Анкет
class GestureWeb:
    def __init__(self, port=5005, debug=True):
        self.detected_text = "Nothing"
        self.camera= ContourWriting()

    def frame_gen(self, camera, kind="frame"):        
        while True:    
            frame = camera.main()
            if frame is None:
                frame = (None, None)
            if frame[0] is None:
                frame = err_img_byte
            elif frame[1] is None:
                draw = err_img_byte
            else:
                frame, draw, gw.detected_text = frame

            frame = (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n'+bytearray(frame)+b'\r\n')
            draw = (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n'+bytearray(draw)+b'\r\n')
            if kind =="frame":
                yield frame
            if kind =="draw":
                yield draw

Метод: frame_gen

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

  • Позвоните в Главный Метод Контурное написание объект.
  • Если это Нет Затем мы передадим изображения ошибок в Frame и Vui часть нашего веб -приложения.
  • Иначе мы разделяем возвращенное значение от Главный Метод в 3 части, Рамка , нарисовать , DETED_TEXT Анкет
  • Затем мы прикрепляем DETEDECTED_TEXT к атрибуту.
  • Мы преобразуем кодированное изображение JPEG в Bytearray и прикрепляем его с помощью нашего типа контента.
  • Затем мы даем соответствующее значение (кадр или VUI).

Индексный файл: index.html

Я хочу, чтобы этот файл отображал работающий подачу и VUI рядом на той же странице. Следовательно, я должен найти способ сложить, затем горизонтально. Я получил помощь из разделов комментариев Stackoverflow. Я не собираюсь объяснять здесь CSS/HTML, потому что я не очень хорош в них.


    
        Tindex
    
    
    
    

Contour Based Writing System

Frames
Canvas

Чтобы просмотреть видео с камерой, мы будем использовать изображение вместо видео. Это может показаться видео с использованием, но правда в том, что комбинация фотографий также является видео. Поэтому я использовал изображение как кадр, мы не узнаем, что это изображение Но мы чувствуем себя видео в реальном времени. И скорость подачи в веб -приложение такая же, как и с камерой. video_feed это маршрут, который возвращает кадр в качестве объекта ответа. Я не собираюсь много объяснять, потому что я тоже учусь. У нас есть отличный путь для различного изображения, для кадров или живого корма, который у нас есть video_feed И для холста у нас есть get_canvas И джинджа сделал это возможным. Таким образом, в основном у нас будет живая подача на левой стороне, а на нашем рисунке на правой стороне.

Чтобы показать обнаруженный текст, у нас есть обнаружение.html файл. Мы показываем это в нижней части нашей страницы, и мы используем его на той же странице по iframe ярлык.

Файл обнаружения: detection.html

В этом файле мы показываем только DETED_TEXT Атрибут объекта класса GestureWeb Анкет




    
        
        
            detection
        
    
    
        

Detected:

{{detected_text}}

Я хочу, чтобы эта страница обновлялась за каждые 5 секунд, чтобы она возвращала обнаруженный текст до 5 секунд.

Приготовьте декораторы/маршруты колбы

Я надеюсь, что кто -то придумает лучший способ выполнения после операций. Я нашел эти способы работы, искав в Интернете целый день, потому что это мой первый проект Flask с нуля.

Перед первым запросом

app = Flask(__name__)

@app.before_first_request
def before_first_request_func():
    global gw 
    gw = GestureWeb()
  • Сначала создайте приложение Flask, используя класс Flask.
  • Используйте декоратор перед_фирст_рекест выполнить некоторые задачи перед первым запросом, сделанным в нашем приложении.

    • Мы сделали наш GestureWeb Объект по всему миру доступен.
    • Сделав наш GestureWeb Объект на ранней стадии создания приложений также делает нашу камеру готовой.

Главный маршрут

Что мы хотим показать, когда пользователь входит в основной URL?

app.route('/')
def index():
    return render_template('index.html')

Мы хотим отобразить index.html Когда пользователь входит в основной URL. И мы хотим показать все на этой странице.

Маршрут: video_feed

Как я уже говорил ранее на index.html Было 2 источника, определенных внутри A IMG тег каждого изображения. Один из них video_feed Анкет

@app.route('/video_feed')
def video_feed():
    fresp = gw.frame_gen(gw.camera, kind="frame")
    return Response(fresp, mimetype='multipart/x-mixed-replace; boundary=frame')

На вышеуказанном коде, gw.frame_gen (gw.camera,) , GW объект GestureWeb. Метод frame_gen является генератором. Мы передаем объект Контурное написание И такая вещь, которую мы ожидаем по возвращении. Наконец, мы возвращаем тип ответа через объект ответа.

Маршрут: get_canvas

Как я уже говорил ранее на index.html Было 2 источника, определенных внутри A IMG тег каждого изображения. Один из них get_canvas Анкет

@app.route('/get_canvas')
def get_canvas():
    fresp = gw.frame_gen(gw.camera, "draw")    
    return Response(fresp, mimetype='multipart/x-mixed-replace; boundary=frame')

На вышеуказанном коде, gw.frame_gen (gw.camera,) , GW объект GestureWeb. Метод frame_gen является генератором. Мы передаем объект Контурное написание И такая вещь, которую мы ожидаем по возвращении.

Маршрут: обнаружение.html

Теперь сделать наш iframe работать над index.html Файл, нам также необходимо сделать маршрут для нашего файла обнаружения. Во время рендеринга этого файла мы также отправим текущий обнаруженный текст. Пожалуйста, посмотрите еще раз на обнаружение.html Файл для получения большего разрешения.

@app.route('/detection.html', methods=['GET', 'POST'])
def detect():
    return render_template("detection.html", detected_text=gw.detected_text)

Запрос на разрыв

Этот декоратор полезен, когда мы закрываем нашу вкладку или ошибку.

@app.teardown_request
def teardown_request_func(error=None):
    global gw
    gw = GestureWeb()   
    return "ok"

Запустить его

Стандартный способ запуска приложения Flask подобен ниже.

if __name__=='__main__':    
    app.run(debug=True)

Коды: app.py

from flask import Flask, render_template, Response
import cv2
import numpy as np
import matplotlib.pyplot as plt
from main import *
import time


err_img=cv2.imread("static\error_frame.png")
_, err_img_byte = cv2.imencode(".jpg", err_img)
err_img_byte = err_img_byte.tobytes()
draw=None



class GestureWeb:
    def __init__(self, port=5005, debug=True):
        self.detected_text = "Nothing"
        self.camera= ContourWriting()

    def frame_gen(self, camera, kind="frame"):        
        while True:    
            frame = camera.main()
            if frame is None:
                frame = (None, None)
            if frame[0] is None:
                frame = err_img_byte
            elif frame[1] is None:
                draw = err_img_byte
            else:
                frame, draw, gw.detected_text = frame
            frame = (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n'+bytearray(frame)+b'\r\n')
            draw = (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n'+bytearray(draw)+b'\r\n')
            if kind =="frame":
                yield frame
            if kind =="draw":
                yield draw
            if kind=="text":
                yield detected_text




app = Flask(__name__)

@app.before_first_request
def before_first_request_func():
    global gw 
    gw = GestureWeb()


@app.route('/')
def index():
    return render_template('index.html')

@app.route('/get_canvas')
def get_canvas():
    fresp = gw.frame_gen(gw.camera, "draw")    
    return Response(fresp, mimetype='multipart/x-mixed-replace; boundary=frame')


@app.route('/video_feed')
def video_feed():
    fresp = gw.frame_gen(gw.camera, kind="frame")
    return Response(fresp, mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/detection.html', methods=['GET', 'POST'])
def detect():
    return render_template("detection.html", detected_text=gw.detected_text)

@app.teardown_request
def teardown_request_func(error=None):
    global gw
    gw = GestureWeb()   
    return "ok"



if __name__=='__main__':    
    app.run(debug=True)


Окончательно

Результат всего кода должен выглядеть ниже. Из любого терминала запустить app.py файл. Я использую VS -код, и у меня есть R

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

Я собираюсь указать на функции и недостатки этой системы.

Коды

Коды для текущей версии системы доступны по ссылке ниже, а если это не так, нажмите на комментарий или оставьте мне сообщение (LinkedIn, Twitter или напишите мне).

Функции

  • Развернуто в веб -приложении.
  • Живой корм и область рисунка расположены рядом.
  • Обнаруженный текст проверяется на 5 секунд.

Недостатки

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

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

Идеи

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

Что дальше?

Я постараюсь решить недостатки в следующий раз. Но мне интересно заставить эту систему работать на мобильных телефонах. В настоящее время я подумываю взять рамки с камеры устройств и обработать ее. Затем используйте какой -нибудь вызов API, чтобы получить эти кадры. Я мог бы использовать единство. Я очень рад попытаться использовать LSTM и другие алгоритмы глубокого обучения искусства, чтобы сделать эту систему более удивительной Но у меня нет доступа в Интернет (кроме сотовых данных), чтобы провести широкие исследования.

Почему бы не прочитать больше?

Редом от ранней версии. Первой лучшей версией всего будет улучшение недостатка. Я применяю ту же концепцию. В предыдущих версиях было много проблем, один из них – структурированный способ кода. Теперь, в этой версии, я использую объектно -ориентированный путь, любой концепции ООП будет достаточно, чтобы понять следующие коды. И незначительная концепция обработки изображений, а также знание начинающего OpenCV будет достаточно, чтобы узнать все в этом блоге. Но для лучшего понимания этого проекта требуется Основная предпосылка для понимания концепций и алгоритмов, используемых в этом блоге, – это просмотреть мои предыдущие блоги . Потому что я мало объясняю на этот раз. Следуйте Прежде чем что -либо Раздел для получения дополнительной информации.

Что теперь?

  • Теперь в этом блоге я буду делиться новым и более оптимизированным и масштабируемым способом написания кодов для выполнения этой задачи. Я представляю путь ООП.
  • Я также добавлю новый режим. И этот режим является наиболее важным. Ползунок, чтобы изменить размер указателя.

Вся система можно разделить на 3 части. И если мне придется сделать диаграмму, то это будет что -то вроде ниже.

Вуи

Класс, представляющий визуальный пользовательский интерфейс, который будет лежать над холстом и содержит значки и взаимодействующую панель. Некоторые общие методы записываются внутри коробки, а среда связи между классами – это объект VAI, а иногда и атрибуты. Одним из примеров связи является написание контуров класса посылает текущее местоположение указателя в VUI, а VUI показывает указатель в области VUI (если лежит). Затем представление применяет анимацию на значках и пытается установить режим. Затем текущее окно VUI отправляется обратно вместе с режимом работы.

Холст

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

Контурное письмо

Класс, который обрабатывает все другие классы и выполняет задачу извлечения контура с помощью OpenCV. Этот класс несет ответственность за многие вещи, и один из них является назначением указателя.

Прежде чем что -либо

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

Кредиты

Я хочу отдать должное этим блогам всем на LinkedIn Кто отреагировал, поделился и прокомментировал мой предыдущий блог и на Twitter Также (большинство ретвитов были от ботов LOL). Я очень благодарен, что мой Этот пост LinkedIn о предыдущей версии Получил более 2,2 тыс. Реакций и почти 30 тыс. Просмотров. Я никогда не думал, что это привлечет столько внимания, и здесь я снова его улучшаю. Если мне нужно взять имя и причину для кредитов:-

Мотивация

Я очень мотивирован поддержкой, которую люди дали мне LinkedIn .

Зависимости

Как обычно, импорт зависимостей при более раннем запуске кода.

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

def show(img, figsize=(10, 10)):
    figure = plt.figure(figsize=figsize)
    plt.imshow(img)
    plt.show()

VUI: визуальный пользовательский интерфейс

Я не знаю, используется ли термин VUI для визуального пользовательского интерфейса, и если этот термин прав сейчас, для использования. Но я все равно использую это на той цели, которую мы взаимодействуем с видео кадрами. На этот раз вместо простой переменной я использую новый и надежный способ определения VUI. Класс Vui.

Инициализировать класс

Класс инициализируется с помощью параметров по умолчанию и настройки полезных параметров перед запуском программы.

class VUI:
    """
        A class for visual user interface. Recommended to use default parameters.
    """
    def __init__(self, icons_dir="icons/", window_size=(525, 700, 3), vui_part=20):
        """
            icons_dir: directory to use icons from.
            window_size: size of an vui window. Recommended to use equal of final window.
            vui_part: How much % of rows from top will be used to stack icons?

        """
        self.idir = icons_dir
        self.window = np.zeros(window_size).astype(np.uint8)
        self.size = window_size
        self.vui_part = int(vui_part/100 * self.size[0])
        self.dd_part = (self.vui_part, int(50/100 * self.size[0]))

        self.modes = [fname.split(".")[0] for fname in os.listdir(self.idir)]
        self.icon_size = (int(self.size[1]/len(self.modes)), self.vui_part) # c, r

        self.current_icons = []
        self.anim_scale = 0.5
        self.anim_color = [5, 15, 2]
        self.prev_mode = "move"
        self.current_mode = "move"
        self.running_mode = None 
        self.hover=None

        self.mode_count = 1
        self.max_count = 5
        self.color_count = 1
        self.max_color = 5

        self.current_pointer = (100, 100)
        self.canvas_pointer = None

        self.draw_color = (0, 0, 255)
        self.previous_color = (0, 0, 255)
        self.current_color = (0, 0, 255)
        self.pointer_color = (100, 200, 200)
        self.point = (10, -3)
        self.colors=None

        self.icons = self.prepare_icons()
        self.get_window()
  • idir : Удерживает каталог икон.
  • окно : держит окно Vui. Изначально пустое изображение.
  • vui_part : Сколько строк из верхнего ряда использовать для значков?
  • dd_part : Сколько рядов из верхнего ряда можно использовать для выпадения?
  • моды : Доступные режимы. Обычно название изображений под idir.
  • icon_size : Размер каждого значка на vui_part. Формат (Cols, ряды).
  • current_icons : Список для хранения значков на каждом кадре.
  • aniste_scale : Анимационная шкала. Как увеличить/уменьшить размер значка?
  • ami_color : Цвет анимации для значков
  • prev_mode : Предыдущий режим, по умолчанию двигаться.
  • current_mode : Текущий режим, по умолчанию перемещение.
  • Rune_Mode : Запуск режима, по умолчанию нет.
  • прокачать : Если курсор выше региона VUI.
  • mode_count : Подсчет количества раз, указатель непрерывно находится над значком тока. По умолчанию 1.
  • max_count : Сколько раз повторяется текущий режим, чтобы изменить режим выполнения в текущий режим?
  • color_count : То же, что и mode_count, но для цвета.
  • max_count : То же, что и max_count, но для цвета.
  • current_pointer : Где в настоящее время находится указатель? Значение, измененное другими классами.
  • canvas_pointer : Где сейчас указатель на холсте?
  • draw_color : Текущий цвет рисования. Красный дефолт.
  • Предыдущий_Color : Где курсор солгал на предыдущем раскрывающемся списке цвета?
  • current_color : Где курсор лежит на текущем выпадении цвета?
  • pointer_color : Какой цвет должен быть указатель.
  • точка : Значение, чтобы настроить размер указателя. (радиус, толщина)
  • цвета : Список цветов в RGB для выбора цвета
  • Значки : Значение, установленное методом PRIPARE_ICONS .

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

Метод: подготовить значки

Метод подготовки значков и их соответствующих режимов. Это называется изначально при инициализации класса.

    def prepare_icons(self):
        """
            A method to prepare icons on initial frame.
            Method sets 4 new attributes.
            cols: List to store (y1, y2) of icon.
            icon_position: Dictionary to store (y1, y2) as key and corresponding image as value
            current_icons: A dictionary initialized with initial icons. Changed on every frame when cursor lies above it.
            mode_pos: Mode as key and its icon's (y1, y2) as value.
        """
        cols = np.linspace(0, self.size[1]-1, len(self.modes)+1).astype(np.int64)
        cols = [(cols[i], cols[i+1]) for i in range(len(cols)-1)]

        icon_pos = {}
        mode_pos = {}
        for i, image_name in enumerate(os.listdir(self.idir)):
            img = cv2.imread(self.idir+image_name)
            img = cv2.resize(img, (cols[i][1]-cols[i][0], self.vui_part))
            icon_pos[cols[i]] = img
            mode_pos[self.modes[i]] = cols[i]
        self.cols = cols   
        self.icon_position = icon_pos
        self.current_icons = icon_pos
        self.mode_pos = mode_pos

Что здесь происходит?

  • Метод начинается с создания массива столбцов для значков. Если у нас есть 9 значков, то, чтобы сложить их, сколько строк требуется? 10.
  • Затем мы изменяем этот массив в список кортежей. Хранить (Y1, Y2) для каждого столбца.
  • Инициализировать словари icon_pos и mode_pos Анкет
  • Для каждого изображения в каталоге значков:
    • Прочитайте изображение и измените его размер, чтобы сформировать это полностью подходит для Cols.
    • Магазин изображение на Иконовый проход , ключ будет холодным, а значение будет изображением.
    • Магазин Cols на mode_pos , ключ как имя режима и значение как Cols.
  • Сделайте атрибуты для кольцо , icon_position , current_icons , mode_pos Анкет

Метод: установить цвета

Этот метод используется для изменения цвета указателя/рисовать, когда указатель находится над ним для некоторых кадров. Мы можем изменить цвет из раскрывающегося меню. Цвета либо предоставляются извне по умолчанию.

    def set_colors(self, col=None, new_colors=None):
        """
            A method to set colors when pointer lies above color icon.
            Initially used subset of {Red, Green, Blue}
            col:- column where current pointer lies.
            new_colors:- If to use other colors.

            Method returns list of available colors on dropdown menu. 
            Changes the draw color, pointer color upon condition meet.
        """
        # earlier pointer was clipped within the vui
        pointer = self.canvas_pointer
        pointer = (pointer[1], self.vui_part+ pointer[0])
        if new_colors is None:
            r = np.array([0, 0, 255])
            g = np.array([0, 255, 0])
            b = np.array([255, 0, 0])
            colors = [r, g, b]
            colors_new = [colors[i]+colors[i+1] for i in range(len(colors)-1)]
            colors.extend(colors_new)
            self.colors = colors
        else:
            self.colors = new_colors
        rows = np.linspace(self.dd_part[0], self.dd_part[1], len(self.colors)+1).astype(np.int64)
        rows = [(rows[i], rows[i+1]) for i in range(len(rows)-1)]
        self.color_pos = {}
        for row, color in zip(rows, colors):
            self.color_pos[row] = color
            if row[0]<=pointer[1]=self.max_color:
                    self.draw_color=self.current_color
                self.pointer_color = (np.abs(np.array([200, 200, 100])-color).tolist())
            self.current_window[row[0]:row[1], col[0]:col[1]] = color

        return self.colors
  • Метод принимает указатель как указатель Canvas, потому что мы используем только указатель VUI на vui_part Только.
  • Мы отредактировали указатель, чтобы сделать его подходящим для нашего дела. Обмен требуется, потому что у нас в основном есть указатель (Y, X).
  • Мы инициализируем цвета из проверки состояния.
  • Поскольку цвета закрыты в закрытом виде, цвета сложены сверху вниз. Поэтому найдите ряды, которые делят цвета.
  • Если x координата текущего указателя находится в любой из строк цвета, то color_count увеличена.
  • Когда color_count достигает max_count Мы меняем цвет указателя и рисоваем цвет.
  • Мы также должны изменить цвет окна в тех регионах, где находится позиция текущего цвета.

Метод: получить окно

Этот метод вызывается из других методов.

    def get_window(self):
        """
            A method to return a VUI window upon called. Sets pointer on VUI canvas.
        """
        self.current_window = np.zeros_like(self.window).astype(np.uint8)
        for col, img in self.current_icons.items():
            self.current_window[:self.vui_part, col[0]:col[1]] = img
        if self.running_mode == "color":
            self.set_colors(col=self.cols[self.modes.index("color")])
        if self.current_pointer is not None and self.current_pointer[0]>0:
            cv2.circle(self.current_window, (self.current_pointer[1], self.current_pointer[0]), self.point[0], self.pointer_color, self.point[1])

        return self.current_window
  • Начинается с инициализации нового пустого изображения.
  • Перевернуть через current_icons Ключ и значения установите изображение значка в точную позицию vui_part .
  • Когда Rune_Mode Является ли цвет, мы должны показать выпадающую сторону, следовательно, вызов set_colors Метод и передайте столбец, где ток указатель.
  • Наконец -то нарисуйте указатель на окно и верните.

Метод: обновление VUI

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

    def update_vui(self, pointer=(100, 100), cpointer=(10, 100)):
        """
            A method to update the entire VUI properties and state.
            pointer: Current pointer on VUI part.
            cpointer: Current pointer on Canvas.

            cpointer is useful when working with color mode.
        """
        self.current_pointer = pointer
        self.canvas_pointer = cpointer
        #print(pointer, canvas_pointer)
        current_icons = {}
        self.hover=None
        if pointer[0]<=self.vui_part:
            for col, mode in zip(self.cols, self.modes):
                icon = self.icon_position[col].copy()
                ishape = icon.shape

                #print(mode)
                if col[0] 1:
                        rd = int((r - ishape[0])/2)
                        cd = int((c - ishape[1])/2)

                        zeros_icon[:, :] = icon[rd:ishape[0]+rd, cd:ishape[1]+cd] 
                    else:
                        rd = int((ishape[0] - r)/2)
                        cd = int((ishape[1] - c)/2)
                        rdd, cdd = 0, 0
                        if ishape[0]-rd-rd > r:
                            rdd=1
                        if ishape[1]-cd-cd > c:
                            cdd=1
                        #print(icon.shape, ishape, rd, abs(r-rd), cd, abs(c-cd))
                        zeros_icon[rd:ishape[0]-rd-rdd, cd:ishape[1]-cd-cdd] = icon[::] 

                    current_icons[col] = zeros_icon.astype(np.uint8) + np.uint8(np.array(self.anim_color)*self.mode_count)


                    if self.prev_mode == self.current_mode:
                        self.mode_count += 1
                    else:
                        self.prev_mode = self.current_mode
                        self.mode_count = 1
                    if self.mode_count >= self.max_count:
                        self.running_mode = self.current_mode
                        self.mode_count = 1
                        self.hover = True

                else:
                    current_icons[col] = icon

            self.current_icons = current_icons
        else:
            self.mode_count = 1

        return self.get_window()
  • Возьми vui_pointer и установите на current_pointer .
  • Возьми canvas_pointer и установите тоже.
  • Инициализировать словарь для хранения current_icons . Мы храним здесь измененный значок вместе с неизменным.
  • Инициализировать Hover никто.
  • Продолжайте только тогда, когда тока x ось лежит ниже vui_part Анкет то есть, если указатель находится на панели значков.
  • Проверка через столбцы значков и режимов одновременно.
    • Возьмите значок, лежащий на этой колонке.
    • Если столбец тока указателя находится в столбцах:
      • Установите текущий режим.
      • Создать пустое изображение формы значка как zeros_icon Анкет
      • Найдите фактор масштаба по нескольким режимам.
      • Найдите новую строку/столбец для этого значка. Мы масштабируем значок, чтобы он выглядел как анимирующий.
      • Сделайте центр New Icon’s Center и Original Icon’s Centre и вставьте его в пустое изображение.
      • Магазин zeros_icon на current_icons Анкет
      • Выполнить проверку счета. И изменить режим, когда количество режимов достигает макс. Установите навредить True.
    • Еще:
      • Значок магазина на current_icon . Потому что указатель не выше этого значка, так что оставьте его таким, каким есть.
  • Вернуть вывод get_window метод

Тестовый класс: VUI

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

vui = VUI()
show(vui.window)

fig = plt.figure(figsize=(30, 30))
ax = fig.subplots(2, 3, sharex='col', sharey='row')

for i in range(2):
    for j in range(3):
        ax[i][j].imshow(vui.update_vui())

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

Холст

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

Инициализировать: холст

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

class Canvas:
    def __init__(self, window_size=(525, 700, 3), draw_color=(100, 100, 100), 
                 pointer_color=(0, 0, 0), bg_color=(25, 25, 25), mode="move", 
                 point=(10, -3), vui=None, ssize=(300, 50, 3)):
        """
            A method to initialize canvas.
            window_size: size of a canvas window.
            draw_color: drawing color in RGB.
            pointer_color: pointer color in RGB.
            bg_color: background color in RGB.
            mode: running mode.
            point: tuple of (pointer radius, thickness)
            vui: VUI object.
            ssize: Slider's size.

        """
        self.size=window_size
        self.draw_color=draw_color
        self.pointer_color = pointer_color
        self.bg_color = bg_color
        self.window = np.zeros(self.size, dtype=np.uint8)
        self.canvas= self.window.copy()+bg_color
        self.mode = mode
        self.pointer = None
        self.point = point
        self.current_window = self.window+self.canvas
        self.vui = vui
        self.ssize = ssize
        self.sregion = ()
  • Холст инициализируется с цветом фона.
  • Текущее окно должно включать указатель и настоящий холст.

Метод: окно обновления

Метод, который выполняет рисование/стирание/перемещение на холсте и показывает указатель.

    def update_window(self, mode, pointer=(400, 100)):
        """
            mode: running mode
            pointer: where is pointer now?
        """
        self.mode = mode
        self.vui.mode=mode
        self.pointer = pointer
        self.draw_color=self.vui.draw_color
        self.pointer_color = self.vui.pointer_color
        #self.pointer = (np.clip(self.vui.vui_part, pointer[0], self.size[0]), pointer[1])
        #print("c", self.draw_color)
        swindow = np.zeros(self.size).astype(np.uint8)
        #print(pointer)
        if 0
  • Некоторые атрибуты первоначально устанавливаются из объекта VUI и параметров.
  • Инициализировать ползунок к пустому изображению.
  • Когда указатель находится в области ползунка, затем вызовите окно слайдера и измените режим для перемещения.
  • Если режим рисуется,
    • Нарисуйте круг по положению указателя с текущими свойствами рисования на холсте.
    • Создайте текущее окно.
    • Нарисуйте круг на текущем окне с цветом указателя.
  • Если режим стирается,
    • Нарисуйте круг по положению указателя с текущими свойствами рисования на холсте. I.G Цвет фона
    • Создайте текущее окно.
    • Нарисуйте круг на текущем окне с цветом указателя.
  • Еще:
    • Создайте текущее окно и нарисуйте круг в положении указателя с цветом указателя.
  • Вернуть текущее окно.

Метод: слайдер

Это новая концепция в этой версии. Я использую слайдер, чтобы изменить размер указателя. Разве это не интересно?

     def slider(self, size=(300, 30, 3), spoint=50, scolor=(100, 55, 100)):
        """
            A method to change the pointer size by moving a slider.
            size: size of slider region.
            spoint: slider point, generally row position of pointer.
            scolor: slider color
        """
        swidth=10
        #swidth=int(5/50*spoint)
        #swidth = np.clip(swidth, 5, spoint)
        swindow=np.zeros(self.size).astype(np.uint8)
        swindow[:self.ssize[0], 0:self.ssize[1]] += np.uint8([255, 255, 255])  
        r1 = np.clip(spoint-swidth, swidth, self.ssize[0]-swidth)
        r2 = np.clip(spoint+swidth, swidth, self.ssize[0]-swidth)
        spoint = int(10/50 * spoint)
        #print(r1, r2, spoint)


        swindow[r1:r2, :self.ssize[1]] = scolor
        self.point=(spoint, self.point[1])
        #cv2.imshow("slider", swindow.astype(np.uint8))
        return swindow.astype(np.uint8)   
  • Метод начинается с определения новой переменной Swidth , который является шириной ползунка.
  • Новое пустое изображение сделано для хранения слайдера.
  • Слайдер изготавливается, сделав прямоугольник на строке точки, мы добавляем ширину как в верхней/нижней части слайдера.
  • Измените наш размер указателя по сравнению с положением ползунка.
  • Вернуть окно ползунка.

Метод: чисто

Метод очистки окна и холста при включении режима очистки.

    def clear(self):
        self.window = np.zeros(self.size, dtype=np.uint8)
        self.canvas= self.window.copy()+self.bg_color

И холст, и окно возвращаются в их первоначальное состояние.

Тест: холст

Давайте протестируем наш холст. Вывод должен выглядеть ниже. Я тестирую это, чтобы работать с слайдером и рисую оба.

c = Canvas(vui=VUI())

print("Draw with normal pointer move.")
# draw the color first.
fig = plt.figure(figsize=(30, 30))
ax = fig.subplots(2, 3, sharex='col', sharey='row')
p = 0
for i in range(2):
    for j in range(3):
        ax[i][j].imshow(c.update_window(mode="draw", pointer=(400, 100+p)))
        p+=20
plt.show()

print("Draw with slider move.")
# test the slider
fig = plt.figure(figsize=(30, 30))
ax = fig.subplots(2, 3, sharex='col', sharey='row')
p=0
for i in range(2):
    for j in range(3):
        ax[i][j].imshow(c.update_window(mode="draw", pointer=(50+p, 10)))
        p+=20
plt.show()

print("Draw with new pointer.")
# Draw again the slider has increased
fig = plt.figure(figsize=(30, 30))
ax = fig.subplots(2, 3, sharex='col', sharey='row')
p = 0
for i in range(2):
    for j in range(3):
        ax[i][j].imshow(c.update_window(mode="draw", pointer=(400, 100+p)))
        p+=20
plt.show()

print("Erase with new pointer.")
# erase it now
fig = plt.figure(figsize=(30, 30))
ax = fig.subplots(2, 3, sharex='col', sharey='row')
p = 0
for i in range(2):
    for j in range(3):
        ax[i][j].imshow(c.update_window(mode="erase", pointer=(400, 100+p)))
        p+=20
plt.show()
Draw with normal pointer move.
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).


Draw with slider move.


Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Draw with new pointer.
Erase with new pointer.

Испытание выше выполняется в режиме рисования и стирает с различным размером указателя. Что я сделал?

  • Сделал фигуру и сюжеты для 6 изображений.
  • Нарисуйте цвет на холсте в положении указателя в разных положениях.
  • Сделайте положение указателя в области слайдера и заставили его двигаться.
  • Нарисуйте с новым размером указателя.
  • Стереть с новым размером указателя.

Тест пройден.

Контурное письмо

Класс для выполнения общей работы с использованием контуров. Я упорно трудился, чтобы поддерживать здесь коды, обновляя предыдущие коды.

Инициализировать: Контурное письмо

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

class ContourWriting:
    """
        A class to bind all other classes uses.
    """
    def __init__(self, count_mode=5, avg_frames=100, 
                 rois={"droi":[250, 400, 480, 681],
                       "mroi":[100, 10, 300, 225], 
                       "vroi":[150, 400, 240, 681]}, 
                 icons_dir="icons/", aweight=0.5):
        """
            count_mode: max count
            avg_frames: number of frames to take average
            rois: types of ROIS. Keys draw, move, vui and values [top, right, bottom, left]
            icons_dir: icons's directory
            aweight: value of delta on running average.


        """
        self.aweight = aweight
        self.avg_frames=avg_frames
        self.roi_boxes = rois
        self.roi_averages = {key:None for key in rois.keys()}
        self.roi_grays = {key:None for key in rois.keys()}
        self.roi_masks = {key:None for key in rois.keys()}
        self.roi_pointer = {key:None for key in rois.keys()}
        self.roi_counts = {key:None for key in rois.keys()}
        self.size = (525, 700)
        self.set_pointer()
        self.vui = VUI()
        self.canvas_shape = (self.size[0]-self.vui.vui_part, self.size[1], 3) 

        self.canvas = Canvas(window_size=self.canvas_shape, vui=self.vui)
        self.running_mode = self.vui.running_mode

        self.force_modes=None
        self.fcount_mode=count_mode
        self.fcurrent_count=0
        self.fprev_mode = "move"
        self.check_force_mode()

  • ROI_AVERAGES : хранить средний изображение каждой рентабельности.
  • roi_grays : хранить изображение серого каждую рентабельность инвестиций.
  • roi_masks : хранить изображение маски каждого ROI.
  • roi_pointer : хранить позицию указателя на каждом ROI.
  • roi_counts : хранить количество точек контура на каждой ROI.
  • set_pointer : Метод для установки указателя в соответствующей позиции.
  • vui : атрибут к объекту хранить Vui учебный класс.
  • canvas_shape : Должно быть ниже vui_part Анкет
  • force_modes : Имя режимы силы из силы или MROI Анкет
  • check_force_mode : Метод для выполнения проверки на MROI Анкет

Метод: Main

Метод вызове извне и обрабатывает все задачи.

    def main(self):
        cam = cv2.VideoCapture(0) 
        self.num_frames = 0
        self.take_average=True
        while True:
            (ret, frame) = cam.read()
            if ret:
                self.key = cv2.waitKey(1) & 0xFF
                frame = imutils.resize(frame, width=self.size[1])
                frame = cv2.flip(frame, 1)
                clone = frame.copy()
                gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY)
                self.set_grays(gray)
                self.size = frame.shape
                #print(self.roi_grays)

                # if to take average and num frames on average taking is lesser than 
                if self.num_frames self.roi_counts["vroi"] and fmode is not None:
                                self.running_mode = fmode
                            else:
                                self.running_mode = self.vui.running_mode       
                        else:
                            self.running_mode = fmode
                    self.perform_mode()
                    self.vui.running_mode=self.running_mode
                    canvas = self.canvas.update_window(mode=self.running_mode, 
                                                       pointer=self.roi_pointer["droi"]).astype(np.uint8)
                    final_window = self.get_window(canvas=canvas, vui=vui)
                    cv2.imshow("CW", final_window)

                    self.roi_pointer["vroi"] = (-1, -1)

                clone = self.make_rectangles(clone)
                cv2.imshow("Feed", clone)
                if self.key==27:
                    break
        cam.release()
        cv2.destroyAllWindows()

  • Начинается с инициализации камеры.
  • Инициализировать num_frames к 0 и take_average к Верно Анкет
  • В то время как True выполните ниже:
    • Прочитайте каждый кадр и прочитайте параметр успеха.
    • Если кадр был прочитал, выполните ниже:
      • Инициализировать проверку ключей.
      • Измените размер рамы и переверните ее, чтобы сделать его зеркальным эффектом.
      • Преобразовать раму в Graysclae и передать его set_grays () Метод для поиска серого на каждом ROI.
      • Если num_frames меньше avg_frames и take_average это Верно В

        • Позвоните rong_average () Анкет
        • Поместите номер кадра на кадр.
        • Увеличить num_frames Анкет
      • Еще:
        • Установить take_average к ЛОЖЬ .
        • Позвоните find_contours () Чтобы получить текущие контуры.
        • Позвоните check_force_mode () Метод для проверки MROI Анкет
        • Позвоните update_vui () о VUI, передав его ток, указатель vui roi и нарисуйте ROI.
        • Если Hover не Нет :

          • Затем установите режим работы в режиме работы VUI.
        • Если количество точек контура на MROI больше, чем vroi Затем попробуйте установить режим работы в режиме принудительного принуждения.
        • Иначе установить running_mode в режим работы VUI.
        • Наконец -то выполните действие режима.
        • Установите режим работы VUI в режим работы.
        • Позвоните update_window () холста, передавая его режим работы и указатель розыгрыша.
        • Наконец -то позвоните get_window () Метод, передавая текущий холст и VUI.
        • Покажите текущее окно письма.
        • Установите ROI VUI в (-1, -1), чтобы предотвратить ложный указатель.
        • Позвоните make_rectangles () Чтобы сделать прямоугольник для каждой рентабельности.
      • Показать клон изображение.
      • Выходная петля в режиме выхода.
    • Отпустите камеру и уничтожьте все окна.

Метод: установить серые

    def set_grays(self, gray_frame):
        """
            Takes current grayscale frame and sets roi_grays dictionary.
        """
        for rname, box in self.roi_boxes.items():
            top, right, bottom, left = box
            gray_roi = gray_frame[top:bottom, right:left]
            gray_roi=cv2.GaussianBlur(gray_roi, (7, 7), 0)
            self.roi_grays[rname] = gray_roi
  • Проверьте каждую ограничивающую коробку каждого ROI.
    • Возьмите верхнюю, справа, внизу, левые координаты коробки и обведите эту область из рамки серого.
    • Добавьте немного размытия в эту обрезанную область и добавьте его в roi_grays к соответствующему rname ключ.

Метод: среднее время работы

Метод для выполнения среднего бега по каждой рентабельности.

    def running_average(self):
        for rname, roi in self.roi_averages.items():
            gimg = self.roi_grays[rname]
            if roi is None:
                roi = gimg.copy().astype("float")
            else:
                cv2.accumulateWeighted(gimg, roi, self.aweight)
            self.roi_averages[rname] = roi
  • Возьмите средние значения каждого ROI и выполняйте среднее значение.
  • Установить среднее значение в ROI_AVERAGES Соответствующий rname ключ.

Метод: найти контуры

    def find_contours(self, clone, threshold=10):
        """
            A method to find contours on each ROIs, draw maximum contours and 
            then set pointer relative to VUI and Canvas.
            clone: current clone of frame.
            threshold: thresholding value.
        """
        self.roi_counts = {key:None for key in self.roi_counts.keys()}
        for rname, ravg in self.roi_averages.items():
            # abs diff betn img and bg
            top, right, bottom, left = self.roi_boxes[rname]
            diff = cv2.absdiff(ravg.astype("uint8"), self.roi_grays[rname])    
            _, th = cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY)
            (cnts, _) = cv2.findContours(th.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            m = (-1, -1)
            if len(cnts)!=0:
                max_cnt = max(cnts, key=cv2.contourArea)
                cv2.drawContours(clone, [max_cnt+(right,top)], -1, (0, 0, 255))   
                sshape = max_cnt.shape
                new_segmented = max_cnt.reshape(sshape[0], sshape[-1])
                m = new_segmented.min(axis=0)
                cv2.circle(clone, (right+m[0], top+m[1]), 15, self.vui.pointer_color, -3)
                self.roi_counts[rname] = len(max_cnt)
                # translate m of this roi to window shape
                #if len(max_cnt)>10:    

                if rname!="mroi":
                    pshape = self.size
                    if rname=="vroi":
                        pshape = (self.vui.vui_part, self.vui.size[1])
                    if rname=="droi":
                        pshape = self.canvas_shape
                    h = bottom - top
                    l = left - right


                    m = (int((m[0]/l)*pshape[1]), int((m[1]/h)*pshape[0]))    
                else:
                    m = (right+m[0], top+m[1])

                self.roi_pointer[rname]=(m[1], m[0])

        return clone  

Метод для поиска контуров на каждом ROI, нарисуйте максимальные контуры, а затем установите указатель по сравнению с VUI и холстом.

  • Инициализируйте roi_counts с ключами, как каждая рентабельность и значения, как нет.
  • Перевернуть в среднем от каждой рентабельности.
    • Возьмите текущий серый из этой рентабельности и найдите абсолютную разницу.
    • Порог Разница изображения к двоичности и найти контуры.
    • Установите m на (-1, -1) изначально.
    • Если длина контуров больше, тогда:
      • Найдите максимальный контур и нарисуйте его на клоне рамы.
      • Найдите минимальную точку контура и нарисуйте указатель на эту точку.
      • Вставьте количество контурных подсчетов в каждую ROI.
      • Выполните перевод указателя по сравнению с MROI, Vroi и Droi.
    • Наконец, установить roi_pointer к каждой рентабельности.
  • Вернуть клон.

Метод: Проверьте режим силы

Обратите внимание, что у нас есть 2 ROI, которые могут изменить режим работы, но когда использовать что? Сильный режим используется из MROI Раздел и другие режимы используются из vroi раздел.

    def check_force_mode(self):
        top, right, bottom, left = self.roi_boxes["mroi"]
        if self.force_modes is None:
            x=np.linspace(right, left, 4).astype(np.int64)
            x=[(x[i],x[i+1]) for i in range(len(x)-1)]
            force_modes = ["move", "draw", "erase"]
            force_modes = {x[i]:force_modes[i] for i in range(len(x))}
            #print(force_modes)
            self.force_modes = force_modes
        elif self.roi_pointer["mroi"][0]>0:
            mpointer = self.roi_pointer["mroi"]

            for col, mode in self.force_modes.items():

                if col[0]<=mpointer[1]=self.fcount_mode:
                        #print("f ", mode)
                        #self.fcurrent_count=0

                        return mode
  • Возьмите верх, вправо, внизу, слева от MROI Значение ключа на roi_boxes Анкет
  • Тогда foce_modes проверяется, если есть Нет или нет.

    • Разделите коробку ROI на части, равные force_modes Анкет По умолчанию использование Перемещать, нарисовать, стирай Анкет
    • Создайте словарь с столбцом в качестве ключа и значения как имя режима.
    • Установить force_modes атрибут.
  • Иначе длина контуров на MROI больше 0, затем возьмите указатель.

    • Проверка по каждому столбцам, режим на force_modes :

      • Если координата y указателя находится в диапазоне столбца для этого режима, установите, выполните проверку предыдущего и текущего режима.

Метод: выполнить режим

Я использовал всего 9 режимов. До сих пор, нарисуйте, стирайте, перемещаются, использовались цвет, но теперь для оставшихся режимов мы должны выполнить какое -то действие. Давайте определяем метод для этого тоже.

    def perform_mode(self):
        if self.running_mode=="clear":
            self.canvas.clear()
            self.running_mode="move"
        if self.running_mode=="restart":
            self.take_average =True
            self.num_frames=0
            self.running_mode="move"
            self.canvas.clear()
        if self.running_mode=="save":
            #cv2.imshow("canvas", self.canvas.canvas.astype(np.uint8))
            cv2.imwrite("canvas.png", self.canvas.canvas.astype(np.uint8))
            #cv2.destroyWindow("canvas")
            self.running_mode="move"
        if self.running_mode=="exit":
            self.key=27
        if self.running_mode=="detect":
            self.running_mode="move"
            self.detector()

  • Если режим работы ясен, то вызовите clear () Метод холст объект.
  • Если режим запуска перезапускается,
    • Установить брать
    • Установить num_frames
    • Установить rong_mode = "Move" Анкет
    • Позвоните в clear () Метод холст объект.
  • Если запуск режима сохраняется:
    • Сохраните текущий холст, нарисуйте на диск и измените режим работы для перемещения.
  • Если режим запуска выходит, тогда измените текущую ключ на 27.
  • Если режим запуска обнаруживается, тогда измените метод работы с запуском и метод детектора вызовов.

Метод: детектор

Метод, который принимает текущий холст и выполняет обнаружение с использованием модели обнаружения, такой как OCR. На данный момент я использую Tesseract.

    def detector(self):
        img = self.canvas.canvas.astype(np.uint8)
        op = pytesseract.image_to_string(img, lang="eng", nice="1")
        print("Detected: ", op)
  • Установить PytesserAct легко. Но вы должны установить еще один файл установки в Windows, чтобы он запустил.
  • Если у вас есть какие -либо проблемы с установкой PytesserAct, оставьте мне почту или комментарий.

Метод: получить окно

Метод, который связывает окно VUI и холста, чтобы они выглядели как один и возвращают новое окно.

    def get_window(self, canvas, vui):
        final_window = vui.copy()
        canvas_cpy = canvas.copy()
        vshape = vui.shape
        cshape = canvas.shape
        #print(self.running_mode)
        if self.running_mode == "color":
            # get part where color lies and make those part of canvas_bg black
            cp = self.vui.mode_pos[self.running_mode]
            canvas[:self.vui.dd_part[1]-self.vui.vui_part, cp[0]:cp[1]] = vui[self.vui.vui_part:self.vui.dd_part[1], cp[0]:cp[1]]


        final_window[self.vui.vui_part:, :] = canvas
        cp = self.roi_pointer["droi"]
        cp = (cp[1], cp[0]+self.vui.vui_part)
        point = self.canvas.point
        cv2.circle(final_window,  cp, point[0], self.canvas.pointer_color, point[1])
        return final_window

  • Простой индексации будет достаточно для выполнения этой операции.
  • Возьмите окно Canvas (не сами холст) и окно Vui.
  • Если режим работы является цветным, то выпадается, чтобы показать.
    • Подготовьте индексы там только раскрывающееся цвет.
  • Назначьте холст частям ниже vui_part из VUI, потому что эти части включают значки.
  • Наконец -то нарисуйте указатель на новом окне. Это не обязательно, хотя.
  • Вернуть последнее окно.

Метод: сделать прямоугольники

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

    def make_rectangles(self, clone):
        # make rectangle for everything, add text on middle of it
        for rname, box in self.roi_boxes.items():
            cv2.putText(clone, f"Curr. Mode: {self.running_mode}", (400, 100),
                                       cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
            top, right, bottom, left = box
            mid = int((top+bottom)/2), int((left+right)/2) 
            if rname == "droi":
                cv2.rectangle(clone, (left, top), (right, bottom), (0, 255, 0), 2)
                cv2.putText(clone, rname, (mid[1], mid[0]),
                                       cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 2)
            if rname == "mroi":

                cv2.rectangle(clone, (left, top), (int((left + right)/3), bottom), (0, 255, 0), 2)
                cv2.rectangle(clone, (int((left + right)/3), top), (2*int((left + right)/3), bottom), (0, 255, 0), 2)
                cv2.rectangle(clone, (2*int((left + right)/3), top), (right, bottom), (0, 255, 0), 2)
                cv2.putText(clone, str("Mv"), (int((right)/1), int((top+bottom)/2)),
                                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                cv2.putText(clone, str("Dr"), (int((left + right)/3), int((top+bottom)/2)),
                                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                cv2.putText(clone, str("Er"), (2*int((left + right)/3), int((top+bottom)/2)),
                                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
            if rname == "vroi":
                gb_indices = int((left-right)/len(self.vui.modes))
                gb_indices = np.arange(right, left, gb_indices)
                gb_indices[-1] = gb_indices[-1]+1
                for i in range(len(gb_indices)-1):
                    _gleft = gb_indices[i]
                    _gright = gb_indices[i+1]
                    cv2.rectangle(clone, (_gleft, top), (_gright, bottom), (255, 0, 255), 3)
                    cv2.putText(clone, self.vui.modes[i][:2], (_gleft+2, int((top+bottom)/2)),
                                                       cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 0, 255), 2)
        return clone

Простая операция происходит здесь.

  • Для каждого ROI:
    • Возьмите верх, вправо, внизу, влево, затем найдите в середине этой коробки.
    • Нарисуйте прямоугольник для каждой рентабельности. И разделите прямоугольник на части, если это необходимо.

Тест: Контурное письмо

Объедините все и давайте попробуем заставить нашу систему работать. Мы создадим и объект класса Контурное написание и назовите его main () метод

gw = ContourWriting(avg_frames=150, aweight=0.9)
gw.main()

Вывод должен выглядеть как видео ниже.

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

Тест пройден.

Окончательно

Окончательная версия кода должна выглядеть ниже.

class VUI:
    """
        A class for visual user interface. Recommended to use default parameters.
    """
    def __init__(self, icons_dir="icons/", window_size=(525, 700, 3), vui_part=20, max_count=15):
        """
            icons_dir: directory to use icons from.
            window_size: size of an vui window. Recommended to use equal of final window.
            vui_part: How much % of rows from top will be used to stack icons?

        """
        self.idir = icons_dir
        self.window = np.zeros(window_size).astype(np.uint8)
        self.size = window_size
        self.vui_part = int(vui_part/100 * self.size[0])
        self.dd_part = (self.vui_part, int(50/100 * self.size[0]))

        self.modes = [fname.split(".")[0] for fname in os.listdir(self.idir)]
        self.icon_size = (int(self.size[1]/len(self.modes)), self.vui_part) # c, r

        self.current_icons = []
        self.anim_scale = 0.5
        self.anim_color = [5, 15, 2]
        self.prev_mode = "move"
        self.current_mode = "move"
        self.running_mode = None 
        self.hover=None

        self.mode_count = 1
        self.max_count = max_count
        self.color_count = 1
        self.max_color = 5

        self.current_pointer = (100, 100)
        self.canvas_pointer = None

        self.draw_color = (0, 0, 255)
        self.previous_color = (0, 0, 255)
        self.current_color = (0, 0, 255)
        self.pointer_color = (100, 200, 200)
        self.point = (10, -3)
        self.colors=None

        self.icons = self.prepare_icons()
        self.get_window()

    def prepare_icons(self):
        """
            A method to prepare icons on initial frame.
            Method sets 4 new attributes.
            cols: List to store (y1, y2) of icon.
            icon_position: Dictionary to store (y1, y2) as key and corresponding image as value
            current_icons: A dictionary initialized with initial icons. Changed on every frame when cursor lies above it.
            mode_pos: Mode as key and its icon's (y1, y2) as value.
        """
        icons = []
        cols = np.linspace(0, self.size[1]-1, len(self.modes)+1).astype(np.int64)
        cols = [(cols[i], cols[i+1]) for i in range(len(cols)-1)]

        icon_pos = {}
        mode_pos = {}
        for i, image_name in enumerate(os.listdir(self.idir)):
            img = cv2.imread(self.idir+image_name)
            img = cv2.resize(img, (cols[i][1]-cols[i][0], self.vui_part))
            icon_pos[cols[i]] = img
            mode_pos[self.modes[i]] = cols[i]
        self.cols = cols   
        self.icon_position = icon_pos
        self.current_icons = icon_pos
        self.mode_pos = mode_pos

    def set_colors(self, col=None, new_colors=None):
        """
            A method to set colors when pointer lies above color icon.
            Initially used subset of {Red, Green, Blue}
            col:- column where current pointer lies.
            new_colors:- If to use other colors.

            Method returns list of available colors on dropdown menu. 
            Changes the draw color, pointer color upon condition meet.
        """
        # earlier pointer was clipped within the vui
        pointer = self.canvas_pointer
        pointer = (pointer[1], self.vui_part+ pointer[0])
        if new_colors is None:
            r = np.array([0, 0, 255])
            g = np.array([0, 255, 0])
            b = np.array([255, 0, 0])
            colors = [r, g, b]
            colors_new = [colors[i]+colors[i+1] for i in range(len(colors)-1)]
            colors.extend(colors_new)
            self.colors = colors
        else:
            self.colors = new_colors
        rows = np.linspace(self.dd_part[0], self.dd_part[1], len(self.colors)+1).astype(np.int64)
        rows = [(rows[i], rows[i+1]) for i in range(len(rows)-1)]
        self.color_pos = {}
        for row, color in zip(rows, colors):
            self.color_pos[row] = color
            if row[0]<=pointer[1]=self.max_color:
                    self.draw_color=self.current_color
                self.pointer_color = (np.abs(np.array([200, 200, 100])-color).tolist())
            self.current_window[row[0]:row[1], col[0]:col[1]] = color

        return self.colors  
    def get_window(self):
        """
            A method to return a VUI window upon called. Sets pointer on VUI canvas.
        """
        self.current_window = np.zeros_like(self.window).astype(np.uint8)
        for col, img in self.current_icons.items():
            self.current_window[:self.vui_part, col[0]:col[1]] = img
        if self.running_mode == "color":
            self.set_colors(col=self.cols[self.modes.index("color")])
        if self.current_pointer is not None and self.current_pointer[0]>0:
            cv2.circle(self.current_window, (self.current_pointer[1], self.current_pointer[0]), self.point[0], self.pointer_color, self.point[1])

        return self.current_window
    def update_vui(self, pointer=(100, 100), cpointer=(10, 100)):
        """
            A method to update the entire VUI properties and state.
            pointer: Current pointer on VUI part.
            cpointer: Current pointer on Canvas.

            cpointer is useful when working with color mode.
        """
        self.current_pointer = pointer
        self.canvas_pointer = cpointer
        #print(pointer, canvas_pointer)
        current_icons = {}
        self.hover=None
        if pointer[0]<=self.vui_part:
            for col, mode in zip(self.cols, self.modes):
                icon = self.icon_position[col].copy()
                ishape = icon.shape

                #print(mode)
                if col[0] 1:
                        rd = int((r - ishape[0])/2)
                        cd = int((c - ishape[1])/2)

                        zeros_icon[:, :] = icon[rd:ishape[0]+rd, cd:ishape[1]+cd] 
                    else:
                        rd = int((ishape[0] - r)/2)
                        cd = int((ishape[1] - c)/2)
                        rdd, cdd = 0, 0
                        if ishape[0]-rd-rd > r:
                            rdd=1
                        if ishape[1]-cd-cd > c:
                            cdd=1
                        #print(icon.shape, ishape, rd, abs(r-rd), cd, abs(c-cd))
                        zeros_icon[rd:ishape[0]-rd-rdd, cd:ishape[1]-cd-cdd] = icon[::] 

                    current_icons[col] = zeros_icon.astype(np.uint8) + np.uint8(np.array(self.anim_color)*self.mode_count)


                    if self.prev_mode == self.current_mode:
                        self.mode_count += 1
                    else:
                        self.prev_mode = self.current_mode
                        self.mode_count = 1
                    if self.mode_count >= self.max_count:
                        self.running_mode = self.current_mode
                        self.mode_count = 1
                        self.hover = True

                else:
                    current_icons[col] = icon

            self.current_icons = current_icons
        else:
            self.mode_count = 1

        return self.get_window()




# vui = VUI()
# #show(vui.window)
# vui.update_vui()
# vui.update_vui()
# vui.update_vui(pointer=(200, 100))
# vui.update_vui(pointer=(200, 100))

class Canvas:
    def __init__(self, window_size=(525, 700, 3), draw_color=(100, 100, 100), 
                 pointer_color=(0, 0, 0), bg_color=(25, 25, 25), mode="move", 
                 point=(10, -3), vui=None, ssize=(300, 50, 3)):
        """
            A method to initialize canvas.
            window_size: size of a canvas window.
            draw_color: drawing color in RGB.
            pointer_color: pointer color in RGB.
            bg_color: background color in RGB.
            mode: running mode.
            point: tuple of (pointer radius, thickness)
            vui: VUI object.
            ssize: Slider's size.

        """
        self.size=window_size
        self.draw_color=draw_color
        self.pointer_color = pointer_color
        self.bg_color = bg_color
        self.window = np.zeros(self.size, dtype=np.uint8)
        self.canvas= self.window.copy()+bg_color
        self.mode = mode
        self.pointer = None
        self.point = point
        self.current_window = self.window+self.canvas
        self.vui = vui
        self.ssize = ssize
        self.sregion = ()

    def slider(self, size=(300, 30, 3), spoint=50, scolor=(100, 55, 100)):
        """
            A method to change the pointer size by moving a slider.
            size: size of slider region.
            spoint: slider point, generally row position of pointer.
            scolor: slider color
        """
        swidth=10
        #swidth=int(5/50*spoint)
        #swidth = np.clip(swidth, 5, spoint)
        swindow=np.zeros(self.size).astype(np.uint8)
        swindow[:self.ssize[0], 0:self.ssize[1]] += np.uint8([255, 255, 255])  
        r1 = np.clip(spoint-swidth, swidth, self.ssize[0]-swidth)
        r2 = np.clip(spoint+swidth, swidth, self.ssize[0]-swidth)
        spoint = int(10/50 * spoint)
        #print(r1, r2, spoint)


        swindow[r1:r2, :self.ssize[1]] = scolor
        self.point=(spoint, self.point[1])
        #cv2.imshow("slider", swindow.astype(np.uint8))
        return swindow.astype(np.uint8)   
    def clear(self):
        self.window = np.zeros(self.size, dtype=np.uint8)
        self.canvas= self.window.copy()+self.bg_color
    def update_window(self, mode, pointer=(400, 100)):
        """
            mode: running mode
            pointer: where is pointer now?
        """
        self.mode = mode
        self.vui.mode=mode
        self.pointer = pointer
        self.draw_color=self.vui.draw_color
        self.pointer_color = self.vui.pointer_color
        #self.pointer = (np.clip(self.vui.vui_part, pointer[0], self.size[0]), pointer[1])
        #print("c", self.draw_color)
        swindow = np.zeros(self.size).astype(np.uint8)
        #print(pointer)
        if 010:    

                if rname!="mroi":
                    pshape = self.size
                    if rname=="vroi":
                        pshape = (self.vui.vui_part, self.vui.size[1])
                    if rname=="droi":
                        # make it self.canvas.shape
                        #pshape = (self.canvas_shape[0], self.canvas_shape[1]-self.canvas.ssize[1]) 
                        pshape=self.canvas_shape
                    h = bottom - top
                    l = left - right


                    m = (int((m[0]/l)*pshape[1]), int((m[1]/h)*pshape[0]))    
                else:
                    m = (right+m[0], top+m[1])

                self.roi_pointer[rname]=(m[1], m[0])

        return clone    

    def get_window(self, canvas, vui):
        final_window = vui.copy()
        canvas_cpy = canvas.copy()
        vshape = vui.shape
        cshape = canvas.shape
        #print(self.running_mode)
        if self.running_mode == "color":
            # get part where color lies and make those part of canvas_bg black
            cp = self.vui.mode_pos[self.running_mode]
            canvas[:self.vui.dd_part[1]-self.vui.vui_part, cp[0]:cp[1]] = vui[self.vui.vui_part:self.vui.dd_part[1], cp[0]:cp[1]]

            #show(canvas)
        #else:#
        final_window[self.vui.vui_part:, :] = canvas
        cp = self.roi_pointer["droi"]
        cp = (cp[1], cp[0]+self.vui.vui_part)
        point = self.canvas.point
        cv2.circle(final_window,  cp, point[0], self.canvas.pointer_color, point[1])
        return final_window

    def check_force_mode(self):
        top, right, bottom, left = self.roi_boxes["mroi"]
        if self.force_modes is None:
            x=np.linspace(right, left, 4).astype(np.int64)
            x=[(x[i],x[i+1]) for i in range(len(x)-1)]
            force_modes = ["move", "draw", "erase"]
            force_modes = {x[i]:force_modes[i] for i in range(len(x))}
            #print(force_modes)
            self.force_modes = force_modes
        elif self.roi_pointer["mroi"][0]>0:
            mpointer = self.roi_pointer["mroi"]

            for col, mode in self.force_modes.items():

                if col[0]<=mpointer[1]=self.fcount_mode:
                        #print("f ", mode)
                        #self.fcurrent_count=0

                        return mode
    def detector(self):
        img = self.canvas.canvas.astype(np.uint8)
        op = pytesseract.image_to_string(img, lang="eng", nice="1")
        print("Detected: ", op)
    def perform_mode(self):
        if self.running_mode=="clear":
            self.canvas.clear()
            self.running_mode="move"
        if self.running_mode=="restart":
            self.take_average =True
            self.num_frames=0
            self.running_mode="move"
            self.canvas.clear()
        if self.running_mode=="save":
            #cv2.imshow("canvas", self.canvas.canvas.astype(np.uint8))
            cv2.imwrite("canvas.png", self.canvas.canvas.astype(np.uint8))
            #cv2.destroyWindow("canvas")
            self.running_mode="move"
        if self.running_mode=="exit":
            self.key=27
        if self.running_mode=="detect":
            self.running_mode="move"
            self.detector()
    def main(self):
        cam = cv2.VideoCapture(0) 
        self.num_frames = 0
        self.take_average=True
        while True:
            (ret, frame) = cam.read()
            if ret:
                self.key = cv2.waitKey(1) & 0xFF
                frame = imutils.resize(frame, width=self.size[1])
                frame = cv2.flip(frame, 1)
                clone = frame.copy()
                gray = cv2.cvtColor(clone, cv2.COLOR_BGR2GRAY)
                self.set_grays(gray)
                self.size = frame.shape

                # if to take average and num frames on average taking is lesser than 
                if self.num_frames self.roi_counts["vroi"] and fmode is not None:
                                self.running_mode = fmode
                            else:
                                self.running_mode = self.vui.running_mode       
                        else:
                            self.running_mode = fmode
                    self.perform_mode()
                    self.vui.running_mode=self.running_mode
                    canvas = self.canvas.update_window(mode=self.running_mode, 
                                                       pointer=self.roi_pointer["droi"]).astype(np.uint8)
                    final_window = self.get_window(canvas=canvas, vui=vui)
                    cv2.imshow("CW", final_window)

                    self.roi_pointer["vroi"] = (-1, -1)

                clone = self.make_rectangles(clone)
                cv2.imshow("Feed", clone)
                if self.key==27:
                    break
        cam.release()
        cv2.destroyAllWindows()

gw = ContourWriting(avg_frames=150, aweight=0.5)
gw.main()

Я собираюсь указать на функции и недостатки этой системы.

Коды

Коды для текущей версии системы доступны по ссылке ниже, а если это не так, нажмите на комментарий или оставьте мне сообщение (LinkedIn, Twitter или напишите мне).

Функции

  • Слайдер можно использовать для изменения размера указателя.
  • Код рерсируется и производится ООП.
  • Двойная рука может использоваться для изменения режимов рисования/перемещения/стирания указателя.
  • Цвет можно выбрать из раскрывающейся моды. Мы можем добавить столько цвета, сколько хотим.
  • Всего доступно 9 режимов.

Недостатки

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

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

Идеи

  • Что если мы сможем создать модель, которая может классифицировать жест, и мы можем определить определенный жест для определенного режима?

Что дальше?

Я постараюсь решить недостатки в следующий раз. Но мне интересно заставить эту систему работать на мобильных телефонах. В настоящее время я подумываю взять рамки с камеры устройств и обработать ее. Затем используйте какой -нибудь вызов API, чтобы получить эти кадры. Я мог бы использовать единство. Я очень рад попытаться использовать LSTM и другие алгоритмы глубокого обучения искусства, чтобы сделать эту систему более удивительной Но у меня нет доступа в Интернет (кроме сотовых данных), чтобы провести широкие исследования.

Почему бы не прочитать больше?

Оригинал: “https://dev.to/qviper/gesture-based-visually-writing-system-web-app-4gd4”