Ненудний SCRUM і сегментація зображення для виділення Post-it наклейок

image
" Натхнення, яке шукав весь ранок,
наздогнало в найневдаліший момент.
І як пояснити, що я йду на SCRUM?
— Підемо зі мною ?! "


imageКомунікація в команді— сувора необхідність великих проектів. Це не повинно виглядати як ешафот або примусове збори анонімних алкоголіків. Від команди потрібно участь, потрібен блиск в очах, кожного має рвати від бажання висловитися, як від пафосності цієї пропозиції. Поступово наша команда еволюціонувала до SCRUM-моделі, багато в чому завдяки простим і наочним наліпок. Який же SCRUM без наклейок? Майже у кожного в дитинстві були наклейки і, десь глибоко в підсвідомості засіли спогади, коли нас, ще будучи дитиною, вчила клеїти вихователька і, якщо наклейка була приклеєна рівно, як заохочення, вона не била по руках. Але навіть в нашому безтурботному дитинстві доводилося робити речі, які здавалися нам нудні і незрозумілі — прибирати іграшки, відтирати стіну від ручки або писати під диктовку. Подорослішавши, у нас з'являється вибір — ми можемо перекласти роботу на інших. А хто захоче за всіх писати backlog (звіт) і потім переносити дані в Jira? Використання Jira безпосередньо в процесі мітингу виводить учасника з обговорення, тому, після прийняття конвенції ООН про скасування рабства, залишається перекласти це завдання на роботів.
У результаті народилася ідея написати програму розпізнавання і відстеження карток завдань на SCRUM-дошці.

Постановка мінімальної задачі бачиться так:
  • прочитати зображення SCRUM-дошки;
  • виділити стікери;
  • зберегти зображення стікерів;
  • визначити зону дошки, де знаходяться стікери;
  • визначити, до якої ставиться завдання стікер;
  • сформувати файл з інформацією про статус завдань.
Власне, завдання виглядає досить елегантною і простий одночасно, і ми пропонували її в цьому році в якості навчального завдання під час нашої літньої школи з програмування. У даній статті пропонується розглянути реалізацію перших трьох вимог.

З оцифруванням зображення дошки правило просте — останній встав зі стільця фотографує дошку. В майбутньому його повинен замінити робот.
Нижче фотографія того, як може виглядати спрощена SCRUM-дошка. Дуже спрощена.


Рис.1. Приклад SCRUM-дошки.

Зверху стікери для необраних завдань, нижче області завдань розробників (синього, зеленого і червоного). Область кожного розробника розбита на дві частини — ліворуч знаходяться завдання, які виконуються, праворуч — виконані.

Сегментація
Почнемо з банальностей — завантаження вихідного зображення з фотографією дошки робиться засобами OpenCV дуже просто:
int main(int argc, char** argv) { 
vector<cv::Mat> stickers;
cv::CommandLineParser parser( argc, argv, keys );
String image_path = parser.get<String>( 0 );

if( image_path.empty() ) {
help();
return -1;
}

cv::Mat image = cv::imread(image_path);

Для представлення зображень в OpenCV використовується клас cv::Mat. Це цікава структура даних і детальну інформацію про цей клас можна знайти на тут.
Далі необхідна основна функція для виділення зображень стікерів:
recognizeStickers(stickers);

У першій версії ми просто збережемо знайдені стікери у файли з іменами sticker1.jpg… stickerN.jpg:
saveStickers(stickers);
}

Розглянемо більш докладно функцію виділення зображень стікерів. Прототип функції може бути таким:
void recognizeStickers(vector<cv::Mat> &stickers);

Алгоритм рішення задачі виділення контрастних об'єктів на однорідному фоні може бути реалізований різними способами:
  • Алгоритм 1. Виділення об'єктів (стікерів), що мають заданий колір за допомогою функції inRange;
  • Алгоритм 2. Виділення яскравих об'єктів (стікерів) з S-каналу HSV-зображення за допомогою функції threshold;


Алгоритм 1 Виділення стікерів за допомогою inRange може бути наступним:
  • визначити діапазон кольорів, характерних для стікера (у першій реалізації задамо діапазон явно, константами) ;
  • виділити точки, колір яких відрізняється від кольору тла, за допомогою ступінчастого перетворення;
  • за допомогою фільтра об'єднати точки передбачуваного стікери для отримання суцільного зображення;
  • виділити межі безперервних областей;
  • визначити межі по вертикалі і горизонталі для груп точок передбачуваних стікерів.
Нижче грубий начерк, ілюструє ідею алгоритму:
void recognizeStickersByRange(cv::Mat image,std::vector<cv::Mat> &stickers) 
{ 
cv::Mat imageHsv; 
std::vector< std::vector<cv::Point> > contours; 

// Перетворимо в hsv, щоб точніше виловлювати колір стікера 
cv::cvtColor(image, imageHsv, cv::COLOR_BGR2HSV); 

cv::Mat tmp_img(image.size(),CV_8U); 

// Виділення відповідних за кольором областей 
cv::inRange(imageHsv, 
cv::Scalar(key_light-delta_light,key_sat-delta_sat,key_hue-delta_hue), 
cv::Scalar(key_light+delta_light,key_sat+delta_sat,key_hue+delta_hue), 
tmp_img); 

// "Замазати" огріхи при виділенні за кольором 
cv::dilate(tmp_img,tmp_img,cv::Mat(),cv::Point(-1,-1),3); 
cv::erode(tmp_img,tmp_img,cv::Mat(),cv::Point(-1,-1),1); 

// Виділення безперервних областей 
cv::findContours(tmp_img,contours,
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); 

for (uint i = 0; i<contours.size(); i++) 
{ 
cv::Mat sticker; 
//Для кожної області визначаємо обмежує прямокутник 
cv::Rect rect=cv::boundingRect(contours[i]); 

image(rect).copyTo(sticker); 

//Додати до масиву розпізнаних стікерів 
stickers.push_back(sticker); 
} 
} 

Після ступінчастого перетворення і розтягування області з використанням фільтра для об'єднання областей, розділених шумом (метод cv::dilate), отримаємо:


Рис.2. Зображення після бінаризації.

Відправимо бинаризованное зображення на вхід алгоритму виділення контурів сv::findConturs і для кожного контуру знайдемо обмежує прямокутник за допомогою cv::boundingRect. Для наочності намалюємо обмежують прямокутники зеленим кольором на вихідному зображенні.
В результаті виділення областей отримуємо успішно виділені стікери. Нижче результат роботи алгоритму на тестовому зображенні.


Рис.3. Стікери виділені.

Знаючи параметри обмежують прямокутників, легко вирізати і зберегти зображення стікерів на диск у вигляді окремих файлів:

for (uint i = 0;i < stickers.size();i++) {
cv::imwrite("sticker"+toString(i+1)+".jpg",stickers[i]);
}


В результаті, на диску буде сформовані файли sticker1.jpg… stickerN.jpg. Приклад вмісту файлу-стікера наведено нижче:


Рис.4. Зображення виділеного стікера.

Необхідно відзначити, що у наведеному вище прикладі ми не реалізували алгоритму визначення кольору стікера, а поставили його константами key_light, key_sat, key_hue в HSV-просторі, що в нормальних умовах недобре. І якщо раптом стікери будуть іншого кольору, алгоритм треба буде перенастроювати. Граничні прямокутники для областей розробників (синій, зелений, червоний) не виділені. Принципово можливо задати константами кольору, а для них вже виділити аналогічним алгоритмом, що дозволить автоматично визначати межі областей та визначати статус завдань.

Алгоритм 2. Скористаємося функцією cv::threshold, як показано в прикладах /1/ /3/. Для початку ми перевели вхідний кадр в HSV-формат за допомогою функції cv::cvtColor, а результат розбили з допомогою cv::split. Результат нижче:

Рис.5. H-канал зображення.

Рис.6. S-канал зображення.

Рис.7. V-канал зображення.
Як видно з рис.5 — рис.7, найбільший інтерес для обробки стікерів на білому тлі представляє S-канал зображення, де значення насичених кольором стікерів буде максимальним, а білого фону — мінімальним. Найбільш наочно це показано тут. Можна припустити, що використовуючи функцію cv::threshold з правильним значенням кордону, ми отримаємо бажане бінарне зображення з виділеними стікерами, з якого стікери можуть бути виділені за допомогою функції cv::findContours, аналогічно алгоритму 1.
std::vector<cv::Mat> hsvPlanes;
cv::split(inputHsvImage, hsvPlanes);
cv::Mat image = hsvPlanes[1];
double thresh = 110;
double maxValue = 255; 
threshold(image,image, thresh, maxValue, cv::THRESH_BINARY);

У наведеному прикладі значення межі в 110 приводить до бажаного результату бінаризації. Як і у випадку з алгоритмом 1, ми знову стикаємося з необхідністю підбирати значення межі, яке можна обчислити шляхом аналізу гістограми зображення.

Рис.8. Гістограма для S каналу зображення.
Так як стікери світлі, то кольорах стікера відповідає правий пік на гістограмі S-каналу. Визначивши його межі за допомогою алгоритму /4/, ми отримаємо шукане значення межі для ступінчастого перетворення.
int findMostRightExtremum(cv::Mat histNorm)
{
vector< float > data;
for (int i=0; i<histNorm.rows; i++)
data.push_back(histNorm.at<float>(i));

// Пошук екстремумів функції.
Persistence1D p;
p.RunPersistence(data);

// Отримати всі екстремуми більше 0,002.
vector< TPairedExtrema > Extrema;
p.GetPairedExtrema(Extrema, 0,002);

sort(Extrema.begin(),Extrema.end(),
[](const TPairedExtrema &a, const TPairedExtrema &b) -> bool
{
return (a.MaxIndex) > (b.MaxIndex);
}
);
// Використовуємо ліву межу самого правого екстремуму на графіку
return (Extrema[0].MinIndex)*(255/histNorm.rows);
}

В результаті бінаризації за допомогою функції cv::threshold отримаємо:


Рис.9. Бінаризація за допомогою cv::threshold для розрахованої межі.

Як можна побачити, виділені і стікери, і кольорові граничні маркери, що визначають границі областей розробників, що дозволить виділити зони дошки для кожного з розробників.
Це перша стаття з циклу статей про впровадження технології комп'ютерного зору в SCRUM-процес. Залишилися поза розгляду наступні завдання:
  • виділення зон дошки («зона необраних завдань», області «в процесі » і «виконано» для розробників");
  • визначення зони дошки, в якій знаходиться стікер;
  • зіставлення стікерів після переміщення на дошці;
  • розпізнавання тексту задачі;
  • взаємодія з jira.
До речі, скоро у нас намічається серія безкоштовних вебінарів, присвячених програмуванню на C++, з прикладами з області обробки зображень і доповненої реальності.

Джерела
  1. Basic Thresholding Operations. docs.opencv.org/master/db/d8e/tutorial_threshold.html
  2. Thresholding Operations using inRange docs.opencv.org/master/da/d97/tutorial_threshold_inRange.html
  3. akaifi.github.io/MultiObjectTrackingBasedhOnColor
  4. Алгоритм пошуку локальних екстремумів. people.mpi-inf.mpg.de/~weinkauf/notes/persistence1d.html
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.