Máte doma starý optický mikroskop z dětství? | Dnes jeden takový vrátíme do 21. století | Připojíme k němu Raspberry Pi a jeho kameru
Před třemi týdny jsme si v našem seriálu o programování elektroniky pohráli s novou oficiální kamerou pro Raspberry Pi Camera Module 3. Její snímací čip Sony IMX708 nabízí 12 megapixelů a modul samotný pak motorizovanou ostřící čočku. Nechybí tedy ani autofokus a softwarové manuální ostření.
Prozkoumejte další články ze seriálu Pojďme programovat elektroniku
Minule jsme nastavovali vzdálenost ostřící čočky otáčením rotačního enkodéru připojeného k Raspberry Pi, no a tentokrát do hry zapojíme starý redakční optický mikroskop AmScope. Není to žádné dělo – spíše taková lepší a už notně zašlá lupa, kterou kdysi doplňoval také nasazovací okulár s kamerou.
Mikroskop před lety používali zejména kolegové z časopisu Computer třeba pro testy tiskáren, prehistorické a dále nevyvíjené ovladače už ale nefungují v novějších verzích Windows a časem se ztratil i samotný okulár s USB kamerou.
Podívejte se na video starého mikroskopu, ke kterému jsme připojili Camera Module 3 a napojili jej na knihovnu OpenCV pro analýzu obrazu:
Adaptér z 3D tiskárny
Namísto původního kamerového okuláru, který měl beztak tragické rozlišení a kvalitu obrazu, proto nasadím na ten běžný optický právě Camera Module 3 připojený k Raspberry Pi 4. Aby to vypadalo k světu, v TinkerCadu jsem si navrhl primitivní nasazovací adaptér s rozměry okuláru, který jsem si poté vyrobil na 3D tiskárně.
Kamera má zorné pole 66°, které se ještě trošku zmenší při zaostření na nejbližší bod, i to je ale stále příliš mnoho, takže při fotografování v plném rozlišení 4608×2592 pixelů využijeme jen středovou část. Zbytek snímku vyplní černé pixely stínu tubusu. Řešením by byla leda výroba vhodné předčočky, která upraví optické parametry našeho kamerového modulu.
Živé video je rychlé, protože z kamery stahujeme snímky v náhledovém rozlišení
Při živém pohledu na obraz kamery ve formě videa, které budeme promítat do okna grafického desktopu Raspberry Pi OS a na běžný monitor, to ale ničemu nevadí, v tu chvíli totiž budeme CM3 používat v režimu náhledu.
Obraz tedy bude mít rozlišení jen 640×480 pixelů a bude se jednat o středový ořez, do kterého nebude tubus téměř vůbec zasahovat. Snížené rozlišení je klíčové proto, abychom mohli s obrazem dále pracovat a promítat jej s co možná nejnižší latencí do okna. Později bychom jej mohli navyšovat a hledat limity toho, co ještě Raspberry Pi utáhne.
Náš mikroskop provádí živou analýzu
Aby byl náš vylepšený mikroskop ještě schopnější než mnohé asijské cetky z AliExpressu, nebudeme v okně promítat jen živý obraz z kamery, ale v reálném čase jej proženeme i několikanásobným zpracováním.
S tím nám pomůže populární knihovna OpenCV pro počítačové vidění. Po spuštění skriptu v Pythonu se proto zobrazí šachovnice čtyř snímků vedle sebe. Originál doplní ještě digitální lupa, negativ, který často pomůže s rozlišením málo kontrastních předělů v pravých barvách, a konečně také černobílý snímek s detekcí hran.
Ten poslední se hodí pro odhalování struktury a může to být také indikátor korektního zaostření (čím více patrné struktury, tím i lepší zaostření).
Program ovládáme klávesovými zkratkami
Okno se čtyřmi náhledy a různým zpracováním reaguje na stisky kláves:
- ESC ukončí program
- p přiblíží náhled digitální lupy o krok
- o oddálí náhled digitální lupy o krok
- s uloží obsah okna jako PNG soubor
- f přepne kameru na režim fotografie a pořídí 12MP neořezaný snímek
Instalujeme OpenCV na Raspberry Pi OS
Knihovna OpenCV pro Python sice není základní součástí systému Raspberry Pi OS, k dispozici je ale balíček pro instalátor pip. Dále předpokládejme, že máte na Malině nejnovější systém Raspberry Pi OS řady Bullseye (podzim 2021 a dál), ve kterém už s kamerou pracujeme skrze vrstvu Libcamera a předinstalovanou knihovnu Picamera2. Vše jsme si vysvětlili v předchozím dílu, takže do něj případně nahlédněte.
Nejprve nainstalujeme prerekvizity pro OpenCV pomocí vestavěného instalátoru Apt:
sudo apt update sudo apt install build-essential cmake pkg-config libjpeg-dev libtiff5-dev libjasper-dev libpng-dev libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libxvidcore-dev libx264-dev libfontconfig1-dev libcairo2-dev libgdk-pixbuf2.0-dev libpango1.0-dev libgtk2.0-dev libgtk-3-dev libatlas-base-dev gfortran libhdf5-dev libhdf5-serial-dev libhdf5-103 python3-pyqt5 python3-dev -y
A poté samotný balíček OpenCV pro Python pomocí instalátoru pip:
pip install opencv-python==4.5.3.56
Anebo pro všechny uživatele:
sudo pip install opencv-python==4.5.3.56
Instalujeme konkrétní verzi OpenCV 4.5.3.56, která zaručeně poběží i na aktuální verzi Raspberry Pi OS. Pokud číslo verze neuvedete, stáhne se ta poslední, která ale nemusí být vždy funkční.
Že je vše v pořádku, ověříte přímo v interpretu Pythonu vložením knihovny a třeba vypsáním verze:
import cv2 cv2.__version__
V mém případě vyskočila chyba odkazují na knihovnu pro práci s komplexními poli NumPy. Pokud se vám to stane také, zkuste ji aktualizovat:
pip install -U numpy
Anebo pro všechny uživatele:
sudo pip install -U numpy
Pokud to stále nebude fungovat, odkážu vás na tento podrobnější návod z GitHubu i s bohatou diskuzí pod ním.
Nejprve nastartujeme kameru
Náš program bude uložený v souboru mikroskop.py a budeme jej moci spustit v interpretu Pythonu příkazem:
python mikroskop.py
Komentovaný zdrojový kód najdete níže, takže si jen stručně projdeme, co se v něm vlastně odehrává.
Na začátku vytvoříme objekt kamery pomocí knihovny Picamera2 pro Python. Ta je součástí systému a nemusíte ji ručně instalovat. Picamera2 pracuje se všemi oficiálními kamerami od Raspberry Pi.
Poté vytvoříme dva profily:
- Náhledový ve formátu YUV420p
- Plnohodnotný profil pro pořizování 12MP fotografií
Každý z profilů může mít hromadu dalších a pokročilých voleb, to už vás ale opět odkážu na oficiální dokumentaci v PDF.
Nakonec nakonfigurujeme kameru s náhledovým profilem a nastartujeme ji.
Ve smyčce čteme snímek za snímkem
Veškerý následující běh programu se odehrává v nekonečné smyčce, ze které vyskočíme stisknutím klávesy ESC (fokus musí mít okno s obrazem z kamery), což poté povede i k ukončení celého skriptu.
V každém cyklu smyčky přečteme pomocí metody capture_array snímek v aktuálním snímacím profilu jako multidimenzionální pole pixelů. Pole je ve formátu knihovny NumPy, se kterým pracuje i OpenCV.
Náhledové záběry stahujeme ve formátu YUV420p, pro jednodušší práci jej proto dále převedeme na formát RGB. Ze snímku poté vytvoříme několik dílčích kopií, přičemž každou z nich proženeme různým filtrem. Jeden bitmapu zvětší a ořeže na stejný rozměr, druhý pomocí vestavěné funkce Canny pro detekci hran algoritmem canny edge detection najde struktury v obrazu, no a třetí elementární metodou bitwise_not spočítá opačnou hodnotu pixelů pro negativ.
Negativní barvou kanálů R, G, a B je v případě osmibitové hloubky vlastně jen rozdíl 255 – hodnota. Z naprosté černé (0, 0, 0) se tedy stane bílá (255, 255, 255), z tmavě šedé (20, 20, 20), téměř bílá (235, 235, 235) a tak dále.
Ještě to poskládat dohromady
Jelikož jsou jednotlivé snímky ve své podstatě jen pole pixelů, můžeme tato pole složit do šachovnice – jednoho velkého pole. Pomůže nám s tím knihovna NumPy a její metody hstack (horizontální skládání) a vstack (vertikální skládání). Tuto složenou scénu konečně zobrazíme na monitoru metodou knihovny OpenCV imshow.
Při stisku „s“ uložíme stejné pole jako obrázek ve formátu PNG pomocí metody imwrite. Pro pořízení plné fotografie při stisku „f“ pak pomocí metody switch_mode knihovny Picamera2 nejprve přepneme na v úvodu vytvořený profil pro fotografie, metodou capture_file uložíme fotografii do souboru, a opětovným voláním metody switch_mode se vrátíme do náhledového režimu.
V obrazu si přepnutí všimnete probliknutím obrazu při novém měření expozice, jinak by ale mělo vše zabrat jen zlomek času.
Kompletní kód mikroskopu v Pythonu pro knihovny Picamera2 a OpenCV
Dnes jsme si tedy ukázali, k čemu jsou dobré prototypovací kamerové moduly pro Raspberry Pi. Nemusejí hned nahrazovat webkameru nebo fotoaparát ve vašem mobilu, ale jako rychlý prototyp vrátí do 21. století třeba starý školní mikroskop z dob uhlí a páry jako v dnešním experimentu.
# Oficialni knihovna Raspberry Pi Picamera2 pro praci s kamwerami # skrze novou vrstvu Libcamera, kterou Raspberry Pi OS pouziva od verze “Bullseye” (podzim 2021) # Manual: https://datasheets.raspberrypi.com/camera/picamera2-manual.pdf from picamera2 import Picamera2 # Knihovna pro analyzu obrazu # Instalace pro Python na Raspberry Pi OS: # https://raspberrypi-guide.github.io/programming/install-opencv # https://opencv.org import cv2 # Knihovna pro pokrocile operace s kompelxnimi poli # https://numpy.org import numpy as np # Vestavena knihovna pro zjisteni aktualniho data a casu # Budeme pouzivat v nazvech ukladanych snimku from datetime import datetime # Pomocna funkce pro digitalni zoom a orez na stejny rozmer # Pozor, pri velkem zoomu narocne, nejprve totiz snimek zvetsim a teprve pak orezu # Pokud operace otocim, nepotrebuji tolik RAM a CPU, ale slozitejsi kod def priblizit_a_vycentrovat(snimek, _priblizeni=2): vyska, sirka = snimek.shape zvetseny = cv2.resize(snimek, None, fx=_priblizeni, fy=_priblizeni) _vyska, _sirka = zvetseny.shape x = int(_sirka/2) – int(sirka/2) y = int(_vyska/2) – int(vyska/2) orezany = zvetseny return orezany # Zacatek behu naseho programu if __name__ == “__main__”: print(“Startuji mikroskop…”) priblizeni = 2 popisky = True # Vytvorim objekt kamery kamera = Picamera2() # Vytvorim konfiguraci pro rychle/nahledove snaimani obrazu ve vychozim nizkem rozliseni 640×480 a barevnem formatu YUV420p # Diky tomu bude Raspberry Pi stihat zobrazovat snimky videa ve vysoke rychlosti config_nahled = kamera.create_preview_configuration({“format”: “YUV420″}) config_fotoaparat = kamera.create_still_configuration() # Nakonfigurujeme a nastartujeme kameru kamera.configure(config_nahled) kamera.start() while True: # Ziskam pole pixelu v barevnem fotmatu YUV420p yuv420 = kamera.capture_array() # Vytvorim si novy snimek, ktery uz bude v RGB snimek = cv2.cvtColor(yuv420, cv2.COLOR_YUV420P2RGB) # Vytvorim priblizeny snimek stejnych rozmeru vycentrovany na stred # Priblizeni mohu ridit pomoci klaves p/o # !!! Pozor, narocny processing kvuli jendoduchosti kodu !!! # Pri priblizeni 10x a vice uz pomalejsi, protoze nejprve snimek zvetsim a pak teprve orezu # Pri zvetseni 10x a vice tedy vytvarim velmi velky obrazek # Jak to predelat? Nejprve podle miry priblizeni spocitam, # jak ma vypadat vyrez, pote jej vyriznu a ten zvetsim na stejne rozmery nahledu snimek_priblizeny = priblizit_a_vycentrovat(snimek, priblizeni) # Vytvorim snimek s detekovanymi hranami pomoci vestaveneho detektoru # a algorimtu Canny Edge Detection (https://docs.opencv.org/4.x/da/d22/tutorial_py_canny.html) # Nejprve snimek zjednodusim prevodem do odstinu sedi mirnym rozmazanim snimek_sedy = cv2.cvtColor(snimek, cv2.COLOR_RGB2GRAY) snimek_rozmazany = cv2.GaussianBlur(snimek_sedy, (3,3), 0) snimek_detekce_hran = cv2.Canny(image=snimek_rozmazany, threshold1=100, threshold2=200) # Snimek je v odstinech sedi, a tak je prevedu zpet do RGB, protoze # pri finalnim skladani scen museji mit vsechny dilci snimky stejny format dat snimek_detekce_hran = cv2.cvtColor(snimek_detekce_hran, cv2.COLOR_GRAY2RGB) # Vytvorim snimek barevneho negativu, ktery muze odhalit skryte detaily # Negativni 8bitovy pixel je vlastne prtepocet 255-pixel. Za nas to udela # rychla vestavena metoda bitwise_not (https://docs.opencv.org/3.4/d0/d86/tutorial_py_image_arithmetics.html) snimek_negativ = cv2.bitwise_not(snimek) # Pripojeni zelenych popisku do jednotlivych snimku if popisky: snimek = cv2.putText(snimek, f”Original”, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 1, cv2.LINE_AA) snimek_priblizeny = cv2.putText(snimek_priblizeny, f”{priblizeni}x”, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 1, cv2.LINE_AA) snimek_negativ = cv2.putText(snimek_negativ, f”Negativ”, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 1, cv2.LINE_AA) snimek_detekce_hran = cv2.putText(snimek_detekce_hran, f”Hrany”, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 1, cv2.LINE_AA) # Pomoci knihovny NumPy pro operace s komplexnimi poli spojim vzdy # dva snimky (tedy pole pixelu) do jednoho radku (vetsiho pole) # pomoci metody hstack (https://numpy.org/doc/stable/reference/generated/numpy.hstack.html) radek1 = np.hstack((snimek, snimek_priblizeny)) radek2 = np.hstack((snimek_negativ, snimek_detekce_hran)) # Nyni analogicky spojim oba radky vertikalne do finaln isceny pomoci # metody vstack (https://numpy.org/doc/stable/reference/generated/numpy.vstack.html) scena = np.vstack((radek1, radek2)) # ZObrazim primitivni GUI okno se scenou cv2.imshow(“Živě.cz mikroskop”, scena) # Detekce stisku klavesy klavesa = cv2.waitKey(1) if klavesa == 27: # ESC ukonci smycku break elif klavesa == ord(“s”): # s ulozi scenu jako PNG s datem a casem v nazvu print(“Ukladam snimek…”) datum = datetime.today().strftime(“%y%m%d_%H%M%S”) cv2.imwrite(f”scena_{datum}.png”, scena) elif klavesa == ord(“f”): # f ulozi fotografii v plnem rozliseni a JPEG opet s datem a casem v nazvu print(“Ukladam fotografii v plnem rozliseni”) kamera.switch_mode(config_fotoaparat) kamera.capture_file(f”fotografie_{datum}.jpg”) kamera.switch_mode(config_nahled) elif klavesa == ord(“p”): # p priblizi snimek o 0.5 (max 10 kvuli narocne operaci) priblizeni += .5 if priblizeni > 10: priblizeni = 10 elif klavesa == ord(“o”): # o oddali snimek o 0.5 priblizeni -= .5 if priblizeni < 1: priblizeni = 1 elif klavesa == ord("t"): # t zobrazi/skryje textove popisky if popisky: popisky = False else: popisky = True kamera.stop() print("Ukončuji mikroskop...")
Přečtěte si také:
- Alza prodává herní počítače jako IKEA nábytek. Pošle díly, vy si je poskládáte
- Prezident z 21. století. Petr Pavel nasdílel novinářům svůj Google kalendář
- Návod, jak dostat maximum z Peněženky Google