The Bare Essential Webcam Capture

Post your Gambas programming questions here.
Post Reply
User avatar
Cedron
Posts: 156
Joined: Thursday 21st February 2019 5:02pm
Location: The Mitten State
Contact:

The Bare Essential Webcam Capture

Post 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
.... and carry a big stick!
User avatar
cogier
Site Admin
Posts: 1118
Joined: Wednesday 21st September 2016 2:22pm
Location: Guernsey, Channel Islands

Re: The Bare Essential Webcam Capture

Post 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 478 times
User avatar
Cedron
Posts: 156
Joined: Thursday 21st February 2019 5:02pm
Location: The Mitten State
Contact:

Re: The Bare Essential Webcam Capture

Post 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".
.... and carry a big stick!
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: The Bare Essential Webcam Capture

Post 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!
User avatar
Cedron
Posts: 156
Joined: Thursday 21st February 2019 5:02pm
Location: The Mitten State
Contact:

Re: The Bare Essential Webcam Capture

Post by Cedron »

WebCamGrab-0.0.1.tar.gz
(12.71 KiB) Downloaded 389 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
.... and carry a big stick!
User avatar
cogier
Site Admin
Posts: 1118
Joined: Wednesday 21st September 2016 2:22pm
Location: Guernsey, Channel Islands

Re: The Bare Essential Webcam Capture

Post 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.
User avatar
Godzilla
Posts: 63
Joined: Wednesday 26th September 2018 11:20am

Re: The Bare Essential Webcam Capture

Post 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.
User avatar
Cedron
Posts: 156
Joined: Thursday 21st February 2019 5:02pm
Location: The Mitten State
Contact:

Re: The Bare Essential Webcam Capture

Post 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
.... and carry a big stick!
Post Reply