МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ «МОСКОВСКИЙ ПОЛИТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» (МОСКОВСКИЙ ПОЛИТЕХ) Факультет информационных технологий Лабораторная работа №1 «Организация хранилища данных для датасета по компьютерному зрению» по дисциплине «Хранилища данных» Группа Студент _____________ Преподаватель _____________ Москва 2023 1 Разработка структуры интерфейса программы для организации хранилища Сформулируем проблему: необходимо разработать информационную систему для обработки изображений с целью дальнейшего использования этих данных для обучения распознаванию клубней картофеля. Для решения поставленной выше проблемы выделим следующие требования: 1. Автоматическое скачивание изображений с веб-страницы. Поиск или самостоятельное производство изображений довольно трудоёмкий и долгий процесс, а скачивание отдельно каждого изображения ещё больше всё замедляет. Можно воспользоваться готовым датасетом, но зачастую объёма информации в нём недостаточно. 2. Для отбора подходящих картинок и определения координат объекта/объектов на «хороших» картинках необходимо разработать окно, в котором можно распределять изображения на условно хорошие и плохие и добавление координат в базу данных для хороших. 3. Нужно создать датасет из отобранных изображений и необходимых координат объектов, состоящий из папок Good и Bad и файлов Good.dat и Bad.dat. 4. Обучение происходит за счёт создания каскадного классификатора Хаара с помощью библиотеки алгоритмов компьютерного зрения OpenCV. Для удобства добавим окно запуска обучения. 5. Применим полученный классификатор на практике и обработаем изображение для обнаружения на нём клубня картофеля. По итогу интерфейс информационной системы должен состоять из пяти окон. Для удобства переключения между ними был создан «Главный экран», с которого можно запускать каждое из них. Интерфейс «Главного экрана» представлен на рисунке 1. Рисунок 1 – «Главный экран» интерфейса На рисунке 1 кнопки выполняют поставленные ранее задачи, помимо одной — «Изменить размер картинок в папке», которая позволяет уменьшить все изображения до 100 пискселей в ширину. Работа с небольшими изображениями может ускорить обучение. Запуск окон будет осуществляться с помощью открытия сторонних файлов параллельно с текущим с помощью функции call() библиотеки subprocess. Приведём пример окна с кнопкой «Открыть окно для скачивания картинок»: #Импорт библиотек from tkinter import * from subprocess import call #Задаём параметры окна root=Tk() root.geometry('500x500') root.title('Главное меню') frame = Frame(root) frame.pack(pady=20,padx=20) #Функция для запуска файла def Open_parse_img(): call(["python", "parse_with_window.py"]) #Кнопка для запуска функции вызова файла btn_img_parse=Button(frame,text='Открыть окно для скачивания картинок',command=Open_parse_img) btn_img_parse.grid(row=2, column=1) root.mainloop() Для остальных кнопок код не отличается от кнопки «Открыть окно для скачивания картинок». 2 Автоматический поиск и скачивание изображений с сайтов Для реализации интерфейса, поиска на веб-страницы изображений и скачивания их понадобятся следующие библиотеки: Tkinter — для создания оконного приложения BeautifulSoup4 — для извлечения данных из файлов HTML Requests — для создания и отправки запросов по протоколу HTTP Os — предоставляет возможность установить взаимодействие между пользователем и операционной системой PIL — для работы с растровой графикой (скачиваемыми изображениями) Для скачивания изображений в папку необходимо запросить у пользователя адрес веб-страницы, с которой будут загружены изображения, и название папки, в которой они должны будут храниться. Для удобства папка будет добавляться в директорию самой программы. Все операции будем осуществлять по клику на одну кнопку. Интерфейс окна для скачивания изображений представлен на рисунке 2. Рисунок 2 – Окно для скачивания изображений с веб-страницы Код интерфейса представляет из себя 2 подписи, 2 поля ввода и одну кнопку: window = Tk() window.title('Скачать картинки со страницы') window.geometry('400x300') frame = Frame(window, padx=10, pady=10) frame.pack(expand=True) url_lb = Label(frame, text="Введите url") url_lb.grid(row=3, column=1) folder_lb = Label(frame, text="Введите назввание папки для охранения",) folder_lb.grid(row=4, column=1) url_tf = Entry(frame,) url_tf.grid(row=3, column=2, pady=5) folder_tf = Entry(frame,) folder_tf.grid(row=4, column=2, pady=5) cal_btn = Button( frame, text='Скачать изображения', command=main) cal_btn.grid(row=5, column=2) window.mainloop() Из кода выше видно, что при клике на кнопку вызывается функция main(), в котрой запускается функция создания папки (folder_create(images)), в которой в свою очередь запускается ункция поиска, скачивания и уменьшения изображений (download_images(images, folder_name)). Код функции main(): def main(): # получаем URL страницы url = str(url_tf.get()) # запрос страницы r = requests.get(url) # Анализ HTML кода soup = BeautifulSoup(r.text, 'html.parser') # находим все изображения по данному URL images = soup.findAll('img') # Вызываем ункцию созданя папки и скачивания в неё картинок folder_create(images) Код функции folder_create(images): def folder_create(images): try: folder_name = str(folder_tf.get()) # создание пустой папки os.mkdir(folder_name) # вывести сообщение, если папка с таким именем уже существует except: exist_lb = Label( frame, text="Папка с таким именем уже существует!" ) exist_lb.grid(row=6, column=1) folder_create() # скачииваем иизображения в папку download_images(images, folder_name) Код функции download_images(images, folder_name): def download_images(images, folder_name): # задаём начальное значение кол-ва картинок count = 0 # вывод кол-ва найденных на веб-странице картинок img_found_lb = Label( frame, text=f" Найдено {len(images)} картинок! img_found_lb.grid(row=6, column=1) ") # проверяем, что кол-во найденных изображений НЕ равно 0 if len(images) != 0: for i, image in enumerate(images): # Извлечём URL-адрес источника изображения из тега изображения # будем искать "data-srcset" в теге img try: # В теге изображения выполним поиск "data-srcset" image_link = image["data-srcset"] # затем мы будем искать "data-src" в img except: try: # В теге изображения выполним поиск "data-src" image_link = image["data-src"] except: try: # В теге изображения выполним поиск "data-fallback-src" image_link = image["data-fallback-src"] except: try: # В теге изображения выполним поиск "src" image_link = image["src"] # если исходный URL не найден except: pass # После получения URL-адреса источника изображения # Попытаемся получить содержимое изображения try: r = requests.get(image_link).content try: # возможность декодирования r = str(r, 'utf-8') except UnicodeDecodeError: # Начинаем скачивание изображений with open(f"{folder_name}/images{i+1}.jpg", "wb+") as f: f.write(r) # Уменьшим изображение до 100px в ширину с сохранением пропорций fixed_width = 100 img = Image.open(f"{folder_name}/images{i+1}.jpg") # получаем процентное соотношение # старой и новой ширины if img.size[0]>100: width_percent = (fixed_width / float(img.size[0])) # на основе предыдущего значения # вычисляем новую высоту float(width_percent))) height_size = int((float(img.size[1]) * # меняем размер на полученные значения new_image = img.resize((fixed_width, height_size)) new_image.save(f"{folder_name}/images{i+1}.jpg") # засчитываем загруженное изображение except: count += 1 pass # если все изображения скачаны if count == len(images): img_down_lb = Label( frame, text="Все картинки скачаны!") img_down_lb.grid(row=7, column=1) # если не все изображения скачаны else: img_down_lb = Label( frame, text=f"Скачано {count} картинок {len(images)}") img_down_lb.grid(row=7, column=1) Осуществим скачивание картинок клубней картофеля со страницы Google https://www.google.ru/search?q=клубень+картофеля&newwindow=1&source=ln ms&tbm=isch&sa=X&ved=2ahUKEwiF3qi6vc79AhXMpIsKHbUiAoIQ_AUoAX oECAEQAw&biw=1366&bih=627&dpr=1. Для копирования адреса в поле ввода используется комбинация Ctrl+V (должна быть английская раскладка). Папку со скачанными айлами назовём downloaded. Сама сраница представлена на риунке 3 сверху, Информация, которая была выедена в окне программы — на рисунке 3 снизу слева. Результат скачивания на рисунке 3 снизу справа. Рисунок 3 – Пример работы программы для скачивания изображений На рисунке 3 видно, что на странице была найдена 21 картинка, из которых было скачано 20. 3 Изменение размера изображений в папке Чем меньше размер изображения, тем меньше размер файла и тем быстрее оно будет обрабатываться. Для программы уменьшения будем использовать упомянутые раньше библиотеки Tkinter, PIL, Os. Интерфейс будет состоять из поля ввода, подписи к нему и кнопки (рисунок 4). Рисунок 4 – Окно программы для уменьшения изображений в папке Код программы состоит из одной функции: def main(): count = 0 #Получаем название папки из поля ввода folder_name = str(folder_tf.get()) #Если в папке есть подпапки получаем их названия и совмещаем с #названиями файлов for address, files in os.walk(folder_name): for name in files: image_file = os.path.join(address, name) fixed_width = 100 img = Image.open(image_file) # получаем процентное соотношение # старой и новой ширины if img.size[0]>100: width_percent = (fixed_width / float(img.size[0])) # на основе предыдущего значения # вычисляем новую высоту height_size = int((float(img.size[1]) * float(width_percent))) # меняем размер на полученные значения new_image = img.resize((fixed_width, height_size)) new_image.save(image_file) # засчитываем изменённое изображение count += 1 #Выводим сообщение о количестве изменённых файлов, что также означает #окончание работы программы img_resize_lb = Label( frame, text=f"Изменён размер {count} картинок!" ) img_resize_lb.grid(row=6, column=1) Стоит отметить, что даже если ширина изображения меньше 100, оно не изменяется, но засчитывается, так как оно, можно сказать, уже обработано. Так как изображения в папке со скачанными картинками уже изменены, обработаем папку paprika с изображениями перца (рисунок 5). Рисунок 5 – Пример работы программы для уменьшения изображений На рисунке 5 сверху слева первый файл папки имеет разрешение 1200х979, на рисунке 5 сверху справа размер первого изображения составляет уже 100х81, всего в папке, как можно заметить на рисунке 5 снизу, 83 изображения. 4 Фильтрация изображений Инормацию о и «плохих» и «хороших» файлах и координаты расположения клубней на «хороших» изображениях будем хранить в реляционной базе данных, так как данные имеют чёткую структуру. У «плохих» файлов будем запоминать только директорию и название, у «хороших», помимо названия и папки, будем хранить координаты прямоугольников, в которых расположены клубни. В базе данных «плохие» и «хорошие» файлы разделим по разным таблицам, к «хорошим» файлам добавим зависимую таблицу координат. Работать будем в СУБД MySQL. Структура реализованной в выбранной СУБД базы данных представлена на рисунке 6. Рисунок 6 – Структура базы данных для формирования датасета, предназначенного для обучения системы распознавания клубней картофеля На рисунке 6 bads — таблица «плохих» фалов, goods — таблица «хороших» файлов, goods _cor — таблица координат прямоугольников для «хороших» файлов, в которой x0,y0 — координаты верхнего левого угла прямоугольника, прямоугольника. а x1,y1 — координаты нижнего правого угла Для добавления «хороших» и «плохих» изображений в базу данных, а также для выделения прямоугольника на изображении и добавления координат этого прямоугольника в базу будем использовать импортируемые ранее библиотеки Tkinter, PIL, Os, а также для подключения к базе данных будем использовать библиотеку PyMySQL. Код подключения к базе данных: connection = pymysql.connect( host='localhost', #название хоста user='root', #имя пользователя db='potato_learning') #название базы данных Интерфейс окна распределения изображений на «хорошие» и «плохие» представлен на рисунке 7. Рисунок 7 – Интерейс окна для фильтрации файлов с изображениями На рисунке 7 при вводе названия папки и нажатии на кнопку «Все файлы в этой папке «плохие»». в базу данных в таблицу bads записывается директория и название файла и выводится сообщение о количестве добавленных файлов (рисунок 8 слева), что также означает окончание работы функции. Рисунок 8 – Добавление «плохих» файлов из папки ginger На рсунке 8 слева видно, что помимо количества добавленных файлов в сообщении выводится информация об общем количестве всех записей в таблице для отслеживания необходимого количества изображений. На рисунке 8 справа можно заметить, что для удобства название папки и название файла представляют собой разные поля таблицы. Код добавления «плохих» файлов: def add_bads_files(): global connection #создаём объект в памяти компьютера с методами для проведения SQL #команд with connection.cursor() as cursor: path = str(folder_tf.get()) #получаем список всех файлов в папке files = os.listdir(path) row=0 for file in files: # Создание нового запроса к таблице bads для добавления #новой записи sql_add_bad = "INSERT INTO `bads` (`bad_file`, `dir_bad_file`) VALUES (%s, %s)" #Выполнение запроса cursor.execute(sql_add_bad, (file, path)) row=row+1 #Запись изменений в базу данных connection.commit() #запрос общего кол-ва записей в таблице sql_count_bad = "SELECT COUNT(id_bad_file) from bads" cursor.execute(sql_count_bad) #Извлекаем первую запись count_bads = cursor.fetchone() #Блок вывода сообщения msg_add_bads = "Добавлено "+str(row)+" 'плохих' файлов. \n Общее количество 'плохих' файлов: "+str(count_bads[0])+"." mb.showinfo("Информация", msg_add_bads) # Закрываем курсор connection.cursor.close() Кнопка «На этих файлах изображены только клубни, и только по одному» позволяет быстро записать файлы, содержащие изображения только одного клубня. Все айлы записываются в таблицу goods и с координатами одного прямоугольника, левая верхняя точка которого (0,0), а правая нижняя — (ширина, высота). Пример хранения таких файлов представлен на рисунке 9. Уведомление для «хороших» файлов выводится аналогичное «плохим», представленное на рисунке 8 слева. Рисунок 9 – Фрагменты таблиц goods и goods_cor На рисунке 9 сверху изображён фрагмент таблицы с координатами, где видно, что ширина всех прямоугольников равна 100, то есть максимальной ширине. Снизу изображён фрагмент таблицы с данными файлов для записей таблицы сверху. Код добавления всех «хороших» файлов в базу, не нуждающихся в обработке: def add_goods_files(): global connection with connection.cursor() as cursor: path = str(folder_tf.get()) files = os.listdir(path) row=0 for file in files: # Запрос к таблице «хороших» файлов goods sql_add_good = "INSERT INTO `goods` (`good_file`, `dir_good_file`) VALUES (%s, %s)" cursor.execute(sql_add_good, (file, path)) im = Image.open(path +"/"+ file) width, height = im.size sql_good_id = "SELECT * FROM goods WHERE id_good_file=LAST_INSERT_ID()" cursor.execute(sql_good_id) id_good = cursor.fetchone() #Запрос к таблице координат «хороших» файлов goods_cor sql_add_good_cor = "INSERT INTO `goods_cor` (`id_good_file`, `x0`, `y0`, `x1`, `y1`) VALUES (%s, %s, %s, %s, %s)" cursor.execute(sql_add_good_cor, (int(id_good[0]), 0, 0, width, height)) connection.commit() row=row+1 sql_count_good = "SELECT COUNT(id_good_file) from goods" cursor.execute(sql_count_good) count_goods = cursor.fetchone() msg_add_goods = "Добавлено "+str(row)+" 'хороших' файлов. \n Общее количество 'хорошх' файлов: "+str(count_goods[0])+"." mb.showinfo("Информация", msg_add_goods) connection.cursor.close() Если не все файлы папки только «хорошие» или только «плохие» или на «хороших» изображения присутствует по несколько объектов, то используется функция кнопки «Файлы этой папки нуждаются в проверке». Тогда вместо стартового изображения (пустая комната на рисунке 7), выводится первое изображения указанной папки Код для открытия папки для дальнейшей обработки файлов представлен ниже: def read_images(): # задаём переменные используемые в функции глобальными для дальнейшего #использования в других функциях global path global files global count_img global count_images global new_image count_img = 0 #Получаем название папки из поля ввода path = str(folder_tf.get()) #Получаем все названия файлов вв папке files = os.listdir(path) #Считаем кол-во файлов в папке count_images = len(files) #Задаём первое изображение для вывода new_image=ImageTk.PhotoImage(file = path +"/"+ files[count_img]) #Меняем стартовое стартовое изображение на первое в папке canvas.itemconfig(image_id, image=new_image) #Изменяем ширину и высоту объекта, в котором выводится изображение #для его корректного отображения canvas.config(width=new_image.width(), height=new_image.height()) Если выведенное изображение является «плохим», то следует использовать кнопку «Это изображение «плохое»». Уведомление о том, что «плохой» файл добавлен аналогично уведомлению на рисунке 8 справа. Код добавления одного «плохого» файла: def add_bad_file(): global connection with connection.cursor() as cursor: # Запрос к таблице bads sql_add_bad = "INSERT INTO `bads` (`bad_file`, `dir_bad_file`) VALUES (%s, %s)" cursor.execute(sql_add_bad, (files[count_img], path)) connection.commit() sql_count_bad = "SELECT COUNT(id_bad_file) from bads" cursor.execute(sql_count_bad) count_bads = cursor.fetchone() msg_add_goods = "'Плохой' файл добавлен. \n Общее количество 'плохих' файлов: "+str(count_bads[0])+"." mb.showinfo("Информация", msg_add_goods) connection.cursor.close() Если изображение не нуждается в обработке и является «хорошим», тогда используется кнопка «Картинка содержит только один клубень», которая добавляет файл и координаты аналогично данным, представленным на рисунке 9. Уведомление о добавлении и об общем количестве «хороших» файлов также выводится. Код добавления одного «хорошего» файла не нуждающегося в обработке: def add_good_file(): global connection with connection.cursor() as cursor: # Запрос к таблице goods sql_add_good = "INSERT INTO `goods` (`good_file`, `dir_good_file`) VALUES (%s, %s)" cursor.execute(sql_add_good, (files[count_img], path)) im = Image.open(path +"/"+ files[count_img]) width, height = im.size sql_good_id = "SELECT * FROM goods WHERE id_good_file=LAST_INSERT_ID()" cursor.execute(sql_good_id) id_good = cursor.fetchone() #Запрос к таблице координат sql_add_good_cor = "INSERT INTO `goods_cor` (`id_good_file`, `x0`, `y0`, `x1`, `y1`) VALUES (%s, %s, %s, %s, %s)" cursor.execute(sql_add_good_cor, (int(id_good[0]), 0, 0, width, height)) connection.commit() sql_count_good = "SELECT COUNT(id_good_file) from goods" cursor.execute(sql_count_good) count_goods = cursor.fetchone() msg_add_goods = "'Хороший' файл добавлен. \n Общее количество 'хорошх' файлов: "+str(count_goods[0])+"." mb.showinfo("Информация", msg_add_goods) connection.cursor.close() Если изображения в принципе не подходит для выборки или нужно переключить на следующее в папке изображение, нажимаем на кнопку «След. картинка», код которой представлен ниже: def show_new_img(): global count_img global count_images global new_image #Если текущее изображением является последним в папке, то выводим #стартовое if count_img >= count_images-1: new_image=ImageTk.PhotoImage(file = "start.jpg") canvas.itemconfig(image_id, image=new_image) canvas.config(width=new_image.width(), height=new_image.height()) #Иначе прибавляем к глобальной переменной подсчёта 1 и выводим #следующее в глобальном списке файлов изображение c изменением размера #объекта else: count_img = count_img + 1 new_image=ImageTk.PhotoImage(file = path + "/" +files[count_img]) canvas.itemconfig(image_id, image=new_image) canvas.config(width=new_image.width(), height=new_image.height()) Пример того, как выглядит вывод файла с другими размерами представлен на рисунке 10. Рисунок 10 – Интерфейс с изображением, размеры которого сильно отличаются от стартового Чтобы обозначить на изображении несколько объектов, для начала нужно нажать на кнопку «На этой картинке несколько клубней», которая запишет в таблицу goods данные файла: def add_good_file_many(): global connection with connection.cursor() as cursor: global id_good # Запрос к таблице goods sql_add_good = "INSERT INTO `goods` (`good_file`, `dir_good_file`) VALUES (%s, %s)" cursor.execute(sql_add_good, (files[count_img], path)) #запрашиваем и запоминаем id последнего добавленного #элемента, чтобы потом добавить к нему координаты sql_good_id = "SELECT * FROM goods WHERE id_good_file=LAST_INSERT_ID()" cursor.execute(sql_good_id) id_good = cursor.fetchone() id_good = int(id_good[0]) Для connection.cursor.close() выделения прямоугольной области создадим невидимый прямоугольник, который будем изменять: # Создадим прямоугольник выделения rect_id = canvas.create_rectangle(topx, topy, topx, topy, dash=(2,2), fill='', outline='white') #Получаем координаты первой точки при нажатии на правую кнопку мыши def get_mouse_posn(event): global topy, topx topx, topy = event.x, event.y #Получаем координаты первой точки при нажатии припередвижении мыши def update_sel_rect(event): global rect_id global topy, topx, botx, boty botx, boty = event.x, event.y #Обновление (выделение) выбранного прямоугольника canvas.coords(rect_id, topx, topy, botx, boty) canvas.bind('<Button-1>', get_mouse_posn) canvas.bind('<B1-Motion>', update_sel_rect) Пример выделенного прямоугольника представлен на рисунке 11. Рисунок 11 – Выделение области на выведенном изображении На рисунке 11 видно, что выделенная область ограничена белой пунктирной линией. Для добавления координат выделенного на рисунке 11 прямоугольника необходимо нажать на кнопку «Запомнить координаты объекта», код котрой представлен ниже: def add_good_file_many_cor(): global connection global id_good with connection.cursor() as cursor: #Делаем запрос к базе данных координат и выполняем запрос #с добавлением глобальных переменных координат topx, topy, botx, boty в #базу данных sql_add_good_cor = "INSERT INTO `goods_cor` (`id_good_file`, `x0`, `y0`, `x1`, `y1`) VALUES (%s, %s, %s, %s, %s)" boty)) cursor.execute(sql_add_good_cor, (id_good, topx, topy, botx, connection.commit() sql_count_good = "SELECT COUNT(id_good_file) from goods" cursor.execute(sql_count_good) count_goods = cursor.fetchone() msg_add_goods = "Координаты добавлены. \n Общее количество 'хорошх' файлов: "+str(count_goods[0])+"." mb.showinfo("Информация", msg_add_goods) connection.cursor.close() Сообщение при добавлении координат, как видно из кода выше, также выводится. Его пример представлен на рисунке 12. Рисунок 12 – Пример сообщения при добавлении координат объекта на «хорошем» файле 5 Формирование папок Good и Bad и файлов Good.dat и Bad.dat При нажатии на кнопку «Сформировать данные» на главном экране, представленном на рисунке 1, открывается окно (рисунок 13 слева сверху), содержащее одну кнопку «Запустить формирование данных для обучения», по клике по которой происходит копирование файлов из директорий, указанных в базе, в директорию Bad «плохих» и директорию Good «хороших» под новыми названиями, чтобы не возникало проблем с тем, что названия фалов одинаковые. А также формируется файл Bad.dat, в котором указываются только путь (Bad) и название «плохих» файлов, и Good.dat, в котром помимо пути (Good) и названия первым числом указывается количество элементов на изображении, затем их координаты подряд через пробел, без отделения каждого прямоугольника. Также по окончании копирования всех файлов по папкам Good и Bad и формирования соответствующих файлов выводится уведомление, которое представлено на рисунке 13 слева снизу. Фрагменты сформированных файлов представлены на рисунке 13 справа. Рисунок 13 – Окно формирования данных, уведомления об окончании и формируемые файлы Код формирования датасета для обучения представлен ниже: #Формирование «плохой» части датасета def form_dir_dat_bad(): global connection global i with connection.cursor() as cursor: #Запрашиваем все плохие файлы sql_bads_files = "SELECT * FROM bads" cursor.execute(sql_bads_files) #Извлекаем все записи bads_files = cursor.fetchall() #Создаём новую папку os.system('mkdir Bad') #Создаём файл Bad.dat bad_file = open("Bad.dat", "w") bad_file.close() i=0 for bad_file in bads_files: #Копируем файл os.system('copy '+bad_file[2]+'\\'+bad_file[1]+' Bad\\image'+str(i)+'.jpg') #Открываем файл Bad.dat для дозаписи bad_file = open("Bad.dat", "a+") #Записываем название скопированного ранее файла bad_file.write("Bad\\image"+str(i)+".jpg\n") bad_file.close() i=i+1 #Формирование «хорошей» части датасета def form_dir_dat_good(): global connection global j with connection.cursor() as cursor: #Запрос к таблице goods sql_goods_files = "SELECT * FROM goods" cursor.execute(sql_goods_files) #Извлекаем все записи goods_files = cursor.fetchall() #Создаём папку для «хороших» файлов os.system('mkdir Good') #Создаём файл Good.dat good_file = open("Good.dat", "w") good_file.close() j=0 for good_file in goods_files: #Крпируем файлы из исходных папок в нувую с новым названием os.system('copy '+good_file[2]+'\\'+good_file[1]+' Good\\image'+str(j)+'.jpg') #Запрос координат объектов для изображения sql_goods_files_cor = "SELECT * FROM goods_cor WHERE id_good_file="+str(good_file[0]) cursor.execute(sql_goods_files_cor) good_file_cors = cursor.fetchall() #Формируем строку с координатами, параллельно подсчитывая их #количество str_good_cor = "" count_cor=0 for good_file_cor in good_file_cors: str_good_cor = str_good_cor + str(good_file_cor[2])+" "+str(good_file_cor[3])+" "+str(good_file_cor[4])+" "+str(good_file_cor[5])+" " count_cor=count_cor+1 #Открываем файл Good.dat для дозаписи good_file = open("Good.dat", "a+") #Записываем в файл название «хорошего» файла, количество координат и №строку с ними good_file.write("Good\\image"+str(j)+".jpg "+str(count_cor)+" "+str_good_cor+"\n") good_file.close() j=j+1 #Вызываем функции для формирования хорошей и плохой части и выводим сообщение def form_data(): form_dir_dat_good() form_dir_dat_bad() msg_add = "Сформирован датасет из \n"+str(j+1)+" 'хороших' файлов. \n и "+str(i+1)+" 'плохих' файлов." mb.showinfo("Информация", msg_add) 6 Обучение Для обучения программы обнаружению клубней картофеля будем использовать библиотеку алгоритмов компьютерного зрения, обработки изображений и численных алгоритмов общего назначения OpenCV версии 2.4.9, которую можно скачать по ссылке https://sourceforge.net/projects/opencvlibrary/files/opencv-win/2.4.9/. Установка данной библиотеки не вызывает никаких проблем и не требует никаких дополнительных настроек. Для формирования файла-каскада, которое будет использоваться для обнаружения клубней нужно привести положительные изображения к одному формату, после чего можно будет запускать команду для запуска обучения. В общем интерфейс окна обучения состоит из двух кнопок, которые можно увидеть на рисунке 14. Рисунок 14 – Окно обучения После завершения приведения положительных изображений к общему формату выводится уведомление, представленное на рисунке 15. Рисунок 15 – Уведомление о завершении приведения положительных изображений к общему формату Код приведения «хороших» изображений к общему формату: def plus_imges(): #Запуск файла opencv_createsamples.exe библиотеки OpenCV os.system(r"e:\opencv\build\x64\vc10\bin\opencv_createsamples.exe info c:\Users\olesi\desktop\potato\good.dat -vec c:\users\olesi\desktop\potato\samples.vec -w 20 -h 30") msg_add = "Положительные изображения приведены к общему формату." mb.showinfo("Информация", msg_add) Стоит отметить, что по завершении работы файла opencv_createsamples.exe, в консоли выводится информация, представленная на рисунке 16. Рисунок 16 – Сообщение о работе файла opencv_createsamples.exe в консоли После завершения процедуры приведения положительных файлов к общему формату формируется файл samples.vec. В итоге у нас имеется датасет состоящий из 22 «хороших» и 330 «плохих» файлов. Запустим обучение для них. Для этого запустим файл opencv_traincascade.exe, сождадим папку haarcascade, где будет храниться файл для дальнейшего использования, укажем расположение файла samples.vec, количество уровней каскада (numStages) — 16, коэффициент, определяющий качество обучения (minhitrate) — 0,8 , уровень ложной тревоги (maxFalseAlarmRate) — 0,4 , количество позитивных примеров (numPos) — 17 (рекомендовано указывать 80% от имеющихся положительных), количество негативных примеров (numNeg) — 330, выделяемая под процесс память (precalcValBufSize и -precalcIdxBufSize) — 1024. Код запуска файла opencv_traincascade.exe: def create_cascade(): os.system('mkdir haarcascade') start = time.time()#Для фиксации времени выполнения os.system(r"e:\opencv\build\x64\vc10\bin\opencv_traincascade.exe data haarcascade -vec c:\users\olesi\desktop\potato\samples.vec -bg C:\Users\olesi\Desktop\potato\bad.dat -numStages 16 -minhitrate 0.8 maxFalseAlarmRate 0.4 -numPos 17 -numNeg 330 -w 20 -h 30 -mode ALL precalcValBufSize 1024 -precalcIdxBufSize 1024") end = (time.time()-start)/60 #Для фиксации времени выполнения #Вывод сообщения об окончании и времени, затраченно на обучение msg_add = "Итоговый каскад создан за "+str(end)+" минут." mb.showinfo("Информация", msg_add) Уводомление, полученное после окончания обучения представлено на рисунке 17. Рисунок 17 – Уведомление об окончании обучения и времени обучения На рисунке 17 видно, что для обучения программы распознаванию клубней картофеля из маленькой выборки состоящей их 22 положительных и 330 негативных потребовалось больше 6 часов. После обучения каждого уровня каскада выводится информация, фрагмент котрой представлен на рисунке 18. Рисунок 18 – Информация выводимая в консоли после каждого уровня На рисунке 18 видно, что обучение было завершено на 13 уровне и в папке haarcascade мы видим 12 уровней (рисунок 19). Рисунок 19 – Файлы сформированные файлом opencv_traincascade.exe библиотеки OpenCV в папке haarcascade Для дальнейшего распознавания клубней нам понадобится файл cascade.xml в папке haarcascade. 7 Поиск клубня на изображении Чтобы запустить окно, в котором нужно ввести названия айла для поиска клубня, нужно нажать на кнопку «Найти клубень на картинке» в главном меню, представленном на рисунке 1. Интерфейс окна, гд необходимо ввести название файла представлено на рисунке 20. Рисунок 20 – Интерфейс окна для поиска клубня на изображении Как видно для поиска нужно нажать на одну кнопку, код которой представлен ниже: def search(): #Импорт билиотеки OpenCV для анализа изображений import cv2 path = str(image_file.get()) # Открытие изображения из файла imaging = cv2.imread(path) # Изменение свойств изображения с помощью библиотеки cv2 imaging_gray = cv2.cvtColor(imaging, cv2.COLOR_BGR2GRAY) imaging_rgb = cv2.cvtColor(imaging, cv2.COLOR_BGR2RGB) # Импорт xml-данных каскадного классификатора Хаара xml_data = cv2.CascadeClassifier('haarcascade/cascade.xml') # Обнаружение объекта на изображении с помощью каскадного классификатора Хаара detecting = xml_data.detectMultiScale(imaging_gray, minSize =(50, 50)) # Количество обнаруженных объектов amountDetecting = len(detecting) # Использование условия if для выделения обнаруженного объекта if amountDetecting != 0: for(a, b, width, height) in detecting: # Выделение обнаруженного объекта прямоугольником cv2.rectangle(imaging_rgb,(a, b), (a + height, b + width), (0, 275, 0), 9) # Построение изображения с помощью subplot() из plt pltd.subplot(1, 1, 1) # Отображение изображения в выходных данных pltd.imshow(imaging_rgb) pltd.show() Попробуем найти клубень на примитивном изображении, представленном на рисунке 21. Рисунок 21 – Изображение для поиска на нём клубня Введём название файла potato.jpg и нажмём «Найти клубень/клубни» в окне поиска. Результат работы программы представлен на рисунке 22 Рисунок 22 – Результат поиска клубня на изображении из рисунка 21 На рисунке 22 видно, что клубень найден и в данном случае распознавание работает корректно.