Skip to content
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

[AE-2] Add zoom, flip, and mirror functionality for GC2145 #689

Merged
merged 12 commits into from
May 26, 2023
Merged
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