実際に、”ESP32_Dev_CAM”でTimeLapsをやっていると、
- 画面の解像度を変更したい。
- 画面の角度を調整したい。(カメラの置き方によっては逆さに写る)
- 明るさ、シャープ、ソフト等調整したい。
- できればちょっと効果を付けたい。
と思うことが有ります。IDEのサンプルで画面を調整した後、その状態でTimeLapsが出来たらと思うのです。とりあえず、サンプルのスケッチを見てみる事にしました。
サンプルのスケッチを見ると
スケッチが複雑過ぎて良く分かりません。ただ、カメラ関係の制御は
- カメラのパラメータを設定する関数は、s->set_xxxx(s,xxxx_data); xxxは設定したい機能で行う。
- パラメターの読み込みはs->status.xxxx; xxxは読み込みたいパラメータで行う。
- 写真の撮影は、fb = esp_camera_fb_get();
で行えそうです。スケッチは書けそうです。
おしゃれなUI(ユーザーインターフェイス)
おしゃれなUIだけあって理解に苦労しました。現在でも完全に理解出来ていません。ファイルが大きいのでスケッチに直接書かずにSDカードにHTML本体、Java Script, CSSファイルの3つに分けて保存する事にしました。
- menu.html
- 本体です。
- プルダウンリスト。スライダーバー。チェックボック。ボタンの集まりです。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script type='text/javascript' src='menu.js'></script>
<link rel='stylesheet' type='text/css' href='menu.css' >
<title>ESP32 OV2460</title>
</head>
<body>
<section class="main">
<div style='font-size:40px'><b><i><u>Timelapse</u></i></b><br></div>
<div id="content">
<div id="sidebar">
<nav id="menu">
<div class="input-group">
<label>Resolution</label>
<select id="10" >
<option value='0' style="display:none">QQVGA(160x120)</option>
<option value='1'>QQVGA(160x120)</option>
<option value='2'>QCIF(176x144)</option>
<option value='3'>HQVGA(240x176)</option>
<option value='4'>QVGA(320x240)</option>
<option value='5'>CIF(400x296)</option>
<option value='6' selected >VGA(640x480)</option>
<option value='7'>SVGA(800x600)</option>
<option value='8'>XGA(1024x768)</option>
<option value='9'>SXGA(1280x1024)</option>
<option value='10'>UXGA(1600x1200)</option>
</select>
</div>
<div class="input-group">
<label>Quality</label>
<div class="range-min">10</div>
<input type="range" id="11" min="10" max="63" value="10" >
<div class="range-max">63</div>
</div>
<div class="input-group">
<label>Brightness</label>
<div class="range-min">-2</div>
<input type="range" id="12" min="-2" max="2" value="0">
<div class="range-max">2</div>
</div>
<div class="input-group">
<label>Contrast</label>
<div class="range-min">-2</div>
<input type="range" id="13" min="-2" max="2" value="0">
<div class="range-max">2</div>
</div>
<div class="input-group">
<label>Saturation</label>
<div class="range-min">-2</div>
<input type="range" id="14" min="-2" max="2" value="0" >
<div class="range-max">2</div>
</div>
<div class="input-group">
<label>Sharpness</label>
<div class="range-min">-2</div>
<input type="range" id="15" min="-2" max="2" value="0" >
<div class="range-max">2</div>
</div>
<div class="input-group">
<label>Special Effect</label>
<select id="16" >
<option value="0" selected="selected">No Effect</option>
<option value="1">Negative</option>
<option value="2">Grayscale</option>
<option value="3">Red Tint</option>
<option value="4">Green Tint</option>
<option value="5">Blue Tint</option>
<option value="6">Sepia</option>
</select>
</div>
<div class="input-group">
<label for="-17">AWB</label>
<div class="switch">
<input id="-17" type="checkbox">
<label class="slider" for="-17"></label>
</div>
</div>
<div class="input-group">
<label for="-18">AWB Gain</label>
<div class="switch">
<input id="-18" type="checkbox">
<label class="slider" for="-18"></label>
</div>
</div>
<div class="input-group">
<label>WB Mode</label>
<select id="19" >
<option value="0" selected="selected">Auto</option>
<option value="1">Sunny</option>
<option value="2">Cloudy</option>
<option value="3">Office</option>
<option value="4">Home</option>
</select>
</div>
<div class="input-group">
<label for="-20">AEC SENSOR</label>
<div class="switch">
<input id="-20" type="checkbox">
<label class="slider" for="-20"></label>
</div>
</div>
<div class="input-group">
<label for="-21">AEC DSP</label>
<div class="switch">
<input id="-21" type="checkbox" >
<label class="slider" for="-21"></label>
</div>
</div>
<div class="input-group">
<label>AE Level</label>
<div class="range-min">-2</div>
<input type="range" id="22" min="-2" max="2" value="0">
<div class="range-max">2</div>
</div>
<div class="input-group">
<label for="-23">AGC</label>
<div class="switch">
<input id="-23" type="checkbox" >
<label class="slider" for="-23"></label>
</div>
</div>
<div class="input-group">
<label>Agc_gain</label>
<div class="range-min">0</div>
<input type="range" id="24" min="0" max="30" value="0">
<div class="range-max">30</div>
</div>
<div class="input-group">
<label>Gain Ceiling</label>
<div class="range-min">2x</div>
<input type="range" id="25" min="0" max="6" value="0">
<div class="range-max">128x</div>
</div>
<div class="input-group">
<label for="-26">BPC</label>
<div class="switch">
<input id="-26" type="checkbox" >
<label class="slider" for="-26"></label>
</div>
</div>
<div class="input-group">
<label for="-27">WPC</label>
<div class="switch">
<input id="-27" type="checkbox">
<label class="slider" for="-27"></label>
</div>
</div>
<div class="input-group">
<label for="-28">Raw GMA</label>
<div class="switch">
<input id="-28" type="checkbox">
<label class="slider" for="-28"></label>
</div>
</div>
<div class="input-group">
<label for="-29">Lens Correction</label>
<div class="switch">
<input id="-29" type="checkbox">
<label class="slider" for="-29"></label>
</div>
</div>
<div class="input-group">
<label for="-30">H-Mirror</label>
<div class="switch">
<input id="-30" type="checkbox">
<label class="slider" for="-30"></label>
</div>
</div>
<div class="input-group">
<label for="-31">V-Flip</label>
<div class="switch">
<input id="-31" type="checkbox">
<label class="slider" for="-31"></label>
</div>
</div>
<div class="input-group">
<label for="-32">DCW (Downsize EN)</label>
<div class="switch">
<input id="-32" type="checkbox">
<label class="slider" for="-32"></label>
</div>
</div>
<div class="input-group">
<label for="-33">Color Bar</label>
<div class="switch">
<input id="-33" type="checkbox" >
<label class="slider" for="-33"></label>
</div>
</div>
<form method='get' id='abc'>
<input name="2" id='123' style="display:none">
</form>
<form method='get'>
<sp>
<button type='submit' name='1' value='2' id="get-still" style='float:left'>Get Still</button>
<button type='submit' name='4' value='2' id="Reset" style='float:right'>Reset</button>
</sp>
</form>
</nav>
</div>
<figure>
<img id="stream" src="./data.jpg">
</figure>
</div>
</section>
</body>
</html>
- menu.css
- 今回初めて知ったのですが、CSSファイルでアニメーションが出来るんですね。チェックボックにアニメーションが設定されていました。
@charset "UTF-8";
body {
font-family: Arial,Helvetica,sans-serif;
background: #181818;
color: #EFEFEF;
font-size: 16px
}
section.main {
display: flex
}
#menu,section.main {
flex-direction: column
}
#menu {
display: flex;
flex-wrap: nowrap;
min-width: 340px;
background: #363636;
padding: 8px;
border-radius: 4px;
margin-top: -10px;
margin-right: 10px;
}
#content {
display: flex;
flex-wrap: wrap;
align-items: stretch
}
.input-group {
display: flex;
flex-wrap: nowrap;
line-height: 22px;
margin: 5px 0
}
.input-group>label {
display: inline-block;
padding-right: 10px;
min-width: 47%
}
.input-group input,.input-group select {
flex-grow: 1
}
.range-max,.range-min {
display: inline-block;
padding: 0 5px
}
button {
display: block;
margin: 5px;
padding: 0 12px;
border: 0;
line-height: 28px;
cursor: pointer;
color: #fff;
background: #ff3034;
border-radius: 5px;
font-size: 16px;
outline: 0
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
- menu.js
- Java Scripファイルです。
- HPに変更が有った時にサーバと通信するように設定しています。
menu.jsここまで書いたところでPCがお亡くなりになりました。急に画面が真っ暗になり、全く反応しなくなりました。急遽新しいPC(実際には中古ですが)を購入してセットアップしたのですが、ちょっと手こずってしまいました。興味が有る方こちらをー>Install
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
var para=['4','10','0','0','0','0','0','1','1','0','1','1','0','1','0','0','0','1','1','1','0','0','1','0'];
document.addEventListener('change', function (event) {
var targetElement = event.target || event.srcElement;
document.getElementById('123').value=targetElement.id + ',' + targetElement.value;
document.getElementById('abc').submit();}
,false);
function get_data(){
var url = "http://esp32devcam.local?3=";
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onreadystatechange = function()
{
if(xhr.readyState === 4 && xhr.status === 200)
{
console.log( xhr.responseText );
b=0;
for(a=0; a<23; a++)
{
para[a]='';
while( xhr.responseText[b] != ',')
{
para[a] += xhr.responseText[b];
b ++;
}
b ++;
}
para[23] = xhr.responseText[b];
for(a=0; a<24; a++)
{
switch(a)
{
case 0:
case 6:
case 9: document.getElementById(String(a + 10)).selectedIndex=Number(para[a]);
break;
case 1:
case 2:
case 3:
case 4:
case 5:
case 12:
case 14:
case 15: document.getElementById(String(a + 10)).value=para[a];
break;
default:
str=true;
if(para[a] == '0') str=false;
document.getElementById(String(-a - 10)).checked=str;
break;
}
}
}
}
}
get_data();
history.pushState(null,null,'/');
//document.getElementById("123").style.display="none";
本体は簡単
以下は今回のスケッチ
test.html
#include "esp_camera.h"
#include "Arduino.h"
#include "SD.h"
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
// Pin definition for CAMERA_MODEL_AI_THINKER
#define PWDN 4
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define sd_sck 14
#define sd_mosi 15
#define sd_ss 13
#define sd_miso 32
WebServer server(80);
const char *SSID = "Enter Your ID";
const char *PASSWORD = "Enter Your Password";
RTC_DATA_ATTR int cam_state[24]={0};
camera_config_t config;
void set_conf()
{
// Init Camera
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_VGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 10;
config.fb_count = 2;
}
void setup() {
pinMode(PWDN, OUTPUT);
digitalWrite(PWDN, LOW);
Serial.begin(115200);
delay(100);
set_conf();
esp_camera_init(&config);
SPI.begin(sd_sck, sd_miso, sd_mosi, sd_ss);
SD.begin(sd_ss);
Serial.println("Connecting to WiFi");
WiFi.disconnect(true);
WiFi.softAPdisconnect(true);
delay(500);
WiFi.mode(WIFI_STA);
WiFi.begin(SSID, PASSWORD);
delay(1000);
// Try forever
while (WiFi.status() != WL_CONNECTED) {
Serial.println("...Connecting to WiFi");
delay(1000);
}
Serial.println("Connected");
Serial.println(SSID);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (MDNS.begin("esp32devcam")) {
Serial.println("MDNS responder started");
}
server.on("/", handleRoot);
server.onNotFound(handleWebRequests);
server.begin();
Serial.println("HTTP server started");
read_State();
}
void read_State()
{
sensor_t * s = esp_camera_sensor_get();
cam_state[0]= s->status.framesize;
cam_state[1]= s->status.quality;
cam_state[2]= s->status.brightness;
cam_state[3]= s->status.contrast;
cam_state[4]= s->status.saturation;
cam_state[5]= s->status.sharpness;
cam_state[6]= s->status.special_effect;
cam_state[7]= s->status.awb;
cam_state[8]= s->status.awb_gain;
cam_state[9]= s->status.wb_mode;
cam_state[10]= s->status.aec;
cam_state[11]= s->status.aec2;
cam_state[12]= s->status.ae_level;
cam_state[13]= s->status.agc;
cam_state[14]= s->status.agc_gain;
cam_state[15]= s->status.gainceiling;
cam_state[16]= s->status.bpc;
cam_state[17]= s->status.wpc;
cam_state[18]= s->status.raw_gma;
cam_state[19]= s->status.lenc;
cam_state[20]= s->status.hmirror;
cam_state[21]= s->status.vflip;
cam_state[22]= s->status.dcw;
cam_state[23]= s->status.colorbar;
}
void loop() {
server.handleClient();
}
void handleRoot() {
String buf,cmd;
int a,b,fl,c_state[24];
File dataFile;
camera_fb_t * fb = NULL;
sensor_t * s;
fl=1;
cmd=server.argName(0);
switch(cmd.toInt())
{
case 1: // Get Still
fb = esp_camera_fb_get();
dataFile = SD.open("/data.jpg", FILE_WRITE);
dataFile.write(fb->buf, fb->len);
dataFile.close();
esp_camera_fb_return(fb);
Serial.printf("Take a photo.\n");
break;
case 2: //Set Camera Parameter
buf=server.arg("2");
a=buf.toInt();
if(a < 0)
{
a = -a; a -= 10;
cam_state[a] ^= 1; cam_state[a] &= 1;
}
else
{
a -= 10;
b=0; while(buf[b] != ',') b ++;
b ++;
cmd=buf.substring(b);
cam_state[a]=cmd.toInt();
}
s = esp_camera_sensor_get();
switch(a)
{
case 0: s->set_framesize(s,(framesize_t)cam_state[0]); break; //framesize
case 1: s->set_quality(s,cam_state[1]); break; //quality
case 2: s->set_brightness(s,cam_state[2]); break; //brightness
case 3: s->set_contrast(s,cam_state[3]); break; //contrast
case 4: s->set_saturation(s,cam_state[4]); break; //saturation
case 5: s->set_sharpness(s,cam_state[5]); break; //sharpness
case 6: s->set_special_effect(s,cam_state[6]); break; //special_effect
case 7: s->set_whitebal(s,cam_state[7]); break; //awb
case 8: s->set_awb_gain(s,cam_state[8]); break; //awb_gain
case 9: s->set_wb_mode(s,cam_state[9]); break; //wb_mode
case 10: s->set_aec2(s,cam_state[10]); break; //aec
case 11: s->set_exposure_ctrl(s,cam_state[11]); break; //aec2
case 12: s->set_ae_level(s,cam_state[12]); break; //ae_level
case 13: s->set_gain_ctrl(s,cam_state[13]); break; //agc
case 14: s->set_agc_gain(s,cam_state[14]); break; //agc_gain
case 15: s->set_gainceiling(s,(gainceiling_t)cam_state[15]); break; //gainceiling
case 16: s->set_bpc(s,cam_state[16]); break; //bpc
case 17: s->set_wpc(s,cam_state[17]); break; //wpc
case 18: s->set_raw_gma(s,cam_state[18]); break; //raw_gma
case 19: s->set_lenc(s,cam_state[19]); break; //lenc
case 20: s->set_hmirror(s,cam_state[20]); break; //hmirror
case 21: s->set_vflip(s,cam_state[21]); break; //vflip
case 22: s->set_dcw(s,cam_state[22]); break; //dcw
case 23: s->set_colorbar(s,cam_state[23]); break; //colorba
}
fl=0;
server.send(204, "text/plain", "");
break;
case 3: // Send camera parameter
cmd="";
for(a=0; a<23; a++) cmd += (String(cam_state[a]) + ',');
cmd += (String(cam_state[23]));
server.send(200, "text/plain", cmd);
fl=0;
break;
case 4: // Reset the camera
esp_camera_deinit();
set_conf();
esp_camera_init(&config);
read_State();
break;
}
if(fl)
{
dataFile = SD.open("/menu.html", FILE_READ);
server.streamFile(dataFile,"text/html");
dataFile.close();
}
}
void handleWebRequests()
{
String dataType = "text/plain";
String path;
File dataFile;
path = server.uri();
if(path.endsWith(".txt")) dataType = "text/plain";
else if(path.endsWith(".css")) dataType = "text/css";
else if(path.endsWith(".js")) dataType = "application/javascript";
else if(path.endsWith(".png")) dataType = "image/png";
else if(path.endsWith(".html")) dataType = "text/html";
else if(path.endsWith(".jpg")) dataType = "image/jpeg";
delay(5);
dataFile = SD.open(path.c_str(), "r");
server.streamFile(dataFile, dataType);
dataFile.close();
delay(5);
}
- 35,36行: Wifiのパラメター
- 自分のルーターのSSID、Passwordをいれて下さい
- 75,76行:カメラの初期設定を行っています。いつもと同じ
- 109行:read_State();
- ここで、今回設定するパラメターの初期状態を読み込んでいます。
- 116行:void read_State()
- 関数本体。24個のパラメータを読み込んでいます。
- パラメータは、sensor_t * s = esp_camera_sensor_get();と宣言
- sを使って s->status.xxxxx; とxxxxxの部分に読み出したいパラメター名を指定する。
- 152行:void handleRoot() { サーバ本体です。
- 163行:case 1: // Get Still
- Web画面でGetSteelボタンを押すとここに来ます。
- 行っている処理は写真を取ってSDに保存です。
- 172行:case 2: //Set Camera Parameter
- Web画面で各パラメターをセットするとここで処理されます。
- 221行:case 3: // Send camera parameter
- カメラのパラメータをクライアントに送信。
- HPのボタン表示に使用しています。
- 229行:case 4: // Reset the camera
- Web画面にあるResetボタンを押すとここに来ます。
- パラメターを初期状態に戻します。
menu.html、 menu.css、 menu.js をSDに保存して、上記のスケッチをコンパイルして実行して下さい。スケッチサイズが小さくなったせいかDIEのボードを、”ESP32 Dev Module” を指定してもコンパイル出来ます。
スケッチはPC_Nameでアクセス出来るように設定しています。ブラウザーのURLの欄に、”esp32devcam.local”と入力して下さい。サーバのHPが表示されます。ドライバーセットの写真を取ったのですが、こんな感じになります。

殆どサンプルスケッチと同じです。
使用して分かった事
- 画面のサイズと解像度:
- 当初、画面のサイズが大きくなると写真の写る範囲が広くなると思っていましたが、実際には写る範囲は同じで解像度が上がる事が分かりました。
- 今回はWebカメラ(ストリーミング)では無いので解像度を上げて綺麗に撮る事を優先します。
- Quality、Brightness等
- 各パラメターの効果は分かるのですが、そんなに大きな変化がある様に見えません。
- Special Effectは面白い機能ですが、TimeLapsをやる上で必要かどうか
- H-Mirror、V-Flip
- 横、縦に反転機能です。これカメラのいちの関係で必要と思ったのですが、動画作成アプリが角度を指定して動画を作成出来る事が分かりました。
- それを使えば良いかって感じです。
次回はこのスケッチにいままでのTimeLapsのスケッチを取り込みます。