Back Original

Poor Man's Polaroid

Skirtingai nei Polaroidas, šis fotoaparatas naudoja čekių spausdintuvą - tokį pat, kuris tau parduotuvėj atspausdina ką ir už kiek pirkai. Nuotraukų kokybė nėra tokia pati kaip Polaroido, bet šios, sakykim, turi savotiško žavesio.

Vargšo žmogaus Polaroidas nereiškia, kad šitas aparatas yra itin pigus, gal labiau papigiai sukurtas, nes pačios dalys man kainavo daugiau nei pigiausias Polaroidas.

Nors dalys kainavo ir daugiau, bet galiausiai išeis sutaupyt ant pačių nuotraukų - Polaroido nuotrauka kainuoja maždaug 1 eurą, o mano fotoaparato nuotrauka - mažiau nei 1 centą (50 metrų čekių popieriaus rulonas kainuoja porą eurų).

Kamera

Šnekant apie kamerą, turiu paminėti, jog ji yra prijungta prie mažo kompiuterio, kuris apdoroja nuotrauką ir nusiunčia ją spausdintuvui.

Kompiuteris - Raspberry Pi Zero

Tai yra tikras kompiuteris, toks pat kaip tavo laptopas ar stacionari dėžė, gal ne toks galingas (nebent nekeitei kompo jau 25 metus), bet vis tiek kompiuteris - gali prijungti ekraną, klaviatūrą, pelę, taip pat yra bluetooth ir wifi. Be viso to, prie jo galima prilituot lempų, mygtukų, sensorių ir užprogramuot juos, kad vykdytų tavo technologines užgaidas.

Taip pat yra jam skirtas kameros modulis, lengvai naudojamas ir neužima daug vietos.

Raspberry Pi kamera su laidu

Elektra

Kompiuterį gali pajungti į rozetę, tačiau toli nuo jos nepafotografuosi, nebent turi labai ilgą elektros laidą. Labai ilgą.

Arba gali nusipirkti pakraunamą batareiką, įtampos keitiklį, krovimo valdiklį ir pasigaminti pakrovėją pats.

Savadarbio pakrovėjo dalys

Kad nereikėtų nešiotis elektros laido ritės ar montuoti batareikų pakrovėjo pačiam, gali įsigyti power banką, ką aš ir padariau. Kažkodėl tai kainavo mažiau nei atskiros dalys.

Power bankas, taip pat rodo baterijos įkrovos lygį

Susirask įrankių, apsauginius akinius ir būk budrus, kada besi kažką aštraus į power banką - jei pradursi pačią bateriją, bus kaip žvakutė ant torto, kurios neįmanoma užpūst, tik žymiai blogiau. Daugumą degančių dalykų gali užgesinti su kibiru vandens, tačiau šitoj situacijoj reikės kibiro smėlio, kad, tiesiogine prasme, uždusintum ugnį.

Power bankas pusiau išrinktas, liepsnų nėra

Išrinktas power bankas, vis dar be liepsnų

Spausdintuvas

Šis spausdintuvas nėra ypatingas - jis tiesiog mažas, reikalauja specifinio popieriaus ir vietoj rašalo spausdina kaitindamas popierių. Gerai, gal kažkiek ir ypatingas... bet kokiu atveju - štai keli spausdintuvai, kuriuos turiu.

Pamiršau padaryti nuotrauką prieš pradėdamas lukštenti vieną iš spausdintuvų

Norint prisijungti prie spausdintuvo, galima naudoti laidą arba bluetooth. Teoriškai gali padaryti nuotrauką su telefonu ir ją nusiųsti spausdintuvui per bluetooth, bet praktiškai tai neturėsi progos pabandyt išrinkti power banką ir padegt namus - pasirinkimas aiškus.

Po teisybei, nelabai daugiau ką turiu pridėt - pirkau juos iš Kinijos, vienas kainavo 20 eurų, kitas 60, turėjo būti vienodi, bet žinoma taip nebuvo. Jei nori įsigyti pats – ieškok modelio "PT-310", ir eee, sėkmės.

Dėžė

Susiradau liniuotę, kad galėčiau išmatuot šitą kreivos formos spausdintuvą.

Galėjo padaryti kvadratinį, bet neee

Pasinaudojau FreeCAD, kad sukurčiau dėžės modelį, ir atspausdinau 3D spausdintuvu.

Išėjo kelios dalys - į vieną įdėsiu spausdintuvą, į kitą bateriją su kompiuteriu, jos turėtų susidėt kaip sumuštinis. Taip pat mygtukų laikiklis ir kosmetinės gražuomenės.

Pašlifavęs sudėjau viską į vietas, kad pamatyt, kaip tai atrodo.

Tuomet užpurškiau glaisto, kad užkišt tarpus.

Ir galiausiai - dažai. Minty geltona su juoda atrodė gerai.

Ė, ir atrodo gerai

Susirinkimas

Beliko užpilt biški klijų, įsukt kelis varžtelius, šiek tiek paprogramuoti ir susirinkimas bus įvykęs. O toliau - susirinkimo dalyviai.

LED lemputės, kas be ko

Įdėjau kelias lemputes, kad būtų aišku, kas vyksta. Mėlyna - elektra yra, žalia - aparatas pasiruošęs naudojimui, raudona - vyksta naudojimas (kitaip sakant, daroma nuotrauka, kai paspaudi mygtuką).

Raspberry Pi neturi įjungimo/išjungimo mygtuko. Gali išjungti ištraukęs maitinimo kabelį (nepatartina), arba įmontuot ir užprogramuot mygtuką, kuris pasakytų kompiuteriui, kad laikas pailsėti (patartina). Deja, šis mygtukas gali tik išjungti kompiuterį, bet neįjungti. Įjungimui prie maitinimo laido įtaisiau jungiklį, kurį reikia paspaust po išjungimo - jis tiesiog atjungia elektrą. Norint vėl įjungti, paspaudi jungiklį ir elektros srovė pradeda tekėti - tas pats, ką darai norėdamas uždegti šviesą tualete.

Išjungimo mygtukas ir elektros jungiklis

Fotoaparato priekyje dar yra mygtukas padaryti nuotraukai - jis matosi viršuj dešinėj, kartu su kompiuteriu, baterija (prispausta juoda plokštele), įjungimo/išjungimo mygtukais ir lemputėmis.

Fotoaparato priekio vidus

Gaila, kad dėl laidų kiekio tai atrodo kaip makaronų lėkštė. Geriausia, ką galėjau padaryti, tai naudoti skirtingų spalvų laidus, kad žinočiau, ką prie ko jungti.

Kitoje fotoaparato pusėje įtaisiau spausdintuvą, pragręžęs jame kelias skyles, kad galėčiau pritvirtint su varžtais.

Ten pat yra power banko modulis (išluptas iš power banko, turi ekraną ir lizdą pakrovimui) ir dar vienas mygtukas, kuris atspausdina paskutinę nuotrauką, jei norėčiau dviejų kopijų (vieną tau ir vieną man).

Zjbs, dar daugiau laidų. Du - mygtukui, keturi - elektrai ir dar pora - dalykui, kurį radau power banke - termometrui. Jei baterija perkaista, termometras tai pajaučia ir atjungia elektros srovę, kad neužsidegtų namai - ką labai vertinu, įtariu, vertina ir mano kaimynai.

Padaryk Nuotrauką

Per daug nesigilinant į detales, programavimui naudojau Python kalbą vien dėl to, kad jau yra daug kitų žmonių parašytų dalykų, kurie man lengvai leis apdoroti nuotraukas, naudoti patį Raspberry Pi ir jo kamerą. Jei smalsu - visą programos kodą gali rast žemiau.

from PIL import Image, ImageEnhance
from escpos.printer import Usb
from picamera2 import Picamera2
import RPi.GPIO as GPIO
import cv2
import numpy as np
import os
import signal
import threading
import time
import io

shots_dir = './shots'
last_image = None
os.makedirs(shots_dir, exist_ok=True)

camera_busy = False
reprint_busy = False

PRINTER_VENDOR_ID = 0x28e9
PRINTER_PRODUCT_ID = 0x0289

GPIO.cleanup()

POWER_LED_PIN = 23
CAMERA_LED_PIN = 4
POWER_BUTTON_PIN = 22
CAMERA_BUTTON_PIN = 27
REPRINT_BUTTON_PIN = 26

GPIO.setmode(GPIO.BCM)
GPIO.setup(POWER_LED_PIN, GPIO.OUT)
GPIO.setup(CAMERA_LED_PIN, GPIO.OUT)
GPIO.setup(POWER_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(CAMERA_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(REPRINT_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

picam = Picamera2()

camera_config = picam.create_still_configuration({"size": (2000, 2000)})
picam.align_configuration(camera_config)
picam.configure(camera_config)
picam.set_controls({"FrameRate": 1})
picam.start()

GPIO.output(CAMERA_LED_PIN, GPIO.LOW)
GPIO.output(POWER_LED_PIN, GPIO.HIGH)

def get_image_brightness(image):
    grayscale = image.convert('L')
    histogram = grayscale.histogram()
    pixels = sum(histogram)
    brightness = sum(i * count for i, count in enumerate(histogram)) / pixels
    return brightness

def histogram_equalization(image):
    img_gray = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2GRAY)
    equalize_hist = cv2.equalizeHist(img_gray)
    equalized_image = Image.fromarray(equalize_hist)
    return equalized_image

def gamma_correction(image, gamma=0.7):
    img_array = np.array(image) / 255.0
    corrected_array = np.power(img_array, gamma)
    corrected_image = Image.fromarray((corrected_array * 255).astype('uint8'))
    return corrected_image

def apply_clahe(image):
    img_gray = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2GRAY)
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
    clahe_applied = clahe.apply(img_gray)
    equalized_image = Image.fromarray(clahe_applied)
    return equalized_image

def contrast_stretch(image, low=50, high=200):
    img_array = np.array(image)
    min_val = np.min(img_array)
    max_val = np.max(img_array)
    
    stretched = (img_array - min_val) * ((high - low) / (max_val - min_val)) + low
    stretched = np.clip(stretched, 0, 255)
    stretched_image = Image.fromarray(stretched.astype('uint8'))
    return stretched_image

def camera_button_callback(channel):
    global last_image, camera_busy

    if camera_busy:
        return

    camera_busy = True

    def job():
        global last_image, camera_busy

        try:
            GPIO.output(CAMERA_LED_PIN, GPIO.HIGH)
            timestamp = int(time.time())
            filename = f"shots/image_{timestamp}.jpg"

            picam.capture_file(filename)

            GPIO.output(CAMERA_LED_PIN, GPIO.LOW)

            image = Image.open(filename)
            image = image.resize((576, int(image.height * 576 / image.width)))

            brightness = get_image_brightness(image)

            if brightness < 60:
                image = histogram_equalization(image)
            elif brightness < 90:
                image = gamma_correction(image)
            elif brightness < 110:
                image = apply_clahe(image)
            elif brightness < 130:
                image = gamma_correction(image)
            else:
                image = contrast_stretch(image)

            converted_filename = f"shots/image_{timestamp}_converted.jpg"
            image.save(converted_filename)

            last_image = io.BytesIO()
            image.save(last_image, format='JPEG')
            last_image.seek(0)

            printer = Usb(PRINTER_VENDOR_ID, PRINTER_PRODUCT_ID) 
            printer.image(converted_filename)
            printer.textln()
            printer.textln()
            printer.close()

            os.remove(converted_filename)
        except Exception as e:
            print(f"Error during capture/print: {e}")

        finally:
            camera_busy = False

    threading.Thread(target=job, daemon=True).start()

def power_button_callback(channel):
    os.system('shutdown now')

def reprint_button_callback(channel):
    global last_image, reprint_busy

    if reprint_busy:
        return

    reprint_busy = True

    def job():
        global last_image, reprint_busy

        try:
            if last_image is None:
                return
            
            GPIO.output(CAMERA_LED_PIN, GPIO.HIGH)

            printer = Usb(PRINTER_VENDOR_ID, PRINTER_PRODUCT_ID) 
            printer.image(Image.open(last_image))
            printer.textln()
            printer.textln()
            printer.close()

            GPIO.output(CAMERA_LED_PIN, GPIO.LOW)
        finally:
            reprint_busy = False

    threading.Thread(target=job, daemon=True).start()

def cleanup():
    GPIO.output(CAMERA_LED_PIN, GPIO.HIGH)
    GPIO.cleanup()
    picam.close()

GPIO.add_event_detect(CAMERA_BUTTON_PIN, GPIO.FALLING, callback=camera_button_callback, bouncetime=200)
GPIO.add_event_detect(POWER_BUTTON_PIN, GPIO.FALLING, callback=power_button_callback, bouncetime=200)
GPIO.add_event_detect(REPRINT_BUTTON_PIN, GPIO.FALLING, callback=reprint_button_callback, bouncetime=200)

stop_event = threading.Event()

def worker():
    while not stop_event.is_set():
        time.sleep(1)

def handle_signal(signal_num, frame):
    cleanup()
    stop_event.set()

signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)

thread = threading.Thread(target=worker, daemon=True)
thread.start()

stop_event.wait()

Raspberry Pi kamera nėra bloga, bet ir ne ypatingai gera - kartais nuotrauka būna per tamsi, kartais per šviesi, todėl kode gali rasti daug funkcijų, kurios apdoroja nuotrauką, priklausomai nuo to, kiek ji yra šviesi.

Skirtingai apdorota ta pati nuotrauka. Labas, Kenny!

Per daugelį bandymų išsiaiškinau, kada su kuria funkcija apdorot nuotrauką, ir aparatas gatavas.

Žemiau gali pažiūrėt video, kaip šitas daiktas veikia.

Jei ir tu nori magiškos foto dėžės - susisiek su manim, kontaktus rasi puslapio apačioj.