diff --git a/platypush/plugins/camera/__init__.py b/platypush/plugins/camera/__init__.py
index 28575fab2..dd9152c01 100644
--- a/platypush/plugins/camera/__init__.py
+++ b/platypush/plugins/camera/__init__.py
@@ -34,19 +34,22 @@ class StreamingOutput:
         return buf.startswith(b'\xff\xd8')
 
     def write(self, buf):
-        if self.is_new_frame(buf):
-            if self.raw:
-                with self.ready:
-                    self.raw_frame = buf
-                    self.ready.notify_all()
-            else:
-                # New frame, copy the existing buffer's content and notify all clients that it's available
-                self.buffer.truncate()
-                with self.ready:
-                    self.frame = self.buffer.getvalue()
-                    self.ready.notify_all()
-                self.buffer.seek(0)
+        if not self.is_new_frame(buf):
+            return
 
+        if self.raw:
+            with self.ready:
+                self.raw_frame = buf
+                self.ready.notify_all()
+                return
+
+        # New frame, copy the existing buffer's content and notify all clients that it's available
+        self.buffer.truncate()
+        with self.ready:
+            self.frame = self.buffer.getvalue()
+            self.ready.notify_all()
+
+        self.buffer.seek(0)
         return self.buffer.write(buf)
 
     def close(self):
diff --git a/platypush/plugins/camera/pi.py b/platypush/plugins/camera/pi.py
index 86353fc64..28a5dede3 100644
--- a/platypush/plugins/camera/pi.py
+++ b/platypush/plugins/camera/pi.py
@@ -20,6 +20,8 @@ class CameraPiPlugin(CameraPlugin):
     Requires:
 
         * **picamera** (``pip install picamera``)
+        * **numpy** (``pip install numpy``)
+
     """
 
     _default_resolution = (800, 600)
@@ -64,9 +66,11 @@ class CameraPiPlugin(CameraPlugin):
         self._time_lapse_thread = None
         self._recording_thread = None
         self._streaming_thread = None
+        self._capturing_thread = None
         self._time_lapse_stop_condition = threading.Condition()
         self._recording_stop_condition = threading.Condition()
         self._can_stream = False
+        self._can_capture = False
 
     # noinspection PyUnresolvedReferences,PyPackageRequirements
     def _get_camera(self, **opts):
@@ -88,11 +92,19 @@ class CameraPiPlugin(CameraPlugin):
         """
         Close an active connection to the camera.
         """
+        import picamera
+
         if self._output and self._camera:
-            self._camera.stop_recording()
+            try:
+                self._camera.stop_recording()
+            except picamera.PiCameraNotRecording:
+                pass
 
         if self._camera and not self._camera.closed:
-            self._camera.close()
+            try:
+                self._camera.close()
+            except picamera.PiCameraClosed:
+                pass
 
         self._camera = None
 
@@ -175,12 +187,38 @@ class CameraPiPlugin(CameraPlugin):
             if camera and close:
                 self.close()
 
+    def _raw_capture(self):
+        import numpy as np
+        resolution = self.camera_args['resolution']
+        camera = self._get_camera()
+
+        while self._can_capture:
+            shape = (resolution[1] + (resolution[1]%16),
+                     resolution[0] + (resolution[0]%32),
+                     3)
+
+            frame = np.empty(shape, dtype=np.uint8)
+            camera.capture(frame, 'bgr')
+            frame.reshape((shape[0], shape[1], 3))
+            self._output.write(frame)
+
     def __enter__(self):
         camera = self._get_camera()
-        self._output = StreamingOutput()
-        camera.start_recording(self._output, format='mjpeg')
+        self._output = StreamingOutput(raw=self.stream_raw_frames)
+        self._can_capture = True
+
+        if self.stream_raw_frames:
+            self._capturing_thread = threading.Thread(target=self._raw_capture)
+            self._capturing_thread.start()
+        else:
+            camera.start_recording(self._output, format='mjpeg')
 
     def __exit__(self, exc_type, exc_val, exc_tb):
+        self._can_capture = False
+        if self._capturing_thread:
+            self._capturing_thread.join()
+            self._capturing_thread = None
+
         self.close()
 
     @action
diff --git a/platypush/plugins/qrcode.py b/platypush/plugins/qrcode.py
index cf11ace32..289c609d4 100644
--- a/platypush/plugins/qrcode.py
+++ b/platypush/plugins/qrcode.py
@@ -106,7 +106,8 @@ class QrcodePlugin(Plugin):
         return QrcodeDecodedResponse(results)
 
     def _convert_frame(self, frame) -> Image:
-        assert isinstance(frame, np.ndarray), 'Image conversion only works with numpy arrays for now'
+        assert isinstance(frame, np.ndarray), \
+                'Image conversion only works with numpy arrays for now (got {})'.format(type(frame))
         mode = 'RGB'
         if len(frame.shape) > 2 and frame.shape[2] == 4:
             mode = 'RGBA'
diff --git a/setup.py b/setup.py
index 4fc727f1a..3f7bbd4a4 100755
--- a/setup.py
+++ b/setup.py
@@ -182,7 +182,7 @@ setup(
         # Support for torrents download
         'torrent': ['python-libtorrent'],
         # Support for RaspberryPi camera
-        'picamera': ['picamera'],
+        'picamera': ['picamera', 'numpy'],
         # Support for inotify file monitors
         'inotify': ['inotify'],
         # Support for Google Assistant