Skip to content

Commit

Permalink
Merge pull request #689 from arduino/zoom-flip-mirror
Browse files Browse the repository at this point in the history
[AE-2] Add zoom, flip, and mirror functionality for GC2145
  • Loading branch information
sebromero authored May 26, 2023
2 parents 26804b6 + cb7ec81 commit e864648
Show file tree
Hide file tree
Showing 15 changed files with 477 additions and 14 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/compile-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ jobs:
- libraries/doom
- libraries/KernelDebug
- libraries/MCUboot
- libraries/Camera/examples
- libraries/Camera/examples/CameraCaptureRawBytes
- libraries/Camera/examples/CameraMotionDetect
- libraries/Portenta_lvgl/examples/Portenta_lvgl
- libraries/Portenta_SDCARD
- libraries/Portenta_SDRAM
Expand Down Expand Up @@ -115,6 +116,7 @@ jobs:
additional-sketch-paths: |
- libraries/PDM
- libraries/Camera/examples/CameraCaptureRawBytes
- libraries/Camera/examples/CameraCaptureZoomPan
- libraries/SE05X
- libraries/STM32H747_System
- libraries/ThreadDebug
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@
Camera cam(galaxyCore);
#define IMAGE_MODE CAMERA_RGB565
#elif defined(ARDUINO_PORTENTA_H7_M7)
// uncomment the correct camera in use
#include "hm0360.h"
HM0360 himax;

// #include "himax.h"
// HM01B0 himax;
// Camera cam(himax);

Camera cam(himax);
#define IMAGE_MODE CAMERA_GRAYSCALE
#elif defined(ARDUINO_GIGA)
Expand Down Expand Up @@ -60,7 +66,7 @@ void setup() {

void loop() {
if(!Serial) {
Serial.begin(921600);
Serial.begin(115200);
while(!Serial);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* This example shows how to use the Nicla Vision to capture images from the camera
* with a zoom window and send them over the serial port.
* The zoom window will move from left to right and top to bottom
* in the predefined steps of pixels (ZOOM_X_STEP and ZOOM_Y_STEP).
*
* Whenever the board sends a frame over the serial port, the blue LED will blink.
*
* Instructions:
* 1. Upload this sketch to Nicla Vision.
* 2. Open the CameraRawBytesVisualizer.pde Processing sketch and change `useGrayScale` to `false`.
* 3. Adjust the serial port in the Processing sketch to match the one used by Nicla Vision.
* 4. Run the Processing sketch.
*
* Initial author: Sebastian Romero @sebromero
*/

#include "camera.h"

#ifndef ARDUINO_NICLA_VISION
#error "This sketch only works on Nicla Vision."
#endif

#include "gc2145.h"
GC2145 galaxyCore;
Camera cam(galaxyCore);
#define IMAGE_MODE CAMERA_RGB565

#define CHUNK_SIZE 512 // Size of chunks in bytes
#define RESOLUTION CAMERA_R1600x1200 // Zoom in from the highest supported resolution
#define ZOOM_WINDOW_RESOLUTION CAMERA_R320x240

constexpr uint16_t ZOOM_WINDOW_WIDTH = 320;
constexpr uint16_t ZOOM_WINDOW_HEIGHT = 240;
constexpr uint16_t ZOOM_X_STEP = 100;
constexpr uint16_t ZOOM_Y_STEP = 100;

FrameBuffer frameBuffer;
uint32_t currentZoomX = 0;
uint32_t currentZoomY = 0;
uint32_t maxZoomX = 0; // Will be calculated in setup()
uint32_t maxZoomY = 0; // Will be calculated in setup()


void blinkLED(uint32_t count = 0xFFFFFFFF)
{
pinMode(LED_BUILTIN, OUTPUT);

while (count--) {
digitalWrite(LED_BUILTIN, LOW); // turn the LED on (HIGH is the voltage level)
delay(50); // wait for a second
digitalWrite(LED_BUILTIN, HIGH); // turn the LED off by making the voltage LOW
delay(50); // wait for a second
}
}

void setup() {
// Init the cam QVGA, 30FPS
if (!cam.begin(RESOLUTION, IMAGE_MODE, 30)) {
blinkLED();
}

blinkLED(5);

pinMode(LEDB, OUTPUT);
digitalWrite(LEDB, HIGH);

// Flips the image vertically
cam.setVerticalFlip(true);

// Mirrors the image horizontally
cam.setHorizontalMirror(true);

// Calculate the max zoom window position
maxZoomX = cam.getResolutionWidth() - ZOOM_WINDOW_WIDTH;
maxZoomY = cam.getResolutionHeight() - ZOOM_WINDOW_HEIGHT;

// Set the zoom window to 0,0
cam.zoomTo(ZOOM_WINDOW_RESOLUTION, currentZoomX, currentZoomY);
}

void sendFrame(){
// Grab frame and write to serial
if (cam.grabFrame(frameBuffer, 3000) == 0) {
byte* buffer = frameBuffer.getBuffer();
size_t bufferSize = cam.frameSize();
digitalWrite(LEDB, LOW);

// Split buffer into chunks
for(size_t i = 0; i < bufferSize; i += CHUNK_SIZE) {
size_t chunkSize = min(bufferSize - i, CHUNK_SIZE);
Serial.write(buffer + i, chunkSize);
Serial.flush();
delay(1); // Small delay to allow the receiver to process the data
}

digitalWrite(LEDB, HIGH);
} else {
blinkLED(20);
}
}

void loop() {
if(!Serial) {
Serial.begin(115200);
while(!Serial);
}

if(!Serial.available()) return;
byte request = Serial.read();

if(request == 1){
sendFrame();
currentZoomX += ZOOM_X_STEP;

if(currentZoomX > maxZoomX){
currentZoomX = 0;
currentZoomY += ZOOM_Y_STEP;
if(currentZoomY > maxZoomY){
currentZoomY = 0;
}
}
cam.zoomTo(ZOOM_WINDOW_RESOLUTION, currentZoomX, currentZoomY);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#include "camera.h"
#include "himax.h"
HM01B0 himax;

// uncomment the correct camera in use
#include "hm0360.h"
HM0360 himax;

// #include "himax.h"
// HM01B0 himax;

Camera cam(himax);

#ifdef ARDUINO_NICLA_VISION
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final int cameraHeight = 240;
final boolean useGrayScale = true;

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

final int cameraBytesPerPixel = useGrayScale ? 1 : 2;
final int cameraPixelCount = cameraWidth * cameraHeight;
Expand Down
95 changes: 90 additions & 5 deletions libraries/Camera/src/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ const uint32_t restab[CAMERA_RMAX][2] = {
{320, 240 }, // QVGA
{320, 320 },
{640, 480 }, // VGA
{0, 0 }, // Empty entry because there's a jump in the resolution enum initializers
{800, 600 }, // SVGA
{1600, 1200}, // UXGA
};
Expand Down Expand Up @@ -504,36 +505,120 @@ int Camera::setFrameRate(int32_t framerate)
return -1;
}

int Camera::setResolution(int32_t resolution)
int Camera::setResolutionWithZoom(int32_t resolution, int32_t zoom_resolution, int32_t zoom_x, int32_t zoom_y)
{
if (this->sensor == NULL || resolution >= CAMERA_RMAX
|| pixformat >= CAMERA_PMAX || pixformat == -1) {
return -1;
}

// resolution = the full resolution to set the camera to
// zoom_resolution = the resolution to crop to when zooming (set equal to resolution for no zoom)
// final_resolution = the resolution to crop to (depends on zoom or not)
int32_t final_resolution;
// Check if zooming is asked for
if (resolution != zoom_resolution)
{
// Can't zoom into a larger window than the original
if (zoom_resolution > resolution)
{
return -1;
}
final_resolution = zoom_resolution;
}
else
{
final_resolution = resolution;
}

/*
* @param X0 DCMI window X offset
* @param Y0 DCMI window Y offset
* @param XSize DCMI Pixel per line
* @param YSize DCMI Line number
*/
HAL_DCMI_EnableCROP(&hdcmi);
uint32_t bpl = restab[resolution][0];
uint32_t bpl = restab[final_resolution][0];
if (pixformat == CAMERA_RGB565 ||
(pixformat == CAMERA_GRAYSCALE && !this->sensor->getMono())) {
// If the pixel format is Grayscale and sensor is Not monochrome,
// the actual pixel format will be YUV (i.e 2 bytes per pixel).
bpl *= 2;
}
HAL_DCMI_ConfigCROP(&hdcmi, 0, 0, bpl - 1, restab[resolution][1] - 1);
HAL_DCMI_ConfigCROP(&hdcmi, 0, 0, bpl - 1, restab[final_resolution][1] - 1);

if (this->sensor->setResolution(resolution) == 0) {
this->resolution = resolution;
if (this->sensor->setResolutionWithZoom(resolution, zoom_resolution, zoom_x, zoom_y) == 0) {
this->resolution = final_resolution;
return 0;
}
return -1;
}

int Camera::setResolution(int32_t resolution)
{
// Check for resolutions that would cause out-of-bounds indexing of restab
// This check is here because original_resolution will be trusted in all other code
if ((resolution < 0) || (resolution >= CAMERA_RMAX))
{
return -1;
}
original_resolution = resolution;
return setResolutionWithZoom(resolution, resolution, 0, 0);
}

int Camera::zoomTo(int32_t zoom_resolution, uint32_t zoom_x, uint32_t zoom_y)
{
// Check for zoom resolutions that would cause out-of-bounds indexing of restab
if ((zoom_resolution < 0) || (zoom_resolution >= CAMERA_RMAX))
{
return -1;
}
// Check if the zoom window goes outside the frame on the x axis
// Notice that this form prevents uint32_t wraparound, so don't change it
if (zoom_x >= (restab[this->original_resolution][0]) - (restab[zoom_resolution][0]))
{
return -1;
}
// Check if the zoom window goes outside the frame on the y axis
// Notice that this form prevents uint32_t wraparound, so don't change it
if (zoom_y >= (restab[this->original_resolution][1]) - (restab[zoom_resolution][1]))
{
return -1;
}
return setResolutionWithZoom(this->original_resolution, zoom_resolution, zoom_x, zoom_y);
}

int Camera::zoomToCenter(int32_t zoom_resolution)
{
// Check for zoom resolutions that would cause out-of-bounds indexing of restab
if ((zoom_resolution < 0) || (zoom_resolution >= CAMERA_RMAX))
{
return -1;
}
uint32_t zoom_x = (restab[this->original_resolution][0] - restab[zoom_resolution][0]) / 2;
uint32_t zoom_y = (restab[this->original_resolution][1] - restab[zoom_resolution][1]) / 2;
return setResolutionWithZoom(this->original_resolution, zoom_resolution, zoom_x, zoom_y);
}

int Camera::setVerticalFlip(bool flip_enable)
{
return (this->sensor->setVerticalFlip(flip_enable));
}

int Camera::setHorizontalMirror(bool mirror_enable)
{
return (this->sensor->setHorizontalMirror(mirror_enable));
}

uint32_t Camera::getResolutionWidth()
{
return (restab[this->original_resolution][0]);
}
uint32_t Camera::getResolutionHeight()
{
return (restab[this->original_resolution][1]);
}

int Camera::setPixelFormat(int32_t pixformat)
{
if (this->sensor == NULL || pixformat >= CAMERA_PMAX) {
Expand Down
Loading

0 comments on commit e864648

Please sign in to comment.