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:
My question is:
- How can I fit the streamed image into the selected
object
dimensions - 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: