Mastering OpenCV with Practical Computer Vision Projects / Опановуюємо OpenCV через практичні проекти комп'ютерного зору: Розділ 5. Розпізнавання номерних пластин з використанням SVM та нейронних мереж

← →
Английский оригинал Перевод на украинский

Сожалеем, но текст оригинала доступен только зарегистрированным пользователям.

У цьому розділі ми познайомимось з процесом створення застосунку автоматичного розпізнавання номерних пластин (ANPR). Існують різноманітні підходи та техніки, що підходять для певних ситуацій, наприклад, інфрачервоних камер, фіксованих позицій автомобілів, умов освітлення і так далі. Можна зробити так, аби застосунок ANPR розпізнавав номерні знаки на фотографіях, знятих за 2-3 метри відстані до машини, у звичайних умовах освітлення, з непаралельно розміщеною землею та мінімальними викривленнями перспективи на пластині.

Atronax 27.02.21 в 08:55

0

Найважливішою метою розділу є пояснення сегментації зображення та виокремлення особливих точок, основи розпізнавання шаблонів, а також два важливі алгоритми, метод опорних векторів та штучні нейронні мережі. Далі покриємо такі теми:
ANPR
Пошук пластини
Розпізнавання пластини
Основи ANPR

Atronax 27.02.21 в 09:07

0

Автоматичне розпізнавання номерних пластин (ANPR), також відоме як визначення дозвільних знаків (ALPR), ідентифікація транспорту (AVI) чи пошук автомобільних пластин (CPR), - це спосіб спостереження, в основі якого лежить оптичне розпізнавання символів (OCR) та інші методи, наприклад сегментація та виявлення, метою яких є зчитування пластин реєстрації транспорту.

Atronax 27.02.21 в 09:22

0

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

Atronax 27.02.21 в 09:57

0

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

Atronax 27.02.21 в 10:05

0

У цьому розділі ми не використовуємо інфрачервоні фотографії, винятково звичайні. Робимо це для того, щоб отримати не найкращі результати, а вищий рівень виявлення похибок розпізнавання та хибних появ, на відміну від результатів застосування інфрачервоної камери; однак, послідовність кроків для підходів однакова.

Atronax 27.02.21 в 10:31

0

Кожна країна має власні розміри номерних пластин та специфікації; щоб здобути кращі результати та знизити рівень похибок, варто розумітися на цьому. Алгоритми даного розділу мають пояснити основи ANPR та специфікації для номерних пластин Іспанії, але результати викладок можна поширити на будь-яку іншу країну.

Atronax 27.02.21 в 10:41

0

Далі працюватимемо з дозвільними пластинами Іспанії. У цій країні існує три різні розміри та форми номерних знаків; розглянемо найпоширеніші (великі), з розмірами 520x110 мм. Дві групи символів розділені вільним простором довжиною 41 мм, а між собою вони розміщені на відстані 14 мм. Перша частина включає 4 числа, а друга - три літери за винятком голосних A, E, I, O, U, а також Ñ і Q; усі символи мають розміри 45x77 мм.

Atronax 27.02.21 в 11:19

0

Ці дані важливі для процесу сегментації символів, адже ми маємо змогу перевірити символ разом із пустим простором, щоб впевнитися у тому, що ми знайшли саме символ, а не інший сегмент зображення. Далі зображено подібну пластину:

Atronax 27.02.21 в 11:40

0

Алгоритм ANPR

Atronax 27.02.21 в 11:40

0

До пояснення коду ANPR маємо визначити головні кроки та завдання алгоритму. Процес складається з двох головних кроків: пошуку та розпізнавання. Метою першого є виявлення положення пластини на всьому кадрі камери. Коли її знайдено, відповідний сегмент передається на другий крок (розпізнавання), який застосовує алгоритм OCR для визначення літер та цифр на пластині.

Atronax 27.02.21 в 11:51

0

На наступному малюнку бачимо два головні кроки алгоритму, виявлення та розпізнавання номерного знака. Після виконання цих кроків програма зображає виявлені символи пластини на поверхні кадрів. Ці алгоритми можуть повернути неякісні або взагалі жодних результатів.

Atronax 27.02.21 в 11:54

0

На кожному етапі з попереднього малюнка визначаємо три додаткові кроки, які зазвичай використовуються в алгоритмах розпізнавання шаблона:
1. Сегментація: виявляємо та виокремлюємо зони інтересу.
2. Пошук особливих точок: для кожного здобутого фрагмента формуємо набір потрібних характеристик.
3. Класифікація: знаходимо символи чи класифікуємо зображення на "пластини" та "не пластини".

Atronax 27.02.21 в 12:30

0

Далі зображено відповідні етапи розпізнавання шаблона:
Крім нюансів головного застосунку, метою якого є виявлення та розпізнавання номерної пластини ліцензії на керування автомобілем, коротко пояснимо дві задачі, про які зазвичай не розповідають:
Як натренувати систему розпізнавання шаблонів
Як оцінити таку систему

Atronax 27.02.21 в 12:36

0

Разом з тим, ці питання можуть виявитися значно важливішими за програму, адже якщо ми не натренуємо систему розпізнавання шаблона коректно, вона може не справитися із задачею; до того ж, різні шаблони потребують різних способів тренування та оцінки. Щоб досягнути кращих результатів, маємо протестувати наявну систему в інших середовищах та за відмінних умов, з інакшими наборами особливих точок. Іноді дві задачі об'єднуються в одну, адже ключові точки можуть продукувати різні результати, як побачимо на етапі оцінки.

Atronax 27.02.21 в 12:57

0

Виявлення пластини

Atronax 27.02.21 в 13:00

0

На цьому етапі маємо знайти всі пластини на поточному кадрі. Щоб виконати завдання, виділимо два кроки: сегментацію та класифікацію сегментів. Етап пошуку особливих точок не пояснений, адже ми використовуємо фрагмент зображення як відповідний вектор.

Atronax 27.02.21 в 13:03

0

На першому етапі (сегментація) застосуємо різні фільтри, морфологічні операції, алгоритми пошуку контурів та перевірки, щоб виокремити ті частини зображення, які можуть включати пластину.
На другому (класифікація), до кожного фрагмента застосуємо метод опорних векторів (SVM). Перед тим, як запрограмувати головний застосунок, тренуємо розпізнавання двох класів об'єктів - пластин та не пластин. Працюємо з паралельними кольоровими фронтальними зображеннями, які мають довжину 800 пікселів та зняті за 2-4 метри від автомобіля. Ці вимоги важливі для коректної сегментації. Ми зможемо виявити потрібні фрагменти, якщо створимо алгоритм, стійкий до зміни масштабу.

Atronax 27.02.21 в 13:11

0

Далі зображені всі процеси, які використовуються для розпізнавання пластин:
Фільтр Собеля
Операція порогу
Морфологічна операція закриття
Маска однієї заповненої зони
Можливі знайдені пластини відмічені червоним кольором (зобр. особливих точок)
Знайдені пластини після використання класифікатора SVM

Atronax 27.02.21 в 13:14

0

Сегментація

Atronax 27.02.21 в 13:14

0

Сегментація - це процес поділу зображення на декілька сегментів, призначений для спрощення аналізу та виокремлення особливих точок.
Важливою особливістю застосування цього процесу для пластини є величезна кількість вертикальних меж, якщо, звісно, маємо фронтальний знімок пластини без повороту чи викривлення перспективи. Це можна використати на першому кроці процесу, щоб видалити фрагменти, які не мають жодних вертикальних меж.

Atronax 27.02.21 в 13:22

0

До пошуку вертикальних меж маємо конвертувати кольорове зображення у відтінки сірого, бо колір не допоможе вирішити цю задачу, та видаляємо можливий шум, згенерований камерою. З цією метою застосуємо розмиття Гауса розмірності 5х5. Інакше отримаємо безліч вертикальних меж, які завадять розпізнаванню.
// конвертуємо зображення у відітінки сірого
Mat img_gray;
cvtColor(input, img_gray, CV_BGR2GRAY);
blur(img_gray, img_gray, Size(5,5));

Atronax 27.02.21 в 13:24

0

Далі використаємо фільтр Собеля та вирахуємо першу похідну по горизонталі. Це математична функція, яка дозволить знайти вертикальні межі на зображенні. Оголошення відповідної функції в бібліотеці OpenCV має такий вигляд:

Atronax 27.02.21 в 13:26

0

void Sobel(InputArray src, OutputArray dst, int ddepth, int xorder,
int yorder, int ksize=3, double scale=1, double delta=0, int
borderType=BORDER_DEFAULT )

Atronax 27.02.21 в 13:27

0

У цьому випадку ddepth відповідає глибині зображення, xorder - це порядок похідної по x, yorder - порядок похідної по y, ksize - розмір ядра, що рівний 1, 3, 5 або 7, scale - опціональний множник для знайдених значень, delta - опціональне значення, що додається до результату, borderType - метод інтерполяції пікселів.

Atronax 27.02.21 в 13:34

0

У нашому випадку використаємо такі значення: xorder = 1, yorder = 0, ksize = 3.
// Номерні пластини щільно заповнені вертикальними межами. Знайдемо їх:
Mat img_sobel;
Sobel(img_gray, img_sobel, CV_8U, 1, 0, 3, 1, 0);

Atronax 27.02.21 в 13:37

0

Після застосування фільтра Собеля, активуємо поріг зі значенням, визначеним за методом Отсу, щоб в результаті здобути необхідне бінарне зображення. Алгоритм для вхідного 8-бітного кадру автоматично визначає оптимальне значення порогу:

Atronax 27.02.21 в 13:39

0

//зображення із застосованим порогом
Mat img_threshold;
threshold(img_sobel, img_threshold, 0, 255,
CV_THRESH_OTSU+CV_THRESH_BINARY);
Щоб визначити метод Отсу для порогової функції, якщо об'єднаємо параметр типу зі значенням CV_THRESH_OTSU, вказане значення порогу буде проігноровано.

Atronax 27.02.21 в 13:44

0

Підказка
Якщо визначено параметр CV_THRESH_OTSU, функція threshold поверне оптимальне значення порогу згідно з алгоритмом Отсу.

Atronax 27.02.21 в 13:50

0

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

Atronax 27.02.21 в 13:56

0

Спершу опишемо структурний елемент, необхідний для використанння морфологічної операції. Скористаємось функцією getStructuringElement, щоб визначити прямокутний фрагмент з розмірами 17x3 пікселів; в інших випадках це значення може відрізнятися:
Mat element = getStructuringElement(MORPH_RECT, Size(17, 3));

Atronax 27.02.21 в 14:00

0

Та використаємо його для морфологічної операції закриття, шляхом виклику фукнції morphologyEx:
morphologyEx(img_threshold, img_threshold, CV_MOP_CLOSE, element);

Atronax 27.02.21 в 14:02

0

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

Atronax 27.02.21 в 14:14

0

// Знаходимо контури можливих пластин
vector< vector< Point> > contours;
findContours(img_threshold,
contours, // вектор контурів
CV_RETR_EXTERNAL, // отримуємо зовнішні контури
CV_CHAIN_APPROX_NONE); // усі пікселі кожного контуру

Atronax 27.02.21 в 14:16

0

Для кожного контура вирахуємо обмежувальний прямокутник мінімальної площі. Для цього бібліотека OpenCV постачає функцію minAreaRect. Вона знаходить об'єкт RotatedRect, що описує прямокутник з можливістю повороту. Потім, скориставшись ітератором для кожного вектора контура, проаналізуємо утворені фігури та зробимо попередні перевірки, перш ніж класифікувати фрагмент:

Atronax 27.02.21 в 14:24

0

//Починаємо ітерацію над кожним знайденим контуром
vector<vector<Point> >::iterator itc= contours.begin();
vector<RotatedRect> rects;
//Видаляємо фрагменти з необмеженою площею чи відношенням сторін.
while (itc!=contours.end()) {
//Формуємо обмежувальний прямокутник
RotatedRect mr= minAreaRect(Mat(*itc));
if( !verifySizes(mr)){
itc= contours.erase(itc);
}else{
++itc;
rects.push_back(mr);
}
}

Atronax 27.02.21 в 14:26

0

Далі підтверджуємо коректність знайдених зображень залежно від площі та відношення сторін. Вважаємо, що фрагмент відповідає пластині, якщо значення відношення близьке до 520/110 = 4.727272 (ширина поділена на висоту) з похибкою, що складає 40 відсотків, та площею прямокутника, що відповідає висоті від 15 до 125 пікселів. ЦІ параметри залежать від розмірів зображення та позиції камери:

Atronax 27.02.21 в 14:34

0

bool DetectRegions::verifySizes(RotatedRect candidate ){
float error=0.4;
//Розміри пластини в Іспанії: 52x11. Відношення сторін: 4,7272
const float aspect=4.7272;
//Встановлюємо мінімальну та максимальну площі. Інші фрагменти відкидаються.
int min= 15*aspect*15; // мінімальна площа
int max= 125*aspect*125; // максимальна площа
//Отримаємо лише фрагменти, що відповідають значенню відношення.
float rmin= aspect-aspect*error;
float rmax= aspect+aspect*error;
int area= candidate.size.height * candidate.size.width;
float r= (float)candidate.size.width / (float)candidate.size.height;
if(r<1)
r= 1/r;
if(( area < min || area > max ) || ( r < rmin || r > rmax )){
return false;
}else{
return true;
}
}

Atronax 27.02.21 в 14:37

0

Можна вдосконалити алгоритм, скориставшись властивістю білого фону пластини. Усі вони мають однаковий колір фону, тому можна використати заповнення кольором, щоб отримати більш точний обмежувальний прямокутник.
На першому кроці маємо отримати декілька вихідних точок поблизу центральної. Далі знаходимо мінімальне значення серед ширини та висоти, яке використаємо далі, для пошуку випадкових позицій навколо центра фрагменту.

Atronax 27.02.21 в 14:46

0

Ми бажаємо виділити білий фрагмент, для цього потрібно, щоб серед декількох вихідних точок був як мінімум один піксель потрібного кольору. Щоб сформувати зображення маски для зберігання найближчого регіону для виокремлення, скористаємося функцією floodFill:

Atronax 27.02.21 в 14:51

0

for(int i=0; i< rects.size(); i++){
//Щоб знайти найкращий прямокутник для кожного фрагмента
//Застосуємо алгоритм floodfill, бо пластина має білий фон
//А потім отримаємо більш чіткі контури
circle(result, rects[i].center, 3, Scalar(0,255,0), -1);
//шукаємо мінімальне значення серед ширини та висоти
float minSize=(rects[i].size.width < rects[i].size.height)?
rects[i].size.width:rects[i].size.height;
minSize=minSize-minSize*0.5;
//налаштовуємо rand та отримуємо 5 точок навколо центра алгоритма floodfill
srand ( time(NULL) );

Atronax 27.02.21 в 14:54

0

//налаштовуємо параметри floodfill та інші змінні
Mat mask;
mask.create(input.rows + 2, input.cols + 2, CV_8UC1);
mask= Scalar::all(0);
int loDiff = 30;
int upDiff = 30;
int connectivity = 4;
int newMaskVal = 255;
int NumSeeds = 10;
Rect ccomp;
int flags = connectivity + (newMaskVal << 8 ) +
CV_FLOODFILL_FIXED_RANGE + CV_FLOODFILL_MASK_ONLY;
for(int j=0; j<NumSeeds; j++){
Point seed;
seed.x=rects[i].center.x+rand()%(int)minSize-(minSize/2);
seed.y=rects[i].center.y+rand()%(int)minSize-(minSize/2);
circle(result, seed, 1, Scalar(0,255,255), -1);
int area = floodFill(input, mask, seed, Scalar(255,0,0), &ccomp,
Scalar(loDiff, loDiff, loDiff), Scalar(upDiff, upDiff, upDiff),
flags);
}

Atronax 27.02.21 в 14:55

0

Починаючи з вихідної точки, функція floodFill заповнює об'єднані частини зображення певним кольором, зберігає результат у вигляді маски, а також встановлює нижні й верхні межі різниці між яскравістю / кольором пікселя для заповнення та його сусідами чи вихідною точкою:

Atronax 27.02.21 в 15:01

0

int floodFill(InputOutputArray image, InputOutputArray mask, Point
seed, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar
upDiff=Scalar(), int flags=4 )

Atronax 27.02.21 в 15:03

0

Параметр newVal визначає новий колір, яким ми бажаємо заповнити визначені фрагменти зображення. Значення loDiff та upDiff позначають максимальні нижні та верхні межі різниці між яскравістю / кольором пікселя для заповнення та його сусідами чи вихідною точкоою.
Флаг є комбінацією таких значень:

Atronax 27.02.21 в 15:06

0

Молодші біти - зберігають значення спорідненості, 4 (за замовчуванням) або 8, використувається функцією. Спорідненість визначає, які сусіди пікселя враховані.
Старші біти - нуль або комбінація наступних значень:
CV_FLOODFILL_FIXED_RANGE та CV_FLOODFILL_MASK_ONLY.
CV_FLOODFILL_FIXED_RANGE встановлює різницю між поточним та вихідним пікселями.
CV_FLOODFILL_MASK_ONLY лише заповнить маску і не змінить власне зображення.

Atronax 27.02.21 в 15:10

0

Отримавши маску відсічення, на основі маски знайдемо прямокутник мінімальної площі та перевіримо коректність розмірів. Для кожного з них шукаємо положення білого пікселя та використовуємо функцію minAreaRect, щоб виокремити найближчий потрібний фрагмент:

Atronax 27.02.21 в 15:17

0

//Перевіряємо нову маску заповнення на коректність.
//Отримаємо всі знайдені точки мінімального прямокутника
vector<Point> pointsInterest;
Mat_<uchar>::iterator itMask= mask.begin<uchar>();
Mat_<uchar>::iterator end= mask.end<uchar>();
for( ; itMask!=end; ++itMask)
if(*itMask==255)
pointsInterest.push_back(itMask.pos());
RotatedRect minRect = minAreaRect(pointsInterest);
if(verifySizes(minRect)){
…

Atronax 27.02.21 в 15:20

0

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

Atronax 27.02.21 в 15:27

0

Для початку маємо згенерувати матрицю перетворень за допомогою функції getRotationMatrix2D, щоб видалити можливі повороти. Особливу увагу слід приділити висоті, адже клас RotatedRect здатний повертати аналогічні прямокутники, але з поворотом 90 градусів. Варто перевірити відношення сторін. Якщо його значення менше одиниці, здійснюємо поворот:

Atronax 27.02.21 в 15:33

0
Минутку...

Следующая страница →