Уроки компьютерного зрения на Python + OpenCV с самых азов. Часть 9. Мой пэт-проект

от автора

Оглавление: Уроки компьютерного зрения. Оглавление / Хабр (habr.com)

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

  • Предобработка изображения.

  • Промежуточная фильтрация.

  • Выявления специальных признаков (фич).

  • Высокоуровневый анализ.

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

Естественно, делать конвейер ради самого конвейера как-то бессмысленно. Надо, чтобы моя программа делала хоть что-то условно полезное. Сначала я хотел написать пэт-проект, который бы анализировал фотографии со спутников и БЛА и превращал их в граф (это перекликается с темой моей магистерской диссертации). Правда, это слишком уж амбициозная задумка для пэт-проекта. Надо что-то по- проще. В комментариях к одному из уроков мне посоветовали добавить в финале пару глав про выделение отдельных символов и распознавание их при помощи общедоступных нейронок. И вот я и подумал, может, начать пэт-проект именно с этой задачи? Распознавание текстов? Это гораздо проще.

Итак, для начала я создал пустой проект и добавил туда две папки: Exec и Libraries. В первой у меня будет запускаемый файл/файлы, во втором всякие библиотечные файлы. В качестве первого библиотечного файла создал Core.py:

class Engine:     """"Движок"""     pass  class ImageProcessingStep:     """Шаг обработки изображений"""     pass  class ImageInfo:     """Содержимое картинки, включая результаты обработки"""     pass

Может, не совсем удачно разместить эти в Core.py, но пока нет видимости, какова будет окончательная структура, а потом просто сделаю рефакторинг.

Итак, начнем программирование:

class Engine:     """"Движок"""      def __init__(self):         """Конструктор"""          self.steps=[]      def process(self, image):         """Выполнить обработку.         image - изображение"""          current_info=ImageInfo(image)         for step in self.steps:             current_info=step.process(current_info)         return current_info 

Из этого фрагмента кода видно, что нужно создать конструктор класса ImageInfo, а у класса ImageProcessingStep надо предусмотреть метод process. И, разумеется, ImageProcessingStep должен быть абстрактным классом, а реализовывать методы будет уже каждый конкретный класс. Стоить заметить, что в Python абстрактных классов нет как таковых (в отличии от C#), они эмулируются при помощи декораторов. Но я этого делать не буду, так как использование декораторов приводит к излишнему расходу процессорного времени. Просто представлю, что раз «нет» значит «нет».

Абстрактный класс я сделаю путем добавления псевдоабстрактных методов, помеченных pass:

class ImageProcessingStep:     """Шаг обработки изображений"""      def process(self,info):         """Выполнить обработку"""         pass 

Теперь займемся классом ImageInfo. По идее, это тоже должны быть псевдоабстрактный класс, в зависимости от шага, должен содержать, кроме картинки, еще какие-то дополнительные данные. А пока реализую конструктор:

class ImageInfo:     """Содержимое картинки, включая результаты обработки"""      def __init__(self,image):         """Конструктор"""          self.image=image 

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

Создадим класс, отвечающий за медианную фильтрацию:

class MedianBlurProcessingStep(ImageProcessingStep):     """Шаг, отвечающий за предобработку типа Медианная фильтрация"""      def __init__(self,ksize):         """Конструктор         ksize - размер ядра фильтра"""          self.ksize=ksize      def process(self,info):         """Выполнить обработку"""          median_image = cv2.medianBlur(info.image, self.ksize)         info.filtered_image=median_image         return info 

Далее, создаем папку Exec, а в ней файл run.py. Вот так теперь будет выглядеть структура папок:

Ну и само содержимое файла run.py:

import cv2  from Libraries.Core import Engine from Libraries.ImageProcessingSteps import MedianBlurProcessingStep  my_photo = cv2.imread('../Photos/MyPhoto1.jpg') core=Engine() core.steps.append(MedianBlurProcessingStep(5)) info=core.process(my_photo)  cv2.imshow('origin', info.image) # выводим исходное изображение в окно cv2.imshow('res', info.filtered_image) # выводим итоговое изображение в окно  cv2.waitKey() cv2.destroyAllWindows()  

Запускаем программу и видим две картинки, одну с царапинами, другую после медианной фильтрации. Да, это уже было на уроке 3, только на этот раз все сделано в соответствии с парадигмой ООП. Теперь пора идти дальше и реализовывать идею распознаванием текста. Для начала попробуем стандартным способом, через tesseract. Прежде всего, я расскажу, как вообще работать с tesseract. И так, ставим библиотеку:

pip install pytesseract

Заходим по этой ссылке: Home · UB-Mannheim/tesseract Wiki (github.com) и качаем оттуда саму tesseract. Если внезапно окажется, что ссылка не актуальная (мало ли, все течет, все меняется), тогда просто погуглите.

После того, как скачали tesseract просто устанавливаем его. В программе на Python нужно будет указать путь к программе, примерно вот так вот это будет выглядеть:

import cv2 import pytesseract  pytesseract.pytesseract.tesseract_cmd = "D:\\Program Files\\Tesseract-OCR\\tesseract.exe"  img = cv2.imread('image.jpg') print(pytesseract.image_to_string(img, lang = 'rus')) 

А вот демонстрация результатов работы программы вместе с исходным изображением:

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

Попробуем распознать сфотографированную страницу книги:

Вот что мы получим на выходе:

Как видим, где-то слова распознались хорошо, где-то довольно криво.

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

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

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

[Python] Изучение OpenCV | Распознавание автомобильных номеров смотреть онлайн видео от Boris Bochkarev | BeTry в хорошем качестве. (rutube.ru)

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

Автор рассказывает все по шагам. Приведу эти шаги. Сначала он предлагает преобразовать текст в оттенки серого и произвести бинаризацию:

import cv2  img = cv2.imread('cars/6108249.jpg') height,width,_=img.shape gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh = cv2.threshold(gray,0,255,cv2.THRESH_OTSU)[1] cv2.imshow('MyPhoto', thresh) cv2.waitKey(0)  cv2.destroyAllWindows() 

Исходное изображение:

Результат бинаризации:

Нетрудно предугадать следующий шаг. Полагаю, автор предложит найти вот этот вот белый прямоугольник:

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

В принципе, неплохо, хотя местами есть помехи:

А в последнем случае номерной знак теряется на белом фоне. Хотя, если приглядеться внимательно, вокруг него есть черный контур:

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

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

import cv2 from imutils import contours import pytesseract  pytesseract.pytesseract.tesseract_cmd = "D:\\Program Files\\Tesseract-OCR\\tesseract.exe"  img = cv2.imread('cars/6108249.jpg') height,width,_=img.shape gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) thresh = cv2.threshold(gray,0,255,cv2.THRESH_OTSU)[1] cnts=cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) cnts,_=contours.sort_contours(cnts[0])  for c in cnts:     area=cv2.contourArea(c)     x,y,w,h=cv2.boundingRect(c)     if area>20:         img_area=img[y:y+h,x:x+h]         result=pytesseract.image_to_string(img_area,lang="rus+eng")         if len(result)>3:             print(result) 

У него, правда, были немного другие пороги. И все работало, он прямо показал в ролике, все работает. На моих фотографиях это не сработало, поэтому я и заменил пороги. И получил странный вывод:

Можно добавить просмотр картинок, чтобы увидеть, где tesseract находит эти надписи:

… print(result) cv2.imshow('MyPhoto', img_area) cv2.waitKey(0) … 

Смотрим фрагменты картинки:

В общем, цифры tesseract почему то не хочет идентифицировать. А он вообще, понимает цифры? Давайте проверим. Создадим в paint-е картинку из цифр:

Скармливаем tesseract. И видим, что вывод пуст.  Погуглил данную тему. Как оказалось, tesseract работает с цифрами через одно место. Можно поискать какие-то другие версия, но это не вариант. Лучше написать какую-то свою распознавалку, например, с использованием нейросетей.

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

И тут я понял, что вот такой вот код:

def process(self, image):     """Выполнить обработку.     image - изображение"""      current_info=ImageInfo(image)     for step in self.steps:         current_info=step.process(current_info)     return current_info  ... ...    def process(self,info):     """Выполнить обработку"""      median_image = cv2.medianBlur(info.image, self.ksize)     info.filtered_image=median_image     return info 

Это плохая идея. Почему? Допустим, я решил сделать бинаризацию картинки. Я могу сделать бинаризацию исходной картинки, а могу после фильтрации. Но после фильтрации она называется не image, а filtered_image. И спрашивается, откуда на шаге бинаризации можно знать, надо обрабатывать image или filtered_image? Поэтому, они должны называется одинаково. А вот историю обработки надо где-то сохранять, чтобы потом восстановить цепочку обработок и проанализировать, как работает вся система.

Для начала исправим метод process класса Engine:

def process(self, image):     """Выполнить обработку.     image - изображение"""      current_info=ImageInfo(image)     history=[]     for step in self.steps:         history.append(current_info)         current_info=step.process(current_info)     return current_info, history

Затем файл: ImageProcessingSteps.py

import cv2 from Libraries.Core import ImageInfo   class ImageProcessingStep:     """Шаг обработки изображений"""      def process(self,info):         """Выполнить обработку"""         pass  class MedianBlurProcessingStep(ImageProcessingStep):     """Шаг, отвечающий за предобработку типа Медианная фильтрация"""      def __init__(self,ksize):         """Конструктор         ksize - размер ядра фильтра"""          self.ksize=ksize      def process(self,info):         """Выполнить обработку"""          median_image = cv2.medianBlur(info.image, self.ksize)         return ImageInfo(median_image)

Ну, и наконец run.py:

import cv2  from Libraries.Core import Engine from Libraries.ImageProcessingSteps import MedianBlurProcessingStep  my_photo = cv2.imread('../Photos/MyPhoto1.jpg') core=Engine() core.steps.append(MedianBlurProcessingStep(5)) res,history=core.process(my_photo)  i=1 for info in history:     cv2.imshow('image'+str(i), info.image) # выводим изображение в окно     i=i+1 cv2.imshow('res', res.image)    cv2.waitKey() cv2.destroyAllWindows()

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

class ThresholdProcessingStep(ImageProcessingStep):     """Шаг, отвечающий за бинаризацию"""      def process(self,info):         """Выполнить обработку"""          gray = cv2.cvtColor(info.image, cv2.COLOR_BGR2GRAY)         image = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)[1]         return ImageInfo(image)

И новый запускаемый файл run1.py:

import cv2  from Libraries.Core import Engine from Libraries.ImageProcessingSteps import MedianBlurProcessingStep, ThresholdProcessingStep  my_photo = cv2.imread('../Photos/car.jpg') core=Engine() core.steps.append(MedianBlurProcessingStep(5)) core.steps.append(ThresholdProcessingStep()) res,history=core.process(my_photo)  i=1 for info in history:     cv2.imshow('image'+str(i), info.image) # выводим изображение в окно     i=i+1 cv2.imshow('res', res.image)  cv2.waitKey() cv2.destroyAllWindows()

При запуске мы увидим вот такую картинку:

Код этого примера можно взять отсюда: https://github.com/megabax/CVContainer

А теперь подытожим: Мы создали ООП-контейнер для обработки изображений, в будущих уроках прикрутим к нему находилку номера и распознавалку цифр на нейросетях.


ссылка на оригинал статьи https://habr.com/ru/articles/733444/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *