Page 1 of 1

The Bare Essential Webcam Capture

Posted: Sunday 16th June 2019 8:20pm
by Cedron
I finally figured out the bare minimum (I think) of what you want in a webcam capture program. It turns out to be so easy that it is short enough to be a code snippet:
' Gambas class file

Private myPlayer As New MediaPlayer As "Player"

'=============================================================================
Public Sub Form_Open()

        myPlayer.URL = "v4l2:///dev/video1"
        myPlayer.Play()
     
End
'=============================================================================
Public Sub Form_Close()

        myPlayer.Stop()

End
'=============================================================================
Public Sub TheButton_Click()

        Dim theImage As Image = myPlayer.Video.Image
        
        theImage.Save(Application.Path &/ Format(Now, "yyyymmdd_hhnnss") & ".jpg")

        ThePictureBox.Image = theImage

End
'=============================================================================
Public Sub Player_Event(ArgMessage As MediaMessage)

        Print Now, "Player_Event ", ArgMessage.Type
        
End
'=============================================================================
Public Sub Player_Message(ArgSource As MediaControl, ArgType As Integer, ArgMessage As String)

        Print Now, "Player_Message", ArgSource.Name, ArgType, ArgMessage
        
        If ArgType = Media.Error Then Me.Close()
    
End
'=============================================================================
Public Sub Player_End()
    
        Print Now, "Player_End"
    
End
'=============================================================================
Step 1. Create a GUI project

Step 2. Add a PictureBox named "ThePictureBox"

Step 3. Add a Button named "TheButton"

Step 4. Copy the Code into the main form class

TADA. You may need to use video0 on your computer.

However, there is a catch. When the Webcam window is closed first, the console output ends with "gb.media: warning: could not catch end of stream".

I haven't been able to figure out a good way to avoid this. Anybody know?

Thanks,

Ced

Re: The Bare Essential Webcam Capture

Posted: Monday 17th June 2019 4:10pm
by cogier
Hi Ced,

Here is a slightly different way to do this in 4 lines of code and no error.

Image
WebCamera.tar.7z
(21.72 KiB) Downloaded 494 times

Re: The Bare Essential Webcam Capture

Posted: Monday 17th June 2019 5:46pm
by Cedron
Thanks.

That's handy, it looks like a higher level wrapper around a MediaPlayer.

However, I don't see a way to capture the current image to an Image object. This is a requirement for me.

Notice, that the first three code lines and the Form_Open are really the only required elements, so declaring the MediaPlayer in code vs the MediaVeiwer on the form becomes the only distinction. The other stuff I added, I considered "minimal want" as opposed to "minimal need".

Re: The Bare Essential Webcam Capture

Posted: Thursday 20th June 2019 9:22am
by Godzilla
I saw this thread and was fascinated by the code. It works! And its so simple!

Hats off to Cedron for the code, and to cogier for the example project.

And I agree with you Cedron. While the high level allows this to code to be short and simple, its also limiting for those who want to use webcam video to a fuller potential.

After much Googling, I found a great Gambas project that uses gb.v4l to capture webcam video, instead of gb.media.form. Cedron, this code is a step lower in level, and may be what you're looking for in trying to capture the current image to an Image object.

Thanks and credit for this Gambas project goes to justlostintime, who uploaded it to GitHub 4 years ago.

https://github.com/justlostintime/gamba ... a/MyWebCam

The code loads and works great in Gambas 3.13.0. However, it does crash when I stop the capture, on the line
hWebCam = Null
The reason for the crash is unclear to me, but I modified it to
Try hWebCam = Null
and that seems to fix it. I didn't test every feature, so there could be other crashes due to the age of the code. I'm sure if the code were to be brought up to current-version specs (by someone more capable than me), it should work flawlessly.

Lastly, I should point out that the video source
/dev/video0
or
/dev/video1
can be unreliable. I found this out the hard way, using motion-activated security-camera software called Motion.

My webcam can switch between the two at random, for no good reason. This switching would render my security software useless for hours at a time. I researched this, and found the solution is to use a symbolic link to your webcam, which never changes and can be created.

However, apparently most modern webcams will have like a "built-in" symbolic link, which has worked flawlessly for me. Its found under
/dev/v4l/by-id
so in my case I'd simply use
/dev/v4l/by-id/usb-Chicony_Electronics_Co._Ltd._USB2.0_HD_UVC_WebCam_0x0001-video-index0
in place of
/dev/videoX
This trick not only works for Motion, but for these Gambas webcam code examples as well.

I hope justlostintime's project is useful to Cedron and to everyone who finds this kind of thing interesting.

EDIT:

After posting this message, I played around with this project further. And to my dismay, I discovered that changing the webcam settings (i.e. brightness, contrast, whiteness, hue) appears to alter the webcam settings permanently! I apologize for that. I'm providing code to properly initialize the sliders, and to reset your webcam settings to their default values.

1) Add a Public Sub Form_Open() and paste this code. This initializes each slider's min, max, and default values to match your webcam's specifications.
Dim TheStart As Integer
  Dim TheLength As Integer
  Dim TheResult As String
  Dim TheLine As String[]
  Dim TheCounter As Integer  
  
  OnSet = True  
  
  ' Shell "v4l2-ctl -d /dev/video0 --list-ctrls" To TheResult
  'or
  Shell "v4l2-ctl -d " & Trim$(TxtDevice.Text) & " --list-ctrls" To TheResult
  
  TheLine = Split(TheResult, gb.CrLf)
  
  For TheCounter = 0 To TheLine.Max
    TheStart = 0
    TheLength = 0
    TheLine[TheCounter] = Trim$(TheLine[TheCounter])
    If IsNull(TheLine[TheCounter]) = False Then
      If InStr(TheLine[TheCounter], "inactive", 1, gb.Binary) = 0 Then
        Select Case True
          Case InStr(TheLine[TheCounter], "brightness", 1, gb.IgnoreCase) > 0
            TheStart = InStr(TheLine[TheCounter], "min=", 1, gb.Binary) + Len("min=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Bright.MinValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "max=", 1, gb.Binary) + Len("max=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Bright.MaxValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "step=", 1, gb.Binary) + Len("step=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Bright.Step = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
          Case InStr(TheLine[TheCounter], "contrast", 1, gb.IgnoreCase) > 0
            TheStart = InStr(TheLine[TheCounter], "min=", 1, gb.Binary) + Len("min=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Contrast.MinValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "max=", 1, gb.Binary) + Len("max=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Contrast.MaxValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "step=", 1, gb.Binary) + Len("step=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Contrast.Step = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
          Case InStr(TheLine[TheCounter], "gamma", 1, gb.IgnoreCase) > 0 'whiteness
            TheStart = InStr(TheLine[TheCounter], "min=", 1, gb.Binary) + Len("min=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Whiteness.MinValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "max=", 1, gb.Binary) + Len("max=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Whiteness.MaxValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "step=", 1, gb.Binary) + Len("step=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Whiteness.Step = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
          Case InStr(TheLine[TheCounter], "hue", 1, gb.IgnoreCase) > 0
            TheStart = InStr(TheLine[TheCounter], "min=", 1, gb.Binary) + Len("min=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Hue.MinValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "max=", 1, gb.Binary) + Len("max=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Hue.MaxValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "step=", 1, gb.Binary) + Len("step=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Hue.Step = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
          Case InStr(TheLine[TheCounter], "saturation", 1, gb.IgnoreCase) > 0
            TheStart = InStr(TheLine[TheCounter], "min=", 1, gb.Binary) + Len("min=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Colour.MinValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "max=", 1, gb.Binary) + Len("max=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Colour.MaxValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
            
            TheStart = InStr(TheLine[TheCounter], "step=", 1, gb.Binary) + Len("step=")
            TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
            Colour.Step = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
        End Select
        
             
      Endif
    Endif 
  Next
  OnSet = False 
2) Add a new button to that project. Call it Reset or whatever you like. Paste this code to its click event. This code resets your webcam's values for brightness, contrast, etc. to its default values.
Dim TheStart As Integer
  Dim TheLength As Integer
  Dim TheResult As String
  Dim TheLine As String[]
  Dim TheCounter As Integer
  Dim TheDefault As Integer
  Dim TheValue As Integer
  Dim TheName As String
  Dim TheMessage As String
  
  ' Shell "v4l2-ctl -d /dev/video0 --list-ctrls" To TheResult
  'or
  Shell "v4l2-ctl -d " & Trim$(TxtDevice.Text) & " --list-ctrls" To TheResult
  
  TheLine = Split(TheResult, gb.CrLf)
  OnSet = True
  
  For TheCounter = 0 To TheLine.Max
    TheLine[TheCounter] = Trim$(TheLine[TheCounter])
    If IsNull(TheLine[TheCounter]) = False Then
      If InStr(TheLine[TheCounter], "inactive", 1, gb.Binary) = 0 Then
        TheStart = 0
        TheLength = 0
        TheName = Null
        
        TheStart = 1
        TheLength = InStr(TheLine[TheCounter], Space(1), 1, gb.Binary) - Len(Space(1))
        TheName = Mid$(TheLine[TheCounter], TheStart, TheLength)
        
        TheStart = 0
        TheLength = 0        
        
        TheStart = InStr(TheLine[TheCounter], "default=", 1, gb.Binary) + Len("default=")
        TheLength = InStr(TheLine[TheCounter], Space(1), TheStart, gb.Binary) - TheStart
        TheDefault = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
        
        TheStart = 0
        TheLength = 0
        
        TheStart = InStr(TheLine[TheCounter], "value=", 1, gb.Binary) + Len("value=")
        TheLength = Len(TheLine[TheCounter])
        TheValue = CInt(Mid$(TheLine[TheCounter], TheStart, TheLength))
        
        If TheDefault <> TheValue Then
          TheMessage &= TheName & " - Resetting " & CStr(TheValue) & " to " & CStr(TheDefault) & "." & gb.CrLf
          Shell "v4l2-ctl -c " & TheName & "=" & CStr(TheDefault)
          Select Case True
            Case InStr(TheName, "brightness", 1, gb.IgnoreCase) > 0
              Bright.Value = TheDefault
            Case InStr(TheName, "contrast", 1, gb.IgnoreCase) > 0
              Contrast.Value = TheDefault
            Case InStr(TheName, "gamma", 1, gb.IgnoreCase) > 0
              Whiteness.Value = TheDefault
            Case InStr(TheName, "hue", 1, gb.IgnoreCase) > 0
              Hue.Value = TheDefault
            Case InStr(TheName, "saturation", 1, gb.IgnoreCase) > 0
              Colour.Value = TheDefault
          End Select
        Endif        
      Endif
    Endif 
  Next
  OnSet = False 
  
  If IsNull(TheMessage) = False Then
    Message(TheMessage)
  Else
    Message("Values are already set to their defaults. No changes are necessary.")
  Endif
3) Optional, but its helpful to add these lines to the bottom of each respective slider _Change event. These lines of code show a tooltip for the respective slider that displays your webcam's minimum value, current value per the slider position, and maximum value.
Bright.Tooltip = Bright.MinValue & " - " & Bright.Value & " - " & Bright.MaxValue
Contrast.Tooltip = Contrast.MinValue & " - " & Contrast.Value & " - " & Contrast.MaxValue
Whiteness.Tooltip = Whiteness.MinValue & " - " & Whiteness.Value & " - " & Whiteness.MaxValue 'gamma
Colour.Tooltip = Colour.MinValue & " - " & Colour.Value & " - " & Colour.MaxValue
Hue.Tooltip = Hue.MinValue & " - " & Hue.Value & " - " & Hue.MaxValue
I hope you find these modifications useful. I apologize that I posted yesterday before realizing some major shortcomings in justlostintime's otherwise awesome project. And before coding and posting proper modifications to the code (albeit probably crude, to more advanced coders).

Please feel free to post any further modifications and/or fixes to justlostintime's code.This stuff is quite cool!

Re: The Bare Essential Webcam Capture

Posted: Saturday 22nd June 2019 2:25am
by Cedron
WebCamGrab-0.0.1.tar.gz
(12.71 KiB) Downloaded 401 times
I totally relate to your desire to have more control over the Webcam. I actually wrote a simple webcam capture to snapshot in pure C++ using the v4l library and the jpeg library, and of course, straight X window programming.

In a word: Yuck.

However, unfortunately, the gb.v4l component is officially depracated, which is why I didn't use it:

http://gambaswiki.org/wiki/comp/gb.v4l

I have not figure out yet if it is possible to have similar control through the MediaPlayer. It was discovering that starting the URL with "v4l2://" allowed the MediaPlayer to capture the Webcam, and the super simple code that ensued, is what led me to start this thread.

I am really impressed by the MediaPlayer. I am also using it in my music player that I will post shortly, as well as being able to single step through .mov files, using the new .Forward() method.

So, anyway, I have attached a slightly more comprehensive webcam capture program. It is an expansion of the original posting.

Enjoy,

Ced

Re: The Bare Essential Webcam Capture

Posted: Saturday 22nd June 2019 2:41pm
by cogier
I am also looking at a simple WebCamera example and I like some of your code which I have stolen. As a payback you can reduce the amount of code in your Form_Open() as the ComboBox.List takes an array so you can use: -
TheVideoDevComboBox.List = Dir("/dev/v4l/by-id", "*").Sort()
TheVideoDevComboBox.Index = 0 'Displays the first item in the ComboBox list


You need to add some code to catch cases where there is no camera connected as it crashes at the moment.

Re: The Bare Essential Webcam Capture

Posted: Sunday 23rd June 2019 8:33am
by Godzilla
Cedron I really like your example WebCamGrab. I love how the combobox lists the webcam symbolic links. And it demonstrates how the MediaPlayer component is more robust than I thought. Not a one-trick pony. :D

As for V4L, does anyone know what its being depreciated in favor of? SDL perhaps? I only found out SDL exists a few days ago. From what little I learned about it, its supposed to be the bees knees.

Re: The Bare Essential Webcam Capture

Posted: Sunday 23rd June 2019 2:23pm
by Cedron
Hey Godzilla,

Thanks for the kind words. I don't truly know the answers to your other questions about v4l so I posted the question to the mailing list.

As for SDL, I'm not a big fan, but I don't want to discourage you. It may be exactly what you are looking for. For me, it is out of consideration since it is incompatible with using Qt form controls. Originally I thought it might be something I could use as it seems to be the only place with gamepad support in official Gambas. Since I have ruled it out for myself, I have not explored it fully. I don't know if the webcam is accessible through SDL.

Instead, I wrote my own shared library for gamepad support. First using the deprecated joystick model, then using the current evdev mode. It is in my pile of "almost ready to be released" projects. As part of researching evdev, I dove into some of the source code for SDL (the SDL itself, not the Gambas interface to it) and was not really impressed by the quality of the code.

The evdev uses similar directory structure design as the video. That is, there is a /dev/event## directory and a symbolic by-id directory. I used the latter in my sample because of your mentioning it in your reply. I should of stated that earlier.

If you, or anyone, has an interest in the gamepad code, I will bump up its priority and get it posted.

Ced