123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780 |
- <!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 {
- 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
- }
- 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">☰ 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="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">
- <div class="close" id="close-stream">×</div>
- <img id="stream" src="">
- </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)
- }
- }
- }
- 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 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)
- }
- // 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>
|