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

511 lines
30 KiB
TeX
Raw Permalink Normal View History

2023-01-27 22:32:16 +03:00
\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}