index_ov2640.html 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1">
  6. <title>ESP32 OV2460</title>
  7. <style>
  8. body {
  9. font-family: Arial,Helvetica,sans-serif;
  10. background: #181818;
  11. color: #EFEFEF;
  12. font-size: 16px
  13. }
  14. h2 {
  15. font-size: 18px
  16. }
  17. section.main {
  18. display: flex
  19. }
  20. #menu,section.main {
  21. flex-direction: column
  22. }
  23. #menu {
  24. display: none;
  25. flex-wrap: nowrap;
  26. min-width: 340px;
  27. background: #363636;
  28. padding: 8px;
  29. border-radius: 4px;
  30. margin-top: -10px;
  31. margin-right: 10px;
  32. }
  33. #content {
  34. display: flex;
  35. flex-wrap: wrap;
  36. align-items: stretch
  37. }
  38. figure {
  39. padding: 0px;
  40. margin: 0;
  41. -webkit-margin-before: 0;
  42. margin-block-start: 0;
  43. -webkit-margin-after: 0;
  44. margin-block-end: 0;
  45. -webkit-margin-start: 0;
  46. margin-inline-start: 0;
  47. -webkit-margin-end: 0;
  48. margin-inline-end: 0
  49. }
  50. figure img {
  51. display: block;
  52. width: 100%;
  53. height: auto;
  54. border-radius: 4px;
  55. margin-top: 8px;
  56. }
  57. @media (min-width: 800px) and (orientation:landscape) {
  58. #content {
  59. display:flex;
  60. flex-wrap: nowrap;
  61. align-items: stretch
  62. }
  63. figure img {
  64. display: block;
  65. max-width: 100%;
  66. max-height: calc(100vh - 40px);
  67. width: auto;
  68. height: auto
  69. }
  70. figure {
  71. padding: 0 0 0 0px;
  72. margin: 0;
  73. -webkit-margin-before: 0;
  74. margin-block-start: 0;
  75. -webkit-margin-after: 0;
  76. margin-block-end: 0;
  77. -webkit-margin-start: 0;
  78. margin-inline-start: 0;
  79. -webkit-margin-end: 0;
  80. margin-inline-end: 0
  81. }
  82. }
  83. section#buttons {
  84. display: flex;
  85. flex-wrap: nowrap;
  86. justify-content: space-between
  87. }
  88. #nav-toggle {
  89. cursor: pointer;
  90. display: block
  91. }
  92. #nav-toggle-cb {
  93. outline: 0;
  94. opacity: 0;
  95. width: 0;
  96. height: 0
  97. }
  98. #nav-toggle-cb:checked+#menu {
  99. display: flex
  100. }
  101. .input-group {
  102. display: flex;
  103. flex-wrap: nowrap;
  104. line-height: 22px;
  105. margin: 5px 0
  106. }
  107. .input-group>label {
  108. display: inline-block;
  109. padding-right: 10px;
  110. min-width: 47%
  111. }
  112. .input-group input,.input-group select {
  113. flex-grow: 1
  114. }
  115. .range-max,.range-min {
  116. display: inline-block;
  117. padding: 0 5px
  118. }
  119. button {
  120. display: block;
  121. margin: 5px;
  122. padding: 0 12px;
  123. border: 0;
  124. line-height: 28px;
  125. cursor: pointer;
  126. color: #fff;
  127. background: #ff3034;
  128. border-radius: 5px;
  129. font-size: 16px;
  130. outline: 0
  131. }
  132. button:hover {
  133. background: #ff494d
  134. }
  135. button:active {
  136. background: #f21c21
  137. }
  138. button.disabled {
  139. cursor: default;
  140. background: #a0a0a0
  141. }
  142. input[type=range] {
  143. -webkit-appearance: none;
  144. width: 100%;
  145. height: 22px;
  146. background: #363636;
  147. cursor: pointer;
  148. margin: 0
  149. }
  150. input[type=range]:focus {
  151. outline: 0
  152. }
  153. input[type=range]::-webkit-slider-runnable-track {
  154. width: 100%;
  155. height: 2px;
  156. cursor: pointer;
  157. background: #EFEFEF;
  158. border-radius: 0;
  159. border: 0 solid #EFEFEF
  160. }
  161. input[type=range]::-webkit-slider-thumb {
  162. border: 1px solid rgba(0,0,30,0);
  163. height: 22px;
  164. width: 22px;
  165. border-radius: 50px;
  166. background: #ff3034;
  167. cursor: pointer;
  168. -webkit-appearance: none;
  169. margin-top: -11.5px
  170. }
  171. input[type=range]:focus::-webkit-slider-runnable-track {
  172. background: #EFEFEF
  173. }
  174. input[type=range]::-moz-range-track {
  175. width: 100%;
  176. height: 2px;
  177. cursor: pointer;
  178. background: #EFEFEF;
  179. border-radius: 0;
  180. border: 0 solid #EFEFEF
  181. }
  182. input[type=range]::-moz-range-thumb {
  183. border: 1px solid rgba(0,0,30,0);
  184. height: 22px;
  185. width: 22px;
  186. border-radius: 50px;
  187. background: #ff3034;
  188. cursor: pointer
  189. }
  190. input[type=range]::-ms-track {
  191. width: 100%;
  192. height: 2px;
  193. cursor: pointer;
  194. background: 0 0;
  195. border-color: transparent;
  196. color: transparent
  197. }
  198. input[type=range]::-ms-fill-lower {
  199. background: #EFEFEF;
  200. border: 0 solid #EFEFEF;
  201. border-radius: 0
  202. }
  203. input[type=range]::-ms-fill-upper {
  204. background: #EFEFEF;
  205. border: 0 solid #EFEFEF;
  206. border-radius: 0
  207. }
  208. input[type=range]::-ms-thumb {
  209. border: 1px solid rgba(0,0,30,0);
  210. height: 22px;
  211. width: 22px;
  212. border-radius: 50px;
  213. background: #ff3034;
  214. cursor: pointer;
  215. height: 2px
  216. }
  217. input[type=range]:focus::-ms-fill-lower {
  218. background: #EFEFEF
  219. }
  220. input[type=range]:focus::-ms-fill-upper {
  221. background: #363636
  222. }
  223. .switch {
  224. display: block;
  225. position: relative;
  226. line-height: 22px;
  227. font-size: 16px;
  228. height: 22px
  229. }
  230. .switch input {
  231. outline: 0;
  232. opacity: 0;
  233. width: 0;
  234. height: 0
  235. }
  236. .slider {
  237. width: 50px;
  238. height: 22px;
  239. border-radius: 22px;
  240. cursor: pointer;
  241. background-color: grey
  242. }
  243. .slider,.slider:before {
  244. display: inline-block;
  245. transition: .4s
  246. }
  247. .slider:before {
  248. position: relative;
  249. content: "";
  250. border-radius: 50%;
  251. height: 16px;
  252. width: 16px;
  253. left: 4px;
  254. top: 3px;
  255. background-color: #fff
  256. }
  257. input:checked+.slider {
  258. background-color: #ff3034
  259. }
  260. input:checked+.slider:before {
  261. -webkit-transform: translateX(26px);
  262. transform: translateX(26px)
  263. }
  264. select {
  265. border: 1px solid #363636;
  266. font-size: 14px;
  267. height: 22px;
  268. outline: 0;
  269. border-radius: 5px
  270. }
  271. .image-container {
  272. position: relative;
  273. min-width: 160px
  274. }
  275. .close {
  276. position: absolute;
  277. right: 5px;
  278. top: 5px;
  279. background: #ff3034;
  280. width: 16px;
  281. height: 16px;
  282. border-radius: 100px;
  283. color: #fff;
  284. text-align: center;
  285. line-height: 18px;
  286. cursor: pointer
  287. }
  288. .hidden {
  289. display: none
  290. }
  291. </style>
  292. </head>
  293. <body>
  294. <section class="main">
  295. <div id="logo">
  296. <label for="nav-toggle-cb" id="nav-toggle">&#9776;&nbsp;&nbsp;Toggle OV2640 settings</label>
  297. </div>
  298. <div id="content">
  299. <div id="sidebar">
  300. <input type="checkbox" id="nav-toggle-cb" checked="checked">
  301. <nav id="menu">
  302. <div class="input-group" id="framesize-group">
  303. <label for="framesize">Resolution</label>
  304. <select id="framesize" class="default-action">
  305. <option value="10">UXGA(1600x1200)</option>
  306. <option value="9">SXGA(1280x1024)</option>
  307. <option value="8">XGA(1024x768)</option>
  308. <option value="7">SVGA(800x600)</option>
  309. <option value="6">VGA(640x480)</option>
  310. <option value="5" selected="selected">CIF(400x296)</option>
  311. <option value="4">QVGA(320x240)</option>
  312. <option value="3">HQVGA(240x176)</option>
  313. <option value="0">QQVGA(160x120)</option>
  314. </select>
  315. </div>
  316. <div class="input-group" id="quality-group">
  317. <label for="quality">Quality</label>
  318. <div class="range-min">10</div>
  319. <input type="range" id="quality" min="10" max="63" value="10" class="default-action">
  320. <div class="range-max">63</div>
  321. </div>
  322. <div class="input-group" id="brightness-group">
  323. <label for="brightness">Brightness</label>
  324. <div class="range-min">-2</div>
  325. <input type="range" id="brightness" min="-2" max="2" value="0" class="default-action">
  326. <div class="range-max">2</div>
  327. </div>
  328. <div class="input-group" id="contrast-group">
  329. <label for="contrast">Contrast</label>
  330. <div class="range-min">-2</div>
  331. <input type="range" id="contrast" min="-2" max="2" value="0" class="default-action">
  332. <div class="range-max">2</div>
  333. </div>
  334. <div class="input-group" id="saturation-group">
  335. <label for="saturation">Saturation</label>
  336. <div class="range-min">-2</div>
  337. <input type="range" id="saturation" min="-2" max="2" value="0" class="default-action">
  338. <div class="range-max">2</div>
  339. </div>
  340. <div class="input-group" id="special_effect-group">
  341. <label for="special_effect">Special Effect</label>
  342. <select id="special_effect" class="default-action">
  343. <option value="0" selected="selected">No Effect</option>
  344. <option value="1">Negative</option>
  345. <option value="2">Grayscale</option>
  346. <option value="3">Red Tint</option>
  347. <option value="4">Green Tint</option>
  348. <option value="5">Blue Tint</option>
  349. <option value="6">Sepia</option>
  350. </select>
  351. </div>
  352. <div class="input-group" id="awb-group">
  353. <label for="awb">AWB</label>
  354. <div class="switch">
  355. <input id="awb" type="checkbox" class="default-action" checked="checked">
  356. <label class="slider" for="awb"></label>
  357. </div>
  358. </div>
  359. <div class="input-group" id="awb_gain-group">
  360. <label for="awb_gain">AWB Gain</label>
  361. <div class="switch">
  362. <input id="awb_gain" type="checkbox" class="default-action" checked="checked">
  363. <label class="slider" for="awb_gain"></label>
  364. </div>
  365. </div>
  366. <div class="input-group" id="wb_mode-group">
  367. <label for="wb_mode">WB Mode</label>
  368. <select id="wb_mode" class="default-action">
  369. <option value="0" selected="selected">Auto</option>
  370. <option value="1">Sunny</option>
  371. <option value="2">Cloudy</option>
  372. <option value="3">Office</option>
  373. <option value="4">Home</option>
  374. </select>
  375. </div>
  376. <div class="input-group" id="aec-group">
  377. <label for="aec">AEC SENSOR</label>
  378. <div class="switch">
  379. <input id="aec" type="checkbox" class="default-action" checked="checked">
  380. <label class="slider" for="aec"></label>
  381. </div>
  382. </div>
  383. <div class="input-group" id="aec2-group">
  384. <label for="aec2">AEC DSP</label>
  385. <div class="switch">
  386. <input id="aec2" type="checkbox" class="default-action" checked="checked">
  387. <label class="slider" for="aec2"></label>
  388. </div>
  389. </div>
  390. <div class="input-group" id="ae_level-group">
  391. <label for="ae_level">AE Level</label>
  392. <div class="range-min">-2</div>
  393. <input type="range" id="ae_level" min="-2" max="2" value="0" class="default-action">
  394. <div class="range-max">2</div>
  395. </div>
  396. <div class="input-group" id="aec_value-group">
  397. <label for="aec_value">Exposure</label>
  398. <div class="range-min">0</div>
  399. <input type="range" id="aec_value" min="0" max="1200" value="204" class="default-action">
  400. <div class="range-max">1200</div>
  401. </div>
  402. <div class="input-group" id="agc-group">
  403. <label for="agc">AGC</label>
  404. <div class="switch">
  405. <input id="agc" type="checkbox" class="default-action" checked="checked">
  406. <label class="slider" for="agc"></label>
  407. </div>
  408. </div>
  409. <div class="input-group hidden" id="agc_gain-group">
  410. <label for="agc_gain">Gain</label>
  411. <div class="range-min">1x</div>
  412. <input type="range" id="agc_gain" min="0" max="30" value="5" class="default-action">
  413. <div class="range-max">31x</div>
  414. </div>
  415. <div class="input-group" id="gainceiling-group">
  416. <label for="gainceiling">Gain Ceiling</label>
  417. <div class="range-min">2x</div>
  418. <input type="range" id="gainceiling" min="0" max="6" value="0" class="default-action">
  419. <div class="range-max">128x</div>
  420. </div>
  421. <div class="input-group" id="bpc-group">
  422. <label for="bpc">BPC</label>
  423. <div class="switch">
  424. <input id="bpc" type="checkbox" class="default-action">
  425. <label class="slider" for="bpc"></label>
  426. </div>
  427. </div>
  428. <div class="input-group" id="wpc-group">
  429. <label for="wpc">WPC</label>
  430. <div class="switch">
  431. <input id="wpc" type="checkbox" class="default-action" checked="checked">
  432. <label class="slider" for="wpc"></label>
  433. </div>
  434. </div>
  435. <div class="input-group" id="raw_gma-group">
  436. <label for="raw_gma">Raw GMA</label>
  437. <div class="switch">
  438. <input id="raw_gma" type="checkbox" class="default-action" checked="checked">
  439. <label class="slider" for="raw_gma"></label>
  440. </div>
  441. </div>
  442. <div class="input-group" id="lenc-group">
  443. <label for="lenc">Lens Correction</label>
  444. <div class="switch">
  445. <input id="lenc" type="checkbox" class="default-action" checked="checked">
  446. <label class="slider" for="lenc"></label>
  447. </div>
  448. </div>
  449. <div class="input-group" id="hmirror-group">
  450. <label for="hmirror">H-Mirror</label>
  451. <div class="switch">
  452. <input id="hmirror" type="checkbox" class="default-action" checked="checked">
  453. <label class="slider" for="hmirror"></label>
  454. </div>
  455. </div>
  456. <div class="input-group" id="vflip-group">
  457. <label for="vflip">V-Flip</label>
  458. <div class="switch">
  459. <input id="vflip" type="checkbox" class="default-action" checked="checked">
  460. <label class="slider" for="vflip"></label>
  461. </div>
  462. </div>
  463. <div class="input-group" id="dcw-group">
  464. <label for="dcw">DCW (Downsize EN)</label>
  465. <div class="switch">
  466. <input id="dcw" type="checkbox" class="default-action" checked="checked">
  467. <label class="slider" for="dcw"></label>
  468. </div>
  469. </div>
  470. <div class="input-group" id="colorbar-group">
  471. <label for="colorbar">Color Bar</label>
  472. <div class="switch">
  473. <input id="colorbar" type="checkbox" class="default-action">
  474. <label class="slider" for="colorbar"></label>
  475. </div>
  476. </div>
  477. <div class="input-group" id="face_detect-group">
  478. <label for="face_detect">Face Detection</label>
  479. <div class="switch">
  480. <input id="face_detect" type="checkbox" class="default-action">
  481. <label class="slider" for="face_detect"></label>
  482. </div>
  483. </div>
  484. <div class="input-group" id="face_recognize-group">
  485. <label for="face_recognize">Face Recognition</label>
  486. <div class="switch">
  487. <input id="face_recognize" type="checkbox" class="default-action">
  488. <label class="slider" for="face_recognize"></label>
  489. </div>
  490. </div>
  491. <section id="buttons">
  492. <button id="get-still">Get Still</button>
  493. <button id="toggle-stream">Start Stream</button>
  494. <button id="face_enroll" class="disabled" disabled="disabled">Enroll Face</button>
  495. </section>
  496. </nav>
  497. </div>
  498. <figure>
  499. <div id="stream-container" class="image-container hidden">
  500. <div class="close" id="close-stream">×</div>
  501. <img id="stream" src="">
  502. </div>
  503. </figure>
  504. </div>
  505. </section>
  506. <script>
  507. document.addEventListener('DOMContentLoaded', function (event) {
  508. var baseHost = document.location.origin
  509. var streamUrl = baseHost + ':81'
  510. const hide = el => {
  511. el.classList.add('hidden')
  512. }
  513. const show = el => {
  514. el.classList.remove('hidden')
  515. }
  516. const disable = el => {
  517. el.classList.add('disabled')
  518. el.disabled = true
  519. }
  520. const enable = el => {
  521. el.classList.remove('disabled')
  522. el.disabled = false
  523. }
  524. const updateValue = (el, value, updateRemote) => {
  525. updateRemote = updateRemote == null ? true : updateRemote
  526. let initialValue
  527. if (el.type === 'checkbox') {
  528. initialValue = el.checked
  529. value = !!value
  530. el.checked = value
  531. } else {
  532. initialValue = el.value
  533. el.value = value
  534. }
  535. if (updateRemote && initialValue !== value) {
  536. updateConfig(el);
  537. } else if(!updateRemote){
  538. if(el.id === "aec"){
  539. value ? hide(exposure) : show(exposure)
  540. } else if(el.id === "agc"){
  541. if (value) {
  542. show(gainCeiling)
  543. hide(agcGain)
  544. } else {
  545. hide(gainCeiling)
  546. show(agcGain)
  547. }
  548. } else if(el.id === "awb_gain"){
  549. value ? show(wb) : hide(wb)
  550. } else if(el.id === "face_recognize"){
  551. value ? enable(enrollButton) : disable(enrollButton)
  552. }
  553. }
  554. }
  555. function updateConfig (el) {
  556. let value
  557. switch (el.type) {
  558. case 'checkbox':
  559. value = el.checked ? 1 : 0
  560. break
  561. case 'range':
  562. case 'select-one':
  563. value = el.value
  564. break
  565. case 'button':
  566. case 'submit':
  567. value = '1'
  568. break
  569. default:
  570. return
  571. }
  572. const query = `${baseHost}/control?var=${el.id}&val=${value}`
  573. fetch(query)
  574. .then(response => {
  575. console.log(`request to ${query} finished, status: ${response.status}`)
  576. })
  577. }
  578. document
  579. .querySelectorAll('.close')
  580. .forEach(el => {
  581. el.onclick = () => {
  582. hide(el.parentNode)
  583. }
  584. })
  585. // read initial values
  586. fetch(`${baseHost}/status`)
  587. .then(function (response) {
  588. return response.json()
  589. })
  590. .then(function (state) {
  591. document
  592. .querySelectorAll('.default-action')
  593. .forEach(el => {
  594. updateValue(el, state[el.id], false)
  595. })
  596. })
  597. const view = document.getElementById('stream')
  598. const viewContainer = document.getElementById('stream-container')
  599. const stillButton = document.getElementById('get-still')
  600. const streamButton = document.getElementById('toggle-stream')
  601. const enrollButton = document.getElementById('face_enroll')
  602. const closeButton = document.getElementById('close-stream')
  603. const stopStream = () => {
  604. window.stop();
  605. streamButton.innerHTML = 'Start Stream'
  606. }
  607. const startStream = () => {
  608. view.src = `${streamUrl}/stream`
  609. show(viewContainer)
  610. streamButton.innerHTML = 'Stop Stream'
  611. }
  612. // Attach actions to buttons
  613. stillButton.onclick = () => {
  614. stopStream()
  615. view.src = `${baseHost}/capture?_cb=${Date.now()}`
  616. show(viewContainer)
  617. }
  618. closeButton.onclick = () => {
  619. stopStream()
  620. hide(viewContainer)
  621. }
  622. streamButton.onclick = () => {
  623. const streamEnabled = streamButton.innerHTML === 'Stop Stream'
  624. if (streamEnabled) {
  625. stopStream()
  626. } else {
  627. startStream()
  628. }
  629. }
  630. enrollButton.onclick = () => {
  631. updateConfig(enrollButton)
  632. }
  633. // Attach default on change action
  634. document
  635. .querySelectorAll('.default-action')
  636. .forEach(el => {
  637. el.onchange = () => updateConfig(el)
  638. })
  639. // Custom actions
  640. // Gain
  641. const agc = document.getElementById('agc')
  642. const agcGain = document.getElementById('agc_gain-group')
  643. const gainCeiling = document.getElementById('gainceiling-group')
  644. agc.onchange = () => {
  645. updateConfig(agc)
  646. if (agc.checked) {
  647. show(gainCeiling)
  648. hide(agcGain)
  649. } else {
  650. hide(gainCeiling)
  651. show(agcGain)
  652. }
  653. }
  654. // Exposure
  655. const aec = document.getElementById('aec')
  656. const exposure = document.getElementById('aec_value-group')
  657. aec.onchange = () => {
  658. updateConfig(aec)
  659. aec.checked ? hide(exposure) : show(exposure)
  660. }
  661. // AWB
  662. const awb = document.getElementById('awb_gain')
  663. const wb = document.getElementById('wb_mode-group')
  664. awb.onchange = () => {
  665. updateConfig(awb)
  666. awb.checked ? show(wb) : hide(wb)
  667. }
  668. // Detection and framesize
  669. const detect = document.getElementById('face_detect')
  670. const recognize = document.getElementById('face_recognize')
  671. const framesize = document.getElementById('framesize')
  672. framesize.onchange = () => {
  673. updateConfig(framesize)
  674. if (framesize.value > 5) {
  675. updateValue(detect, false)
  676. updateValue(recognize, false)
  677. }
  678. }
  679. detect.onchange = () => {
  680. if (framesize.value > 5) {
  681. alert("Please select CIF or lower resolution before enabling this feature!");
  682. updateValue(detect, false)
  683. return;
  684. }
  685. updateConfig(detect)
  686. if (!detect.checked) {
  687. disable(enrollButton)
  688. updateValue(recognize, false)
  689. }
  690. }
  691. recognize.onchange = () => {
  692. if (framesize.value > 5) {
  693. alert("Please select CIF or lower resolution before enabling this feature!");
  694. updateValue(recognize, false)
  695. return;
  696. }
  697. updateConfig(recognize)
  698. if (recognize.checked) {
  699. enable(enrollButton)
  700. updateValue(detect, true)
  701. } else {
  702. disable(enrollButton)
  703. }
  704. }
  705. })
  706. </script>
  707. </body>
  708. </html>