BMSTU/02-dip-lab-02-report.tex

511 lines
30 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\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}