Servo-CAM with M5CAMERA or ESP32CAM and two SG90 servos

Place your projects here
Post Reply
User avatar
PeterN
Posts: 97
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 41 times
Been thanked: 45 times
Contact:

Servo-CAM with M5CAMERA or ESP32CAM and two SG90 servos

Post by PeterN »

A bit of ANNEX-code to controll an ESP32 based pan/tilt servo CAM.
My intention for HW and SW was "Keep it simple":
turn_left.GIF
.

So I stacked two SG90-servos to carry a M5CAMERA001.
The webinterface was derived from CiccioCB's sample code. Horizontal and vertical position are manualy set by sliders that are integrated in the live-picture. An auto pan can slowly turn the cam.
All possible resolutions can be selected.
Some pictures can be stored in the internal flash ( or better on SD-card if inserted in an ESP32CAM).
3860C928-BB81-4FDB-BDBC-2D794821010F.jpeg
AF4569A4-B302-40CE-B674-C32934112FAD.jpeg
54B20915-9FAF-45E8-B16F-B5050CCCA471.jpeg

Next thing I want to try is
# to implement face detection and face recognition, which are unhappily only available in the 320x240 mode. This worked fundamentally well for me in a separate code snippet - except that I now need to "find" some javascript to draw a frame at the returned Face-position in the browser window and perhaps to print an apropriate name for one of the seven recognizable stored faces.

# A mode to follow a face with the servos then seems to be the next simple step ... BUT: the max resolution for face detection is 320x240 - not really nice to look at.

# A mode to automatically regularly take a picture and store it on a FTP-server may be handy


This is my current ANNEX-code for the described functions:
If you do use only a horizontal PAN servo, or even no servo ... the code is prepared to simply set some
variables to (de)activate the servo functions and the sliders.
If running on an ESPCAM the onboard white flash LED can be switched ON/OFF by an additional button.
If the camera or the mount is turned or mounted over the top: Look at the variables to invert/mirror the movements or the picture. I have tried to explain the purpose by the names and with some comment-lines.

Code: [Local Link Removed for Guests]


' A N N E X 3 2 C A M - p r o g r a m  
'  - to show a life camera picture in browser in some resolutions 
'  - to controll the pan/tilt servos (manually or auto-pan)
'  - to take some still pictures in NV-RAM or on SD-Card
'  - to controll the onboard white LED (ESP32CAM only)
'  - to set some camera parameters  
' Hardware: 
'   ESP32CAM  or M5CAM version "B"  with or without two servos
'     ESP32CAM : H-Servo at Pin GPIO12; V-Servo at Pin GPIO3 = RX
'     M5CAM    : H-Servo at Pin GPIO4;  V-Servo at Pin GPIO13; pins at grove-port
' DB9JG@ME:COM   2021/06

VERSION$    = "V9.1"
CALL$       = "DB9JG"

'Save snapshots in local NVRAM or SD (if ANNEX runs on the SD)?
'PIC_NUM    = 0 ' => do not save or show  pics
PIC_NUM     = 5 ' number of  pics to save  <<ATTENTION: NEEDS SUFFICIENT FLASH-RAM OR SD-CARD
MIN_SPACE   = 100000    'take pictures only if some space is left on flash or SD-card

'set camera lense orientation according to HW-setup
PIC_V_FLIP  = 1    'Vertical orientation   0 = normal, 1 = flipped
PIC_H_MIRROR= 1    'Horizontal orientation 0 = normal, 1 = flipped

'Set servo orientation according to construction of servo mount
SERVO_H_FLIP= 1    'PAN  servo  0 = normal, 1 = inverted
SERVO_V_FLIP= 0    'TILT servo  0 = normal, 1 = inverted

'----------------------------------------------
'SERVO range  (my settings for two SG90 servos)
SERVO_H_POS = 280  '<<<initial slider position,  if 0 then no servo and no slider 
SERVO_V_POS = 230  '<<<initial slider position,  if 0 then no servo and no slider
SERVO_H_MIN = 100  'horizontal leftmost position
SERVO_H_MAX = 470  'horizontal rightmost position
SERVO_V_MIN = 160  'vertical leftmost position
SERVO_V_MAX = 420  'vertical rightmost position
HSTEP       = 12   'STEP width for automatic panning

SHOW_PARAMS = 0   '0 = starts with CLOSED parameter-menue
ms_per_pic  = 100 '<< 100ms/pic = 10pics/sec at HTML-page <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

M5CAM            = 202 'HW-device-numbers returned by BAS.DEVICE
ESP32CAM         = 201
BUT_OFF$         = "butgreen"
BUT_ON$          = "butred"
SHOW_PARAMS_TXT$ = "SETTINGS=OFF"
MAKE_FOTO_COL$   = BUT_OFF$
MAKE_FOTO_TXT$   = "make FOTO"
ret              = file.mkdir("/images")   'ensure folder for saving pics
FOTO$            = "/images/PIC_1.jpg"
RES_NEW$         = ""
MAKE_UNIQUE$     = str$(millis)
T$               = ""
L                = 0
FOTO_LOCKED      = 0
DateTime$        = ""
SHOW_PARAMS_COL$ = BUT_OFF$
SHOW_PARAMS_TXT$ = "SETTINGS=OFF"
LED_ON_OFF_COL$  = BUT_OFF$
LED_ON_OFF_TXT$  = "LED=OFF"
AUTO_PAN_TXT$    = "AUTO_PAN=OFF"
AUTO_PAN_COL$    = BUT_OFF$
INFO$            = CALL$ + "-CAM " + VERSION$

option.wdt  10000  'watchdog timer 10seconds  
wifi.power  20     'maximize wifi-power

'SERVO-setup if used with pan/tilt servos 
If SERVO_H_POS then gosub SERVO_H_INIT
If SERVO_V_POS then gosub SERVO_V_INIT

'use onboard LEDs only at ESP32CAM not with M5CAM
if BAS.DEVICE = ESP32CAM then
  pin.mode 33, output  'red LED
  pin.mode 4, output   'white LED
  pin(33)        = 0  'rote LED AUS
  pin(4)         = 1  'WHITE LED ON
  pause 100
  pin(4)         = 0   'WHITE LED OFF
endif

gosub        CAM_INIT
if (file.exists(FOTO$) = 0) and (PIC_NUM > 0) then 
 print CAMERA.PICTURE(FOTO$) ' save the first picture
endif
gosub        MAIN
onHtmlReload setpage
gosub        setpage
onHtmlChange paramchange

timer0 250,  MAIN
wait

'#########################################################
'#########################################################
'#########################################################
MAIN:
'#########
if T$ <> time$ then
  L = 1-L  'toggle for LED
  T$ = time$
  DateTime$ = "20" + date$(2) + "_" + T$
  DateTime$ = replace$(DateTime$,"/","-")
  'DateTime$ = replace$(DateTime$,":","-")
  option.wdtreset
  if flashfree <  MIN_SPACE then
    MAKE_FOTO_TXT$ = "NO SPACE FOR FOTO!"
    MAKE_FOTO_COL$ = BUT_ON$
  else
    MAKE_FOTO_TXT$ = "make Foto"
    MAKE_FOTO_COL$ = BUT_OFF$
  endif
endif
IF SERVO_H_POS gosub SERVO_H_SET
IF SERVO_V_POS gosub SERVO_V_SET

'Set new resolution
IF RES_NEW$ <> RES$ goto "res_" + RES_NEW$

if BAS.DEVICE = ESP32CAM pin(33)=1-pin(33)   'toggles the red LED only at ESP32CAM

return

'############################################
setpage:
'#########
A$ = ""
A$ = A$ + |<table><td>| + textbox$(INFO$, "tbox_top") +  textbox$(DateTime$, "tbox_top")
A$ = A$ + |<img id='camera' src="picture" alt="NO PIC from AnnexCam" style="width:112%;">|
A$ = A$ + |<br>|
IF SERVO_H_POS then A$ = A$ + slider$(SERVO_H_POS, SERVO_H_MIN, SERVO_H_MAX, "slider_hor" )
A$ = A$ + |<td>|
IF SERVO_V_POS then A$ = A$ + slider$(SERVO_V_POS, SERVO_V_MIN, SERVO_V_MAX, "slider_vert" )
A$ = A$ + |</table>|
A$ = A$ + "<center>"
'''''A$ = A$ + textbox$(INFO$, "tbox") + LED$(L)+  textbox$(DateTime$, "tbox") + "<br>"
if BAS.DEVICE = ESP32CAM then A$ = A$ + button$(LED_ON_OFF_TXT$, LED_ON_OFF, LED_ON_OFF_COL$)+ " "
A$ = A$ + listbox$(RES_NEW$,"320x240,640x480,800x600,1024x768,1280x1024,1600x1200","lbox")
A$ = A$ + button$(SHOW_PARAMS_TXT$, SHOW_PARAMS_ON_OFF, SHOW_PARAMS_COL$)+ " "
A$ = A$ + button$(MAKE_FOTO_TXT$, MAKE_FOTO, MAKE_FOTO_COL$) + " "
IF SERVO_H_POS then A$ = A$ + button$(AUTO_PAN_TXT$, AUTO_PAN, AUTO_PAN_COL$)
A$ = A$ + "<br></center>"

if SHOW_PARAMS = 1 then
  A$ = A$ + |<center><table>|
  '''A$ = A$ + slider$(quality, 10, 63, "slider") + "<td>jpg-Compression"
  A$ = A$ + |<tr><td>|
  A$ = A$ + slider$(brightness, -2, 2, "slider2") + "<td>Brightness"
  A$ = A$ + |<tr><td>|
  A$ = A$ + slider$(contrast, -2, 2, "slider") + "<td>Contrast"
  A$ = A$ + |<tr><td>|
  A$ = A$ + slider$(saturation, -2, 2, "slider2") + "<td>Saturation"
  A$ = A$ + |<tr><td>|
  'A$ = A$ + checkbox$(awb) + "AWB"
  'A$ = A$ + |<tr><td>|
  'A$ = A$ + checkbox$(awb_gain) + "<td>AWB gain"
  ' this is a checkbox using the variable 'awb_gain' (data-var='awb_gain')
  A$ = A$ + |<div class="onoffswitch">|
  A$ = A$ + |    <input type="checkbox" data-var='awb_gain' onchange='cmdChange(event)' class="onoffswitch-checkbox" id="mycheck1" checked>|
  A$ = A$ + |    <label class="onoffswitch-label" for="mycheck1">|
  A$ = A$ + |        <span class="onoffswitch-inner"></span>|
  A$ = A$ + |        <span class="onoffswitch-switch"></span>|
  A$ = A$ + |    </label>|
  A$ = A$ + |</div>|
  A$ = A$ + |<td> AWB gain|
  A$ = A$ + |<tr><td>|
  A$ = A$ + slider$(wb_mode, 0, 4, "slider") + "<td>WB mode"
  A$ = A$ + |<tr><td>|
  'A$ = A$ + checkbox$(aec) + "AEC "
  'A$ = A$ + |<tr><td>|
  A$ = A$ + slider$(ae_level, -2, 2, "slider2" ) + "<td>AE Level"
  A$ = A$ + |<tr><td>|
  ' this is a checkbox using the variable 'aec2' (data-var='aec2')
  A$ = A$ + |<div class="onoffswitch">|
  A$ = A$ + |    <input type="checkbox" data-var='aec2' onchange='cmdChange(event)' class="onoffswitch-checkbox" id="mycheck2" checked>|
  A$ = A$ + |    <label class="onoffswitch-label" for="mycheck2">|
  A$ = A$ + |        <span class="onoffswitch-inner2"></span>|
  A$ = A$ + |        <span class="onoffswitch-switch"></span>|
  A$ = A$ + |    </label>|
  A$ = A$ + |</div>|
  A$ = A$ + |<td> AEC 2|
  A$ = A$ + |</table><center>|
  A$ = A$ + |</center></table></center>|
  
else
  ' Compute links to stored fotos. The fotos open in a NEW window
  ' This prevents from opening to many files (max 5!)  on the CAM-webserver at the same time
  A$ = A$ + |<center>SHOW  <a href="/images/PIC_1.jpg" target="_blank"> THE LATEST FOTO </a> IN A NEW WINDOW </center>|
  A$ = A$ + |<center>SHOW |
  for I = 1 to PIC_NUM
    A$ = A$ + | <a href="/images/PIC_|+ str$(I) +|.jpg?dummy=|+ MAKE_UNIQUE$ + |" target="_blank"> PIC_|+ str$(I) +| ,  </a> |
  next I
  if flashfree <  MIN_SPACE then
    A$ = A$ + "<br>"+ button$("DELETE ALL FOTOS", DELETE_FOTOS, BUT_ON$) + "<br>"
    
  endif
endif
cls
html a$
a$ = ""
autorefresh 1000
jscall "set_pictimer(" + str$(ms_per_pic) + ");"
a$ = ""

CSS cssid$("slider_vert", "position: relative;right: 130%;transform:rotate(-90deg);height:80px;width:400%; ")
CSS cssid$("slider_hor", "position: relative; top: -30px;height:20px;width:110%; ")
CSS cssid$(BUT_ON$, "background-color:red;;height:50px;")
CSS cssid$(BUT_OFF$, "background-color:#44c767;height:50px;")
CSS cssid$("butyellow", "background-color:#ffec64;height:50px;")
CSS cssid$("tbox","text-align: center; background-color:lightgrey;")
CSS cssid$("tbox_top","text-align: left;position: relative;top: 30px; background-color:lightgrey;opacity: 0.3;")
CSS cssid$("lbox", "background-color:#44c767;;height:50px;")

return

'############################################
DELETE_FOTOS:
'#########
D$ = FILE.DIR$("/images/*.jpg")
While D$ <> ""
  r = file.delete("/images/"+ D$)
  D$ = FILE.DIR$
Wend
gosub setpage
return


'############################################
AUTO_PAN:
'#########
'Toggle AUTO_PAN ON or OFF
if AUTO_PAN_TXT$ = "AUTO_PAN=OFF" then
  AUTO_PAN_TXT$ = "AUTO_PAN=ON"
  AUTO_PAN_COL$ = BUT_ON$
  gosub AUTO_PAN_STEP
  timer1 4000,AUTO_PAN_STEP  'more auto-panning each 4 seconds
else
  AUTO_PAN_TXT$ = "AUTO_PAN=OFF"
  AUTO_PAN_COL$ = BUT_OFF$
  timer1 0                   'stopp auto-panning
endif
gosub setpage
return

'############################################
AUTO_PAN_STEP:
'#########
'slowly pan the camera completely and endlessly
' as this is called every 3 seconds by timer1
if SERVO_H_POS> SERVO_H_MAX then HSTEP = 0-abs(HSTEP)
if SERVO_H_POS< SERVO_H_MIN then HSTEP = abs(HSTEP)
SERVO_H_POS = SERVO_H_POS + HSTEP
return


'############################################
LED_ON_OFF:
'#########
pin(4)=1-pin(4)
if pin(4)= 0 then
  LED_ON_OFF_COL$ = BUT_OFF$
  LED_ON_OFF_TXT$ = "LED=OFF"
else
  LED_ON_OFF_COL$ = "butyellow"
  LED_ON_OFF_TXT$ = "LED=ON"
endif
gosub setpage
return

'############################################
MAKE_FOTO:
'#########
IF not FOTO_LOCKED then   'Prevent a parallel second start
  FOTO_LOCKED      = 1
  if flashfree >  MIN_SPACE then
    MAKE_FOTO_TXT$ = "--WAIT---"
    MAKE_FOTO_COL$ = BUT_ON$
    ms_per_pic_old = ms_per_pic
    ms_per_pic     = 3000  'slow down live frame rate to give time to take and save the full pic
    gosub setpage
    pause 500
    FOTO$          = "/images/PIC_"+STR$(PIC_NUM+1)+".jpg"
    if file.exists(FOTO$) then print file.delete(FOTO$)
    for I = (PIC_NUM) to 1 step -1
      print file.rename("/images/PIC_"+STR$(I)+".jpg","/images/PIC_"+STR$(I+1)+".jpg")
    next I
    FOTO$          = "/images/PIC_1.JPG"
    print CAMERA.PICTURE(FOTO$) ' ###### save the picture ####
    MAKE_FOTO_TXT$ = "make FOTO"
    MAKE_FOTO_COL$ = BUT_OFF$
  else
    MAKE_FOTO_TXT$ = "NO SPACE FOR FOTO!"
    MAKE_FOTO_COL$ = BUT_ON$
    gosub setpage
  endif
  MAKE_UNIQUE$   = str$(millis)
  ms_per_pic     = ms_per_pic_old
  gosub setpage
  FOTO_LOCKED    = 0
endif
return

'############################################
SHOW_PARAMS_ON_OFF:
'#########
SHOW_PARAMS = 1-SHOW_PARAMS
IF SHOW_PARAMS = 0 then
  SHOW_PARAMS_COL$ = BUT_OFF$
  SHOW_PARAMS_TXT$ = "SETTINGS=OFF"
else
  SHOW_PARAMS_COL$ = BUT_ON$
  SHOW_PARAMS_TXT$ = "SETTINGS=ON"
endif
gosub setpage
refresh
return

'############################################
res_320x240:
'#########
print camera.params("framesize", 4)
RES$="320x240"
return

'############################################
res_640x480:
'#########
print camera.params("framesize", 6)
RES$="640x480"
gosub setpage
return

'############################################
res_800x600:
'#########
print camera.params("framesize", 7)
RES$="800x600"
gosub setpage
return

'############################################
res_1024x768:
'#########
print camera.params("framesize", 8)
RES$="1024x768"
gosub setpage
return

'############################################
res_1280x1024:
'#########
print camera.params("framesize", 9)
RES$="1280x1024"
gosub setpage
return

'############################################
res_1600x1200:
'#########
print camera.params("framesize", 10)
RES$="1600x1200"
gosub setpage
return

'############################################
v_flip:
'#########
v = camera.getvalue("vflip")
print camera.params("vflip", 1 - v)
return

'############################################
h_mirror:
'#########
v = camera.getvalue("hmirror")
print camera.params("hmirror", 1 - v)
return

'############################################
paramchange:
'#########
V$=""
if VX$ <> "X" print HtmlEventVar$
if VX$ <> "X" v$ = HtmlEventVar$
if (vx$ = "X") or (v$ = "quality")     then print camera.params("quality", quality)
if (vx$ = "X") or (v$ = "brightness")  then print camera.params("brightness", brightness)
if (vx$ = "X") or (v$ = "contrast")    then print camera.params("contrast", contrast)
if (vx$ = "X") or (v$ = "saturation")  then print camera.params("saturation", saturation)
if (vx$ = "X") or (v$ = "awb")         then print camera.params("awb", awb)
if (vx$ = "X") or (v$ = "awb_gain")    then print camera.params("awb_gain", awb_gain)
if (vx$ = "X") or (v$ = "wb_mode")     then print camera.params("wb_mode", wb_mode)
if (vx$ = "X") or (v$ = "aec")         then print camera.params("aec", aec)
if (vx$ = "X") or (v$ = "aec2")        then print camera.params("aec2", aec2)
if (vx$ = "X") or (v$ = "ae_level")    then print camera.params("ae_level", ae_level)
if (vx$ = "X") or (v$ = "agc")         then print camera.params("agc", agc)
if (vx$ = "X") or (v$ = "agc_gain")    then print camera.params("agc_gain", agc_gain)
if (vx$ = "X") or (v$ = "gainceiling") then print camera.params("gainceiling", gainceiling)
vx$ = ""
return

'######################################################################

SERVO_H_INIT:
'Horizontal servo -------------------------------
S_H  = SERVO_H_POS
if BAS.DEVICE = ESP32CAM SERVO_H_PIN  = 12 'FOR ESP32CAM
if BAS.DEVICE = M5CAM    SERVO_H_PIN  = 13 'FOR M5CAM only
SERVO_H_CHAN = 7
PWM.SETUP SERVO_H_PIN, SERVO_H_CHAN, SERVO_H_POS, 50, 12
GOSUB SERVO_H_SET
pause 1000
return

'######################################################################

SERVO_V_INIT:
'Vertical servo -------------------------------
S_V  = SERVO_V_POS
if BAS.DEVICE = ESP32CAM SERVO_V_PIN  = 3   '= RX-Pin!!!!            '<<<<<<<<<<<<<<<
if BAS.DEVICE = M5CAM    SERVO_V_PIN  = 4   'FOR M5CAM only
SERVO_V_CHAN = 8
PWM.SETUP SERVO_V_PIN, SERVO_V_CHAN, SERVO_V_POS, 50, 12
GOSUB SERVO_V_SET
return

'############################################
SERVO_H_SET:
'#########
if abs(S_H - SERVO_H_POS)> 4 then
  S_H = int( S_H+((SERVO_H_POS - S_H )/10))
  if SERVO_H_FLIP then
    PWM.OUT SERVO_H_CHAN, (600-S_H)        '<<<<<<<<<<horizontal servo inverted
  else
    PWM.OUT SERVO_H_CHAN, (S_H)            '<<<<<<<<<<horizontal servo normal
  endif
endif
return

'############################################
SERVO_V_SET:
'#########
if abs (S_V - SERVO_V_POS) > 4 then
  S_V = int( S_V+((SERVO_V_POS - S_V )/10))
  if SERVO_V_FLIP then
    PWM.OUT SERVO_V_CHAN, (600 - S_V)                '<<<<<<<<<<vertical servo inverted
  else
    PWM.OUT SERVO_V_CHAN, (S_V)                      '<<<<<<<<<<vertical servo normal
  endif
endif
return


'############################################
CAM_INIT:
'#########
' variables with CAM parameters
quality     = 10   : brightness  = 0
contrast    = 0    : saturation  = 0
awb         = 1    : awb_gain    = 1
wb_mode     = 0    : aec         = 0
aec2        = 1    : ae_level    = 0
agc         = 0    : agc_gain    = 0
gainceiling = 0

vx$ ="X"           ' "X" = change all parameters 
gosub paramchange  ' initialize camera with this parameters

'set CAM-ORIENTATION
print camera.params("vflip",  PIC_V_FLIP)
print camera.params("hmirror",PIC_H_MIRROR)

'set initial CAM-resolution            '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
'gosub res_800x600
gosub res_1024x768
'gosub res_1280x1024
RES_NEW$ = RES$
if file.exists(FOTO$) = 0 then print CAMERA.PICTURE(FOTO$) ' save the first picture
return



And that is the web interface:
IMG_6542.PNG
IMG_6543.PNG
IMG_6544.PNG
You do not have the required permissions to view the files attached to this post.
User avatar
PANNO
Posts: 57
Joined: Thu Feb 25, 2021 4:03 am
Has thanked: 51 times
Been thanked: 9 times

Re: Servo-CAM with M5CAMERA or ESP32CAM and two SG90 servos

Post by PANNO »

Great Idea ! A full Motion Web Cam with Ftp Support and no data Spy . Maybe u can use Face detection and if you have found a Face change the Camera Resolution.

Btw: holiday on Balkonien ?

Gtx
User avatar
PeterN
Posts: 97
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 41 times
Been thanked: 45 times
Contact:

Re: Servo-CAM with M5CAMERA or ESP32CAM and two SG90 servos

Post by PeterN »

Hi panno,

that's an interesting thought - toggeling the resolution between 320x240 (for facedetection and autopositioning) and a higher value - I will try!

And yes - my place for the hobby often has only one wall in this summertime :-)

I posted some more thoughts about this - including your suggestion at [External Link Removed for Guests]
cicciocb
Site Admin
Posts: 309
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 85 times
Been thanked: 197 times
Contact:

Re: Servo-CAM with M5CAMERA or ESP32CAM and two SG90 servos

Post by cicciocb »

Very nice application, as usual, Peter.
lyizb
Posts: 9
Joined: Fri Feb 12, 2021 8:23 pm
Has thanked: 8 times
Been thanked: 3 times

Re: Servo-CAM with M5CAMERA or ESP32CAM and two SG90 servos

Post by lyizb »

Clever mounting of the pan servo to the base--I never thought of that, and have produced convoluted mechanisms instead. I also like the springy folding of the cable on the camera--excellent idea.

Very neat and compact arrangement overall--thanks for posting.
Post Reply