From 755c8b52ecde9310f2e4e0aec39ae4110675c507 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Wed, 25 Sep 2019 15:36:56 +0200 Subject: [PATCH] Extended features of MLX90640 infrared camera plugin --- docs/source/conf.py | 1 + .../plugins/camera/ir/mlx90640/__init__.py | 84 ++++++++++++++++++- requirements.txt | 3 + 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index ffc0db8c..016f9a5f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -224,6 +224,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers', 'gps', 'picamera', 'pwm3901', + 'PIL', ] sys.path.insert(0, os.path.abspath('../..')) diff --git a/platypush/plugins/camera/ir/mlx90640/__init__.py b/platypush/plugins/camera/ir/mlx90640/__init__.py index e565ba27..1b6d050d 100644 --- a/platypush/plugins/camera/ir/mlx90640/__init__.py +++ b/platypush/plugins/camera/ir/mlx90640/__init__.py @@ -1,7 +1,11 @@ +import base64 +import math import os import subprocess import time +from PIL import Image, ImageCms + from platypush.plugins import Plugin, action @@ -24,15 +28,20 @@ class CameraIrMlx90640Plugin(Plugin): $ make bcm2835 $ make examples/rawrgb I2C_MODE=LINUX + Requires: + + * **mlx90640-library** installation (see instructions above) + * **PIL** image library (``pip install Pillow``) """ _img_size = (24, 32) - def __init__(self, fps=16, skip_frames=2, scale_factor=10, rawrgb_path=None, **kwargs): + def __init__(self, fps=16, skip_frames=2, scale_factor=10, rotate=0, rawrgb_path=None, **kwargs): """ :param fps: Frames per seconds (default: 16) :param skip_frames: Number of frames to be skipped on sensor initialization/warmup (default: 2) :param scale_factor: The camera outputs 24x32 pixels artifacts. Use scale_factor to scale them up to a larger image (default: 10) + :param rotate: Rotation angle in degrees (default: 0) :param rawrgb_path: Specify it if the rawrgb executable compiled from https://github.com/pimoroni/mlx90640-library is in another folder than `/lib/examples`. @@ -47,19 +56,25 @@ class CameraIrMlx90640Plugin(Plugin): assert os.path.isfile(rawrgb_path) self.fps = fps + self.rotate = rotate self.skip_frames = skip_frames self.scale_factor = scale_factor self.rawrgb_path = rawrgb_path @action - def capture(self, frames=1, skip_frames=None): + def capture(self, frames=1, fps=None, skip_frames=None): """ Capture one or multiple frames and return them as raw RGB :param frames: Number of frames to be captured (default: 1) + :param fps: If set it overrides the fps parameter specified on the object (default: None) :param skip_frames: If set it overrides the skip_frames parameter specified on the object (default: None) + :returns: list[str]. Each item is a base64 encoded raw RGB representation of a frame """ + if fps is None: + fps = self.fps + if skip_frames is None: skip_frames = self.skip_frames @@ -71,16 +86,77 @@ class CameraIrMlx90640Plugin(Plugin): stderr=subprocess.PIPE) as camera: while len(captured_frames) < frames: frame = camera.stdout.read(input_size) - size = len(frame) if skip_frames > 0: time.sleep(sleep_time) skip_frames -= 1 continue + frame = base64.encodebytes(frame).decode() captured_frames.append(frame) + time.sleep(sleep_time) - return frames + camera.terminate() + + return captured_frames + + @action + def capture_to_file(self, output_image, frames=1, grayscale=False, fps=None, skip_frames=None, scale_factor=None, rotate=None): + """ + Capture one or multiple frames to one or multiple image files. + + :param output_image: Can be either the path to a single image file or a format string (e.g. 'snapshots/image-{:04d}') in case of multiple frames + :param fps: If set it overrides the fps parameter specified on the object (default: None) + :param frames: Number of frames to be captured (default: 1) + :param grayscale: Save the image as grayscale - black pixels will be colder, white pixels warmer (default: False) + :param skip_frames: If set it overrides the skip_frames parameter specified on the object (default: None) + :param scale_factor: If set it overrides the scale_factor parameter specified on the object (default: None) + :param rotate: If set it overrides the rotate parameter specified on the object (default: None) + :returns: list[str] containing the saved image file names + """ + + if scale_factor is None: + scale_factor = self.scale_factor + + if rotate is None: + rotate = self.rotate + + files = [] + + for i in range(0, frames): + encoded_frame = self.capture(frames=1, fps=fps, skip_frames=skip_frames).output[0] + frame = base64.decodebytes(encoded_frame.encode()) + size = (self._img_size[1], self._img_size[0]) + image = Image.frombytes('RGB', size, frame) + new_image = Image.new('L', image.size) + + if grayscale: + for i in range(0, image.size[0]): + for j in range(0, image.size[1]): + r, g, b = image.getpixel((i, j)) + value = int(2.0*r - 0.5*g - 1.5*b) + + if value > 255: + value = 255 + if value < 0: + value = 0 + + new_image.putpixel((i, j), value) + + image = new_image + + if rotate: + image = image.rotate(rotate) + + if scale_factor != 1: + size = tuple(i*scale_factor for i in size) + image = image.resize(size, Image.ANTIALIAS) + + filename = os.path.abspath(os.path.expanduser(output_image.format(i))) + image.save(filename) + files.append(filename) + + return files # vim:sw=4:ts=4:et: diff --git a/requirements.txt b/requirements.txt index 7633d0f5..bbe26db1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -165,3 +165,6 @@ pyScss # Support for PWM3901 2-Dimensional Optical Flow Sensor # pwm3901 + +# Support for MLX90640 thermal camera +# Pillow