Duet3D Logo Duet3D
    • Tags
    • Documentation
    • Order
    • Register
    • Login

    Setting up video streaming using raspberry pi

    Scheduled Pinned Locked Moved
    Third-party software
    3
    7
    416
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • OwenDundefined
      OwenD
      last edited by OwenD

      Recently I had to set up a Raspberry Pi is camera to use as a monitoring device for my printer.
      I found that pretty much all the online information was outdated and had no end trouble getting it to work on the current version of Pi OS (Bullseye Bookworm).
      The entire camera and video handling system has changed over the years.
      So I decided to document what worked for me.
      In the end it was relatively easy (once you knew the correct steps0

      I must first point out I know bugger all about Linux.
      All this work is taken from bits and pieces I found on various sites and cobbled together, so my apologies for not crediting the original author(s)

      NOTE: These instructions were done using a Raspberry Pi 4 running Bullseye Bookworm 64 bit and an official Pi camera
      Also if you're running RRF in SBC mode, you don't need to do this as I believe it has a built in camera setup.

      The first step is to install the OS using the Pi Imager
      https://www.raspberrypi.com/software/

      step1.png

      When prompted, choose your user name, password and Wifi details.
      NOTE: The current version no longer uses the default user pi and password raspberry

      when you get to this point, click on "edit settings"
      step2.png

      Enable SSH so that you can connect to the Pi via PUTTY rather than always needing a monitor and keyboard.
      If you installed a Pi OS that has a desktop you can use the inbuilt command line terminal for all the steps listed below.

      Then click on YES to apply the settings.

      Once the image has been loaded onto the SD card, insert the card in your Pi and start it up.

      Start Putty (or some other terminal) and SSH into the Pi
      If you used the default settings, you should be able to go to
      raspberrypi.local
      step4.png

      You should see something like this
      putty.png

      You may get any updates/upgrade by using these commands

      sudo apt update
      
      sudo apt upgrade
      
      sudo apt install
      

      Change directory

      cd /usr/local/bin/
      

      Open a text edior

      sudo nano streamVideo.py
      

      Paste in the following code

      #!/usr/bin/python3
      
      # This is the same as mjpeg_server.py, but uses the h/w MJPEG encoder.
      
      import io
      import logging
      import socketserver
      from http import server
      from threading import Condition
      
      from picamera2 import Picamera2
      from picamera2.encoders import MJPEGEncoder
      from picamera2.outputs import FileOutput
      
      PAGE = """\
      <html>
      <head>
      <title>3D Printer Camera</title>
      </head>
      <body>
      <img src="stream.mjpg" width="800" height="600" />
      </body>
      </html>
      """
      
      
      class StreamingOutput(io.BufferedIOBase):
          def __init__(self):
              self.frame = None
              self.condition = Condition()
      
          def write(self, buf):
              with self.condition:
                  self.frame = buf
                  self.condition.notify_all()
      
      
      class StreamingHandler(server.BaseHTTPRequestHandler):
          def do_GET(self):
              if self.path == '/':
                  self.send_response(301)
                  self.send_header('Location', '/index.html')
                  self.end_headers()
              elif self.path == '/index.html':
                  content = PAGE.encode('utf-8')
                  self.send_response(200)
                  self.send_header('Content-Type', 'text/html')
                  self.send_header('Content-Length', len(content))
                  self.end_headers()
                  self.wfile.write(content)
              elif self.path == '/stream.mjpg':
                  self.send_response(200)
                  self.send_header('Age', 0)
                  self.send_header('Cache-Control', 'no-cache, private')
                  self.send_header('Pragma', 'no-cache')
                  self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
                  self.end_headers()
                  try:
                      while True:
                          with output.condition:
                              output.condition.wait()
                              frame = output.frame
                          self.wfile.write(b'--FRAME\r\n')
                          self.send_header('Content-Type', 'image/jpeg')
                          self.send_header('Content-Length', len(frame))
                          self.end_headers()
                          self.wfile.write(frame)
                          self.wfile.write(b'\r\n')
                  except Exception as e:
                      logging.warning(
                          'Removed streaming client %s: %s',
                          self.client_address, str(e))
              else:
                  self.send_error(404)
                  self.end_headers()
      
      
      class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
          allow_reuse_address = True
          daemon_threads = True
      
      
      picam2 = Picamera2()
      picam2.configure(picam2.create_video_configuration(main={"size": (640, 480)}))
      output = StreamingOutput()
      picam2.start_recording(MJPEGEncoder(), FileOutput(output))
      
      try:
          address = ('', 8888)
          server = StreamingServer(address, StreamingHandler)
          server.serve_forever()
      finally:
          picam2.stop_recording()
      

      Press CTRL + X to exit and choose Y to save file

      change directory

      cd /etc/systemd/system
      

      Open the text editor to create a system service file

      sudo nano streamVideo.service
      

      Paste in the following code

      [Unit]
      Description=A script for straming video to http
      After=syslog.target network.target
      
      [Service]
      WorkingDirectory=/usr/local/bin/
      ExecStart=/usr/bin/python3 /usr/local/bin/streamVideo.py
      
      Restart=always
      RestartSec=120
      
      [Install]
      WantedBy=multi-user.target
      

      PRess CTRL + X
      press Y and enter to save

      Enter the following to reload the daemon

      sudo systemctl daemon-reload
      

      Enable the service

      sudo systemctl enable streamVideo.service
      

      You should see something like
      Created symlink /etc/systemd/system/multi-user.target.wants/streamVideo.service → /etc/systemd/system/streamVideo.service.

      Restart the Pi

      sudo shutdown -r now
      

      After the Pi has rebooted, you should be able to access the stream by going to the following URL in your browser

      http://raspberrypi.local:8888/index.html

      In DWC you need to enter this URL on order to get the stream

      http://raspberrypi.local:8888/stream.mjpg

      dwc.png

      Your video stream should now be visible in the Webcam tab
      webcam.png

      I hope this saves someone some frustration.

      OwenDundefined 1 Reply Last reply Reply Quote 4
      • OwenDundefined
        OwenD @OwenD
        last edited by OwenD

        I have done a bit more tinkering in an effort to get a high and low resolution stream.

        I'm not sure it's the "right" way to go about this as I have no python or Pi experience, but it appears to work.
        CPU usage doesn't appear high in use.
        Perhaps someone more knowledgeable than I can correct or enhance it.

        This is the main file that replaces streamVideo.py above

        
        #!/usr/bin/python3
        
        # This is the same as mjpeg_server.py, but uses the h/w MJPEG encoder.
        
        import io
        import logging
        import socketserver
        import libcamera
        from http import server
        from threading import Condition
        from libcamera import controls
        from picamera2 import Picamera2
        from picamera2.encoders import MJPEGEncoder
        from picamera2.outputs import FileOutput
        
        PAGE = """\
        <html>
        <head>
        <title>3D Printer Camera</title>
        </head>
        <body>
        <H2> Camera Feed Low Res</H2>
        <img src="lores.mjpg" width="640" height="480" />
        <H2> Camera Feed Hi Res</H2>
        <img src="stream.mjpg" width="1280" height="720" />
        </body>
        </html>
        """
        
        
        class StreamingOutput(io.BufferedIOBase):
            def __init__(self):
                self.frame = None
                self.condition = Condition()
        
            def write(self, buf):
                with self.condition:
                    self.frame = buf
                    self.condition.notify_all()
        
        
        class StreamingHandler(server.BaseHTTPRequestHandler):
            def do_GET(self):
                if self.path == '/':
                    self.send_response(301)
                    self.send_header('Location', '/index.html')
                    self.end_headers()
                elif self.path == '/index.html':
                    content = PAGE.encode('utf-8')
                    self.send_response(200)
                    self.send_header('Content-Type', 'text/html')
                    self.send_header('Content-Length', len(content))
                    self.end_headers()
                    self.wfile.write(content)
                elif self.path == '/stream.mjpg':
                    self.send_response(200)
                    self.send_header('Age', 0)
                    self.send_header('Cache-Control', 'no-cache, private')
                    self.send_header('Pragma', 'no-cache')
                    self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
                    self.end_headers()
                    try:
                        while True:
                            with output1.condition:
                                output1.condition.wait()
                                frame = output1.frame
                            self.wfile.write(b'--FRAME\r\n')
                            self.send_header('Content-Type', 'image/jpeg')
                            self.send_header('Content-Length', len(frame))
                            self.end_headers()
                            self.wfile.write(frame)
                            self.wfile.write(b'\r\n')
                    except Exception as e:
                        logging.warning(
                            'Removed streaming client %s: %s',
                            self.client_address, str(e))
                elif self.path == '/lores.mjpg':
                    self.send_response(200)
                    self.send_header('Age', 0)
                    self.send_header('Cache-Control', 'no-cache, private')
                    self.send_header('Pragma', 'no-cache')
                    self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
                    self.end_headers()
                    try:
                        while True:
                            with output2.condition:
                                output2.condition.wait()
                                frame = output2.frame
                            self.wfile.write(b'--FRAME\r\n')
                            self.send_header('Content-Type', 'image/jpeg')
                            self.send_header('Content-Length', len(frame))
                            self.end_headers()
                            self.wfile.write(frame)
                            self.wfile.write(b'\r\n')
                    except Exception as e:
                        logging.warning(
                            'Removed streaming client %s: %s',
                            self.client_address, str(e))
        
                else:
                    self.send_error(404)
                    self.end_headers()
        
        
        class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
            allow_reuse_address = True
            daemon_threads = True
        
        
        picam2 = Picamera2()
        picam2.video_configuration.enable_lores()
        
        #adjust resolution as required
        main_resolution = (1280, 720)
        low_resolution= (640, 480)
        #adjust hflip/vflip to 0 or 1 to flip horizontally or vertically
        orientation=libcamera.Transform(hflip=1, vflip=1)
        
        video_config = picam2.create_video_configuration(main={"size": main_resolution, "format": "RGB888"}, 
                                                         lores={"size": low_resolution, "format": "YUV420"},
                                                         transform=orientation) 
        picam2.configure(video_config)
        output1 = StreamingOutput()
        output2 = StreamingOutput()
        output =  [output1,output2]
        encoder1=MJPEGEncoder()
        encoder2=MJPEGEncoder()
        
        picam2.start_recording(encoder1, FileOutput(output1))
        picam2.start_recording(encoder2, FileOutput(output2),name="lores")
        picam2.set_controls({"AfMode": controls.AfModeEnum.Continuous})  # set autofocus
        
        try:
            address = ('', 8888)
            server = StreamingServer(address, StreamingHandler)
            server.serve_forever()
        finally:
            picam2.stop_recording()
        
        
        

        Then change the DWC setup so that the standard view is the low res stream and clicking on that opens the high res stream

        Low res link
        http://raspberrypi.local:8888/lores.mjpg

        Hi Res link
        http://raspberrypi.local:8888/stream.mjpg

        hilow.png

        droftartsundefined 1 Reply Last reply Reply Quote 2
        • droftartsundefined
          droftarts administrators @OwenD
          last edited by

          @OwenD thanks for this! I guess this is mainly for a RPi running NOT as an SBC connected to a Duet? Though I’d imagine it could work like that too.

          You say this is on Bullseye, but Bookworm is the latest RPi OS, see https://www.raspberrypi.com/software/operating-systems/
          I think there were quite a few changes to the camera sub-system, so may be worth updating and running through your instructions and see if they still work. I’m going to do the same thing next week with the instructions here: https://docs.duet3d.com/en/User_manual/Reference/DWC_webcam#motion-on-a-raspberry-pi-running-dsf

          Ian

          Bed-slinger - Mini5+ WiFi/1LC | RRP Fisher v1 - D2 WiFi | Polargraph - D2 WiFi | TronXY X5S - 6HC/Roto | CNC router - 6HC | Tractus3D T1250 - D2 Eth

          OwenDundefined 1 Reply Last reply Reply Quote 0
          • oliofundefined
            oliof
            last edited by

            very good write up.

            In the Klipper world the solution to camera streaming is crowsnest but since that's a mountain of shell scripts I am not super convinced it's something I would recommend people to use unless prepackaged. It does have some nice features like multiple camera support for nozzle cams and the likes, so maybe it's worth looking at for inspiration.

            <>RatRig V-Minion Fly Super5Pro RRF<> V-Core 3.1 IDEX k*****r <> RatRig V-Minion SKR 2 Marlin<>

            1 Reply Last reply Reply Quote 0
            • OwenDundefined
              OwenD @droftarts
              last edited by

              @droftarts said in Setting up video streaming using raspberry pi:

              @OwenD thanks for this! I guess this is mainly for a RPi running NOT as an SBC connected to a Duet? Though I’d imagine it could work like that too.

              Yes, it's for those of us that choose not to run SBC

              You say this is on Bullseye, but Bookworm is the latest RPi OS, see https://www.raspberrypi.com/software/operating-systems/

              My mistake!
              It's actually Bookworm that I'm running.

               cat /etc/os-release
              PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
              NAME="Debian GNU/Linux"
              VERSION_ID="12"
              VERSION="12 (bookworm)"
              VERSION_CODENAME=bookworm
              ID=debian
              HOME_URL="https://www.debian.org/"
              SUPPORT_URL="https://www.debian.org/support"
              BUG_REPORT_URL="https://bugs.debian.org/"
              

              I think there were quite a few changes to the camera sub-system, so may be worth updating and running through your instructions and see if they still work. I’m going to do the same thing next week with the instructions here: https://docs.duet3d.com/en/User_manual/Reference/DWC_webcam#motion-on-a-raspberry-pi-running-dsf

              Ian

              Yes, there have been many changes which rendered most of the tutorials online useless.
              I had to try to cobble this together from various documents in the picamera documentation.

              droftartsundefined 1 Reply Last reply Reply Quote 0
              • droftartsundefined
                droftarts administrators @OwenD
                last edited by

                @OwenD great! I was wondering if you meant Bookworm, as there were parts I didn’t recognise from previous times I’d messed around with it on Buster and Bullseye.

                According to @chrishamm Motion at least provides 32 and 64 bit versions.

                Ian

                Bed-slinger - Mini5+ WiFi/1LC | RRP Fisher v1 - D2 WiFi | Polargraph - D2 WiFi | TronXY X5S - 6HC/Roto | CNC router - 6HC | Tractus3D T1250 - D2 Eth

                1 Reply Last reply Reply Quote 0
                • OwenDundefined
                  OwenD
                  last edited by OwenD

                  I found another application that works quite easily and has the benefit of being able to adjust all the settings from the web interface.

                  It's called picamera2-WebUI-Lite

                  webui-lite-screen.png

                  There's a slight error on the instructions to install and it doesn't tell you how to set it up as a service, so I've listed thesteps.

                  To install.

                  SSH into your PI (or open a terminal if using a desktop)

                  Change directory

                  cd /usr/local/bin/
                  

                  Clone the repositry

                   sudo git clone https://github.com/monkeymademe/picamera2-WebUI-Lite.git
                  

                  Change directory

                  cd /usr/local/bin/picamera2-WebUI-Lite 
                  

                  Test that it's running

                   python3 app.py 
                  

                  Open your browser and go to
                  http://raspberrypi.local:8080/

                  To see just the video feed go to
                  http://raspberrypi.local:8080/video_feed

                  Go back to the terminal and hot CTRL + C to shut the app down

                  To set it up as a service
                  Change directory

                  cd /etc/systemd/system
                  

                  Open a text editor to create a system service file

                  sudo nano webui-lite.service
                  

                  Paste in the following

                  [Unit]
                  Description=Start script for WebUi-Lite as a service
                  After=syslog.target network.target
                  [Service]
                  WorkingDirectory=/usr/local/bin/picamera2-WebUI-Lite/
                  ExecStart=/usr/bin/python3 /usr/local/bin//picamera2-WebUI-Lite/app.py
                  Restart=always
                  RestartSec=120
                  [Install]
                  WantedBy=multi-user.target
                  

                  Press CTRL + X to exit and Y to save

                  Reload the daemon

                  sudo systemctl daemon-reload
                  

                  Enable the service

                  sudo systemctl enable webui-lite.service
                  

                  NOTE that if you already have the other streamVideo service I listed above using Picamera you will have to disable it as you can't run both.

                  sudo systemctl disable streamVideo.service
                  

                  Reboot the system to make sure the service starts

                  sudo shutdown -r now
                  

                  Adjust your DWC settings as follows
                  webui.png

                  This will allow you to easily get to the settings page by clicking on the webcam image in DWC
                  From there adjust your resolution and you can also zoom in a particular area using the scaler crop settings.

                  Full view
                  fullscreen.png

                  Cropped view
                  zoomed.png

                  1 Reply Last reply Reply Quote 4
                  • First post
                    Last post
                  Unless otherwise noted, all forum content is licensed under CC-BY-SA