|
|
<!--
|
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
|
{
|
|
|
"original_hash": "9c4320311c0f2c1884a6a21265d98a51",
|
|
|
"translation_date": "2025-08-28T10:51:17+00:00",
|
|
|
"source_file": "5-retail/lessons/2-check-stock-device/single-board-computer-count-stock.md",
|
|
|
"language_code": "bg"
|
|
|
}
|
|
|
-->
|
|
|
# Бройте наличности от вашето IoT устройство - Виртуален IoT хардуер и Raspberry Pi
|
|
|
|
|
|
Комбинацията от предсказанията и техните ограничителни кутии може да се използва за преброяване на наличности в изображение.
|
|
|
|
|
|
## Показване на ограничителни кутии
|
|
|
|
|
|
Като полезна стъпка за отстраняване на грешки, можете не само да отпечатате ограничителните кутии, но и да ги нарисувате върху изображението, което е записано на диска, когато е заснето изображение.
|
|
|
|
|
|
### Задача - отпечатайте ограничителните кутии
|
|
|
|
|
|
1. Уверете се, че проектът `stock-counter` е отворен в VS Code и виртуалната среда е активирана, ако използвате виртуално IoT устройство.
|
|
|
|
|
|
1. Променете израза `print` в цикъла `for` на следното, за да отпечатате ограничителните кутии в конзолата:
|
|
|
|
|
|
```python
|
|
|
print(f'{prediction.tag_name}:\t{prediction.probability * 100:.2f}%\t{prediction.bounding_box}')
|
|
|
```
|
|
|
|
|
|
1. Стартирайте приложението с камерата, насочена към някакви наличности на рафт. Ограничителните кутии ще бъдат отпечатани в конзолата с леви, горни, широчинни и височинни стойности от 0 до 1.
|
|
|
|
|
|
```output
|
|
|
pi@raspberrypi:~/stock-counter $ python3 app.py
|
|
|
tomato paste: 33.42% {'additional_properties': {}, 'left': 0.3455171, 'top': 0.09916268, 'width': 0.14175442, 'height': 0.29405564}
|
|
|
tomato paste: 34.41% {'additional_properties': {}, 'left': 0.48283678, 'top': 0.10242918, 'width': 0.11782813, 'height': 0.27467814}
|
|
|
tomato paste: 31.25% {'additional_properties': {}, 'left': 0.4923783, 'top': 0.35007596, 'width': 0.13668466, 'height': 0.28304994}
|
|
|
tomato paste: 31.05% {'additional_properties': {}, 'left': 0.36416405, 'top': 0.37494493, 'width': 0.14024884, 'height': 0.26880276}
|
|
|
```
|
|
|
|
|
|
### Задача - нарисувайте ограничителни кутии върху изображението
|
|
|
|
|
|
1. Пакетът Pip [Pillow](https://pypi.org/project/Pillow/) може да се използва за рисуване върху изображения. Инсталирайте го със следната команда:
|
|
|
|
|
|
```sh
|
|
|
pip3 install pillow
|
|
|
```
|
|
|
|
|
|
Ако използвате виртуално IoT устройство, уверете се, че сте изпълнили тази команда в активираната виртуална среда.
|
|
|
|
|
|
1. Добавете следния израз за импортиране в началото на файла `app.py`:
|
|
|
|
|
|
```python
|
|
|
from PIL import Image, ImageDraw, ImageColor
|
|
|
```
|
|
|
|
|
|
Това импортира код, необходим за редактиране на изображението.
|
|
|
|
|
|
1. Добавете следния код в края на файла `app.py`:
|
|
|
|
|
|
```python
|
|
|
with Image.open('image.jpg') as im:
|
|
|
draw = ImageDraw.Draw(im)
|
|
|
|
|
|
for prediction in predictions:
|
|
|
scale_left = prediction.bounding_box.left
|
|
|
scale_top = prediction.bounding_box.top
|
|
|
scale_right = prediction.bounding_box.left + prediction.bounding_box.width
|
|
|
scale_bottom = prediction.bounding_box.top + prediction.bounding_box.height
|
|
|
|
|
|
left = scale_left * im.width
|
|
|
top = scale_top * im.height
|
|
|
right = scale_right * im.width
|
|
|
bottom = scale_bottom * im.height
|
|
|
|
|
|
draw.rectangle([left, top, right, bottom], outline=ImageColor.getrgb('red'), width=2)
|
|
|
|
|
|
im.save('image.jpg')
|
|
|
```
|
|
|
|
|
|
Този код отваря изображението, което беше записано по-рано, за редактиране. След това преминава през предсказанията, извличайки ограничителните кутии, и изчислява долния десен координат, използвайки стойностите на ограничителните кутии от 0 до 1. Те след това се преобразуват в координати на изображението, като се умножават по съответното измерение на изображението. Например, ако стойността за ляво е 0.5 на изображение с широчина 600 пиксела, това ще се преобразува в 300 (0.5 x 600 = 300).
|
|
|
|
|
|
Всяка ограничителна кутия се рисува върху изображението с червена линия. Накрая редактираното изображение се записва, като се презаписва оригиналното изображение.
|
|
|
|
|
|
1. Стартирайте приложението с камерата, насочена към някакви наличности на рафт. Ще видите файла `image.jpg` в изследователя на VS Code и ще можете да го изберете, за да видите ограничителните кутии.
|
|
|
|
|
|

|
|
|
|
|
|
## Бройте наличности
|
|
|
|
|
|
На показаното по-горе изображение ограничителните кутии имат малко припокриване. Ако това припокриване беше много по-голямо, тогава ограничителните кутии може да показват един и същ обект. За да преброите обектите правилно, трябва да игнорирате кутиите със значително припокриване.
|
|
|
|
|
|
### Задача - бройте наличности, игнорирайки припокриването
|
|
|
|
|
|
1. Пакетът Pip [Shapely](https://pypi.org/project/Shapely/) може да се използва за изчисляване на пресечната точка. Ако използвате Raspberry Pi, първо ще трябва да инсталирате библиотечна зависимост:
|
|
|
|
|
|
```sh
|
|
|
sudo apt install libgeos-dev
|
|
|
```
|
|
|
|
|
|
1. Инсталирайте пакета Shapely:
|
|
|
|
|
|
```sh
|
|
|
pip3 install shapely
|
|
|
```
|
|
|
|
|
|
Ако използвате виртуално IoT устройство, уверете се, че сте изпълнили тази команда в активираната виртуална среда.
|
|
|
|
|
|
1. Добавете следния израз за импортиране в началото на файла `app.py`:
|
|
|
|
|
|
```python
|
|
|
from shapely.geometry import Polygon
|
|
|
```
|
|
|
|
|
|
Това импортира код, необходим за създаване на полигони за изчисляване на припокриването.
|
|
|
|
|
|
1. Над кода, който рисува ограничителните кутии, добавете следния код:
|
|
|
|
|
|
```python
|
|
|
overlap_threshold = 0.20
|
|
|
```
|
|
|
|
|
|
Това определя процента на припокриване, който е допустим, преди ограничителните кутии да се считат за един и същ обект. 0.20 определя 20% припокриване.
|
|
|
|
|
|
1. За да изчислите припокриването с помощта на Shapely, ограничителните кутии трябва да бъдат преобразувани в полигони на Shapely. Добавете следната функция, за да направите това:
|
|
|
|
|
|
```python
|
|
|
def create_polygon(prediction):
|
|
|
scale_left = prediction.bounding_box.left
|
|
|
scale_top = prediction.bounding_box.top
|
|
|
scale_right = prediction.bounding_box.left + prediction.bounding_box.width
|
|
|
scale_bottom = prediction.bounding_box.top + prediction.bounding_box.height
|
|
|
|
|
|
return Polygon([(scale_left, scale_top), (scale_right, scale_top), (scale_right, scale_bottom), (scale_left, scale_bottom)])
|
|
|
```
|
|
|
|
|
|
Това създава полигон, използвайки ограничителната кутия на предсказание.
|
|
|
|
|
|
1. Логиката за премахване на припокриващи се обекти включва сравняване на всички ограничителни кутии и ако някои двойки предсказания имат ограничителни кутии, които се припокриват повече от прага, изтриване на едно от предсказанията. За да сравните всички предсказания, сравнявате предсказание 1 с 2, 3, 4 и т.н., след това 2 с 3, 4 и т.н. Следният код прави това:
|
|
|
|
|
|
```python
|
|
|
to_delete = []
|
|
|
|
|
|
for i in range(0, len(predictions)):
|
|
|
polygon_1 = create_polygon(predictions[i])
|
|
|
|
|
|
for j in range(i+1, len(predictions)):
|
|
|
polygon_2 = create_polygon(predictions[j])
|
|
|
overlap = polygon_1.intersection(polygon_2).area
|
|
|
|
|
|
smallest_area = min(polygon_1.area, polygon_2.area)
|
|
|
|
|
|
if overlap > (overlap_threshold * smallest_area):
|
|
|
to_delete.append(predictions[i])
|
|
|
break
|
|
|
|
|
|
for d in to_delete:
|
|
|
predictions.remove(d)
|
|
|
|
|
|
print(f'Counted {len(predictions)} stock items')
|
|
|
```
|
|
|
|
|
|
Припокриването се изчислява с помощта на метода `Polygon.intersection` на Shapely, който връща полигон с припокриването. Площта след това се изчислява от този полигон. Този праг на припокриване не е абсолютна стойност, а трябва да бъде процент от ограничителната кутия, така че се намира най-малката ограничителна кутия и прагът на припокриване се използва за изчисляване на площта, която припокриването може да има, за да не надвишава прага на процентното припокриване на най-малката ограничителна кутия. Ако припокриването надвишава това, предсказанието се маркира за изтриване.
|
|
|
|
|
|
След като предсказанието е маркирано за изтриване, то не е необходимо да се проверява отново, така че вътрешният цикъл прекъсва, за да провери следващото предсказание. Не можете да изтривате елементи от списък, докато го обхождате, така че ограничителните кутии, които се припокриват повече от прага, се добавят към списъка `to_delete`, след което се изтриват накрая.
|
|
|
|
|
|
Накрая броят на наличностите се отпечатва в конзолата. Това след това може да бъде изпратено към IoT услуга, за да се сигнализира, ако нивата на наличностите са ниски. Целият този код е преди ограничителните кутии да бъдат нарисувани, така че ще видите предсказанията за наличности без припокривания върху генерираните изображения.
|
|
|
|
|
|
> 💁 Това е много опростен начин за премахване на припокривания, като просто се премахва първото в припокриваща се двойка. За производствен код бихте искали да добавите повече логика тук, като например разглеждане на припокриванията между множество обекти или ако една ограничителна кутия е съдържана в друга.
|
|
|
|
|
|
1. Стартирайте приложението с камерата, насочена към някакви наличности на рафт. Резултатът ще покаже броя на ограничителните кутии без припокривания, които надвишават прага. Опитайте да промените стойността на `overlap_threshold`, за да видите как предсказанията се игнорират.
|
|
|
|
|
|
> 💁 Можете да намерите този код в папката [code-count/pi](../../../../../5-retail/lessons/2-check-stock-device/code-count/pi) или [code-count/virtual-iot-device](../../../../../5-retail/lessons/2-check-stock-device/code-count/virtual-iot-device).
|
|
|
|
|
|
😀 Вашата програма за броене на наличности беше успешна!
|
|
|
|
|
|
---
|
|
|
|
|
|
**Отказ от отговорност**:
|
|
|
Този документ е преведен с помощта на AI услуга за превод [Co-op Translator](https://github.com/Azure/co-op-translator). Въпреки че се стремим към точност, моля, имайте предвид, че автоматизираните преводи може да съдържат грешки или неточности. Оригиналният документ на неговия роден език трябва да се счита за авторитетен източник. За критична информация се препоръчва професионален човешки превод. Ние не носим отговорност за недоразумения или погрешни интерпретации, произтичащи от използването на този превод. |