I have an esp32_cam which records a 160120 grayscale image which i would like to shrink to 28x28 (best case) or at least reduce its dimensions by e.g 5 to 3224. I'm not very experienced with c++ and I have problems with putting the problem into code. What would be an easy way to implement this?
An example code would be great but i'll appreciate all hints on this.
5x5 would be easier, like "sum 25 pixels and divide by 25" simple*
*rinse and repeat.
How would I start if I have the image in a 1D array of size HEIGHT*WIDTH?
I'm not very familiar with c++ yet
The stride of your image row is 160.
As a first quick and dirty stab, I'd setup five pointers to the start of consecutive rows
Assuming byte pixels:byte* row[5] = { image, image + STRIDE, image + (STRIDE *2), image + (STRIDE *3), image + (STRIDE *4)};
Don't forget to move down by five rows each iteration.
I have an esp32_cam which records a 160*120 grayscale image
How is the image held on the ESP32 ? Is it in an array ?
yes, i can read the image with
for (int i = 0; i < HEIGHT*WIDTH; i++)
{
img_array[i] =fb->buf[i];
Serial.println(",");
}
Lots of ways to skin this cat - it depends if which resource you have most of; time or memory, but either way, no more than a twenty lines of code.
You should be able to adapt this bilinear interpolation algorithm (source below):
#include <stdint.h>
typedef struct {
uint32_t *pixels;
unsigned int w;
unsigned int h;
} image_t;
#define getByte(value, n) (value >> (n*8) & 0xFF)
uint32_t getpixel(image_t *image, unsigned int x, unsigned int y){
return image->pixels[(y*image->w)+x];
}
float lerp(float s, float e, float t){return s+(e-s)*t;}
float blerp(float c00, float c10, float c01, float c11, float tx, float ty){
return lerp(lerp(c00, c10, tx), lerp(c01, c11, tx), ty);
}
void putpixel(image_t *image, unsigned int x, unsigned int y, uint32_t color){
image->pixels[(y*image->w) + x] = color;
}
void scale(image_t *src, image_t *dst, float scalex, float scaley){
int newWidth = (int)src->w*scalex;
int newHeight= (int)src->h*scaley;
int x, y;
for(x= 0, y=0; y < newHeight; x++){
if(x > newWidth){
x = 0; y++;
}
float gx = x / (float)(newWidth) * (src->w-1);
float gy = y / (float)(newHeight) * (src->h-1);
int gxi = (int)gx;
int gyi = (int)gy;
uint32_t result=0;
uint32_t c00 = getpixel(src, gxi, gyi);
uint32_t c10 = getpixel(src, gxi+1, gyi);
uint32_t c01 = getpixel(src, gxi, gyi+1);
uint32_t c11 = getpixel(src, gxi+1, gyi+1);
uint8_t i;
for(i = 0; i < 3; i++){
//((uint8_t*)&result)[i] = blerp( ((uint8_t*)&c00)[i], ((uint8_t*)&c10)[i], ((uint8_t*)&c01)[i], ((uint8_t*)&c11)[i], gxi - gx, gyi - gy); // this is shady
result |= (uint8_t)blerp(getByte(c00, i), getByte(c10, i), getByte(c01, i), getByte(c11, i), gx - gxi, gy -gyi) << (8*i);
}
putpixel(dst,x, y, result);
}
}
(source -- Bilinear interpolation - Rosetta Code)
to work with a greyscale image.
You should be able to read your pixels with x and y coordinates.
and you should know the current height and width and desired height and width.
make a new image the desired size to paint too.
first of all you need a ratio between the new and old image sizes.
you simply devide. this needs to be a float value. so you need to do some casting.
float ratioX = float(oldimageWidth)/ float(newWidth);
float ratioY = float(oldimageHeight)/float(newHeight);
then you would use a loop within a loop to page through all the pixels of the NEW image.
Use the above ratios to tell what pixel to copy from the old image.
int x = NewimageWidth;
while(x>0){x--;
int y = NewimageHeight;
while(y>0){y--;
int color = Oldimage.GetPixel(int(xratioX),int(yratioY));
NewImage.SetPixel(x,y,color);
}}
there are more complex sampling techniques that could be added to this if you felt like grabbing surrounding pixels so that you could either get an average for your output color or set a threshold, but this basic method usually looks good. if you need more let me know.