
Сразу сделаю оговорку — «Бюджетным полароидом» я эту штуку называю не потому, что она недорогая (ибо детали суммарно мне обошлись дороже, чем самый дешёвый полароид), а потому что это просто наколенночный проект. Который всё же работает. И при этом вышло, что в долгосрок это обходится дешевле — одна фотка на оригинальный полароид стоит около 1 евро, а на эту камеру — около цента (исходя из стоимости рулона термоленты).
Но мы не будем напрямую сравнивать полученные фото, потому что это совсем разные истории. Итак, давайте про эту поделку.
Де-факто у меня получилась моментальная камера, которая печатает фотографии с помощью термопринтера — как чеки на контрольно-кассовых терминалах. Само собой, качество фотографий не такое высокое, как у самопроявляющейся пленки Polaroid. Но своё, гм, очарование у этого тоже есть.
Камера
Камера подключена к небольшому компьютеру (я взял Raspberry Pi Zero), который обрабатывает изображение и отправляет его на принтер.

Этого малыша мне хватило – тут есть порты для монитора, клавиатуры и мыши, а ещё Bluetooth и Wi-Fi. Плюс пара контактов, к которым можно припаять светодиоды, кнопки, датчики и запрограммировать всё это добро на выполнение любых действий.
Сама камера совсем простая в использовании.

Питание
Raspberry Pi нужен источник питания. Конечно, никто не мешает просто взять и подключить его к розетке, но тут мы немного теряем фактор портативности камеры. Можно купить пару аккумуляторов, преобразователь постоянного тока, контроллер заряда аккумулятора и сделать свой собственный перезаряжаемый источник питания.

Или поступить ещё проще (как я) — купить внешний аккумулятор, в котором есть все вышеперечисленное, и разобрать его. Оно ещё, кстати, заметно дешевле, чем покупать все детали отдельно и собирать их потом.

Сразу важное — не забывайте про технику безопасности (особенно защитные очки), ибо если вы будете разбирать павербанк и каким-то острым предметом проткнёте аккумулятор, скорее всего, он загорится. И приятного в этом будет не сильно много. Ну а если вы всё же перестарались, тушите всё это песком, а не водой.


Принтер
Смахивает на обычный (просто маленький) принтер, только ему нужна не обычная бумага и чернила, а тепло.

Подключение — по кабелю или Bluetooth. Вообще, можно упороться и сделать снимок на смартфон, а потом отправить его на принтер по Bluetooth. Но тут уже кому что интереснее.
Про принтеры в целом добавить нечего, я заказал оба из Китая, один за 20 евро, другой — 60. Хотите такой же — используйте для поиска «PT-310».
Корпус
Первым делом я измерил линейкой сам принтер.

Затем запустил старый добрый FreeCAD, чтобы сделать модель и распечатать ее на 3D-принтере.
Вышло несколько деталек: корпус для принтера, корпус для аккумулятора и компьютера. Они должны соединяться, как бутерброд. А, ну и несколько кнопок и декоративных элементов.



Сборка
Как вы видите по количеству деталек, сложной сборку назвать нельзя. Если мы про корпус.

Добавил несколько статусных диодов: синий — питание включено, зеленый — камера включена, красный — делается снимок.
На Raspberry Pi нет кнопки «питание», его можно выключить, отсоединив кабель питания, или запрограммировать кнопку так, чтобы при нажатии она выключала устройство. Но дальше тогда встаёт вопрос — как включить его, если нет кнопки питания? Вынуть кабель питания и вставить его обратно, то есть выполнить цикл включения и выключения.
Меня это не особо устраивало, так что я добавил переключатель, который позволяет подключать кабель питания. Оба переключателя находятся на панели сбоку от камеры.
В общем, у меня получился аналог вот такого процесса.


На передней панели корпуса есть еще одна кнопка — для съемки фотографий. На фото она в правом верхнем углу, рядом с компьютером, аккумулятором (под пластиковой пластиной, которая удерживает его на месте) и кнопками питания с индикаторами.

Да, небольшой бардак с проводами, но по-другому никак. Единственное, что я смог сделать — это промаркировать их по цветам, чтобы понимать, чего куда.
А вот другая сторона корпуса: я просверлил в принтере пару отверстий и прикрепил его к корпусу с помощью винтов.
Здесь тоже есть схема внешнего аккумулятора и кнопка, при нажатии на которую распечатывается последняя сделанная фотография — на случай, если мне понадобится две одинаковые.

Два провода для кнопки, четыре — для питания и два для того, что я нашел в павербанке, — термодатчика. Если аккумулятор перегревается, срабатывает датчик и питание отключается, чтобы не спалить дом. Что вообще неплохо.
Как заставить это работать
Python. Потому что с его помощью можно работать со всеми доступными библиотеками для Raspberry Pi, камерой и функциями обработки изображений.
Если вам интересно, то вот код.
Много кода
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()
Камера Pi в целом-то неплохая, но иногда изображение получается слишком темным, а иногда — слишком светлым. Поэтому в приведенном выше коде вы можете увидеть набор функций, которые регулируют яркость изображения.

Методом проб и ошибок я выяснил, когда какую настройку использовать.


А вот он в действии.
Комментарии (4)

Moog_Prodigy
17.04.2026 11:26Уже была подобная детская камера, тоже с термопринтером и чековой лентой. Дешево. https://habr.com/ru/articles/856610/

tormozedison
17.04.2026 11:26Да, это продаётся готовое. Но на коленке сделанное всё равно интересно.
Вот как на коленке сделать, чтобы изображение на термобумаге не исчезало через несколько месяцев. Кроме, как обвести вручную (тоже можно), способы не придумываются.
zionweeds
Крутяк;))