Заглавие: Computer Vision: Борбата с шума или как да открием обект в реални условия (Част 2)
Въведение: Светът не е идеален (за един компютър)
В първата част на нашия проект се научихме как да заредим изображение и разбрахме, че за компютъра снимката е просто огромна таблица от числа, където всяко число казва колко светъл е даден пиксел. Но в реалния свят снимките рядко са "чисти". Има сенки, различно осветление и, в моя случай – пръсти, които държат обекта.
Днес ще се впуснем в дебрите на дигиталната обработка, за да научим компютъра да разпознава обекти. Нашата цел е от хаоса на цветовете да извлечем един единствен обект – банкнотата от 100 евро – и да кажем на програмата: „Ето това ни интересува, начертай рамка около него“.
Преди да започнем: За да работи кодът правилно, първо трябва да заредим снимката и да кажем на Python, че ще използваме стандартната цветова схема (RGB). Ето как започва нашият скрипт:
Код:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Зареждаме снимката
image = cv2.imread('euro_100.jpg')
# Конвертираме я в RGB, за да изглеждат цветовете естествено в Matplotlib
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
Надпис под снимката: Нашата изходна снимка – 100 евро, държани с пръсти на променлива светлина.
Стъпка 1: Дигиталната изолация чрез Thresholding
Thresholding е процес, при който казваме на компютъра: „Всичко по-светло от това става чисто бяло, а всичко по-тъмно – чисто черно“. Така изрязваме излишните цветове.
За да открием контур, първо трябва да кажем на компютъра какво е "обект" и какво е "фон". Най-лесният начин е да превърнем снимката в бинарна – това е изображение, в което съществуват само два цвята: чисто черно и чисто бяло (без сиво).
Използваме алгоритъма на Otsu – това е интелигентен метод, който позволява на компютъра сам да намери идеалната граница между бялото и черното. Вместо ние да нагласяме числата ръчно при всяка промяна на светлината, Otsu прави това автоматично.
За целта той анализира хистограмата на пикселите. Представете си хистограмата като графика (стълбчета), която показва колко тъмни и колко светли точки има в снимката. Otsu "разглежда" тази графика и намира точното място, където да "отреже" фона от обекта, така че банкнотата да изпъкне най-добре.
КОД:
# 1. Превръщаме снимката в сива скала
# Компютърът не се нуждае от цветове, за да разпознае контур.
# Превръщайки я в сиво, ние премахваме излишната информация.
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 2. Автоматично прагово отрязване (Otsu's Thresholding)
# Командата казва: „Всичко по-тъмно от определена граница става черно,
# а всичко по-светло – чисто бяло“. Otsu сам намира тази граница.
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Показваме резултата
plt.imshow(thresh, cmap='gray')
plt.title("Черно-бяла маска")
plt.axis('off')
plt.show()
В снимката се вижда, че банкнотата е бяла, но има хиляди малки бели точки (шум) и ръката ми е станала част от маската.
Стъпка 2: Изчистване на шума (Opening)
Тук нещата стават интересни. Имаме проблем: пръстите ми са „залепени“ за банкнотата. За да ги „отлепим“, използваме операцията Opening (Отваряне).
Как работи това? Тя действа в два етапа: първо изтънява всички бели форми, докато най-малките от тях (шумът) изчезнат напълно, а след това връща нормалния размер на онова, което е останало.
За да определим колко точно да „чистим“, използваме Kernel (Ядро). Това е малко квадратно прозорче (в случая 7x7 пиксела), с което компютърът проверява снимката. Ако даден бял детайл е по-малък от това прозорче, той се изтрива.
КОД:
# 1. Създаваме "ядро" (структурен елемент) - квадрат 7x7 пиксела
# Представете си ядрото като гумичка с определен размер.
# Всичко, което е по-малко от това квадратче (като шума и точките), ще бъде изтрито.
kernel = np.ones((7,7), np.uint8)
# 2. Прилагаме операцията Opening (Отваряне)
# Тази функция изпълнява два етапа: първо изтънява формите (за да изчезне шумът),
# а после ги връща към оригиналния им размер.
# С "iterations=2" казваме на компютъра да повтори процеса два пъти за по-сигурен резултат.
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# Показваме резултата
# Тук виждаме как пръстите се "отделиха" от банкнотата, но тя самата стана на дупки.
plt.imshow(opening, cmap='gray')
plt.title("След премахване на шума (Opening)")
plt.axis('off')
plt.show()
Виждате ли как банкнотата прилича на швейцарско сирене? Това е цената, която плащаме за премахването на шума.
Стъпка 3: Запълване на формата (Dilation)
След като почистихме малките точки в предишната стъпка, банкнотата остана надупчена и разкъсана. За да я очертаем правилно, ни трябва плътен обект без дупки. Тук на помощ идва операцията Dilation (Разширяване).
При нея казваме на всеки бял пиксел да се „разпъне“ и да запълни черните празнини около себе си. Това е процесът, с който „закърпваме“ формата. Резултатът е масивно бяло петно, което повтаря точните очертания на 100-те евро. Сега вече компютърът няма да се обърква от детайлите върху хартията, а ще вижда само един общ обект.
КОД:
# 1. Използваме малко по-голямо ядро за запълване
# Тук създаваме нов инструмент (5x5 пиксела), който ще ни помогне
# да "разтеглим" белите зони.
kernel_fill = np.ones((5,5), np.uint8)
# 2. Дилатация - разширяваме бялото, за да затворим дупките
# Командата "dilate" кара всеки бял пиксел да се разпъне и да заеме мястото
# на черните празнини около него. С "iterations=4" повтаряме това 4 пъти,
# докато всички малки дупчици изчезнат напълно.
dilated = cv2.dilate(opening, kernel_fill, iterations=4)
# Показваме резултата
# Сега банкнотата вече не е на дупки, а е едно голямо и плътно бяло петно.
plt.imshow(dilated, cmap='gray')
plt.title("Запълнена маска (Dilation)")
plt.axis('off')
plt.show()
Вече имаме масивна бяла зона. Тя е готова за следващия етап – извличането на координатите.
Стъпка 4: Финален резултат и изчертаване на контури
След като подготвихме терена с плътното бяло петно, е време за финалната стъпка. Използваме библиотеката OpenCV (това е „мозъкът“, който учи компютъра да вижда), за да открием контурите. Представете си контура като „невидима нишка“, която обхожда границата между черното и бялото.
Тъй като на снимката може да са останали други малки петна, ние използваме логика за филтриране:
- Караме програмата да открие абсолютно всички затворени форми на снимката.
- Сортираме ги по тяхната площ (размер).
- Избираме само най-голямата форма – в нашия случай това е банкнотата.
Накрая казваме на OpenCV да начертае тази „нишка“ директно върху оригиналната ни цветна снимка с яркозелен цвят.
КОД:
# 1. Намираме всички контури върху дилатираната маска
# Използваме "RETR_EXTERNAL", което казва на компютъра: „Гледай само най-външните очертания“.
# Така той няма да се разсейва от малки петънца, които може да са останали вътре в обекта.
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 2. Сортираме ги по площ и взимаме най-големия
# Понеже на снимката може да има и други малки обекти, ние казваме:
# „Сортирай всички открити форми по големина и ми дай най-голямата“.
# В нашия случай това със сигурност е банкнотата.
if len(contours) > 0:
main_contour = sorted(contours, key=cv2.contourArea, reverse=True)[0]
# 3. Рисуваме го върху ОРИГИНАЛНАТА снимка
# Правим копие на оригиналната цветна снимка и рисуваме върху нея.
# (0, 255, 0) е кодът за яркозелен цвят, а 10 е дебелината на линията.
final_result = image_rgb.copy()
cv2.drawContours(final_result, [main_contour], -1, (0, 255, 0), 10)
# Показваме финала
# Накрая извеждаме снимката на екрана, за да видим крайния успех.
plt.figure(figsize=(10, 8))
plt.imshow(final_result)
plt.title("ФИНАЛЕН РЕЗУЛТАТ")
plt.axis('off')
plt.show()
else:
# Ако компютърът не намери нищо, ще ни предупреди с това съобщение.
print("Не бяха намерени контури!")
Финалният резултат: Открит контур върху оригиналното изображение.
Въпреки че зелената рамка включва и част от пръстите ми (защото те се докосват до банкнотата и компютърът ги вижда като един общ обект), ние постигнахме целта си. Вече имаме точните координати на 100-те евро в пространството!
Заключение: Какво научихме днес?
Днес видяхме, че за компютъра „виждането“ не е просто снимане, а поредица от стъпки за изчистване на образа:
- Thresholding (Преобразуване): Направихме снимката черно-бяла, за да разделим обекта от фона.
- Morphology (Изчистване): Изтрихме малките боклуци и запълнихме дупките в банкнотата, за да стане плътна.
- Contour Detection (Очертаване): Намерихме границата на най-голямата форма и я оградихме със зелена нишка.
Въпреки че нашата зелена рамка включва и част от пръста ми (защото той се докосва до банкнотата и компютърът ги вижда като едно цяло), ние постигнахме най-важното – машината вече „знае“ точно къде се намира обектът.
Какво следва?
В следващата статия ще направим нещо магическо: ще изрежем тази наклонена банкнота и ще я „изправим“ така, че да изглежда все едно сме я сложили директно в скенер. Това се нарича Perspective Transform (Промяна на перспективата). Това е същата технология, която използват приложенията в телефона ви, когато снимате лист хартия и той автоматично става на перфектен документ!