Skip to content

Add camera library. #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions libraries/Camera/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 📹 Library for Arduino Supported Cameras

The Arduino camera library captures pixels from supported cameras on Arduino boards and stores them in a buffer for continuous retrieval and processing.

📖 For more information about this library please read the documentation [here](./docs/).
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include "camera.h"

Camera cam;

void fatal_error(const char *msg) {
Serial.println(msg);
pinMode(LED_BUILTIN, OUTPUT);
while (1) {
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
}

void setup(void) {
Serial.begin(115200);
if (!cam.begin(320, 240, CAMERA_RGB565)) {
fatal_error("Camera begin failed");
}
cam.setVerticalFlip(false);
cam.setHorizontalMirror(false);
}

void loop() {
FrameBuffer fb;
if (cam.grabFrame(fb)) {
if (Serial.read() == 1) {
Serial.write(fb.getBuffer(), fb.getBufferSize());
}
cam.releaseFrame(fb);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
This sketch reads a raw Stream of RGB565 pixels
from the Serial port and displays the frame on
the window.
Use with the Examples -> CameraCaptureRawBytes Arduino sketch.
This example code is in the public domain.
*/

import processing.serial.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

Serial myPort;

// must match resolution used in the Arduino sketch
final int cameraWidth = 320;
final int cameraHeight = 240;

// Must match the image mode in the Arduino sketch
boolean useGrayScale = false;

// Must match the baud rate in the Arduino sketch
final int baudRate = 115200;

final int cameraBytesPerPixel = useGrayScale ? 1 : 2;
final int cameraPixelCount = cameraWidth * cameraHeight;
final int bytesPerFrame = cameraPixelCount * cameraBytesPerPixel;
final int timeout = int((bytesPerFrame / float(baudRate / 10)) * 1000 * 2); // Twice the transfer rate

PImage myImage;
byte[] frameBuffer = new byte[bytesPerFrame];
int lastUpdate = 0;
boolean shouldRedraw = false;

void setup() {
size(640, 480);

// If you have only ONE serial port active you may use this:
//myPort = new Serial(this, Serial.list()[0], baudRate); // if you have only ONE serial port active

// If you know the serial port name
//myPort = new Serial(this, "COM5", baudRate); // Windows
myPort = new Serial(this, "/dev/ttyACM0", baudRate); // Linux
//myPort = new Serial(this, "/dev/cu.usbmodem14301", baudRate); // Mac

// wait for a full frame of bytes
myPort.buffer(bytesPerFrame);

myImage = createImage(cameraWidth, cameraHeight, ALPHA);

// Let the Arduino sketch know we're ready to receive data
myPort.write(1);
}

void draw() {
// Time out after a few seconds and ask for new data
if(millis() - lastUpdate > timeout) {
println("Connection timed out.");
myPort.clear();
myPort.write(1);
}

if(shouldRedraw){
PImage img = myImage.copy();
img.resize(640, 480);
image(img, 0, 0);
shouldRedraw = false;
}
}

int[] convertRGB565ToRGB888(short pixelValue){
//RGB565
int r = (pixelValue >> (6+5)) & 0x01F;
int g = (pixelValue >> 5) & 0x03F;
int b = (pixelValue) & 0x01F;
//RGB888 - amplify
r <<= 3;
g <<= 2;
b <<= 3;
return new int[]{r,g,b};
}

void serialEvent(Serial myPort) {
lastUpdate = millis();

// read the received bytes
myPort.readBytes(frameBuffer);

// Access raw bytes via byte buffer
ByteBuffer bb = ByteBuffer.wrap(frameBuffer);

// Ensure proper endianness of the data for > 8 bit values.
// The 1 byte bb.get() function will always return the bytes in the correct order.
bb.order(ByteOrder.BIG_ENDIAN);

int i = 0;

while (bb.hasRemaining()) {
if(useGrayScale){
// read 8-bit pixel data
byte pixelValue = bb.get();

// set pixel color
myImage.pixels[i++] = color(Byte.toUnsignedInt(pixelValue));
} else {
// read 16-bit pixel data
int[] rgbValues = convertRGB565ToRGB888(bb.getShort());

// set pixel RGB color
myImage.pixels[i++] = color(rgbValues[0], rgbValues[1], rgbValues[2]);
}
}

myImage.updatePixels();

// Ensures that the new image data is drawn in the next draw loop
shouldRedraw = true;

// Let the Arduino sketch know we received all pixels
// and are ready for the next frame
myPort.write(1);
}

void keyPressed() {
if (key == ' ') {
useGrayScale = !useGrayScale; // change boolean value of greyscale when space is pressed
}
}
9 changes: 9 additions & 0 deletions libraries/Camera/library.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name=Camera
version=1.0
author=Arduino
maintainer=Arduino <[email protected]>
sentence=Library to capture pixels from supported cameras on Arduino boards.
paragraph=The Arduino camera library is a C++ library designed to capture frames from cameras on supported Arduino products.
category=Device Control
url=https://github.com/arduino/ArduinoCore-zephyr/tree/master/libraries/Camera
architectures=zephyr,zephyr_portenta,zephyr_nicla,zephyr_giga
170 changes: 170 additions & 0 deletions libraries/Camera/src/camera.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2025 Arduino SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Camera driver.
*/
#include "Arduino.h"
#include "camera.h"

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>

FrameBuffer::FrameBuffer() : vbuf(NULL) {

}

uint32_t FrameBuffer::getBufferSize() {
if (this->vbuf) {
return this->vbuf->bytesused;
}
}

uint8_t* FrameBuffer::getBuffer() {
if (this->vbuf) {
return this->vbuf->buffer;
}
}

Camera::Camera() : vdev(NULL), byte_swap(false), yuv_to_gray(false) {
for (size_t i = 0; i < ARRAY_SIZE(this->vbuf); i++) {
this->vbuf[i] = NULL;
}
}

bool Camera::begin(uint32_t width, uint32_t height, uint32_t pixformat, bool byte_swap) {
#if DT_HAS_CHOSEN(zephyr_camera)
this->vdev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
#endif

if (!this->vdev || !device_is_ready(this->vdev)) {
return false;
}

switch (pixformat) {
case CAMERA_RGB565:
this->byte_swap = byte_swap;
pixformat = VIDEO_PIX_FMT_RGB565;
break;
case CAMERA_GRAYSCALE:
// There's no support for mono sensors.
this->yuv_to_gray = true;
pixformat = VIDEO_PIX_FMT_YUYV;
break;
default:
break;
}

// Get capabilities
struct video_caps caps = {0};
if (video_get_caps(this->vdev, VIDEO_EP_OUT, &caps)) {
return false;
}

for (size_t i=0; caps.format_caps[i].pixelformat != NULL; i++) {
const struct video_format_cap *fcap = &caps.format_caps[i];
if (fcap->width_min == width &&
fcap->height_min == height &&
fcap->pixelformat == pixformat) {
break;
}
if (caps.format_caps[i+1].pixelformat == NULL) {
Serial.println("The specified format is not supported");
return false;
}
}

// Set format.
static struct video_format fmt = {
.pixelformat = pixformat,
.width = width,
.height = height,
.pitch = width * 2,
};

if (video_set_format(this->vdev, VIDEO_EP_OUT, &fmt)) {
Serial.println("Failed to set video format");
return false;
}

// Allocate video buffers.
for (size_t i = 0; i < ARRAY_SIZE(this->vbuf); i++) {
this->vbuf[i] = video_buffer_aligned_alloc(fmt.pitch * fmt.height,
CONFIG_VIDEO_BUFFER_POOL_ALIGN,
K_FOREVER);
if (this->vbuf[i] == NULL) {
Serial.println("Failed to allocate video buffers");
return false;
}
video_enqueue(this->vdev, VIDEO_EP_OUT, this->vbuf[i]);
}

// Start video capture
if (video_stream_start(this->vdev)) {
Serial.println("Failed to start capture");
return false;
}

return true;
}

bool Camera::grabFrame(FrameBuffer &fb, uint32_t timeout) {
if (this->vdev == NULL) {
return false;
}

if (video_dequeue(this->vdev, VIDEO_EP_OUT, &fb.vbuf, K_MSEC(timeout))) {
return false;
}

if (this->byte_swap) {
uint16_t *pixels = (uint16_t *) fb.vbuf->buffer;
for (size_t i=0; i<fb.vbuf->bytesused / 2; i++) {
pixels[i] = __REVSH(pixels[i]);
}
}

if (this->yuv_to_gray) {
uint8_t *pixels = (uint8_t *) fb.vbuf->buffer;
for (size_t i=0; i<fb.vbuf->bytesused / 2; i++) {
pixels[i] = pixels[i*2];
}
fb.vbuf->bytesused /= 2;
}

return true;
}

bool Camera::releaseFrame(FrameBuffer &fb) {
if (this->vdev == NULL) {
return false;
}

if (video_enqueue(this->vdev, VIDEO_EP_OUT, fb.vbuf)) {
return false;
}

return true;
}

bool Camera::setVerticalFlip(bool flip_enable) {
return video_set_ctrl(this->vdev, VIDEO_CID_VFLIP, (void *) flip_enable) == 0;
}

bool Camera::setHorizontalMirror(bool mirror_enable) {
return video_set_ctrl(this->vdev, VIDEO_CID_HFLIP, (void *) mirror_enable) == 0;
}
Loading
Loading