esp-who/examples/single_chip/camera_web_server/main/www/index_ov2640.html

821 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32 OV2460</title>
<style>
body {
font-family: Arial,Helvetica,sans-serif;
background: #181818;
color: #EFEFEF;
font-size: 16px
}
h2 {
font-size: 18px
}
section.main {
display: flex
}
#menu,section.main {
flex-direction: column
}
#menu {
display: none;
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
}
figure {
padding: 0px;
margin: 0;
-webkit-margin-before: 0;
margin-block-start: 0;
-webkit-margin-after: 0;
margin-block-end: 0;
-webkit-margin-start: 0;
margin-inline-start: 0;
-webkit-margin-end: 0;
margin-inline-end: 0
}
figure img {
display: block;
width: 100%;
height: auto;
border-radius: 4px;
margin-top: 8px;
}
@media (min-width: 800px) and (orientation:landscape) {
#content {
display:flex;
flex-wrap: nowrap;
align-items: stretch
}
figure img {
display: block;
max-width: 100%;
max-height: calc(100vh - 40px);
width: auto;
height: auto
}
figure {
padding: 0 0 0 0px;
margin: 0;
-webkit-margin-before: 0;
margin-block-start: 0;
-webkit-margin-after: 0;
margin-block-end: 0;
-webkit-margin-start: 0;
margin-inline-start: 0;
-webkit-margin-end: 0;
margin-inline-end: 0
}
}
section#buttons {
display: flex;
flex-wrap: nowrap;
justify-content: space-between
}
#nav-toggle {
cursor: pointer;
display: block
}
#nav-toggle-cb {
outline: 0;
opacity: 0;
width: 0;
height: 0
}
#nav-toggle-cb:checked+#menu {
display: flex
}
.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, .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
}
.save {
position: absolute;
right: 25px;
top: 0px;
height: 16px;
line-height: 16px;
padding: 0 4px;
text-decoration: none;
cursor: pointer
}
button:hover {
background: #ff494d
}
button:active {
background: #f21c21
}
button.disabled {
cursor: default;
background: #a0a0a0
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
height: 22px;
background: #363636;
cursor: pointer;
margin: 0
}
input[type=range]:focus {
outline: 0
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 2px;
cursor: pointer;
background: #EFEFEF;
border-radius: 0;
border: 0 solid #EFEFEF
}
input[type=range]::-webkit-slider-thumb {
border: 1px solid rgba(0,0,30,0);
height: 22px;
width: 22px;
border-radius: 50px;
background: #ff3034;
cursor: pointer;
-webkit-appearance: none;
margin-top: -11.5px
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #EFEFEF
}
input[type=range]::-moz-range-track {
width: 100%;
height: 2px;
cursor: pointer;
background: #EFEFEF;
border-radius: 0;
border: 0 solid #EFEFEF
}
input[type=range]::-moz-range-thumb {
border: 1px solid rgba(0,0,30,0);
height: 22px;
width: 22px;
border-radius: 50px;
background: #ff3034;
cursor: pointer
}
input[type=range]::-ms-track {
width: 100%;
height: 2px;
cursor: pointer;
background: 0 0;
border-color: transparent;
color: transparent
}
input[type=range]::-ms-fill-lower {
background: #EFEFEF;
border: 0 solid #EFEFEF;
border-radius: 0
}
input[type=range]::-ms-fill-upper {
background: #EFEFEF;
border: 0 solid #EFEFEF;
border-radius: 0
}
input[type=range]::-ms-thumb {
border: 1px solid rgba(0,0,30,0);
height: 22px;
width: 22px;
border-radius: 50px;
background: #ff3034;
cursor: pointer;
height: 2px
}
input[type=range]:focus::-ms-fill-lower {
background: #EFEFEF
}
input[type=range]:focus::-ms-fill-upper {
background: #363636
}
.switch {
display: block;
position: relative;
line-height: 22px;
font-size: 16px;
height: 22px
}
.switch input {
outline: 0;
opacity: 0;
width: 0;
height: 0
}
.slider {
width: 50px;
height: 22px;
border-radius: 22px;
cursor: pointer;
background-color: grey
}
.slider,.slider:before {
display: inline-block;
transition: .4s
}
.slider:before {
position: relative;
content: "";
border-radius: 50%;
height: 16px;
width: 16px;
left: 4px;
top: 3px;
background-color: #fff
}
input:checked+.slider {
background-color: #ff3034
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
transform: translateX(26px)
}
select {
border: 1px solid #363636;
font-size: 14px;
height: 22px;
outline: 0;
border-radius: 5px
}
.image-container {
position: relative;
min-width: 160px
}
.close {
position: absolute;
right: 5px;
top: 5px;
background: #ff3034;
width: 16px;
height: 16px;
border-radius: 100px;
color: #fff;
text-align: center;
line-height: 18px;
cursor: pointer
}
.hidden {
display: none
}
</style>
</head>
<body>
<section class="main">
<div id="logo">
<label for="nav-toggle-cb" id="nav-toggle">&#9776;&nbsp;&nbsp;Toggle OV2640 settings</label>
</div>
<div id="content">
<div id="sidebar">
<input type="checkbox" id="nav-toggle-cb" checked="checked">
<nav id="menu">
<div class="input-group" id="framesize-group">
<label for="framesize">Resolution</label>
<select id="framesize" class="default-action">
<option value="10">UXGA(1600x1200)</option>
<option value="9">SXGA(1280x1024)</option>
<option value="8">XGA(1024x768)</option>
<option value="7">SVGA(800x600)</option>
<option value="6">VGA(640x480)</option>
<option value="5" selected="selected">CIF(400x296)</option>
<option value="4">QVGA(320x240)</option>
<option value="3">HQVGA(240x176)</option>
<option value="0">QQVGA(160x120)</option>
</select>
</div>
<div class="input-group" id="quality-group">
<label for="quality">Quality</label>
<div class="range-min">10</div>
<input type="range" id="quality" min="10" max="63" value="10" class="default-action">
<div class="range-max">63</div>
</div>
<div class="input-group" id="brightness-group">
<label for="brightness">Brightness</label>
<div class="range-min">-2</div>
<input type="range" id="brightness" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="contrast-group">
<label for="contrast">Contrast</label>
<div class="range-min">-2</div>
<input type="range" id="contrast" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="saturation-group">
<label for="saturation">Saturation</label>
<div class="range-min">-2</div>
<input type="range" id="saturation" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="special_effect-group">
<label for="special_effect">Special Effect</label>
<select id="special_effect" class="default-action">
<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" id="awb-group">
<label for="awb">AWB</label>
<div class="switch">
<input id="awb" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="awb"></label>
</div>
</div>
<div class="input-group" id="awb_gain-group">
<label for="awb_gain">AWB Gain</label>
<div class="switch">
<input id="awb_gain" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="awb_gain"></label>
</div>
</div>
<div class="input-group" id="wb_mode-group">
<label for="wb_mode">WB Mode</label>
<select id="wb_mode" class="default-action">
<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" id="aec-group">
<label for="aec">AEC SENSOR</label>
<div class="switch">
<input id="aec" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="aec"></label>
</div>
</div>
<div class="input-group" id="aec2-group">
<label for="aec2">AEC DSP</label>
<div class="switch">
<input id="aec2" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="aec2"></label>
</div>
</div>
<div class="input-group" id="ae_level-group">
<label for="ae_level">AE Level</label>
<div class="range-min">-2</div>
<input type="range" id="ae_level" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="aec_value-group">
<label for="aec_value">Exposure</label>
<div class="range-min">0</div>
<input type="range" id="aec_value" min="0" max="1200" value="204" class="default-action">
<div class="range-max">1200</div>
</div>
<div class="input-group" id="agc-group">
<label for="agc">AGC</label>
<div class="switch">
<input id="agc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="agc"></label>
</div>
</div>
<div class="input-group hidden" id="agc_gain-group">
<label for="agc_gain">Gain</label>
<div class="range-min">1x</div>
<input type="range" id="agc_gain" min="0" max="30" value="5" class="default-action">
<div class="range-max">31x</div>
</div>
<div class="input-group" id="gainceiling-group">
<label for="gainceiling">Gain Ceiling</label>
<div class="range-min">2x</div>
<input type="range" id="gainceiling" min="0" max="6" value="0" class="default-action">
<div class="range-max">128x</div>
</div>
<div class="input-group" id="bpc-group">
<label for="bpc">BPC</label>
<div class="switch">
<input id="bpc" type="checkbox" class="default-action">
<label class="slider" for="bpc"></label>
</div>
</div>
<div class="input-group" id="wpc-group">
<label for="wpc">WPC</label>
<div class="switch">
<input id="wpc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="wpc"></label>
</div>
</div>
<div class="input-group" id="raw_gma-group">
<label for="raw_gma">Raw GMA</label>
<div class="switch">
<input id="raw_gma" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="raw_gma"></label>
</div>
</div>
<div class="input-group" id="lenc-group">
<label for="lenc">Lens Correction</label>
<div class="switch">
<input id="lenc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="lenc"></label>
</div>
</div>
<div class="input-group" id="hmirror-group">
<label for="hmirror">H-Mirror</label>
<div class="switch">
<input id="hmirror" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="hmirror"></label>
</div>
</div>
<div class="input-group" id="vflip-group">
<label for="vflip">V-Flip</label>
<div class="switch">
<input id="vflip" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="vflip"></label>
</div>
</div>
<div class="input-group" id="dcw-group">
<label for="dcw">DCW (Downsize EN)</label>
<div class="switch">
<input id="dcw" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="dcw"></label>
</div>
</div>
<div class="input-group" id="colorbar-group">
<label for="colorbar">Color Bar</label>
<div class="switch">
<input id="colorbar" type="checkbox" class="default-action">
<label class="slider" for="colorbar"></label>
</div>
</div>
<div class="input-group" id="led-group">
<label for="led_intensity">LED Intensity</label>
<div class="range-min">0</div>
<input type="range" id="led_intensity" min="0" max="255" value="0" class="default-action">
<div class="range-max">255</div>
</div>
<div class="input-group" id="face_detect-group">
<label for="face_detect">Face Detection</label>
<div class="switch">
<input id="face_detect" type="checkbox" class="default-action">
<label class="slider" for="face_detect"></label>
</div>
</div>
<div class="input-group" id="face_recognize-group">
<label for="face_recognize">Face Recognition</label>
<div class="switch">
<input id="face_recognize" type="checkbox" class="default-action">
<label class="slider" for="face_recognize"></label>
</div>
</div>
<section id="buttons">
<button id="get-still">Get Still</button>
<button id="toggle-stream">Start Stream</button>
<button id="face_enroll" class="disabled" disabled="disabled">Enroll Face</button>
</section>
</nav>
</div>
<figure>
<div id="stream-container" class="image-container hidden">
<a id="save-still" href="#" class="button save" download="capture.jpg">Save</a>
<div class="close" id="close-stream">×</div>
<img id="stream" src="" crossorigin>
</div>
</figure>
</div>
</section>
<script>
document.addEventListener('DOMContentLoaded', function (event) {
var baseHost = document.location.origin
var streamUrl = baseHost + ':81'
const hide = el => {
el.classList.add('hidden')
}
const show = el => {
el.classList.remove('hidden')
}
const disable = el => {
el.classList.add('disabled')
el.disabled = true
}
const enable = el => {
el.classList.remove('disabled')
el.disabled = false
}
const updateValue = (el, value, updateRemote) => {
updateRemote = updateRemote == null ? true : updateRemote
let initialValue
if (el.type === 'checkbox') {
initialValue = el.checked
value = !!value
el.checked = value
} else {
initialValue = el.value
el.value = value
}
if (updateRemote && initialValue !== value) {
updateConfig(el);
} else if(!updateRemote){
if(el.id === "aec"){
value ? hide(exposure) : show(exposure)
} else if(el.id === "agc"){
if (value) {
show(gainCeiling)
hide(agcGain)
} else {
hide(gainCeiling)
show(agcGain)
}
} else if(el.id === "awb_gain"){
value ? show(wb) : hide(wb)
} else if(el.id === "face_recognize"){
value ? enable(enrollButton) : disable(enrollButton)
} else if(el.id == "led_intensity"){
value > -1 ? show(ledGroup) : hide(ledGroup)
}
}
}
function updateConfig (el) {
let value
switch (el.type) {
case 'checkbox':
value = el.checked ? 1 : 0
break
case 'range':
case 'select-one':
value = el.value
break
case 'button':
case 'submit':
value = '1'
break
default:
return
}
const query = `${baseHost}/control?var=${el.id}&val=${value}`
fetch(query)
.then(response => {
console.log(`request to ${query} finished, status: ${response.status}`)
})
}
document
.querySelectorAll('.close')
.forEach(el => {
el.onclick = () => {
hide(el.parentNode)
}
})
// read initial values
fetch(`${baseHost}/status`)
.then(function (response) {
return response.json()
})
.then(function (state) {
document
.querySelectorAll('.default-action')
.forEach(el => {
updateValue(el, state[el.id], false)
})
})
const view = document.getElementById('stream')
const viewContainer = document.getElementById('stream-container')
const stillButton = document.getElementById('get-still')
const streamButton = document.getElementById('toggle-stream')
const enrollButton = document.getElementById('face_enroll')
const closeButton = document.getElementById('close-stream')
const saveButton = document.getElementById('save-still')
const ledGroup = document.getElementById('led-group')
const stopStream = () => {
window.stop();
streamButton.innerHTML = 'Start Stream'
}
const startStream = () => {
view.src = `${streamUrl}/stream`
show(viewContainer)
streamButton.innerHTML = 'Stop Stream'
}
// Attach actions to buttons
stillButton.onclick = () => {
stopStream()
view.src = `${baseHost}/capture?_cb=${Date.now()}`
show(viewContainer)
}
closeButton.onclick = () => {
stopStream()
hide(viewContainer)
}
streamButton.onclick = () => {
const streamEnabled = streamButton.innerHTML === 'Stop Stream'
if (streamEnabled) {
stopStream()
} else {
startStream()
}
}
enrollButton.onclick = () => {
updateConfig(enrollButton)
}
saveButton.onclick = () => {
var canvas = document.createElement("canvas");
canvas.width = view.width;
canvas.height = view.height;
document.body.appendChild(canvas);
var context = canvas.getContext('2d');
context.drawImage(view,0,0);
try {
var dataURL = canvas.toDataURL('image/jpeg');
saveButton.href = dataURL;
var d = new Date();
saveButton.download = d.getFullYear() + ("0"+(d.getMonth()+1)).slice(-2) + ("0" + d.getDate()).slice(-2) + ("0" + d.getHours()).slice(-2) + ("0" + d.getMinutes()).slice(-2) + ("0" + d.getSeconds()).slice(-2) + ".jpg";
} catch (e) {
console.error(e);
}
canvas.parentNode.removeChild(canvas);
}
// Attach default on change action
document
.querySelectorAll('.default-action')
.forEach(el => {
el.onchange = () => updateConfig(el)
})
// Custom actions
// Gain
const agc = document.getElementById('agc')
const agcGain = document.getElementById('agc_gain-group')
const gainCeiling = document.getElementById('gainceiling-group')
agc.onchange = () => {
updateConfig(agc)
if (agc.checked) {
show(gainCeiling)
hide(agcGain)
} else {
hide(gainCeiling)
show(agcGain)
}
}
// Exposure
const aec = document.getElementById('aec')
const exposure = document.getElementById('aec_value-group')
aec.onchange = () => {
updateConfig(aec)
aec.checked ? hide(exposure) : show(exposure)
}
// AWB
const awb = document.getElementById('awb_gain')
const wb = document.getElementById('wb_mode-group')
awb.onchange = () => {
updateConfig(awb)
awb.checked ? show(wb) : hide(wb)
}
// Detection and framesize
const detect = document.getElementById('face_detect')
const recognize = document.getElementById('face_recognize')
const framesize = document.getElementById('framesize')
framesize.onchange = () => {
updateConfig(framesize)
if (framesize.value > 5) {
updateValue(detect, false)
updateValue(recognize, false)
}
}
detect.onchange = () => {
if (framesize.value > 5) {
alert("Please select CIF or lower resolution before enabling this feature!");
updateValue(detect, false)
return;
}
updateConfig(detect)
if (!detect.checked) {
disable(enrollButton)
updateValue(recognize, false)
}
}
recognize.onchange = () => {
if (framesize.value > 5) {
alert("Please select CIF or lower resolution before enabling this feature!");
updateValue(recognize, false)
return;
}
updateConfig(recognize)
if (recognize.checked) {
enable(enrollButton)
updateValue(detect, true)
} else {
disable(enrollButton)
}
}
})
</script>
</body>
</html>