Home > Enterprise >  Zooming mjpeg in object
Zooming mjpeg in object

Time:06-15

Using gocv I'm streaming an image to an object element at my html5 page.

The page is:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Cam Streaming with gocv</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/style.css" rel="stylesheet">
    </head>
    <body>
    <!-- <div id ="content"></div> -->
        <object data="http://localhost:8080/camera" width="300" height="200" alt="Cam streaming"></object>
    </body>
    <<script>
     /*   (function(){
                document.getElementById("content").innerHTML='<object type="text/html" data="http://localhost:8080/cam" ></object>';
        })();
        */
    </script>
</html>

Ang my go code is:

// This example opens a video capture device, then streams MJPEG from it.
// Once running point your browser to the hostname/port you passed in the
// command line (for example http://localhost:8080) and you should see
// the live video stream.
//
// How to run:
//
// mjpeg-streamer [camera ID] [host:port]
//
//      go get -u github.com/hybridgroup/mjpeg
//      go run ./cmd/mjpeg-streamer/main.go 1 0.0.0.0:8080
//

package main

import (
    "fmt"
    "log"
    "net/http"

    _ "net/http/pprof"
    "opencv/mjpeg"

    "gocv.io/x/gocv"
)

var (
    deviceID int
    err      error
    webcam   *gocv.VideoCapture
    stream   *mjpeg.Stream
)

func main() {
    /*  if len(os.Args) < 3 {
            fmt.Println("How to run:\n\tmjpeg-streamer [camera ID] [host:port]")
            return
        }
    */
    // parse args
    deviceID := 0   // os.Args[1]
    host := ":8080" //os.Args[2]

    // open webcam
    webcam, err = gocv.OpenVideoCapture(deviceID)
    if err != nil {
        fmt.Printf("Error opening capture device: %v\n", deviceID)
        return
    }
    defer webcam.Close()

    // create the mjpeg stream
    stream = mjpeg.NewStream()

    // start capturing
    go mjpegCapture()

    fmt.Println("Capturing. Point your browser to "   host)

    // start http server
    http.Handle("/camera", stream)
    log.Fatal(http.ListenAndServe(host, nil))
}

func mjpegCapture() {
    img := gocv.NewMat()
    defer img.Close()

    for {
        if ok := webcam.Read(&img); !ok {
            fmt.Printf("Device closed: %v\n", deviceID)
            return
        }
        if img.Empty() {
            continue
        }

        buf, _ := gocv.IMEncode(".jpg", img)
        stream.UpdateJPEG(buf.GetBytes())
        buf.Close()
    }
}

The streaming function at opencv/mjpeg is:

// Package mjpeg implements a simple MJPEG streamer.
//
// Stream objects implement the http.Handler interface, allowing to use them with the net/http package like so:
//  stream = mjpeg.NewStream()
//  http.Handle("/camera", stream)
// Then push new JPEG frames to the connected clients using stream.UpdateJPEG().
package mjpeg

import (
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"
)

// Stream represents a single video feed.
type Stream struct {
    m             map[chan []byte]bool
    frame         []byte
    lock          sync.Mutex
    FrameInterval time.Duration
}

const boundaryWord = "MJPEGBOUNDARY"
const headerf = "\r\n"  
    "--"   boundaryWord   "\r\n"  
    "Content-Type: image/jpeg\r\n"  
    "Content-Length: %d\r\n"  
    "X-Timestamp: 0.000000\r\n"  
    "\r\n"

// ServeHTTP responds to HTTP requests with the MJPEG stream, implementing the http.Handler interface.
func (s *Stream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    log.Println("Stream:", r.RemoteAddr, "connected")
    w.Header().Add("Content-Type", "multipart/x-mixed-replace;boundary=" boundaryWord)

    c := make(chan []byte)
    s.lock.Lock()
    s.m[c] = true
    s.lock.Unlock()

    for {
        time.Sleep(s.FrameInterval)
        b := <-c
        _, err := w.Write(b)
        if err != nil {
            break
        }
    }

    s.lock.Lock()
    delete(s.m, c)
    s.lock.Unlock()
    log.Println("Stream:", r.RemoteAddr, "disconnected")
}

// UpdateJPEG pushes a new JPEG frame onto the clients.
func (s *Stream) UpdateJPEG(jpeg []byte) {
    header := fmt.Sprintf(headerf, len(jpeg))
    if len(s.frame) < len(jpeg) len(header) {
        s.frame = make([]byte, (len(jpeg) len(header))*2)
    }

    copy(s.frame, header)
    copy(s.frame[len(header):], jpeg)

    s.lock.Lock()
    for c := range s.m {
        // Select to skip streams which are sleeping to drop frames.
        // This might need more thought.
        select {
        case c <- s.frame:
        default:
        }
    }
    s.lock.Unlock()
}

// NewStream initializes and returns a new Stream.
func NewStream() *Stream {
    return &Stream{
        m:             make(map[chan []byte]bool),
        frame:         make([]byte, len(headerf)),
        FrameInterval: 50 * time.Millisecond,
    }
}

My output is as below:

enter image description here

My question is:

  1. How can I fit the streamed image into the selected object dimensions
  2. Is there a way to stream it to video element, I tried but failed.

CodePudding user response:

I found the an answer in the go code, that is doing image resizing using:

"image/jpeg"
"golang.org/x/image/draw"

As:

    // Decode the image (from PNG to image.Image):
    src, _ := j.Decode(bytes.NewReader(jpeg))

    // Set the expected size that you want:
    dst := image.NewRGBA(image.Rect(0, 0, src.Bounds().Max.X/3, src.Bounds().Max.Y/3))

    // Resize:
    draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)

    buf := new(bytes.Buffer)

    // Encode to `buf`:
    j.Encode(buf, dst, nil)

    copy(s.frame, header)
    //  copy(s.frame[len(header):], jpeg)
    copy(s.frame[len(header):], buf.Bytes())

So, my streaming full code became:

// Package mjpeg implements a simple MJPEG streamer.
//
// Stream objects implement the http.Handler interface, allowing to use them with the net/http package like so:
//  stream = mjpeg.NewStream()
//  http.Handle("/camera", stream)
// Then push new JPEG frames to the connected clients using stream.UpdateJPEG().
package mjpeg

import (
    "bytes"
    "fmt"
    "image"
    j "image/jpeg"
    "log"
    "net/http"
    "sync"
    "time"

    "golang.org/x/image/draw"
)

// Stream represents a single video feed.
type Stream struct {
    m             map[chan []byte]bool
    frame         []byte
    lock          sync.Mutex
    FrameInterval time.Duration
}

const boundaryWord = "MJPEGBOUNDARY"
const headerf = "\r\n"  
    "--"   boundaryWord   "\r\n"  
    "Content-Type: image/jpeg\r\n"  
    "Content-Length: %d\r\n"  
    "X-Timestamp: 0.000000\r\n"  
    "\r\n"

// ServeHTTP responds to HTTP requests with the MJPEG stream, implementing the http.Handler interface.
func (s *Stream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    log.Println("Stream:", r.RemoteAddr, "connected")
    w.Header().Add("Content-Type", "multipart/x-mixed-replace;boundary=" boundaryWord)

    c := make(chan []byte)
    s.lock.Lock()
    s.m[c] = true
    s.lock.Unlock()

    for {
        time.Sleep(s.FrameInterval)
        b := <-c
        _, err := w.Write(b)
        if err != nil {
            break
        }
    }

    s.lock.Lock()
    delete(s.m, c)
    s.lock.Unlock()
    log.Println("Stream:", r.RemoteAddr, "disconnected")
}

// UpdateJPEG pushes a new JPEG frame onto the clients.
func (s *Stream) UpdateJPEG(jpeg []byte) {
    header := fmt.Sprintf(headerf, len(jpeg))
    if len(s.frame) < len(jpeg) len(header) {
        s.frame = make([]byte, (len(jpeg) len(header))*2)
    }

    // Decode the image (from PNG to image.Image):
    src, _ := j.Decode(bytes.NewReader(jpeg))

    // Set the expected size that you want:
    dst := image.NewRGBA(image.Rect(0, 0, src.Bounds().Max.X/3, src.Bounds().Max.Y/3))

    // Resize:
    draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)

    buf := new(bytes.Buffer)

    // Encode to `buf`:
    j.Encode(buf, dst, nil)

    copy(s.frame, header)
    //  copy(s.frame[len(header):], jpeg)
    copy(s.frame[len(header):], buf.Bytes())

    s.lock.Lock()
    for c := range s.m {
        // Select to skip streams which are sleeping to drop frames.
        // This might need more thought.
        select {
        case c <- s.frame:
        default:
        }
    }
    s.lock.Unlock()
}

// NewStream initializes and returns a new Stream.
func NewStream() *Stream {
    return &Stream{
        m:             make(map[chan []byte]bool),
        frame:         make([]byte, len(headerf)),
        FrameInterval: 50 * time.Millisecond,
    }
}

Any my output became:

enter image description here

  • Related