Serwisy partnerskie:
Close icon
Serwisy partnerskie

Jak zbudować detektor Koronawirusa? Termometr bezkontaktowy

Epidemia koronawirusa daje się nam wszystkim we znaki. Nasze życie uległo istotnym zmianom. Prezentowany projekt jest niejako ich pokłosiem. Autor opracował projekt, który pozwoli szybko i we względnie tani sposób wykryć osoby z podniesioną temperaturą ciała.
Article Image

Prezentowany układ jest jeszcze jednym termometrem pozwalającym na pomiar temperatury ciała. Ale termometrem niezwyczajnym, a wręcz nadzwyczajnym. Nadzwyczajna jest bowiem aktualna sytuacja nie tylko w Polsce, ale na całym świecie. Układ powstał w szybkim tempie jako jeszcze jedna propozycja wygodnego i szybkiego segregowania osób, które zostały zarażone koronawirusem.

Wszyscy już wiedzą, że jest to wirus szczególnie groźny dla osób starszych, rozprzestrzeniający się drogą kropelkową i po kilku (niestety) dniach od infekcji dający objawy w postaci wysokiej gorączki. Właśnie ten objaw można łatwo wykryć niniejszym urządzeniem.

Pomiar temperatury może być przeprowadzony metodami kontaktowymi i bezkontaktowymi. W przypadku epidemii metody kontaktowe odpadają. Dodatkową przewagą metod bezkontaktowych jest szybkość działania czujnika. Oczywiście nic za darmo – płacimy za to dokładnością pomiarów.

Otóż każde ciało powyżej temperatury zera bezwzględnego emituje promieniowanie. Częstotliwość tego promieniowania jest funkcją temperatury – wystarczy spojrzeć na rozżarzone żelazo. Im bardziej gorące – tym kolor bardziej zbliżony do białego. Dociekliwi znajdą sporo informacji w dostępnej literaturze. Zatem jeśli będziemy w stanie odebrać to promieniowanie i przekształcić na informację o temperaturze, mamy gotowy miernik.

Proste, prawda? Oczywiście nie do końca. Wartość temperatury ciała jest uzależniona od wielu czynników, ale jednym z ważniejszych jest współczynnik emisyjności. Matematyczne wzory są poprawne dla ciała tzw. doskonale czarnego, ale łatwo się domyślić, że drewno i np. płyta aluminiowa o tej samej temperaturze dadzą inne wyniki pomiaru.

Komercyjnie do pomiaru temperatury można wykorzystać albo pomiar jednopunktowy – z czujnikiem PIR, albo matrycę takich czujników w kamerze termowizyjnej. Czujniki PIR, a raczej jednopunktowe termometry oparte na nich, kosztują około 100 PLN. Osoby, które mają małe dzieci, wiedzą, na ile są one (nie) dokładne. Z kolei prawdziwa kamera termowizyjna to koszt zaczynający się od kilku tysięcy złotych.

W proponowanym tutaj, niejako pośrednim, rozwiązaniu wykorzystano czujnik D6T-44L produkcji Omron. Jest to dość interesująca konstrukcja, pozwalająca na pomiar temperatury z 16 obszarów tworzących matrycę 4×4. Zatem możliwe jest jednoczesne określenie temperatury 16 obszarów. Fajnie jeszcze byłoby móc określić, z jakich obszarów.

I tu z pomocą przychodzi znany też wielu czytelnikom EdW jednopłytkowy komputerek RaspberryPI, który niewielkim kosztem można wyposażyć w kamerkę. Przedstawiony projekt pozwala niejako na naniesienie matrycy temperatur na obraz widziany z kamerki, co znakomicie ułatwia interpretację wyników pomiaru i decyzję.

Opis układu - termometr bezkontaktowy

Dla lepszego zrozumienia budowy i działania tego dość skomplikowanego urządzenia warto przeanalizować schemat blokowy, pokazany na rysunku 1. Centralną częścią jest tu komputer jednopłytkowy RaspberyPI. Autor miał, i wykorzystał model 3B, ale z powodzeniem powierzone zadanie może pełnić RaspberryPi Zero, do pozyskania za naprawdę niewielkie pieniądze.

Rys.1 Schemat blokowy detektora koronawirusa

Do Raspberry PI konieczne jest podłączenie zasilania – może to być np. dowolna ładowarka współczesnego smartfona ze złączem microUSB. Przydaje się również monitor do obrazowania temperatury. Z uwagi na specyfikę projektu monitor powinien wykorzystywać sygnał ze złącza HDMI. Można zatem wykorzystać dowolny „duży” monitor komputerowy, jak też mniejsze rozwiązanie: 3- lub 5-calowy minidisplay. Należy jednak unikać monitora sterowanego przez magistralę dostępną na złączu RaspberryPI. Może się okazać, że przepustowość złącza będzie zbyt niska.

Jako dysk RaspberryPI wykorzystuje kartę SD. Autor wykorzystał kartę z systemem Raspbian.

Czujnik D6T-44L podłączony jest do magistrali I2C na złączu krawędziowym. W projekcie wykorzystano dodatkowo dedykowany dla RaspberryPI moduł kamery CSI. Jest to kamera o rozdzielczości 5 megapikseli w wersji V.1.3.

Całością steruje program napisany w języku Python, który z jednej strony pozwala na wygodne sterowanie kamerką, jak i wykonywanie pomiarów z czujnika. Zatem program wyświetla obraz z kamerki w trybie ciągłym i nakłada na niego obraz – mapę bitową utworzoną na podstawie danych z czujnika temperatury. Raz na sekundę czujnik jest odpytywany, budowana jest mapa bitowa, usuwana stara nakładka na obraz z kamery i podkładana nowa. Pełny kod programu umieszczony jest w materiałach dodatkowych do tego numeru.

Bardzo interesujący jest tutaj sposób pomiaru realizowany przez czujnik temperatury. Jest to czujnik zaliczany do grupy MEMS, gdzie pomiar temperatury dokonywany jest przy użyciu... termopar. Strukturę tego czujnika pokazuje rysunek 2 pochodzący ze strony producenta.

Rys.2 Struktura czujnika MEMS

Każdemu z nas termopara kojarzy się z pomiarem raczej wysokich temperatur i pojawia się jeszcze problem kompensacji tzw. zimnej strony/złącza termopary. W użytym czujniku zastosowano 16 termopar, z których zimny koniec każdej z nich jest podłączony do wspólnego podłoża. Zatem każda termopara mierzy różnicę temperatury wyprowadzonego „gorącego” końca w stosunku do uśrednionej temperatury podłoża. Te niewielkie sygnały są następnie na miejscu wzmacniane i przetwarzane na postać cyfrową – wyjaśnia to bliżej rysunek 3, również pochodzący ze strony producenta.

W rezultacie po odpytaniu czujnika za pomocą magistrali I2C dostajemy 17 wartości – 16 temperatur odpowiadających poszczególnym polom pomiarowym oraz temperaturę podłoża. Dzięki zastosowanej soczewce krzemowej uzyskujemy czujnik o polu widzenia ok. 81×84 cm w odległości metra od czujnika (kąty 44,2 i 45,7 w kierunku X i Y). Więcej na stronie producenta pod adresem.

Rys.3 Działanie termopary - grafika

Montaż i uruchomienie - termometr bezkontaktowy

Układ nie jest trudny w montażu, aczkolwiek należy zwrócić uwagę na kilka szczegółów. Zacznę od łatwiejszych. Podłączenie RaspberryPI do monitora HDMI, zasilacza i włożenie doń karty SD z systemem operacyjnym nie powinno nastręczać kłopotów. Również instalacja kamery dedykowanej do RaspberryPI jest typowa. Wkładamy taśmę FPC do odpowiedniego slotu RaspberryPI, pamiętając o odpowiedniej kolejności. Aby to razem chciało działać, należy dokonać niezbędnych modyfikacji w konfiguracji Raspbiana. O tym za chwilę.

Nieco kłopotu może przysporzyć montaż czujnika temperatury. Wymaga on odpowiedniego złącza i przygotowania przewodów zakończonych żeńskim złączem goldpin. Do czujnika producent przewidział złącze SM04B-GHS-TB (JST) ze stykami SSHL-002T-P0.2 (JST) i w obudowie GHR-04V-S (JST). Ja w swoim prototypie po prostu wylutowałem złącze i odpowiednie przewody przylutowałem bezpośrednio do czujnika.

Wykorzystałem w tym celu taśmę wielożyłową do złączy zaciskanych – taką, jaka była stosowana do łączenia dysków twardych ATA. Całą 40-pinowa taśma jest dość sztywna, ale wydzielone z niej cztery przewody są raczej elastyczne i dają się lutować. Efekt jest widoczny na fotografiach 1 i 2.

Fot.1 Wylutowane złącze SM04B-GHS-TB (JST) 
Fot.2 Kable przylutowane bezpośrednio do czujnika

Na drugim końcu taśmy przylutowałem złącza żeńskie. Wykorzystałem w tym celu same blaszki, które zacisnąłem szczypcami na przewodzie, po czym przylutowałem z użyciem niewielkiej ilości topnika. Mając odrobinę wprawy, można to włożyć do plastikowej osłonki na pojedynczy pin żeński, ja jednak wolę kolorowe rurki termokurczliwe, które pozwalają na dodatkowe oznaczenie przewodów. Wyniki prac przedstawia fotografia 3.

Tak przygotowany czujnik można podłączyć do RaspberryPI. Musimy jednak zwrócić uwagę na jeden problem. Czujnik D6T-44L zasilany jest napięciem 5V i współpracująca z nim magistrala również pracuje w logice 5V. Na złączu RaspberryPI występuje interfejs I2C, który pracuje w logice 3,3V. Idealnym rozwiązaniem byłoby zastosowanie w budowanym układzie konwertera poziomów napięć.

Ja w swoim prototypie sprawdziłem możliwość pracy interfejsu I2C przy napięciu 3,3V. Dodatkowym atutem jest fakt, że magistrala I2C komputerka RaspberryPI jest wewnętrznie podciągnięta do zasilania 3,3V dwoma rezystorami o wartości 1k8. Zatem poza podpięciem przewodów do magistrali nic nie trzeba było robić. Jedyne ryzyko, które wynikało z takiego podejścia, polegało na tym, że układ nie zadziała z uwagi na niedokładne dostosowanie napieć progowych.

Szczęśliwie zadziałał, co zaoszczędziło mi dodatkowej płytki konwertera napięć. Puryści mogą jednak takową zainstalować, co nie powinno nic zmienić, poza ewentualną minimalizacją ilości błędów transmisji w magistrali I2C.

Fot.3 Czujnik z taśmą do podłączenia

Bardzo istotnym elementem jest odpowiednie i stałe ułożenie obu kamer obok siebie dla minimalizacji efektu paralaksy. Powinny one być na tyle blisko siebie, aby nie wprowadzać istotnych zmian. Dodatkowo w trakcie pracy nie powinny się względem siebie przemieszczać. Efekt ten uzyskano, przygotowując projekt uchwytu do obu kamer i drukując go za pomocą drukarki 3D. Obudowa do modułu kamery v.1.3 i czujnika temperatury pokazana jest na rysunku 4. Pliki *.stl i źródłowe pliki dla programu FreeCAD – darmowej alternatywy do modelowania 3D – dostępne są do pobrania w materiałach dodatkowych do projektu.

Uchwyt na kamerkę i czujnik przymocowałem do obudowy wyświetlacza za pomocą dodatkowego uchwytu wykonanego z tworzywa sztucznego i taśmy dwustronnie klejącej. Efekty prac przedstawia fotografia 4. Dalszych detali mechanicznych nie publikuję, gdyż w swoich zasobach mam monitor 5-calowy z ekranem dotykowym – do RaspberryPI. Monitor zasilany jest przez listwę krawędziową, ale ma możliwość jej przedłużenia – co wykorzystałem do podłączenia czujnika. Szczegóły na fotografii 5.

Rys.4 Obudowa do modułu kamery czujnika temperatury

Oczywiście przy takiej ilości sprzętu do jego obsługi niezbędne jest odpowiednie oprogramowanie. Dla wygody użytkownika wszystkie pliki niezbędne do działania programu można znaleźć w Elportalu w materiałach dodatkowych do tego numeru. Najwygodniej będzie pobrać program bezpośrednio na RaspberryPI. Pamiętajmy, że choć niewielkich rozmiarów, jest to jednak w pełni funkcjonalny komputer, który można podłączyć do sieci i korzystać np. z przeglądarki internetowej.

Po pobraniu danych należy uruchomić terminal i stworzyć katalog np. omron poleceniem mkdir omron. Potem wchodzimy do katalogu omron – polecenie cd omron i należy rozpakować plik zip z kodem. Jeśli pobieraliśmy plik z przeglądarki, to powinien on znajdować się w katalogu /home/pi/Downloads (UWAGA !!! systemy UNIX-owe są wrażliwe na wielkie i małe litery!!!). Pobrany plik ma (powinien mieć) nazwę coviddet_soft1.zip – zatem rozpakujmy go właśnie do naszego katalogu.

W tym celu posłużymy się poleceniem unzip /home/pi/Downloads/coviddet_soft1.zip. Po wykonaniu polecenia można sprawdzić poleceniem ls -laF, czy w katalogu pojawiły się niezbędne pliki. Jeśli nie – warto sprawdzić, czy mamy zainstalowany program zip. W razie problemów zainstalujemy go bezpośrednio z sieci poleceniem sudo apt-get install zip.

Fot.5 Czujnik podłączony do przedłużonej listwy krawędziowej

Od tej chwili po każdorazowym uruchomieniu systemu należałoby uruchomić terminal, wykonać polecenie sudo pigpiod, po którym uruchamiamy aplikację poleceniem python test1.py. Jeśli chcielibyśmy, żeby to była aplikacja, która uruchomi się samoczynnie po starcie programu – to należałoby zmodyfikować skrypty startowe systemu operacyjnego.

Dla dociekliwych – oprogramowanie Całość programu stworzyłem w języku Python, co pozwoliło w nieco ponad 100 liniach zawrzeć wszystko, co niezbędne do działania programu. Zanim jednak przystąpimy do omawiania go, należy dokonać konfiguracji ustawień w systemie operacyjnym, aby wszystko chciało poprawnie działać.

Zanim RaspberryPI zacznie działać, musimy zainstalować na nim system operacyjny. Najczęściej i najchętniej stosowanym systemem operacyjnym jest Raspbian. Jest to odmiana systemu Debian Linux przeznaczona dla komputerów RaspberryPI. System operacyjny znajduje się i uruchamia się całkowicie z karty SD (najczęściej microSD). Zatem najprostszą metodą instalacji jest wgranie systemu bezpośrednio na kartę SD. W tym celu ze strony należy pobrać ostatnią wersję systemu.

Paczka pełnej dystrybucji zajmuje ok. 2,5GB, więc osoby z wolniejszym łączem do Internetu mogą mieć z tym problem. Alternatywą jest tutaj pobranie oprogramowania NOOBS w wersji Lite – 38MB. Jest to instalator, który sam pobiera i instaluje wybraną wersję systemu – wymaga to stałego podłączenia do sieci na czas instalacji. Oczywiście spośród wielu opcji należy wybrać Raspbiana. W przypadku NOOBS wystarczy włożyć do czytnika czystą kartę SD o pojemności 8 lub 16GB i przekopiować rozpakowaną zawartość pliku na tę kartę. W przypadku, gdy wszystko przebiegnie poprawnie, po włożeniu do komputerka RaspberryPI karty SD po chwili uruchomi się instalator.

Rys.5 Wejście do konfiguratora RaspberryPI 

I tu mała uwaga – jeśli cokolwiek z kartą będzie nie tak, najczęściej objawem będzie zero reakcji – ciemny ekran monitora i brak świecenia diod LED. Warto spróbować jeszcze raz. Mniej doświadczone osoby mogą po prostu zakupić kartę SD z preinstalowanym Raspbianem.

Mimo tego po włożeniu karty SD do czytnika ze świeżo zainstalowanym systemem trzeba go przy pierwszym uruchomieniu odpowiednio skonfigurować. Musimy wskazać na język, układ klawiatury, zmienić domyślne hasło i opcjonalnie skonfigurować dostęp do sieci i zaktualizować system.

Aby można było rozpocząć nasze pomiary, należy także pobrać kod programu oraz dodatkowe przydatne pakiety. Najpierw jednak musimy uruchomić konfigurator, w którym włączymy obsługę interfejsów i kamery. Bez tego system operacyjny nie będzie obsługiwał ani czujnika, ani kamery.

W tym celu należy wybrać z menu (ikonka maliny w lewym górnym rogu) opcję Preferencje i dalej RaspberryPI Configuration (rysunek 5). Tutaj musimy wybrać zakładkę „interfaces” i zaznaczyć „Enable” dla „Camera” i „I2C” jako minimum – jak pokazano na rysunkach 6 i 7. Ja zaznaczyłem więcej opcji, gdyż wykorzystuję ten komputerek do różnych celów i rozmaite interfejsy są mi po prostu potrzebne. Po zaznaczeniu tej opcji i wybraniu „OK” system poprosi o restart. Zgadzamy się i po ponownym uruchomieniu systemu obsługa kamery i I2C powinna działać.

Zanim wgramy program finalny, warto też sprawdzić czy kamerka działa i czy po podłączeniu czujnika jest on „widziany” przez magistalę I2C. W celu sprawdzenia kamerki wystarczy uruchomić terminal i wydać polecenie raspistill -o a.jpg. Spowoduje to wyświetlenie obrazu z kamerki na kilka sekund i zapisanie obrazu do pliku a.jpg. Warto także zaobserwować świecenie diody zainstalowanej przy kamerce. W przypadku problemów należy sprawdzić połączenia RaspberryPI z kamerką i ustawienia konfiguracyjne.

Rys.6 Włączenie obsługi interfejsów i kamery w RaspberryPI (krok 1) 
Rys.7 Włączenie obsługi interfejsów i kamery w RaspberryPI (krok 2)

W celu przetestowania połączenia czujnika temperatury oczywiscie należy go uprzednio podłączyć do płytki RaspberryPI. Pamiętajmy, żeby ten etap wykonać na wyłączonym RaspberryPI.

Następnie uruchamiamy program terminalu i uruchamiamy polecenie i2cdetect -y -r 1. Program i2cdetect dokonuje skanowania wszystkich adresów na wybranej magistrali I2C i jeśli zaadresowane urządzenie odpowie (ACK), to wyświetli jego adres. Czujnik D6T44L ma adres 0x0A w notacji 7-bitowej i taki adres powinien się pojawić w programie.

Różnice pomiędzy wykonaniem programu przed dopięciem czujnika i po jego podłączeniu pokazuje rysunek 8. Jeśli nasz czujnik nie odpowiada mimo wielu prób – warto rozważyć zastosowanie konwertera poziomu napięć, ewentualnie sprawdzić działanie czujnika na innym sprzęcie. Ostatecznie można zbadać oscyloskopem przebiegi na liniach SDA i SCL.

Teraz można zainstalować program docelowy. Cały program składa się z kilku plików źródłowych. Jeden z nich to biblioteka obsługująca czujnik. Wykorzystuje ona bibliotekę pigpio, pozwalającą na dostęp do portów za pośrednictwem języka Python. Zatem przed uruchomieniem programu należy uruchomić program pigpiod poleceniem sudo pigpiod.

Polecenie sudo – uwaga dla mniej wtajemniczonych, pozwala na uruchomienie następującego po nim programu z uprawnieniami administratora (roota). Program pigiod jest programem, który pośredniczy pomiędzy językiem python a warstwą sprzętową, która nie zawsze będzie dla użytkownika dostępna (ograniczenia uprawnień).

Rys.8 Skanowanie wszystkich adresów na wybranej magistrali I2C - przed i po podłączeniu czujnika 

Teraz można przystąpić do uruchomienia całości. Po pierwsze – programu do obsługi czujnika temperatury nie pisałem sam – pobrałem go z repozytorium git spod adresu. Projekt zawiera również własne GUI – niestety napisane z użyciem modułu matplotlib, którego nie udało mi się zainstalować na Raspberry PI. Na szczęście projekt jest napisany modułowo i można wykorzystać poszczególne jego części. Tak też uczyniłem.

Po podłączeniu wszystkich sensorów, pobraniu plików z githuba i uruchomieniu pigpiod można przetestować działanie samego czujnika. W tym celu trzeba z poziomu terminalu przejść do katalogu, gdzie zapisały się pliki z githuba i wydać polecenie python ./sample_d6t.py. Wynikiem działania powinny być indywidualne temperatury poszczególnych pól czujnika oraz średnia temperatura wszystkich pól:

([24.1, 24.0, 24.1, 24.1, 24.1, 24.1, 24.2, 24.4, 24.0, 24.1, 24.0, 22.7, 23.7, 23.9, 22.8, 22.1], ‚PTAT : 28.2’)

([24.1, 24.0, 24.1, 24.1, 24.2, 24.1, 24.2, 24.4, 24.0, 24.1, 24.0, 22.7, 23.8, 24.0, 22.8, 22.1], ‚PTAT : 28.2’).

Program działa w nieskończonej pętli do momentu wciśnięcia kombinacji klawiszy Ctrl-C.

Gdy upewnimy się, że podłączony czujnik działa poprawnie, warto skupić się na obsłudze kamery. Tutaj z pomocą przychodzi szereg tutoriali z sieci. W RaspberryPI są gotowe moduły do obsługi kamery, co znakomicie upraszcza tworzenie programów. W swoim projekcie wykorzystałem informacje zawarte na stronie. Python w pigułce – dla bardzo dociekliwych :)

Zacznijmy od najprostszego programu, który wyświetli obraz z kamery na ekranie. I tutaj należy się jeszcze drobny komentarz dotyczący obsługi takiego strumienia danych. W ostatnich numerach EdW w temacie wyświetlaczy pojawiły się szacowania dotyczące przepustowości danych argumentujące, dlaczego Arduino nie nadaje się do sterowania takimi wyświetlaczami.

Ze współczesnymi kamerami jest podobnie. Strumień danych z kamery to nieustanny ciąg danych o rozdzielczości kamery razy 3 (bo RGB) i powtarzający się FPS razy na sekundę. Przy kamerze o rozdzielczości 2592×1944 i 25 klatkach na sekundę daje to 377913600 bajtów danych na każdą sekundę strumienia. Dane te musi przyjąć procesor i jakoś przetworzyć.

Jak widać, przetworzenie prawie 400MB danych przez procesor o częstotliwości taktowania nawet 2GHz to problem nie lada. A potrzebne są jeszcze dodatkowe moce obliczeniowe na obsługę zadań systemu operacyjnego i aplikacji użytkownika. Dlatego obsługą takich urządzeń we współczesnych procesorach aplikacyjnych zajmują się dedykowane jednostki pracujące równolegle z rdzeniem głównym procesora. Procesor zainstalowany w RaspberryPI ma jeszcze jeden blok – własną kartę graficzną i można go odpowiednio skonfigurować tak, żeby strumień danych z kamery przekierował wprost do karty graficznej.

Taki tryb wyświetlania strumienia często nazywa się trybem preview. Dane bez udziału CPU trafiają wprost na wyświetlacz. Mało tego, dedykowany procesor graficzny może do wyświetlanego obrazu dodać zawartość obrazu. Kod z listingu 1 pokazuje taki właśnie scenariusz, gdzie na strumień z kamery nakładany jest obraz z pliku. Listing pochodzi bezpośrednio z linku. Aby ten program można było uruchomić, należy stworzyć obrazek i zapisać go w pliku o nazwie overlay.png.

Listing 1

import picamera
from PIL import Image
from time import sleep
camera = picamera.PiCamera()
camera.resolution = (1280, 720)
camera.framerate = 24
camera.start_preview()
# Load the arbitrarily sized image
img = Image.open(‘overlay.png’)
# Create an image padded to the required size with
# mode ‘RGB’
pad = Image.new(‘RGB’, (
((img.size[0] + 31) // 32) * 32,
((img.size[1] + 15) // 16) * 16,
))
# Paste the original image into the padded one
pad.paste(img, (0, 0))
# Add the overlay with the padded image as the source,
# but the original image’s dimensions
o = camera.add_overlay(pad.tobytes(), size=img.size)
# By default, the overlay is in layer 0, beneath the
# preview (which defaults to layer 2). Here we make
# the new overlay semi-transparent, then move it above
# the preview
o.alpha = 128
o.layer = 3
# Wait indefinitely until the user terminates the script
while True:
sleep(1)

Listing 1 ukazuje piękno i przejrzystość programowania w Pythonie. Cały program, po usunięciu komentarzy, to jakieś 13–14 poleceń. A gdy bliżej go przeanalizujemy, to okaże się, że poza inicjalizacją sprzętu program... nie robi nic! Nasza aplikacja powinna jednak coś robić, więc należy zmodyfikować ten program tak, żeby w pętli głównej mierzył temperaturę, przygotowywał obrazek do podłożenia go na strumieniu danych i go niejako podmienił.

Pętlę z zawartością naszego głównego programu przedstawia listing 2.

#!/usr/bin/python
import picamera
from PIL import Image, ImageDraw, ImageFont
from time import sleep
import grove_d6t
import pigpio
import time
from PIL import Image
import numpy as np
#[dla czytelności pominięto tu kilka funkcji,
o których za chwilę]
d6t = grove_d6t.GroveD6t()
camera = picamera.PiCamera()
camera.resolution = (800, 600)
camera.framerate = 24
camera.start_preview()
tpn = omron_scan()
img = create_img(tpn)
pad = Image.new(‚RGB’, (((img.size[0] + 31) // 32) * 32,((img.size[1] + 15) // 16) * 16, ))
pad.paste(img, (0, 0))
o = camera.add_overlay(pad.tobytes(), size=img.size)
o.alpha = 128
o.layer = 3
while True:
pad.close();
tpn = omron_scan()
img = create_img(tpn)
camera.remove_overlay(o)
pad = Image.new(‚RGB’, ( ((img.size[0] + 31) // 32) * 32, ((img.size[1] + 15) // 16) * 16,))
pad.paste(img, (0, 0))
o = camera.add_overlay(pad.tobytes(), size=img.size, alpha=128 , layer=3)
sleep(1)

Jak widać, program się nieco wydłużył, ale i tak całość to tylko 104 linie. Pierwsza linia to dyrektywa środowiska Unix, wskazująca, jakim programem ma być „potraktowana” reszta kodu w pliku. Tu wskazujemy na interpreter języka Python.

Kolejne linie zaczynające się od import… lub from… import to deklaracje zewnętrznych funkcji, bądź to standardowych bibliotecznych, bądź stworzonych bezpośrednio w kodzie. Następnie kilka linii to częściowo znane z listingu 1 inicjalizacje kamery oraz inicjalizacja obiektu obsługi czujnika. Funkcja i wszystkie deklaracje tego obiektu znajdują się w pliku grove_d6t.py i te deklaracje są dołączone w odpowiednim imporcie na początku kodu. Następnie funkcją omron_scan wypełniamy tablicę tpn – tablicę temperatur. Funkcja ta jest przerobionym fragmentem kodu pobranego z gita. To, co było wywoływane w pętli głównej, zamknąłem w funkcji. Mając tablicę temperatur, należy przygotować obraz, który będzie nakładany na obraz z kamery.

Tą częścią zajmuje się funkcja create_img, którą umieściłem na listingu 3. Obraz to taka trójwymiarowa tablica punktów. Trójwymiarowa, bo kolor każdego punktu ma 3 składowe: R, G, B. Przestrzeń na wypełnianie punktów tworzymy za pomocą funkcji np. zeros. Przedrostek „np” oznacza, że korzystamy z biblioteki do obliczeń numerycznych numpy, którą po prostu zdeklarowaliśmy na początku kodu.

Listing 3

def create_img(tpn):
w, h = 512, 512
maxt = np.amax(tpn)
mint = np.amin(tpn)
data = np.zeros((h, w, 3), dtype=np.uint8)
data[0:127 , 0:127] = getcolormap(tpn[0],mint,maxt)
data[ 0:127,128:255] = getcolormap(tpn[1],mint,maxt)
data[ 0:127,256:383] = getcolormap(tpn[2],mint,maxt)
data[ 0:127,384:511] = getcolormap(tpn[3],mint,maxt)
data[ 128:255,0:127 ] = getcolormap(tpn[4],mint,maxt)
data[ 128:255,128:255] = getcolormap(tpn[5],mint,maxt)
data[ 128:255,256:383] = getcolormap(tpn[6],mint,maxt)
data[ 128:255,384:511] = getcolormap(tpn[7],mint,maxt)
data[ 256:383,0:127 ] = getcolormap(tpn[8],mint,maxt)
data[ 256:383,128:255] = getcolormap(tpn[9],mint,maxt)
data[ 256:383,256:383] = getcolormap(tpn[10],mint,maxt)
data[ 256:383,384:511] = getcolormap(tpn[11],mint,maxt)
data[ 384:511, 0:127] = getcolormap(tpn[12],mint,maxt)
data[ 384:511,128:255] = getcolormap(tpn[13],mint,maxt)
data[ 384:511,256:383] = getcolormap(tpn[14],mint,maxt)
data[ 384:511,384:511] = getcolormap(tpn[15],mint,maxt)
fnt = ImageFont.truetype(„/usr/local/lib/python2.7/
dist-packages/matplotlib-2.2.5-py2.7-linux-armv7l.egg/
matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf”, 16)
img1 = Image.fromarray(data, ‚RGB’)
d = ImageDraw.Draw(img1)
d.text((10,64),(„0: %3.1f”%tpn[0]),font=fnt, fill = (0,255,0))
d.text((138,64),(„1: %3.1f”%tpn[1]),font=fnt, fill = (0,255,0))
d.text((266,64),(„2: %3.1f”%tpn[2]),font=fnt, fill = (0,255,0))
d.text((394,64),(„3: %3.1f”%tpn[3]),font=fnt, fill = (0,255,0))
d.text((10,138),(„4: %3.1f”%tpn[4]),font=fnt, fill = (0,255,0))
d.text((138,138),(„5:%3.1f”%tpn[5]),font=fnt, fill = (0,255,0))
d.text((266,138),(„6: %3.1f”%tpn[6]),font=fnt, fill = (0,255,0))
d.text((394,138),(„7: %3.1f”%tpn[7]),font=fnt, fill = (0,255,0))
d.text((10,266),(„8: %3.1f”%tpn[8]),font=fnt, fill = (0,255,0))
d.text((138,266),(„9: %3.1f”%tpn[9]),font=fnt, fill = (0,255,0))
d.text((266,266),(„10: %3.1f”%tpn[10]),font=fnt, fill = (0,255,0))
d.text((394,266),(„11: %3.1f”%tpn[11]),font=fnt, fill = (0,255,0))
d.text((10,394),(„12: %3.1f”%tpn[12]),font=fnt, fill = (0,255,0))
d.text((138,394),(„13: %3.1f”%tpn[13]),font=fnt, fill = (0,255,0))
d.text((266,394),(„14: %3.1f”%tpn[14]),font=fnt, fill = (0,255,0))
d.text((394,394),(„15: %3.1f”%tpn[15]),font=fnt, fill = (0,255,0))
img1.save(‚overlay.png’)
return(img1)

Docelowy obraz, który wypełniamy, ma rozdzielczość 512×512 pikseli. Dzielimy to na 16 podobszarów i każdy z podobszarów wypełniamy kolorem, który zależy od zmierzonej temperatury. Kolor z kolei to wektor 3 składowych [R, G, B], z których każda może przyjąć wartości od 0 do 255. Za generowanie kolorów odpowiada funkcja getkolormap. Jest ona pokazana na listingu 3.

Tak zbudowana macierz obrazu przekształcana jest w obiekt obrazu, na którego tle możemy dalej umieszczać napisy (d.text…). Ostatecznie funkcja zwraca obiekt obrazu, który w pętli głównej nakładany jest na obraz z kamery. Funkcja ta zwraca wartość koloru, jaki przypiszemy zmierzonej temperaturze. Kolor jest przypisywany na podstawie wartości temperatury oraz minimalnej i maksymalnej temperatury w zmierzonych danych. Jeśli temperatura jest większa niż 36,6°C, to zwracana mapa kolorów ma tylko składową czerwoną, jeśli mniejsza niż 36,6 – to mamy kolory w odcieniach szarości.

Warto odnotować, że dla osoby zdrowej temperatura czoła jest mniejsza niż 36,6°C. W moim przypadku to jakieś 33°C. Jest to normalne, bo mierzymy powierzchnię styku ciała z otoczeniem i tam zawsze temperatura będzie niższa niż ta mierzona pod pachą zwykłym termometrem, gdzie przez czas trwania pomiaru izolujemy główkę pomiarową termometru od otoczenia. Zainteresowani mogą w tej funkcji zmienić wartość temperatury na inną (listing 4), żeby przetestować funkcjonowanie programu.

więkdef
getcolormap(temp,mintemp=20,maxtemp=40):
tt = ((temp - mintemp)*255)/(maxtemp-mintemp)
if temp > 36.6 :
return([tt,0,0])
else:
return ([tt,tt,tt])

W fotografii tytułowej wykorzystałem 30°C jako próg detekcji. Na koniec warto jeszcze przyjrzeć się funkcji omron_scan. W praktyce czujnik czasami się „zacina” i nie zwraca poprawnych wartości temperatur. Wtedy warto wyłapać takie przypadki i np. powtórzyć odczyt. W tym celu wykorzystano dyrektywę try, która obsługuje takie właśnie błędy. Zatem na początku działania funkcji przygotowujemy się na odczyt temperatur w pętli nieskończonej. Jeśli jednak pomiar się powiedzie – to przerywamy pętlę, wychodząc z funkcji. Jeśli jednak napotkamy błąd – to pomiary będą powtarzane do skutku (listing 5).

def omron_scan():
while(1):
try:
tpn, tptat = d6t.readData()
if tpn == None:
continue
print(tpn,”PTAT : %.1f” %tptat)
#time.sleep(1.0)
return(tpn)
except IOError:
print(„IOError”)

Dalsze modyfikacje

Mam nadzieję, że przynajmniej część użytkowników udało mi się zachęcić do korzystania z dobrodziejstw systemu Linux i programowania w Pythonie. Mając już pewne, aczkolwiek skromne doświadczenie, program można stworzyć w półtora wieczoru. To świadczy jedynie o mnóstwie dokumentacji i przykładów, dotyczących RaspberryPI jako platformy, a także możliwości języka Python.

Muszę przyznać, że początkowo próbowałem osiągnąć zbliżony efekt, pisząc w języku C, ale tutaj przeszkodą była konieczność analizy ogromnej ilości kodów źródłowych, dotyczących zarówno programowania kamery, jak i różnych funkcji graficznych. Python przypomina tutaj klocki Lego. Mamy dobrze podany zestaw klocków – funkcji i bibliotek, które możemy wykorzystać w swoim kodzie. Mało tego, mnóstwo przykładów w sieci nierzadko pozwala znaleźć prawie gotowe rozwiązanie naszego problemu. Dalsze modyfikacje programu zależą jedynie od naszej wyobraźni – w języku Python mamy np. dostępne moduły odtwarzania dźwięku oraz mnóstwo przepisów na komunikację w sieci czy współpracę z bazami danych.

Zatem po naprawdę drobnych modyfikacjach program mógłby odtwarzać plik .wav z wybranym komunikatem czy też dźwiękiem albo wysłać fotografię osoby w razie wykrycia przekroczenia temperatury na wybrany serwer zewnętrzny – czy to za pomocą ftp, czy https. Tu zachęcam wszystkich do samodzielnej eksploracji fantastycznego świata funkcji i modułów języka Python. Na zakończenie dodam, że udało mi się bez większych problemów uruchomić aplikację na starszym RaspberryPI zero.

Fot.6 Zmontowany układ do bezkontaktowego pomiaru temperatury

Zmontowany układ widać na fotografii 6 a wynik działania programu na fotografii 7. Tu miałem kamerę szerokokątną, więc mapa temperatury nie jest dokładnie odwzorowana. Przy kamerze o kącie zbliżonym do pola widzenia czujnika będzie lepszy efekt. Jeśli nie potrzebujemy połączenia z siecią – taki komputer kosztuje 30 złotych!

Zatem największym kosztem będzie zdobycie samego czujnika. Warto też dodać, że w swojej ofercie OMRON ma czujniki z tej serii o rozdzielczości 32×32 piksele, co otwiera całkiem nowe perspektywy rozwoju projektu.

Fot.7 Mapa temperatur mierzonych przez układ

Ostrzeżenie Wykonane urządzenie nie jest urządzeniem medycznym!

Nie można na jego podstawie wyciągać daleko idących wniosków i należy zachować duży dystans do uzyskanych danych. Problem polega na zaufaniu do wyników – mamy tu cztery klasyczne przypadki – false positive, false negative, true positive i true negative. Nie wchodząc w dalsze szczegóły, jest to problem zdiagnozowania choroby, której nie ma, ewentualnie niezdiagnozowania choroby, która jest – czyli dwa niekorzystne przypadki, których liczbę należy w automatycznej diagnostyce eliminować (oczywiście dążymy do maksymalizacji false negative i true positive – czyli poprawnej klasyfikacji).

Odrębnym problemem jest sama wiarygodność pomiarów przy użyciu czujnika D6t-44L. Temperatura w polu widzenia tego czujnika jest uśredniana po polu widzenia. Co za tym idzie, jeżeli znajdziemy się zbyt daleko od czujnika, to temperatura zmierzona będzie pochodziła nie tylko od naszej twarzy, ale i otoczenia.

Dodatkowo, jak już wszyscy doskonale wiemy, wysoka temperatura pojawia się po pewnym czasie (nawet kilku dni) od zakażenia, zatem proponowane urządzenie może służyć do detekcji podwyższonej temperatury ciała, bez wskazania na jej przyczynę. Ale w okolicznościach walki w pandemią liczy się każdy oręż. Czy ktoś na polu walki pyta sanitariusza, czy ręka, którą powstrzymuje krwawienie, ma atest medyczny?

Do pobrania
Download icon Materiały do: Jak zbudować detektor Koronawirusa? Termometr bezkontaktowy
Tematyka materiału: termometr, koronawirus
AUTOR
Źródło
Elektronika dla Wszystkich czerwiec 2020
Udostępnij
UK Logo
Elektronika dla Wszystkich
Zapisując się na nasz newsletter możesz otrzymać GRATIS
najnowsze e-wydanie magazynu "Elektronika dla Wszystkich"