Skip to content

Commit

Permalink
Merge pull request #41 from iabdalkader/camera_library
Browse files Browse the repository at this point in the history
Add camera library.
  • Loading branch information
facchinm authored Jan 29, 2025
2 parents ab684ac + 0ddba96 commit b28f7a1
Show file tree
Hide file tree
Showing 10 changed files with 549 additions and 17 deletions.
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

0 comments on commit b28f7a1

Please sign in to comment.