Prolog
W Internecie krąży teraz wideo pokazujące, jak autopilot Tesli widzi drogę.
Już od dawna nosiłem się z zamiarem transmisji wideo wzbogaconego o detektor i to w czasie rzeczywistym.
Problem w tym, że chcę transmitować wideo z Raspberry, a działanie detektora sieci neuronowych na nim pozostawia wiele do życzenia.
Kij komputerowy Intel Neural Computer
Rozważałem różne rozwiązania.
В
Mimo że Intel zapewnia konwertery dla głównych platform, istnieje wiele pułapek.
Na przykład format wymaganej sieci może być niekompatybilny, a jeśli jest kompatybilny, niektóre warstwy mogą nie być obsługiwane na urządzeniu, a jeśli są obsługiwane, mogą wystąpić błędy podczas procesu konwersji, w wyniku czego na wyjściu otrzymujemy dziwne rzeczy.
Ogólnie rzecz biorąc, jeśli potrzebujesz dowolnej sieci neuronowej, może ona nie działać z NCS. Dlatego postanowiłem spróbować rozwiązać problem za pomocą najbardziej powszechnych i dostępnych narzędzi.
Chmura
Oczywistą alternatywą dla lokalnego rozwiązania sprzętowego jest przejście do chmury.
Gotowe opcje - moje oczy szaleją.
Wszyscy liderzy:
...I dziesiątki mniej znanych.
Wybór spośród tej odmiany wcale nie jest łatwy.
I zdecydowałem się nie wybierać, ale owinąć stary dobry działający schemat na OpenCV w Dockerze i uruchomić go w chmurze.
Zaletą takiego podejścia jest elastyczność i kontrola - możesz zmieniać sieć neuronową, hosting, serwer - w sumie dowolnie.
Server
Zacznijmy od lokalnego prototypu.
Tradycyjnie korzystam z Flaska dla REST API, OpenCV i sieci MobileSSD.
Po zainstalowaniu aktualnych wersji na Dockerze odkryłem, że OpenCV 4.1.2 nie działa z Mobile SSD v1_coco_2018_01_28 i musiałem wrócić do sprawdzonej wersji 11/06_2017.
Na starcie usługi ładujemy nazwy klas i sieć:
def init():
tf_labels.initLabels(dnn_conf.DNN_LABELS_PATH)
return cv.dnn.readNetFromTensorflow(dnn_conf.DNN_PATH, dnn_conf.DNN_TXT_PATH)
Na lokalnym oknie dokowanym (na niezbyt młodym laptopie) zajmuje to 0.3 sekundy, na Raspberry - 3.5.
Zacznijmy obliczenia:
def inference(img):
net.setInput(cv.dnn.blobFromImage(img, 1.0/127.5, (300, 300), (127.5, 127.5, 127.5), swapRB=True, crop=False))
return net.forward()
Docker – 0.2 sek., Malinowy – 1.7.
Zamiana wydechu tensora w czytelny json:
def build_detection(data, thr, rows, cols):
ret = []
for detection in data[0,0,:,:]:
score = float(detection[2])
if score > thr:
cls = int(detection[1])
a = {"class" : cls, "name" : tf_labels.getLabel(cls), "score" : score}
a["x"] = int(detection[3] * cols)
a["y"] = int(detection[4] * rows)
a["w"] = int(detection[5] * cols ) - a["x"]
a["h"] = int(detection[6] * rows) - a["y"]
ret.append(a)
return ret
Dalej
Alternatywna opcja, w której więcej pracy zostaje przerzuconych na serwer: on sam okrąża znalezione obiekty i zwraca gotowy obraz.
Ta opcja jest dobra, gdy nie chcemy przeciągać opencv na serwer.
Doker
Zbieramy obraz.
Kod jest przeczesywany i publikowany dalej
Jako platformę przyjmiemy ten sam Debian Stretch, co na Raspberry – nie odejdziemy od sprawdzonego stosu technologicznego.
Musisz zainstalować flask, protobuf, żądania, opencv_python, pobrać Mobile SSD, kod serwera z Github i uruchomić serwer.
FROM python:3.7-stretch
RUN pip3 install flask
RUN pip3 install protobuf
RUN pip3 install requests
RUN pip3 install opencv_python
ADD http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_11_06_2017.tar.gz /
RUN tar -xvf /ssd_mobilenet_v1_coco_11_06_2017.tar.gz
ADD https://github.com/tprlab/docker-detect/archive/master.zip /
RUN unzip /master.zip
EXPOSE 80
CMD ["python3", "/docker-detect-master/detect-app/app.py"]
Prosty
Publikowanie w Docker Hub
Rejestry Dockerów mnożą się z prędkością nie mniejszą niż detektory chmur.
Aby nie zawracać sobie głowy, przejdziemy konserwatywnie
- Zarejestrować
- Zaloguj sie:
login dokera - Wymyślmy sensowną nazwę:
tag dokera opencv-detect tprlab/opencv-detect-ssd - Prześlij obraz na serwer:
docker push tprlab/opencv-detect-ssd
Uruchamiamy w chmurze
Wybór miejsca umieszczenia kontenera również jest dość szeroki.
Wszyscy duzi gracze (Google, Microsoft, Amazon) oferują mikroinstancję za darmo przez pierwszy rok.
Po eksperymentach z Microsoft Azure i Google Cloud zdecydowałem się na tę drugą opcję, ponieważ działała szybciej.
Nie pisałem tutaj instrukcji, ponieważ ta część jest bardzo specyficzna dla wybranego dostawcy.
Próbowałem różnych opcji sprzętowych,
Niskie poziomy (współdzielone i dedykowane) - 0.4 - 0.5 sekundy.
Mocniejsze samochody - 0.25 - 0.3.
Cóż, nawet w najgorszym przypadku wygrana jest trzykrotna, możesz spróbować.
Wideo
Uruchamiamy prosty streamer wideo OpenCV na Raspberry, wykrywający poprzez Google Cloud.
Do eksperymentu wykorzystano plik wideo, który został kiedyś nakręcony w przypadkowym skrzyżowaniu.
def handle_frame(frame):
return detect.detect_draw_img(frame)
def generate():
while True:
rc, frame = vs.read()
outFrame = handle_frame(frame)
if outFrame is None:
(rc, outFrame) = cv.imencode(".jpg", frame)
yield(b'--framern' b'Content-Type: image/jpegrnrn' + bytearray(outFrame) + b'rn')
@app.route("/stream")
def video_feed():
return Response(generate(), mimetype = "multipart/x-mixed-replace; boundary=frame")
Z detektorem uzyskujemy nie więcej niż trzy klatki na sekundę, wszystko idzie bardzo wolno.
Jeśli weźmiesz potężną maszynę do GCloud, możesz wykryć 4-5 klatek na sekundę, ale różnica jest prawie niewidoczna dla oka, nadal jest powolna.
Chmura i koszty transportu nie mają z tym nic wspólnego; wykrywacz działa na zwykłym sprzęcie i działa z taką prędkością.
Neuronowy kij komputerowy
Nie mogłem się oprzeć i przeprowadziłem test porównawczy na NCS.
Szybkość detektora była nieco mniejsza niż 0.1 sekundy, w każdym razie 2-3 razy większa niż chmura na słabej maszynie, czyli 8-9 klatek na sekundę.
Różnicę w wynikach tłumaczy fakt, że NCS korzystał z Mobile SSD w wersji 2018_01_28.
PS Poza tym eksperymenty wykazały, że nieco lepszy komputer stacjonarny z procesorem I7 radzi sobie z nieco lepszymi wynikami i okazało się, że da się na nim wycisnąć 10 klatek na sekundę.
Klaster
Eksperyment poszedł dalej i zainstalowałem detektor na pięciu węzłach w Google Kubernetes.
Same strąki były słabe i każdy z nich nie potrafił przetworzyć więcej niż 2 klatki na sekundę.
Ale jeśli uruchomisz klaster z N węzłami i przeanalizujesz ramki w N wątkach, to przy wystarczającej liczbie węzłów (5) możesz osiągnąć pożądane 10 klatek na sekundę.
def generate():
while True:
rc, frame = vs.read()
if frame is not None:
future = executor.submit(handle_frame, (frame.copy()))
Q.append(future)
keep_polling = len(Q) > 0
while(keep_polling):
top = Q[0]
if top.done():
outFrame = top.result()
Q.popleft()
if outFrame:
yield(b'--framern' b'Content-Type: image/jpegrnrn' + bytearray(outFrame) + b'rn')
keep_polling = len(Q) > 0
else:
keep_polling = len(Q) >= M
Oto co się stało:
Trochę wolniej niż z NCS, ale za to bardziej energicznie niż w jednym strumieniu.
Wzmocnienie oczywiście nie jest liniowe - istnieją nakładki do synchronizacji i głębokiego kopiowania obrazów opencv.
wniosek
Ogólnie rzecz biorąc, eksperyment pozwala nam stwierdzić, że jeśli spróbujesz, możesz obejść się bez prostej chmury.
Ale potężny komputer stacjonarny lub sprzęt lokalny pozwala osiągnąć lepsze wyniki i to bez żadnych sztuczek.
referencje
Źródło: www.habr.com