\documentclass[a4paper,fontsize=14bp]{article} \input{../common-preamble} \input{../bmstu-preamble} \input{../fancy-listings-preamble} \numerationTop \begin{document} \thispagestyle{empty} \makeBMSTUHeader % ... работе, номер, тема, предмет, ?а, кто \makeReportTitle{лабораторной}{2}{Распознавание объекта по цвету}{Цифровая обработка изображений}{}{Большаков В.Э.} \newpage \thispagestyle{empty} \tableofcontents \newpage \pagestyle{fancy} \sloppy \section{Цель} Основной целью лабораторной работы является изучение методов распознания объекта по цвету с помощью библиотеки цифровой обработки изображений scikit-image, а также разработка программы распознавания блюд на подносе. \section{Задание} \begin{itemize} \item Подготовить выборку 10 цветных цифровых изображений блюд бауманской столовой (по аналогии с примером). \item В среде Spyder (сборка Anaconda) на языке Python (3.5 и старше) создать проект и подключить библиотеку scikit-image. \item Из л.р. 1 взять модуль загрузки цветного цифрового изображения и модуль обработки пикселей. \item Запрограммировать формулу перевода цветного цифрового изображения в цветовое пространство в соответствии с вариантом (В1: RGB, В2: HSV, В3: CMYK, В4: YUV, В5: Hough Circle+HSV, В6: Hough Circle +HSL) \item Создать классификацию блюд из цифровых изображений. \item Для каждого блюда определить цветовые характеристики. Задать распределение значений каждого цвета. \item Провести эксперимент по распознаванию блюд с визуализацией результатов. \end{itemize} Вариант (по номеру в списке группы) 9, то есть цветовое пространство CMYK. Для распознавания разрешается использовать только цвет. \section{Теоретическая часть} \subsection{Цветовое пространство CMYK} Система CMYK создана и используется для типографической печати. Аббревиатура \textbf{CMYK} означает названия основных красок, использующихся для четырехцветной печати: голубой (Сyan), пурпурный (Мagenta) и жёлтый (Yellow). Буквой К обозначают черную краску (BlacK), позволяющую добиться насыщенного черного цвета при печати. Используется последняя, а не первая буква слова, чтобы не путать CMYK Black и RGB Blue. На рисунке \hrf{pic:cmyk-rgb} показана цветовая палитра CMYK вместе с RGB. \begin{figure}[H] \centering \includegraphics[width=12cm]{02-dip-lab02-cmyk-rgb.png} \caption{Цветовые пространства CMYK и RGB} \label{pic:cmyk-rgb} \end{figure} \subsection{Особенность формирования цвета} Каждое из чисел, определяющее цвет в CMYK, представляет собой процент краски данного цвета, составляющей цветовую комбинацию. Например, для получения тёмно-оранжевого цвета следует смешать 30\% голубой краски, 45\% пурпурной краски, 80\% жёлтой краски и 5\% чёрной. Это можно обозначить следующим образом: CMYK(30/45/80/5). \subsection{Перевод из RGB в CMYK} Для того, чтобы перевести пиксель RGB в пиксель CMYK можно применить формулу, описанную на языке Java и представленную в таблице \hrf{code:rgb-cmyk}. \begin{lstlisting}[language=Java, style=JCodeStyle, caption={Формула перевода из RGB в CMYK}, label={code:rgb-cmyk}] int black = Math.min(Math.min(255 - red, 255 - green), 255 - blue); if (black!=255) { int cyan = (255-red-black)/(255-black); int magenta = (255-green-black)/(255-black); int yellow = (255-blue-black)/(255-black); return new int[] {cyan,magenta,yellow,black}; } else { int cyan = 255 - red; int magenta = 255 - green; int yellow = 255 - blue; return new int[] {cyan,magenta,yellow,black}; } \end{lstlisting} Пример полученного изображения с использованием преобразования пикселей представлен на рисунке \hrf{pic:trans}. При преобразовании таким методом исходного изображения (\hrf{pic:trans-rgb}) будет получено изображение не естественных цветов (\hrf{pic:trans-cmyk}). \begin{figure}[H] \centering \begin{subfigure}[b]{0.4\textwidth} \centering \includegraphics[width=\textwidth]{02-dip-lab02-trans-rgb.png} \caption{Исходное изображение в пространстве RGB} \label{pic:trans-rgb} \end{subfigure} \hfill \begin{subfigure}[b]{0.4\textwidth} \centering \includegraphics[width=\textwidth]{02-dip-lab02-trans-cmyk.png} \caption{Преобразованное изображение CMYK} \label{pic:trans-cmyk} \end{subfigure} \caption{Пример попиксельного перевода изображения} \label{pic:trans} \end{figure} Данные результаты не являются правильными. Для более точного преобразования следует использовать ICC профили, описывающие как должен отображаться цвет на мониторе и бумаге. \subsection{Цветовое профилирование} Для достоверного управления цветом необходимы ICC-совместимые профили всех цветовоспроизводящих устройств. Например, без точного профиля сканера хорошо отсканированное изображение может отображаться в другой программе неправильно из-за различий между алгоритмами отображения, используемыми сканером и программой. Недостоверность цветопередачи может привести к внесению в хорошее изображение ненужных и, возможно, вредных "улучшений". При наличии точного профиля программа, импортирующая изображение, способна скорректировать разницу с устройством и воспроизвести достоверные цвета отсканированного изображения. Профили документов описывают конкретное цветовое пространство, используемое в документе. Путем назначения профиля, или пометки документа профилем, приложение определяет фактические цвета документа. Например, запись R = 127, G = 12, B = 107 — это просто набор чисел, которые разные устройства будут отображать по-разному. Однако при пометке цветовым пространством Adobe RGB эти числа определяют фактический цвет или длину световой волны. На рисунке \hrf{pic:icc-profile} представлена назначение профилей ICC. \begin{figure}[H] \centering \includegraphics[width=5cm]{02-dip-lab02-icc-profile.png} \caption{Пример использования профилей ICC} \label{pic:icc-profile} \end{figure} При использовании профилей преобразованные изображения получаются более правдоподобными. На рисунке \hrf{pic:icc-trans} представлено такое преобразование, \hrf{pic:icc-trans-rgb} пространство RGB, \hrf{pic:icc-trans-cmyk} пространство CMYK. \begin{figure}[H] \centering \begin{subfigure}[b]{0.4\textwidth} \centering \includegraphics[width=\textwidth]{02-dip-lab02-trans-rgb.png} \caption{Исходное изображение в пространстве RGB} \label{pic:icc-trans-rgb} \end{subfigure} \hfill \begin{subfigure}[b]{0.4\textwidth} \centering \includegraphics[width=\textwidth]{02-dip-lab02-trans-rgb.png} \caption{Преобразованное изображение CMYK} \label{pic:icc-trans-cmyk} \end{subfigure} \caption{Пример преобразования с использованием профилей} \label{pic:icc-trans} \end{figure} \section{Практическая часть} \subsection{Состав исполняемых файлов} Программа по распознаванию изображений состоит из двух скриптов: \begin{itemize} \item основного скрипта для выполнения программы \code{main.py}; \item скрипта для обработки изображений \code{colorFoodView.py}. \end{itemize} Основной скрипт для тренировки принимает 2 аргумента для запуска: \begin{itemize} \item \code{-c}, файл конфигурации программы в формате json; \item \code{-f}, файл описывающий блюда и их цвета. \end{itemize} Например, скрипт для запуска тренировки может выглядеть следующим образом: \begin{lstlisting}[style=CCodeStyle] main.py -c "./configs.json" -f "./foods.json" \end{lstlisting} \subsection{Конфигурационные файлы} Для корректной работы программы, ей необходимо передать два файла в формате json: \begin{enumerate} \item файл конфигурации (далее \code{config.json}); \item файл описания блюд (далее \code{food.json}). \end{enumerate} Параметры файла \code{config.json} представлены в таблице \hrf{table:config-json}, а пример заполненного файла в листинге \hrf{code:conf-json}. \begin{table}[H] \centering \begin{tabular}{|l|p{100mm}|} \hline Наименование параметра & Описание \\ \hline \code{RGB_profile} & Путь до профиля ICC RGB описывающего цветовое пространство исходного изображения \\ \hline \code{CMYK_profile} & Путь до профиля ICC CMYK в чье цветовое пространство должно быть переведено изображение \\ \hline \code{Path_to_food} & Путь до папки с изображениями \\ \hline \code{Delta_for_color} & Процент погрешности для пикселей \\ \hline \code{Color_swap} & Цвет в формате CMYK в который необходимо преобразовать найденные пиксели \\ \hline \code{Mode} & Режим работы программы. Если режим работы Test, то в консоль будет выводиться количество найденных пикселей для искомого блюда\\ \hline \end{tabular} \caption{Параметры конфигурации} \label{table:config-json} \end{table} \begin{lstlisting}[language=C,style=CCodeStyle, caption={Пример настроек параметров в программе}, label={code:conf-json}] { "RGB_profile": "./AdobeICCProfiles/RGB/AdobeRGB1998.icc", "CMYK_profile": "./AdobeICCProfiles/CMYK/WebCoatedSWOP2006Grade5.icc", "Path_to_food": "./WorkFood", "Delta_for_color": 5, "Color_swap": [100, 78, 0, 0], "Mode": "Product" } \end{lstlisting} Параметры файла \code{food.json} представлены в таблице \hrf{table:food-json}, а пример заполненного файла в листинге \hrf{code:food-json}. Файл описывает список блюд, а именно параметры каждого из элементов списка. \begin{table}[H] \centering \begin{tabular}{|l|p{100mm}|} \hline Наименование параметра & Описание \\ \hline Name & Наименование блюда \\ \hline CMYK & Пиксели которые описывают данное блюдо \\ \hline Count & Минимальное количество пикселей, которое должно быть у изображения для того, чтобы можно было говорить о том, что на изображении присутствует искомое блюдо. Данное значение можно получить в тестовом режиме \\ \hline \end{tabular} \caption{Параметры конфигурации} \label{table:food-json} \end{table} \begin{lstlisting}[language=C,style=CCodeStyle, caption={Пример настроек параметров в программе}, label={code:food-json}] { "Foods": [ { "Name": "Банан в шоколаде", "CMYK": [ { "C": 23, "M": 29, "Y": 44, "K": 7 }, { "C": 29, "M": 40, "Y": 57, "K": 11 }, { "C": 34, "M": 61, "Y": 63, "K": 40 } ], "Count": 2000 }, { "Name": "Сосиска", "CMYK": [ { "C": 27, "M": 65, "Y": 76, "K": 25 }, { "C": 14, "M": 47, "Y": 67, "K": 3 }, { "C": 24, "M": 65, "Y": 85, "K": 20 }, { "C": 28, "M": 83, "Y": 99, "K": 30 } ], "Count": 9000 }, //... ] } \end{lstlisting} \subsection{Описание алгоритма работы программы} Алгоритм работы основной программы заключается в загрузке и настройки конфигурации и передаче их в скрипт обработки изображения. После выполнения поиска блюд и перед началом нового цикла работа можно скорректировать \code{json} файлы конфигурации и изменения будут применены без необходимости перезапуска программы. В скрипте обработки изображения используется следующий алгоритм работы: \begin{itemize} \item преобразовать все изображения в цветовое пространство, описанное профилем CMYK; \item для каждого изображения найти пиксели из выбранного блюда; \item если пиксель найден, то его цвет меняется на цвет, заданный в настройках; \item проверяется количество найденных пикселей, если их меньше порогового значения, то изображение отбрасывается; \item изображения, прошедшие проверку, возвращаются в основную программу для дальнейшей работы. \end{itemize} \subsection{Выборка для тестирования} Для тестирования была подготовлена выборка из десяти подносов с едой. Каждая картинка была приведена к разрешению 400х300 пикселей. Изначальный размер изображений (более 4000х3000 пикселей) не позволяет добиться приемлемых результатов с использованием только цвета как характеристики для поиска изображений на фото. На рисунке \hrf{pic:food-src} представлена подготовленная для работы выборка. \begin{figure}[H] \centering \includegraphics[width=3cm]{01-dip-lab02-rgb1.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb2.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb3.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb4.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb5.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb6.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb7.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb8.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb9.jpg} \includegraphics[width=3cm]{01-dip-lab02-rgb10.jpg} \caption{Исходная выборка изображений} \label{pic:food-src} \end{figure} \subsection{Подготовка данных} Для анализа изображений на предмет присутствия искомых пикселей они должны быть правильно заданы. Для этого используется скрипт \code{converter.py} преобразующий рабочее изображение в пространство CMYK. В дальнейшем с помощью дополнительного инструментария\footnote{онлайн пикер цвета https://www.ginifab.com/feeds/pms/color\_picker\_from\_image.php}, возможно узнать цвет пикселей в пространстве CMYK и внести в \code{food.json}. \section{Результат работы} \subsection{Листинг} Исходный код скриптов \code{main.py}, \code{colorFoodView.py} и \code{converter.py} представлены в приложениях \hrf{app:main}, \hrf{app:find} и \hrf{app:conv} соответственно. \subsection{Интерфейс и измерения} Программа использует терминальный текстовый интерфейс для взаимодействия с пользователем. На основе содержания \code{food.json} строятся возможные варианты выбора в основном меню. Предусмотрен контроль ошибок ввода. В результате измерений программа вернет изображения где, по её мнению, присутствуют искомые продукты. Найденные пиксели будут закрашены в заданный в конфигурации цвет. Результат работы для поиска яичницы приведен на рисунке \hrf{pic:result}. \begin{figure}[H] \centering \includegraphics[width=12cm]{01-dip-lab02-result.png} \caption{Результат работы программы} \label{pic:result} \end{figure} \subsection{Ошибки} В ходе вычислений программа допускает некоторое количество ошибок. Чем качественнее изображение, тем больше шансов что искомый пиксель будет присутствовать на изображении в неожиданных местах. Рабочая выборка подверглась минимальной предварительной обработке и часто в искомую область попадают объекты, не связанные с продуктами, такие как стол или поднос. В таблице \hrf{table:errors} представлены ошибки первого и второго рода для измерений. \begin{table}[H] \centering \begin{tabular}{||r|c|c||} \hline \multirow{2}{*}{Результаты} & \multicolumn{2}{c|}{Верная гипотеза} \\ \cline{2-3} & H0 & H1 \\ \hline H0 & 27 & 36 \\ \hline H1 & 0 & 524 \\ \hline \end{tabular} \caption{Ошибка первого и второго рода} \label{table:errors} \end{table} \begin{itemize} \item [] Ошибка первого рода: $0\%$ \item [] Ошибка второго рода: $36 / 524 * 200 = 7\%$ \end{itemize} Данные результаты легко объяснить. Коэффициенты для срабатывания определения блюда настроены таким образом, чтобы находить блюдо в наборе. И программа хорошо справляется с этой задачей. Ошибки второго рода могут возникнуть, когда цвет блюда очень общий для изображения и срабатывает в нем во многих местах. Таких ситуаций не много, однако они имеют место быть. Основная масса ошибок второго рода отсекаются настраиваемым коэффициентом для каждого блюда. \section{Заключение} В ходе выполнения задания были изучены методы по обработке изображения и работы с цветом. Исходя из результатов тестирования можно сделать вывод, что использование только цвета для поиска объектов на картинке не является достаточным, а подготовке рабочей выборки нужно уделять большое внимание. Разный источник света и разные условия сьемки сильно изменяют цвета, которые можно использовать для идентификации объектов. \newpage \appendix \section*{Приложения} \addcontentsline{toc}{section}{Приложения} \renewcommand{\thesubsection}{\Asbuk{subsection}} \subsection{Основная программа} \label{app:main} \begin{lstlisting}[language=Python,style=PyCodeStyle] import argparse # Парсим аргументы вызова скрипта import codecs import json import sys from ColorFoodView import find_food def create_parser(): parser = argparse.ArgumentParser() parser.add_argument('-c', '--configs') parser.add_argument('-f', '--foods') return parser def menu(foods): print("Допустимый выбор:") print("0 : Выход") i = 1 for food in foods["Foods"]: print(i, ":", food["Name"]) i += 1 print("Введите команду:") i -= 1 while 1: try: chose = int(input()) if 0 > chose or chose > i: print("Данной команды не существует. Выберите из предложенного набора и попробуйте еще раз.") continue return chose except: print("Необходимо ввести число соответсвующее вашему выбору. Попробуйте еще раз") # поиск продуктов def food_name(foods, chose): return foods["Foods"][chose - 1] namespace = create_parser().parse_args(sys.argv[1:]) while 1: # получение конфигурационных данных configs = json.loads(codecs.open(namespace.configs, 'r', encoding='utf-8').read()) # получение данных для справочника продуктов foods = json.loads(codecs.open(namespace.foods, 'r', encoding='utf-8').read()) # меню выбора chose = menu(foods) if chose == 0: break # запрос на поиск продуктов images_food = find_food(configs, food_name(foods, chose)) for image in images_food: image.show() print("Нажмите любую кнопку чтобы продолжить.") input() \end{lstlisting} \subsection{Поиск объектов по цвету} \label{app:find} \begin{lstlisting}[language=Python,style=PyCodeStyle] import os import re from PIL import Image from PIL.ImageCms import profileToProfile color_swap = [0, 0, 0, 255] delta_for_color = 0 mode = "Product" # Возвращает список изображений в цветовом пространтстве CMYK def get_list_cmyk_image(path, rgb_profile, cmyk_profile): list_image = [] for d, dirs, files in os.walk(path): # Достаем все каталоги по пути for f in files: # Достаем все файлы if re.search(".jpg", f): # Сортировка по типу path = os.path.join(d, f) # Получаем путь до изображения im = Image.open(path) picture_cmyk = profileToProfile(im, rgb_profile, cmyk_profile, outputMode='CMYK') list_image.append(picture_cmyk) # Добавляем полученное изображение в список return list_image # переводит список процентов в соответствующее значение [0..255] def percent_to_scale_list(percent): list = [] for p in percent: list.append(percent_to_scale(p)) return list # преобразует процент в соответветсвующее значение [0..255] def percent_to_scale(percent): return int(percent / 100 * 255) # получение минимум # значение пикселя - процент погрешности def get_min(param): result = param - delta_for_color if result < 0: return 0 else: return result # получение максимума # значение пикселя + процент погрешности def get_max(param): result = param + delta_for_color if result > 255: return 255 else: return result # проверка входит ли значение пикселя в заданные границы def pixel_equal(pixel, mask_pixel): min_c = get_min(pixel) max_c = get_max(pixel) return min_c <= mask_pixel <= max_c # проверка пикселя на соответсвие искомого def range_equal(pixel, mask_pixel): c = pixel_equal(pixel[0], mask_pixel[0]) m = pixel_equal(pixel[1], mask_pixel[1]) y = pixel_equal(pixel[2], mask_pixel[2]) k = pixel_equal(pixel[3], mask_pixel[3]) if c and m and y and k: return 1 else: return 0 # ищем пиксели из изображения и сравниваем с искомыми # если поиск был успешен, то заменяем его на заданные в конфигурации цвет def swap_pixel(image, mask_pixels, color_count): count = 0 for i in range(image.width): for j in range(image.height): pixel = image.getpixel((i, j)) for mask_pixel in mask_pixels: if range_equal(pixel, mask_pixel): image.putpixel((i, j), tuple(color_swap)) count += 1 if mode == "Test": print(count) if count > color_count: return image else: return None # Получение списка пикселей для дальшейшего их поиска в изображении def get_mask_pixel(food_name): list_tuple = [] for pixel in food_name["CMYK"]: c = percent_to_scale(pixel["C"]) m = percent_to_scale(pixel["M"]) y = percent_to_scale(pixel["Y"]) k = percent_to_scale(pixel["K"]) list_tuple.append(tuple([c, m, y, k])) return list_tuple # поиск блюд на изображении def find_food_on_image(cmyk_image_list, food_name): list_found_image = [] for image in cmyk_image_list: mask_pixel = get_mask_pixel(food_name) image_swap_pixel = swap_pixel(image, mask_pixel, food_name["Count"]) if image_swap_pixel is not None: list_found_image.append(image_swap_pixel) return list_found_image # точка входа в скрипт, настройка параметров и вызов функций def find_food(configs, food_name): global delta_for_color, color_swap, mode delta_for_color = percent_to_scale(configs["Delta_for_color"]) color_swap = percent_to_scale_list(configs["Color_swap"]) mode = configs["Mode"] cmyk_image_list = get_list_cmyk_image(configs["Path_to_food"], configs["RGB_profile"], configs["CMYK_profile"]) food_on_image_list = find_food_on_image(cmyk_image_list, food_name) return food_on_image_list \end{lstlisting} \subsection{Конвертер изображений} \label{app:conv} \begin{lstlisting}[language=Python,style=PyCodeStyle] from PIL import Image from PIL.ImageCms import profileToProfile RGB_profile = "./AdobeICCProfiles/RGB/AdobeRGB1998.icc" CMYK_profile = "./AdobeICCProfiles/CMYK/WebCoatedSWOP2006Grade5.icc" for i in range(1, 11): image_path = "./WorkFood/" + str(i) + ".jpg" im = Image.open(image_path) converted_image = profileToProfile(im, RGB_profile, CMYK_profile, outputMode='CMYK') converted_image.save("./CMYK_Food/" + str(i) + ".jpg") \end{lstlisting} \end{document}