Software pro stanovení hmotnosti zvířete
-
Název výstupu:Software pro stanovení hmotnosti zvířete
-
Popis výstupu:Software automaticky určí průměrnou hmotnost jednotlivých zvířat (výkrmový brojleři) vážených ve vyhrazené části chovné haly na základě získaných obrazových dat. Využívá principů strojového vidění a učení
-
Klíčová slova:Zemědělství 4.0; precizní zemědělství; strojové vidění; chov drůbeže
-
Dosažení výstupu:09/2024
-
Autoři:
Petr Bartoš, Roman Bumbálek, Radim Kuneš, Luboš Smutný, Jean de Dieu Marcel Ufitikirezi, Tomáš Zoubek
-
Vlastníci:Jihočeská univerzita v Českých Budějovicích, IČ: 60076658, podíl na výsledku: 50 %.
AGROSOFT Tábor, s.r.o., IČ: 25169165, podíl na výsledku: 50 %. -
Kód důvěrnosti:C = podléhá obchodnímu tajemství
Popis aplikace

Obrázek 1: Stanovení hmotnosti brojlerů na základě plochy jejich těla v obrazu
Popis použitého programovacího jazyka a doplňujících balíků
Pro vytvoření softwaru k systému analýzy obrazu umožňující automatizované hodnocení welfare chovu byl využit interpretovaný programovací jazyk Python 3.9 společně s následujícími knihovnami, které byly využity pro své specifické vlastnosti a funkce potřebné k vytvoření výsledného softwaru:
- numpy 1.23.5
- torch 1.13.1
- cv2 1.0.0
- datetime 4.9
- multiprocessing 2.6.2.1
- psycopg2 2.9.5
- matplotlib 3.6.3
Balík NumPy je základním nástrojem pro provádění vědeckých výpočtů v jazyce Python. Mezi jeho hlavní funkcí je poskytování možností využití N-rozměrných polí, sofistikovaných funkcí, funkce lineární algebry, Fourierovy transformace a náhodných čísel.
Balík PyTorch umožňuje implementaci metod hlubokého učení v rámci Pythonu včetně výpočtů na bázi tenzorů poskytujících silnou akceleraci na GPU.
OpenCV je knihovna s otevřeným zdrojovým kódem, která obsahuje široké spektrum konvenčních algoritmů zpracování obrazu.
Balík DateTime usnadňuje práci s časovými formáty a umožňuje využití speciálních funkcí pro jejich úpravu a zpracování.
Knihovna Multiprocessing podporuje paralelní souběh procesů pomocí API podobného modulu Threading. Je schopen místo vláken využívat podprocesy, čímž je schopen umožnit lokální i vzdálenou souběžnost procesů.
Psycopg je implementací nejpoužívanějšího, spolehlivého a funkčně bohatého adaptéru PostgreSQL pro Python.
Matplotlib je komplexní knihovna pro vytváření statických, animovaných a interaktivních vizualizací v jazyce Python
Popis vytrénovaného modelu
Pro vytrénování modelu sloužícího ke stanovení hmotnosti zvířete byla využita architektura konvoluční neuronové sítě YOLO. V rámci trénovacího procesu bylo nutné optimalizovat hyperparametry Learning rate, Batch size a Optimizér.
Learning rate (LR) je hyperparametr optimizéru, který určuje velikost změny v každé iteraci, tj. jak rychle se posunujeme ve směru lokálního optima. Z praktického pohledu na kvalitu nalezených výsledků, příliš velké LR způsobí tzv. overfitting, tj. model se příliš rychle specializuje na nalezené lokální optimum, a nedosáhne optima globálního. Což vede v konečném důsledku k horším dosaženým výsledkům. Příliš malé LR naopak vede k velmi pomalému procesu trénování, tedy v přiděleném čase nemusí konvergovat do optima.
Batch size (BS) je velikost mini batch v každém kroku trénovaní. BS ovlivňuje trénování následujícím způsobem. Větší BS znamená vetší nároky na paměť GPU během trénování, vede k lineárnímu zrychlení trénování a dále velikost BS ovlivňuje stabilitu konvergence, a tedy nalezeného řešení.
Optimizér je z obecného pohledu postup, který říká, jak se v modelu při backpropagaci mají upravit váhy. Z praktického hlediska výběr vhodného optimizéru ovlivňuje jak kvalitu výsledného nalezeného řešení (konvergenci), tak dobu trénovaní.
Na základě porovnání vybraných metrik (Precision, Recall, mAP_0.5 a mAP_0.5:0.95) došlo k nalezení ideálních nastavení výše zmíněných hyperparametrů na hodnoty uvedené v tabulce 1.
Tabulka 1: Zjištěné optimální hodnoty hyperparametrů použitých pro vytrénování modelu
Hyperparametr | Získaná hodnota |
---|---|
Learning rate | 0,01 |
Batch size | 48 |
Optimizér | SGD |
Schopnost klasifikace vytrénovaného modelu je znázorněna pomocí matice záměn predikcí vyobrazené na obrázku 2. Třída chicken_full byla správně klasifikována v 99 % případů, v 11 případech byla určena jako třída chicken_part, ve 2 případech byla zaměněna za třídu chicken_active a v 6 případech nebyla modelem zaznamenána vůbec. U třídy chicken_part byla přesnost klasifikace 97 %, zaměněna byla s třídou chicken_full v 10 případech predikcí a celkem 8krát nebyla modelem detekována. Poslední třída chicken_activa byla modelem predikována s přesností 92 %, v jednom případě nebyla detekována. Z hlediska účelu využití aplikace je největším problémem záměna tříd chicken_part a chicken_activity za třídu chicken_full, u níž jako u jediné dochází k odhadování hmotnosti. K takové záměně došlo v 10 případech u třídy chicken_part.
Na obrázku 3 je představena predikce polygonálních masek sledovaných objektů vytrénovaným modelem CNN s architekturou YOLO, který je schopen identifikované obrysy klasifikovat na třídy chicken_full, chicken_part a chicken_activity.

Obrázek 3: Matice záměn predikcí pro vytrénovaný model CNN s architekturou YOLO

Obrázek 3: Predikce polygonální masek sledovaných objektů vytrénovaným modelem
Popis regresní analýzy sloužící k určení vztahu mezi plochou sledovaných objektů v obraze a jejich reálnou hmotností
Po vytrénování detekčního modelu s implementovanou segmentací instancí, který umožňuje nejen jednotlivé objekty lokalizovat a určit jejich třídu, ale také přesně vymezit jejich krajní/obvodovou křivku, byly pořízené snímky spárovány s naměřenými reálnými hmotnostmi, kdy každému sledovanému kusu byla přiřazena jeho reálná hmotnost. Na základě těchto dat byla provedena regresní analýza pro zjištění vztahu mezi plochou sledovaných objektů obraze a jejich reálnou hmotností, přičemž bylo aplikováno šest regresních funkcí, viz obrázek 4, a to regrese lineární, kvadratická, polynomiální třetího řádu, polynomiální čtvrtého řádu, polynomiální pátého řádu a exponenciální, kdy se jako optimální jevila polynomiální funkce 5. řádu, která vůči datasetu vykazovala index determinace 0,97489. Rovnice této funkce dále použita pro stanovení hmotnosti jednotlivých kusů drůbeže na základě plochy jejich těla v obrazu.

Obrázek 4: Výstupy regresní analýzy
Doporučené hardwarové požadavky na přetrénování modelu
Pro nasazení v rozdílných stájových prostředích je doporučen nejprve testovací provoz spojený se sběrem dat, která jsou následně použita k přetrénování původního modelu, čímž dojde k zpřesnění predikce definovaných tříd. Samotné přetrénování modelu pro konkrétní podmínky provozu probíhá na výkonném GPU serveru, pro následnou aplikaci SW v reálném provozu zahrnující provedení inferencí vedoucí k získání detekcí požadovaných objektů již není potřeba tak velké množství výpočetního výkonu. Pro analýzu obrazu z kamery umístěné nad oblastí vyhrazené k vážení drůbeže lze doporučit výpočetní stanici obsahující GPU s níže uvedenými hardwarovými parametry.
Tabulka 2: Doporučené minimální hardwarové parametry GPU
Parametr | Hodnota |
---|---|
Velikost paměti | 6 GB |
Typ pamětí | GDDR6/HBM |
Propustnost paměti | 360 GB/s |
Počet CUDA jader | 3 500 |
Teoretický výkon v FP32 | 12 TFLOPS |
Příloha
Přílohou dokumentace softwarového řešení je také zdrojový kód. Z důvodu ochrany know-how řešitelů jsou uvedeny pouze určité pasáže.
def result2anot(txtfile):
anot = open(txtfile, 'r').readlines()
bboxes = []
pols = []
cats = []
areas = []
points = []
w, h = 1920, 1080
mask = np.zeros((h, w))
for k in range(0, len(anot)):
coords = [float(num) for num in anot[k].split(' ') if num != '']
c = int(coords[0])
pol0 = coords[1:]
x = np.array(pol0[::2], dtype=float) * w
y = np.array(pol0[1::2], dtype=float) * h
bbox = [int(np.min(x)), int(np.min(y)), int(np.max(x)), int(np.max(y))]
pol = np.array((x, y), dtype=int).transpose().reshape(1, 2 * len(x))
points0 = np.reshape(pol, (round(pol.shape[1] / 2), 2))
points0 = np.reshape(points0, (-1, 1, 2))
cv2.fillPoly(mask, [points0], 255)
points.append(points0)
area = cv2.contourArea(points0)
bboxes.append(bbox)
pols.append(pol.tolist())
cats.append(c)
areas.append(area) #plocha všech anotací v obrázku
return bboxes, pols, cats, areas, points, mask
full_chicken = 0
for i in os.listdir(impath):
if i.endswith('jpg'):
img = cv2.imread(f'{impath}/{i}')
h, w, _ = img.shape
txt = f'{impath}/detect/labels/{os.path.splitext(i) [0]}.txt'
bboxes, pols, cats, areas, points, mask = result2anot(txt)
mask1 = np.zeros((1080, 1920, 3), np.uint8)
mask1[mask>0] = img[mask>0]
for n, j in enumerate(bboxes):
if cats[n] != full_chicken:
cv2.line(mask1, (j[0] + int(0.25*(j[2]-j[0])), j[1] + int(0.25*(j[3]-j[1]))), (j[2] - int(0.25*(j[2]-j[0])), j[3] - int(0.25*(j[3]-j[1]))), color=(0,0,255), thickness=5)
cv2.line(mask1, (j[0] + int(0.25*(j[2]-j[0])), j[3] - int(0.25*(j[3]-j[1]))), (j[2] - int(0.25*(j[2]-j[0])), j[1] + int(0.25*(j[3]-j[1]))), color=(0,0,255), thickness=5)
cv2.polylines(mask1, [points[n]], True, color=cols[cats[n]], thickness=3)
else:
w = calcweight(areas[n])
cv2.polylines(mask1, [points[n]], True, color=cols[cats[n]], thickness=3)
cv2.putText(mask1, f'{w} g', org=(j[0] + int(0.3*(j[2]-j[0])), j[1] + int(0.5*(j[3]-j[1]))), fontFace=cv2.FONT_HERSHEY_SIMPLEX,color=cols[cats[n]], thickness=2, fontScale=1.2, lineType=cv2.LINE_AA)
cv2.imwrite(f'{impath}/detect/weights/{i}', mask1)