├── LICENSE.md ├── README.md ├── jetcam ├── __init__.py ├── camera.py ├── csi_camera.py ├── usb_camera.py └── utils.py ├── notebooks ├── csi_camera │ ├── csi_camera.ipynb │ └── multi_csi_camera.ipynb └── usb_camera │ └── usb_camera.ipynb └── setup.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JetCam 2 | 3 | JetCam is an easy to use Python camera interface for NVIDIA Jetson. 4 | 5 | * Works with various USB and CSI cameras using Jetson's [Accelerated GStreamer Plugins](https://developer.download.nvidia.com/embedded/L4T/r32_Release_v1.0/Docs/Accelerated_GStreamer_User_Guide.pdf?uIzwdFeQNE8N-vV776ZCUUEbiJxYagieFEqUoYFM9XSf9tbslxWqFKnVHu8erbZZS20A7ADAIgmSQJvXZTb0LkuGl9GoD5HJz4263HcmYWZW0t2OeFSJKZOfuWZ-lF51Pva2DSDtu2QPs-junm7BhMB_9AMQRwExuDb5zIhf_o8PIbA4KKo) 6 | * Easily read images as ``numpy`` arrays with ``image = camera.read()`` 7 | 8 | * Set the camera to ``running = True`` to attach callbacks to new frames 9 | 10 | JetCam makes it easy to prototype AI projects in Python, especially within the Jupyter Lab programming environment installed in [JetCard](http://github.com/NVIDIA-AI-IOT/jetcard). 11 | 12 | If you find an issue, please [let us know](../..//issues)! 13 | 14 | ## Setup 15 | 16 | ```bash 17 | git clone https://github.com/NVIDIA-AI-IOT/jetcam 18 | cd jetcam 19 | sudo python3 setup.py install 20 | ``` 21 | 22 | > JetCam is tested against a system configured with the [JetCard](http://github.com/NVIDIA-AI-IOT/jetcard) setup. Different system configurations may require additional steps. 23 | 24 | ## Usage 25 | 26 | Below we show some usage examples. You can find more in the [notebooks](notebooks). 27 | 28 | ### Create CSI camera 29 | 30 | Call ``CSICamera`` to use a compatible CSI camera. ``capture_width``, ``capture_height``, and ``capture_fps`` will control the capture shape and rate that images are aquired. ``width`` and ``height`` control the final output shape of the image as returned by the ``read`` function. 31 | 32 | ```python 33 | from jetcam.csi_camera import CSICamera 34 | 35 | camera = CSICamera(width=224, height=224, capture_width=1080, capture_height=720, capture_fps=30) 36 | ``` 37 | 38 | ### Create USB camera 39 | 40 | Call ``USBCamera`` to use a compatbile USB camera. The same parameters as ``CSICamera`` apply, along with a parameter ``capture_device`` that indicates the device index. You can check the device index by calling ``ls /dev/video*``. 41 | 42 | ```python 43 | from jetcam.usb_camera import USBCamera 44 | 45 | camera = USBCamera(capture_device=1) 46 | ``` 47 | 48 | ### Read 49 | 50 | Call ``read()`` to read the latest image as a ``numpy.ndarray`` of data type ``np.uint8`` and shape ``(224, 224, 3)``. The color format is ``BGR8``. 51 | 52 | ```python 53 | image = camera.read() 54 | ``` 55 | 56 | The ``read`` function also updates the camera's internal ``value`` attribute. 57 | 58 | ```python 59 | camera.read() 60 | image = camera.value 61 | ``` 62 | 63 | ### Callback 64 | 65 | You can also set the camera to ``running = True``, which will spawn a thread that acquires images from the camera. These will update the camera's ``value`` attribute automatically. You can attach a callback to the value using the [traitlets](https://traitlets.readthedocs.io/en/stable/api.html#callbacks-when-trait-attributes-change) library. This will call the callback with the new camera value as well as the old camera value 66 | 67 | ```python 68 | camera.running = True 69 | 70 | def callback(change): 71 | new_image = change['new'] 72 | # do some processing... 73 | 74 | camera.observe(callback, names='value') 75 | ``` 76 | 77 | ## Cameras 78 | 79 | ### CSI Cameras 80 | 81 | These cameras work with the [``CSICamera``](jetcam/csi_camera.py) class. Try them out by following the example [notebook](notebooks/csi_camera/csi_camera.ipynb). 82 | 83 | | Model | Infared | FOV | Resolution | Cost | 84 | |:-------|:-----:|:---:|:---:|:----:| 85 | | [Raspberry Pi Camera V2](https://www.amazon.com/Raspberry-Pi-Camera-Module-Megapixel/dp/B01ER2SKFS/ref=sr_1_3?keywords=raspberry+pi+v2+camera&qid=1554831689&s=electronics&sr=1-3) | | 62.2 | 3280x2464 | $25 | 86 | | [Raspberry Pi Camera V2 (NOIR)](https://www.amazon.com/RPi-Camera-V2-Official-Raspberry/dp/B07P7GBJTK/ref=sr_1_1_sspa?keywords=raspberry+pi+v2+camera&qid=1554831658&s=electronics&sr=1-1-spons&psc=1) | x | 62.2 | 3280x2464 | $31 | 87 | | [Arducam IMX219 CS lens mount](https://www.robotshop.com/en/arducam-8mp-sony-imx219-camera-module-cs-lens-2718-raspberry-pi.html?gclid=EAIaIQobChMIzMKg38bD4QIVrR6tBh3UoAdjEAYYCSABEgLg-_D_BwE) | | | 3280x2464 | $65 | 88 | | [Arducam IMX219 M12 lens mount](https://www.robotshop.com/en/arducam-8mp-sony-imx219-camera-module-m12-lens-ls40136-raspberry-pi.html) | | | 3280x2464 | $60 | 89 | | [LI-IMX219-MIPI-FF-NANO](https://leopardimaging.com/product/li-imx219-mipi-ff-nano/) | | | 3280x2464 | $29 | 90 | | [WaveShare IMX219-77](https://www.waveshare.com/IMX219-77-Camera.htm) | | 77 | 3280x2464 | $19 | 91 | | [WaveShare IMX219-77IR](https://www.waveshare.com/IMX219-77IR-Camera.htm) | x | 77 | 3280x2464 | $21 | 92 | | [WaveShare IMX219-120](https://www.waveshare.com/IMX219-120-Camera.htm) | | 120 | 3280x2464 | $20 | 93 | | [WaveShare IMX219-160](https://www.waveshare.com/IMX219-160-Camera.htm) | | 160 | 3280x2464 | $23 | 94 | | [WaveShare IMX219-160IR](https://www.waveshare.com/IMX219-160IR-Camera.htm) | x | 160 | 3280x2464 | $25 | 95 | | [WaveShare IMX219-200](https://www.waveshare.com/IMX219-200-Camera.htm) | | 200 | 3280x2464 | $27 | 96 | 97 | ### USB Cameras 98 | 99 | These cameras work with the [``USBCamera``](jetcam/usb_camera.py) class. Try them out by following the example [notebook](notebooks/usb_camera/usb_camera.ipynb). 100 | 101 | | Model | Infared | FOV | Resolution | Cost | 102 | |:-------|:-----:|:---:|:---:|:----:| 103 | | [Logitech C270](https://www.amazon.com/Logitech-Widescreen-designed-Calling-Recording/dp/B004FHO5Y6) | | 60 | 1280x720 | $18 | 104 | 105 | ## See also 106 | 107 | - [JetBot](http://github.com/NVIDIA-AI-IOT/jetbot) - An educational AI robot based on NVIDIA Jetson Nano 108 | 109 | - [JetRacer](http://github.com/NVIDIA-AI-IOT/jetracer) - An educational AI racecar using NVIDIA Jetson Nano 110 | - [JetCard](http://github.com/NVIDIA-AI-IOT/jetcard) - An SD card image for web programming AI projects with NVIDIA Jetson Nano 111 | - [torch2trt](http://github.com/NVIDIA-AI-IOT/torch2trt) - An easy to use PyTorch to TensorRT converter 112 | -------------------------------------------------------------------------------- /jetcam/__init__.py: -------------------------------------------------------------------------------- 1 | from .camera import Camera -------------------------------------------------------------------------------- /jetcam/camera.py: -------------------------------------------------------------------------------- 1 | import traitlets 2 | import threading 3 | import numpy as np 4 | 5 | 6 | class Camera(traitlets.HasTraits): 7 | 8 | value = traitlets.Any() 9 | width = traitlets.Integer(default_value=224) 10 | height = traitlets.Integer(default_value=224) 11 | format = traitlets.Unicode(default_value='bgr8') 12 | running = traitlets.Bool(default_value=False) 13 | 14 | def __init__(self, *args, **kwargs): 15 | super(Camera, self).__init__(*args, **kwargs) 16 | if self.format == 'bgr8': 17 | self.value = np.empty((self.height, self.width, 3), dtype=np.uint8) 18 | self._running = False 19 | 20 | def _read(self): 21 | """Blocking call to read frame from camera""" 22 | raise NotImplementedError 23 | 24 | def read(self): 25 | if self._running: 26 | raise RuntimeError('Cannot read directly while camera is running') 27 | self.value = self._read() 28 | return self.value 29 | 30 | def _capture_frames(self): 31 | while True: 32 | if not self._running: 33 | break 34 | self.value = self._read() 35 | 36 | @traitlets.observe('running') 37 | def _on_running(self, change): 38 | if change['new'] and not change['old']: 39 | # transition from not running -> running 40 | self._running = True 41 | self.thread = threading.Thread(target=self._capture_frames) 42 | self.thread.start() 43 | elif change['old'] and not change['new']: 44 | # transition from running -> not running 45 | self._running = False 46 | self.thread.join() -------------------------------------------------------------------------------- /jetcam/csi_camera.py: -------------------------------------------------------------------------------- 1 | from .camera import Camera 2 | import atexit 3 | import cv2 4 | import numpy as np 5 | import threading 6 | import traitlets 7 | 8 | 9 | class CSICamera(Camera): 10 | 11 | capture_device = traitlets.Integer(default_value=0) 12 | capture_fps = traitlets.Integer(default_value=30) 13 | capture_width = traitlets.Integer(default_value=640) 14 | capture_height = traitlets.Integer(default_value=480) 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(CSICamera, self).__init__(*args, **kwargs) 18 | try: 19 | self.cap = cv2.VideoCapture(self._gst_str(), cv2.CAP_GSTREAMER) 20 | 21 | re, image = self.cap.read() 22 | 23 | if not re: 24 | raise RuntimeError('Could not read image from camera.') 25 | except: 26 | raise RuntimeError( 27 | 'Could not initialize camera. Please see error trace.') 28 | 29 | atexit.register(self.cap.release) 30 | 31 | def _gst_str(self): 32 | return 'nvarguscamerasrc sensor-id=%d ! video/x-raw(memory:NVMM), width=%d, height=%d, format=(string)NV12, framerate=(fraction)%d/1 ! nvvidconv ! video/x-raw, width=(int)%d, height=(int)%d, format=(string)BGRx ! videoconvert ! appsink' % ( 33 | self.capture_device, self.capture_width, self.capture_height, self.capture_fps, self.width, self.height) 34 | 35 | def _read(self): 36 | re, image = self.cap.read() 37 | if re: 38 | return image 39 | else: 40 | raise RuntimeError('Could not read image from camera') -------------------------------------------------------------------------------- /jetcam/usb_camera.py: -------------------------------------------------------------------------------- 1 | from .camera import Camera 2 | import atexit 3 | import cv2 4 | import numpy as np 5 | import threading 6 | import traitlets 7 | 8 | 9 | class USBCamera(Camera): 10 | 11 | capture_fps = traitlets.Integer(default_value=30) 12 | capture_width = traitlets.Integer(default_value=640) 13 | capture_height = traitlets.Integer(default_value=480) 14 | capture_device = traitlets.Integer(default_value=0) 15 | 16 | def __init__(self, *args, **kwargs): 17 | super(USBCamera, self).__init__(*args, **kwargs) 18 | try: 19 | self.cap = cv2.VideoCapture(self._gst_str(), cv2.CAP_GSTREAMER) 20 | 21 | re , image = self.cap.read() 22 | 23 | if not re: 24 | raise RuntimeError('Could not read image from camera.') 25 | 26 | except: 27 | raise RuntimeError( 28 | 'Could not initialize camera. Please see error trace.') 29 | 30 | atexit.register(self.cap.release) 31 | 32 | def _gst_str(self): 33 | return 'v4l2src device=/dev/video{} ! video/x-raw, width=(int){}, height=(int){}, framerate=(fraction){}/1 ! videoconvert ! video/x-raw, format=(string)BGR ! appsink'.format(self.capture_device, self.capture_width, self.capture_height, self.capture_fps) 34 | 35 | def _read(self): 36 | re, image = self.cap.read() 37 | if re: 38 | image_resized = cv2.resize(image,(int(self.width),int(self.height))) 39 | return image_resized 40 | else: 41 | raise RuntimeError('Could not read image from camera') 42 | -------------------------------------------------------------------------------- /jetcam/utils.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | 3 | 4 | def bgr8_to_jpeg(value, quality=75): 5 | return bytes(cv2.imencode('.jpg', value)[1]) -------------------------------------------------------------------------------- /notebooks/csi_camera/csi_camera.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "First, we create our camera class like this. Please note, you can only create one CSICamera instance." 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from jetcam.csi_camera import CSICamera\n", 17 | "\n", 18 | "camera = CSICamera(width=224, height=224)" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "We can then capture a frame from the camera like this" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 4, 31 | "metadata": {}, 32 | "outputs": [ 33 | { 34 | "name": "stdout", 35 | "output_type": "stream", 36 | "text": [ 37 | "(224, 224, 3)\n" 38 | ] 39 | } 40 | ], 41 | "source": [ 42 | "image = camera.read()\n", 43 | "\n", 44 | "print(image.shape)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "Calling ``read`` also updates the camera's internal ``value``" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 8, 57 | "metadata": {}, 58 | "outputs": [ 59 | { 60 | "name": "stdout", 61 | "output_type": "stream", 62 | "text": [ 63 | "(224, 224, 3)\n" 64 | ] 65 | } 66 | ], 67 | "source": [ 68 | "print(camera.value.shape)" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "You can create a widget to display this image. You'll need to convert from bgr8 format to jpeg to stream to browser" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 9, 81 | "metadata": {}, 82 | "outputs": [ 83 | { 84 | "data": { 85 | "application/vnd.jupyter.widget-view+json": { 86 | "model_id": "d909a510a1634de5822dfffd9aa06d6c", 87 | "version_major": 2, 88 | "version_minor": 0 89 | }, 90 | "text/plain": [ 91 | "Image(value=b'\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\xff\\xdb\\x00C\\x00\\x02\\x01\\x0…" 92 | ] 93 | }, 94 | "metadata": {}, 95 | "output_type": "display_data" 96 | } 97 | ], 98 | "source": [ 99 | "import ipywidgets\n", 100 | "from IPython.display import display\n", 101 | "from jetcam.utils import bgr8_to_jpeg\n", 102 | "\n", 103 | "image_widget = ipywidgets.Image(format='jpeg')\n", 104 | "\n", 105 | "image_widget.value = bgr8_to_jpeg(image)\n", 106 | "\n", 107 | "display(image_widget)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "You can set the ``running`` value of the camera to continuously update the value in background. This allows you to attach callbacks to the camera value changes. " 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 10, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "camera.running = True\n", 124 | "\n", 125 | "def update_image(change):\n", 126 | " image = change['new']\n", 127 | " image_widget.value = bgr8_to_jpeg(image)\n", 128 | " \n", 129 | "camera.observe(update_image, names='value')" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "You can unattach the callback like this" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 11, 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [ 145 | "camera.unobserve(update_image, names='value')" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "You can also use the traitlets ``dlink`` method to connect the camera to the widget, using a transform inbetween" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 12, 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "import traitlets\n", 162 | "\n", 163 | "camera_link = traitlets.dlink((camera, 'value'), (image_widget, 'value'), transform=bgr8_to_jpeg)" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "You can remove this link like this" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 8, 176 | "metadata": {}, 177 | "outputs": [], 178 | "source": [ 179 | "camera_link.unlink()" 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "And reconnect it like this" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "camera_link.link()" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "That's all for this notebook!" 203 | ] 204 | } 205 | ], 206 | "metadata": { 207 | "kernelspec": { 208 | "display_name": "Python 3", 209 | "language": "python", 210 | "name": "python3" 211 | }, 212 | "language_info": { 213 | "codemirror_mode": { 214 | "name": "ipython", 215 | "version": 3 216 | }, 217 | "file_extension": ".py", 218 | "mimetype": "text/x-python", 219 | "name": "python", 220 | "nbconvert_exporter": "python", 221 | "pygments_lexer": "ipython3", 222 | "version": "3.6.7" 223 | } 224 | }, 225 | "nbformat": 4, 226 | "nbformat_minor": 2 227 | } 228 | -------------------------------------------------------------------------------- /notebooks/csi_camera/multi_csi_camera.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "First, we create our camera class like this. Please note, you can only create one CSICamera instance." 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from jetcam.csi_camera import CSICamera\n", 17 | "\n", 18 | "camera0 = CSICamera(capture_device=0, width=224, height=224)\n", 19 | "camera1 = CSICamera(capture_device=1, width=224, height=224)" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "We can then capture a frame from the camera like this" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "image0 = camera0.read()\n", 36 | "\n", 37 | "print(image0.shape)" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "image1 = camera1.read()\n", 47 | "\n", 48 | "print(image1.shape)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "Calling ``read`` also updates the camera's internal ``value``" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "print(camera0.value.shape)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "print(camera1.value.shape)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "You can create a widget to display this image. You'll need to convert from bgr8 format to jpeg to stream to browser" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "import ipywidgets\n", 90 | "from IPython.display import display\n", 91 | "from jetcam.utils import bgr8_to_jpeg\n", 92 | "\n", 93 | "image0_widget = ipywidgets.Image(format='jpeg')\n", 94 | "image1_widget = ipywidgets.Image(format='jpeg')\n", 95 | "\n", 96 | "image0_widget.value = bgr8_to_jpeg(image0)\n", 97 | "image1_widget.value = bgr8_to_jpeg(image1)\n", 98 | "\n", 99 | "ipywidgets.HBox([image0_widget, image1_widget])" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "You can set the ``running`` value of the camera to continuously update the value in background. This allows you to attach callbacks to the camera value changes. " 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "camera0.running = True\n", 116 | "camera1.running = True\n", 117 | "\n", 118 | "def update_image0(change):\n", 119 | " image0 = change['new']\n", 120 | " image0_widget.value = bgr8_to_jpeg(image0)\n", 121 | "def update_image1(change):\n", 122 | " image1 = change['new']\n", 123 | " image1_widget.value = bgr8_to_jpeg(image1)\n", 124 | " \n", 125 | "camera0.observe(update_image0, names='value')\n", 126 | "camera1.observe(update_image1, names='value')" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "metadata": {}, 132 | "source": [ 133 | "You can unattach the callback like this" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "camera0.unobserve(update_image0, names='value')" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "camera1.unobserve(update_image1, names='value')" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "You can also use the traitlets ``dlink`` method to connect the camera to the widget, using a transform inbetween" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [ 167 | "import traitlets\n", 168 | "\n", 169 | "camera0_link = traitlets.dlink((camera0, 'value'), (image0_widget, 'value'), transform=bgr8_to_jpeg)\n", 170 | "camera1_link = traitlets.dlink((camera1, 'value'), (image1_widget, 'value'), transform=bgr8_to_jpeg)" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": {}, 176 | "source": [ 177 | "You can remove this link like this" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "camera0_link.unlink()" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "camera1_link.unlink()" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "And reconnect it like this" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": {}, 209 | "outputs": [], 210 | "source": [ 211 | "camera0_link.link()" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [ 220 | "camera1_link.link()" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "That's all for this notebook!" 228 | ] 229 | } 230 | ], 231 | "metadata": { 232 | "kernelspec": { 233 | "display_name": "Python 3", 234 | "language": "python", 235 | "name": "python3" 236 | }, 237 | "language_info": { 238 | "codemirror_mode": { 239 | "name": "ipython", 240 | "version": 3 241 | }, 242 | "file_extension": ".py", 243 | "mimetype": "text/x-python", 244 | "name": "python", 245 | "nbconvert_exporter": "python", 246 | "pygments_lexer": "ipython3", 247 | "version": "3.6.8" 248 | } 249 | }, 250 | "nbformat": 4, 251 | "nbformat_minor": 4 252 | } 253 | -------------------------------------------------------------------------------- /notebooks/usb_camera/usb_camera.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "First, we create our camera class like this. Please note, you can only create one USBCamera instance." 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from jetcam.usb_camera import USBCamera\n", 17 | "\n", 18 | "camera = USBCamera(width=224, height=224, capture_width=640, capture_height=480, capture_device=1)" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "We can then capture a frame from the camera like this" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "image = camera.read()\n", 35 | "\n", 36 | "print(image.shape)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "Calling ``read`` also updates the camera's internal ``value``" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "print(camera.value.shape)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "You can create a widget to display this image. You'll need to convert from bgr8 format to jpeg to stream to browser" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "import ipywidgets\n", 69 | "from IPython.display import display\n", 70 | "from jetcam.utils import bgr8_to_jpeg\n", 71 | "\n", 72 | "image_widget = ipywidgets.Image(format='jpeg')\n", 73 | "\n", 74 | "image_widget.value = bgr8_to_jpeg(image)\n", 75 | "\n", 76 | "display(image_widget)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "You can set the ``running`` value of the camera to continuously update the value in background. This allows you to attach callbacks to the camera value changes. " 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 5, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "camera.running = True\n", 93 | "\n", 94 | "def update_image(change):\n", 95 | " image = change['new']\n", 96 | " image_widget.value = bgr8_to_jpeg(image)\n", 97 | " \n", 98 | "camera.observe(update_image, names='value')" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "You can unattach the callback like this" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 7, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "camera.unobserve(update_image, names='value')" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "You can also use the traitlets ``dlink`` method to connect the camera to the widget, using a transform inbetween" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 8, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "import traitlets\n", 131 | "\n", 132 | "camera_link = traitlets.dlink((camera, 'value'), (image_widget, 'value'), transform=bgr8_to_jpeg)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "You can remove this link like this" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 9, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "camera_link.unlink()" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "And reconnect it like this" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": null, 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [ 164 | "camera_link.link()" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "That's all for this notebook!" 172 | ] 173 | } 174 | ], 175 | "metadata": { 176 | "kernelspec": { 177 | "display_name": "Python 3", 178 | "language": "python", 179 | "name": "python3" 180 | }, 181 | "language_info": { 182 | "codemirror_mode": { 183 | "name": "ipython", 184 | "version": 3 185 | }, 186 | "file_extension": ".py", 187 | "mimetype": "text/x-python", 188 | "name": "python", 189 | "nbconvert_exporter": "python", 190 | "pygments_lexer": "ipython3", 191 | "version": "3.6.7" 192 | } 193 | }, 194 | "nbformat": 4, 195 | "nbformat_minor": 2 196 | } 197 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='jetcam', 5 | version='0.0.0', 6 | description='An easy to use camera interface for NVIDIA Jetson', 7 | packages=find_packages(), 8 | install_requires=[ 9 | ], 10 | ) 11 | --------------------------------------------------------------------------------