Hardware

iteratec. Jak stworzyliśmy inteligentne drzwi iteraDoor

Podobno dobry administrator to leniwy administrator. Mimo że “lenistwo” ma pejoratywny wydźwięk, to jednak w tym przypadku chodzi bardziej o automatyzowanie nudnych i często czasochłonnych procesów. Nie inaczej jest z programistami, a nasza firma jest tego doskonałym przykładem. Komu nie zdarzyło się zapomnieć klucza do biura, w którym nie ma portierni? My postanowiliśmy skończyć z jego noszeniem, szukaniem i wsadzeniem do zamka, aby ostatecznie otworzyć wrota naszego oddziału. Stworzyliśmy inteligentne drzwi, które w oparciu o sieć neuronową i uczenie maszynowe, strzegą nas przed nieznajomymi, a przed nami stoją zawsze otworem…

Łukasz Gąbka. Software Engineer w firmie iteratec Sp. z o.o. Absolwent Politechniki Wrocławskiej. Programista z ponad 5-letnim doświadczeniem. Od początku związany z backendem (Java). Posiadający umiejętności w projektowaniu i implementacji oprogramowania. Doświadczenie zdobywał pracując dla międzynarodowych klientów z branży Automotive. W wolnym czasie poza programowaniem oddaje się gotowaniu, oglądaniu seriali i zabawie z córką.


Tomasz Kluza. Software Engineer w firmie iteratec Sp. z o.o.. Absolwent Politechniki Wrocławskiej. Programista na początku związany z technologiami mobilnymi – Android i Xamarin. Od dwóch lat backend developer (Java). Entuzjasta czystego kodu i wzorców projektowych. Miłośnik nowinek technologicznych, dobrego jedzenia i zakupów ze znanego sklepu zza oceanu. Wolny czas poświęca na podróże.

 


Rozproszenie uwagi kosztuje nas 23 minuty i 15 sekund

Nikt nie lubi być odrywanym od pracy. Jakże irytujący potrafi być kolejny kurier stojący pod drzwiami, czy kolejny “Pan <cokolwiek>”. Szczególnie cenny jest dla nas stan przepływu (ang. flow state; in the zone), który opisuje Cal Newport w swoim bestsellerze Deep Work. Jest to czas, podczas którego jesteśmy najbardziej skoncentrowani i produktywni. Chwila rozproszenia jest droga i według Glorii Mark kosztuje nas 23 minuty i 15 sekund. Właśnie tyle czasu średnio potrzebujemy, aby wrócić do właściwego zadania. W pracy programistów to niezwykle istotny element. Drobny błąd w środowisku produkcyjnym może kosztować naprawdę sporo, nie mówiąc już o złości kolegów poprawiających go w piątkowe popołudnie.

Wyszliśmy naprzeciw temu problemowi i postanowiliśmy stworzyć inteligentne drzwi, które pozwolą zaoszczędzić nasz cenny czas. Możemy przeznaczyć go na stworzenie jeszcze lepszego oprogramowania, a przy okazji się czegoś nauczyć.

Projekt w ramach Innovation Days

Jako że jesteśmy małą firmą, a nasze biuro nie jest nowo wybudowanym biurowcem, tylko klimatycznym miejscem w secesyjnej kamienicy z początku XX wieku, to nie mieliśmy wejścia otwieranego kartami, czy chipami. W pierwszej iteracji chcieliśmy stworzyć rozwiązanie, dzięki któremu wchodzenie do siedziby firmy byłoby znacznie prostsze i nie wymagałoby udziału innych pracowników. Jednocześnie niezwykle istotną kwestią było zapewnienie odpowiedniego stopnia bezpieczeństwa. Oczywistym jest, iż nie chcieliśmy, aby osoby nieupoważnione mogły w równie łatwy sposób co my, dostać się do środka.

Nadszedł czas Innovation Days. Jest to wewnętrzna inicjatywa projektowa, podczas której każdy z pracowników, w czasie jednego tygodnia w roku, może realizować swoje pomysły w wybranych przez siebie technologiach. To idealny moment na projektowanie, a następnie kodowanie tego typu pomysłów. Założycielem projektu jest Mateusz Gocz, ale ostatecznie praktycznie każdy z naszych pracowników dołożył do projektu kilka swoich linijek.

Wybrany został następujący stos technologiczny:

  • Spring Boot 2.0
  • Angular 6
  • PostgreSQL

Wersja pierwsza – już nie zabieramy klucza do pracy!

W pierwszej wersji projektu, wchodzenie do biura odbywało się za pomocą tokena NFC. Struktura prezentowała się następująco:

  • itera-door-server – Spring Boot 2.0

Właściwa logika aplikacji została zaszyta w pakiecie core. To tutaj zaimplementowane zostały serwisy i repozytoria zgodnie z praktyką Package by Feature, które przetwarzają żądania i zapisują je do bazy danych. Do komunikacji z pozostałymi modułami służy web, w którym znajduje się REST API.

  • itera-door-gui – Angular 6

Panel administratora, który pozwala dodawać i usuwać użytkowników, jak również zmieniać informacje o nich. Kolejną funkcjonalnością jest możliwość podglądu historii wejść do biura za pomocą tokenów NFC.

  • DB – PostgreSQL

To obecnie jedna z najpopularniejszych, relacyjnych baz danych. Często z niej korzystamy w przypadku naszych projektów.

  • NFC/ broker MQTT

Do stworzenia tego modułu wykorzystano dwie płytki NodeMCU v2 z układem ESP8266. Co z naszego punktu widzenia było niezwykle istotne to fakt, iż posiadają one moduł Wi-Fi i obsługują sieć w standardach 802.11 b/g/n. Czytnik tagów RFID/NFC został oparty o układ PN532, a do otwierania samych drzwi posłużył elektrozaczep. Do komunikacji pomiędzy układami wykorzystano protokół MQTT, a dokładnie jego implementację w postaci Eclipse Mosquitto.

W pierwszej iteracji informacje o tokenach były zapisywane bezpośrednio w urządzeniu zamontowanym w drzwiach. Dodawanie nowych tokenów zajmowało sporo czasu, gdyż wiązało się to z wgrywaniem nowej wersji aplikacji bezpośrednio do urządzenia.

Kamera wkracza na pokład

Pewnego dnia przy kawie usłyszeliśmy o rozwiązaniu, które stworzyli koledzy z jednego z niemieckich oddziałów firmy. Służy ono do rozpoznawania twarzy osób pracujących w firmie. Projekt opierał się na sieci neuronowej, która została stworzona na podstawie popularnego projektu FaceNet. Pomyśleliśmy, że można byłoby zintegrować ten projekt z naszymi drzwiami. Dowiedzieliśmy się, że aplikacja szuka osób na zdjęciu, po czym określa z prawdopodobieństwem rozpoznanego pracownika.

Wydaje się, że możemy podłączyć kamerkę i użyć tego systemu. Jeśli rozpoznamy osobę z naszego biura, to zwolnimy zamek do drzwi. Brzmi jak rozwiązanie idealne. Na początek dowiedzieliśmy się o tym, jak można wykryć ruch. Chwila googlowania i znaleźliśmy informację o bibliotece OpenCV służącej do przetwarzania obrazów. Zaczęliśmy zgłębiać temat dalej, by następnie pojawiły się znane ze studiów pojęcia: rozmycie Gaussa, skala szarości, progowanie (ang. threshold).

Ale jak to zrobić? Co przetwarzać lokalnie? Kiedy i co wysyłać na serwer?

Bez problemów się nie obędzie

Największy problem związany był z przechwytywaniem obrazu z kamery i wykrywaniem ruchu. W jaki sposób najlepiej to zaimplementować? Pierwsza myśl: porównując ze sobą kolejne klatki. Na początek wygładzanie za pomocą rozmycia Gaussa, aby zmniejszyć szumy i zakłócenia. Przeczytaliśmy, że aby porównać dwa obrazy najlepiej użyć trybu monochromatycznego. Dokonaliśmy więc odpowiedniej konwersji. To jednak nie koniec problemów. Okazuje się bowiem, że kamera rejestrowała ruch praktycznie cały czas. Kolejne klatki różniły się między sobą minimalnie (zdecydowanie nie można tego nazwać ruchem, raczej subtelnymi zmianami światła i ekspozycji). Warto byłoby takie drobne zmiany pominąć. Rozwiązaniem okazało się progowanie obrazu. Pozwoliło ono usunąć szumy i zaznaczyć miejsca, w których różnica między kolejnymi klatkami była większa, niż zadany próg. W ten sposób, po przeprowadzeniu kilku eksperymentów, byliśmy w stanie ocenić kiedy był ruch pomiędzy poszczególnymi zdjęciami.

Optymalizacja liczby zapytań

Sporym zaskoczeniem, okazała się liczba zapytań oraz zdjęć wysyłanych na serwer. Przelatująca mucha, włączone światło na korytarzu albo machnięcie ręki – takie działanie było dalekie od idealnego. Skoro jesteśmy w stanie rozpoznać ruch, to czy nie możemy też rozpoznać twarzy? Z pomocą przyszedł klasyfikator kaskadowy wykorzystujący cechy Haara. Kolejne dopasowanie kodu, aby sprawdzić czy wykryto ruch oraz dodatkowo, czy na obrazku rozpoznano twarz. Co prawda sam klasyfikator czasem błędnie rozpoznaje twarze, ale liczba zapytań zmniejszyła się kilkunastokrotnie. Wystarczyło kilka testów, zmian parametrów, aby rezultat był zadowalający (wypróbowaliśmy jeszcze klasyfikator lbp, ale w tym przypadku działał on gorzej).

Diagram aktualnej iteracji

W aktualnej wersji, projekt został rozszerzony o kolejne moduły:

itera-door-camera - Spring Boot 2.0 / OpenCV

Moduł ten odpowiada za komunikację z kamerą. To tutaj zaszyta jest logika wykrywania ruchu i wysyłania wybranych zdjęć na serwer.

itera-face - FaceNet 1.0.3 / Flask 1.0

Sieć neuronowa stworzona przez kolegów z Monachium. W celu optymalizacji, wystawiliśmy ją na lokalnym serwerze skracając tym samym czas odpowiedzi.

Podsumowanie

Stworzone w ramach Innovation Days rozwiązanie opiera się na nowych technologiach, z których korzystamy w naszych projektach. Właśnie przez takie inicjatywy najlepiej poznajemy dostępne narzędzia, dzięki czemu jesteśmy w stanie jeszcze lepiej i precyzyjniej odpowiedzieć na potrzeby naszych klientów.

Teraz nie musimy już szukać kluczy, przykładać tokenów. Program sam zrobi nam zdjęcie i wyśle do systemu rozpoznawania pracowników. Automatycznie określi czy, nawiązując do popularnego programu, „twoja twarz wygląda znajomo…” i jeśli tak, to usłyszymy dźwięk zwalnianego zamka. Może trzeba byłoby jeszcze zamontować jakiś siłownik, którzy faktycznie otworzy je przed nami. Wtedy dopiero można by powiedzieć, że drzwi same stają przed nami otworem (w obecnej wersji należy je jeszcze popchnąć).

Na koniec wrzucamy krótki poradnik, w którym opisaliśmy w jaki sposób zacząć przygodę z OpenCV w celu rozpoznawania twarzy. Teraz Twoja kolej – udanej implementacji!

Rozpoznawanie twarzy za pomocą OpenCV – poradnik

1. Dodanie zależności do projektu

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5</version>
</dependency>

2. Przechwycenie kadru z kamery

OpenCVFrameGrabber openCVFrameGrabber= new OpenCVFrameGrabber(0);
IplImage frame = converter.convert(openCVFrameGrabber.grab());
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
openCVFrameGrabber.start();
while (frame = converter.convert(grabber.grab()) != null) { 
   ... 
}

3. Wygładzenie klatki – rozmycie Gaussa

cvSmooth(frame, frame, CV_GAUSSIAN, 9, 9, 2, 2);

4. Konwersja obrazu na skalę szarości

grayImage = IplImage.create(width, height, IPL_DEPTH_8U, 1); cvCvtColor(frame,                grayImage, CV_RGB2GRAY);

5. Wyodrębnienie różnic między obrazami – różnica bezwzględna

cvAbsDiff(grayImage, prevGrayImage, diff);

6. Progowanie obrazu

cvThreshold(diff, diff, 60, 255, CV_THRESH_BINARY);

7. Znalezienie konturów

CvSeq contour = new CvSeq(null); 
cvFindContours(diff, storage,contour, Loader.sizeof(CvContour.class), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

8. Znalezienie konturu wiąże się wykryciem ruchu

if (!contour.isNull()) {
    motionDetected = true; 
}

9. Wczytanie klasyfikatora

private CascadeClassifier loadClassifier(String classifierPath) throws IOException {
   String classifierName;
   URL url = new URL(classifierPath);
   File file = Loader.cacheResource(url);
   classifierName = file.getAbsolutePath();

   CascadeClassifier classifier = new CascadeClassifier(classifierName);
   if (classifier == null) {
       System.err.println("Error loading classifier file "" + classifierName + "".");
       System.exit(1);
   }
   return classifier;
}

10. Wykrywanie twarzy

private long detectFaces(final IplImage grayImage, final IplImage frame, final CascadeClassifier classifier, CvScalar color, int size) {
   RectVector faces = new RectVector();
   Mat image = new Mat(grayImage);
   classifier.detectMultiScale(image, faces, 1.2, 5, 0, null, null);
   long total = faces.size();
   if (total > 0) {
       drawFaceFrames(frame, faces, total, color, size);
   }
   return total;
}

private void drawFaceFrames(final IplImage frame, final RectVector faces, final long total, CvScalar color, int size) {
   for (long i = 0; i < total; i++) {
       Rect face = faces.get(i);
       drawFrame(frame, face, color, size);
   }
}

private void drawFrame(final IplImage frame, final Rect face, CvScalar color, int size) {
   int x = face.x(), y = face.y(), w = face.width(), h = face.height();
   cvRectangle(frame, new CvPoint(x - size, y - size), new CvPoint(x + w + size, y + h + size), color, 1, CV_AA, 0);
}

Wraz z Tomaszem Gańskim jestem współtwórcą justjoin.it - największego job boardu dla polskiej branży IT. Portal daje tym samym największy wybór spośród branżowych stron na polskim rynku. Rozwijamy go organicznie, serdecznie zapraszam tam również i Ciebie :)

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://geek.justjoin.it/iteratec-jak-stworzylismy-inteligentne-drzwi-iteradoor/" order_type="social" width="100%" count_of_comments="8" ]