ArduCAM to FTP
0. Motivation
ArduinoのカメラモジュールArduCAM-Mini-2MPで撮った、連続した2画像の差分が大きい場合のみ、その画像をサーバ上にコピーしたかったのすが、ググっても撮った画像をSDカード(モジュール)上に保存するサンプルしか見つかりませんでした。
1. ArduCAMでjpgフォーマットとして画像を撮る
下記にArduCAM-Mini-2MPでjpg画像として撮って、Arduionoのバッファ上に保持するサンプルはありました。
https://github.com/Dasch0/esp32-arducam-edge-impulse/blob/main/src/main.cpp
2. jpg画像をデコードして2枚の画像の差分を取る
下記Tiny JPEG Decompressorはjpg画像をArduino内蔵メモリでもデコードできます。
http://elm-chan.org/fsw/tjpgd/en/appnote.html
これを使って、連続した2枚のjpg画像をデコードし、ピクセル毎に比較することにしました。
上記Exampleのstruct IODEVを、ファイルのポインタではなくArduCAMで撮ったjpg画像(char型の配列)のポインタをInput stream *jbufとして渡せるように、そのサイズもlbyteとして渡せるように修正してます。加えて、デコード時に、前回デコードされた画像とのピクセル間の差分の和を保持するdiffというメンバも追加しています。
// TJpgDec Session identifier for input/output functions (name, members and usage are as user defined)
typedef struct {
char *jbuf; /* Input stream */
uint8_t *fbuf; /* Output frame buffer */
unsigned int wfbuf; /* Width of the frame buffer [pix] */
uint32_t lbyte; /* Number bytes left in the input stream */
int diff; /* difference between current and previous frame buffers */
} IODEV;
同様にinput functionを、ファイル操作でなく上記struct IODEVで渡されるjpg画像(*jbuf)をmemcpyでread buffer buffにnbyte分コピーしするよう修正しています。
// TJpgDec User defined input function
size_t in_func ( /* Returns number of bytes read (zero on error) */
JDEC* jd, /* Decompression object */
uint8_t* buff, /* Pointer to the read buffer (null to remove data) */
size_t nbyte /* Number of bytes to read/remove */
)
{
IODEV *dev = (IODEV*)jd->device; /* Session identifier (5th argument of jd_prepare function) */
/* Copy input stream, dev->jbuf, to read buffer, buff */
if (nbyte > dev->lbyte)
nbyte = dev->lbyte;
if (buff) /* Read data from imput stream */
memcpy(buff, dev->jbuf, nbyte);
dev->lbyte -= nbyte;
dev->jbuf += nbyte;
return nbyte;
}
output functionは、入力jpg画像をデコードしたビットマップ画像bitmapを出力frame buffer wfbufにコピーする際に、wfbufに残っている前回のピクセル値とbitmapの同じピクセルの値の差分の二乗和平方根の和を上記struct IODEVのメンバdiffに保持します。
// TJpgDec User defined output function
unsigned int out_func ( /* Returns 1 to continue, 0 to abort */
JDEC* jd, /* Decompression object */
void* bitmap, /* Bitmap data to be output */
JRECT* rect /* Rectangular region of output image */
)
{
IODEV *dev = (IODEV*)jd->device; /* Session identifier (5th argument of jd_prepare function) */
uint8_t *src, *dst;
uint16_t y, bws, i;
unsigned int bwd;
/* Progress indicator */
if (rect->left == 0) {
if(Serial)
Serial.printf("\r%lu%%. ", (rect->top << jd->scale) * 100UL / jd->height);
}
/* Copy the output image rectangle to the frame buffer */
src = (uint8_t*)bitmap; /* Output bitmap */
dst = dev->fbuf + N_BPP * (rect->top * dev->wfbuf + rect->left); /* Left-top of rectangle in the frame buffer */
bws = N_BPP * (rect->right - rect->left + 1); /* Width of the rectangle [byte] */
bwd = N_BPP * dev->wfbuf; /* Width of the frame buffer [byte] */
for (y = rect->top; y <= rect->bottom; y++) {
for (i = 0; i < bws/N_BPP; i++){ /* Calculate the difference between output bitmap and previous frame buffer before copy */
dev->diff += sq((int)dst[i*3] - (int)src[i*3]);
dev->diff += sq((int)dst[i*3 + 1] - (int)src[i*3 + 1]);
dev->diff += sq((int)dst[i*3 + 2] - (int)src[i*3 + 2]);
}
memcpy(dst, src, bws); /* Copy a line */
src += bws; dst += bwd; /* Next line */
}
return 1; /* Continue to decompress */
}
3. jpg画像をFTPサーバに送る
下記で対象をFTPサーバにコピーできます。
https://playground.arduino.cc/Code/FTP/
上記で計算した連続した2枚のjpg画像の差分diffが、ある一定値(ここでは20000000)以上だと、FTPで新しい方の画像をftpサーバに保存してます。
if (devid.diff > 20000000) { // ftp the current jpg image if the difference between current and previous and images is more than this value
doFTP(jpeg_buffer, jpeg_size);
...
}
4. まとめ
上記三つのサンプルを下記の通り組み合わせて、所望の(連続した2画像のうち差分の大きい)jpg画像をFTPサーバに送れました。
https://github.com/kamotsuru/arducam2ftp/blob/main/arducam2ftp.ino