在对象中放大MJPEG

huangapple go评论76阅读模式
英文:

Zooming mjpeg in object

问题

使用gocv库,我正在将图像流传输到我的HTML5页面上的object元素。

页面代码如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>使用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="摄像头流传输"></object>
    </body>
    <<script>
     /*   (function(){
                document.getElementById("content").innerHTML='<object type="text/html" data="http://localhost:8080/cam" ></object>';
        })();
        */
    </script>
</html>

我的Go代码如下:

// 此示例打开一个视频捕获设备,然后从中流式传输MJPEG。
// 运行后,将浏览器指向命令行中传入的主机名/端口号(例如http://localhost:8080),您应该能够看到实时视频流。
//
// 如何运行:
//
// 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
		}
	*/
	// 解析参数
	deviceID := 0   // os.Args[1]
	host := ":8080" //os.Args[2]

	// 打开摄像头
	webcam, err = gocv.OpenVideoCapture(deviceID)
	if err != nil {
		fmt.Printf("打开捕获设备时出错:%v\n", deviceID)
		return
	}
	defer webcam.Close()

	// 创建MJPEG流
	stream = mjpeg.NewStream()

	// 开始捕获
	go mjpegCapture()

	fmt.Println("正在捕获。请将浏览器指向" + host)

	// 启动HTTP服务器
	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("设备已关闭:%v\n", deviceID)
			return
		}
		if img.Empty() {
			continue
		}

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

opencv/mjpeg中的流式传输函数如下:

// 包mjpeg实现了一个简单的MJPEG流传输器。
//
// Stream对象实现了http.Handler接口,可以像下面这样与net/http包一起使用:
//	stream = mjpeg.NewStream()
//	http.Handle("/camera", stream)
// 然后使用stream.UpdateJPEG()将新的JPEG帧推送到连接的客户端。
package mjpeg

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

// Stream表示单个视频源。
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使用MJPEG流响应HTTP请求,实现了http.Handler接口。
func (s *Stream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println("流:", r.RemoteAddr, "已连接")
	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("流:", r.RemoteAddr, "已断开连接")
}

// UpdateJPEG将新的JPEG帧推送到客户端。
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 {
		case c <- s.frame:
		default:
		}
	}
	s.lock.Unlock()
}

// NewStream初始化并返回一个新的Stream。
func NewStream() *Stream {
	return &Stream{
		m:             make(map[chan []byte]bool),
		frame:         make([]byte, len(headerf)),
		FrameInterval: 50 * time.Millisecond,
	}
}

我的输出如下:

在对象中放大MJPEG

我的问题是:

  1. 如何将流式传输的图像适应所选的object元素尺寸?
  2. 是否有办法将其流式传输到video元素?我尝试过但失败了。
英文:

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

The page is:

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

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 (
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net/http&quot;

	_ &quot;net/http/pprof&quot;
	&quot;opencv/mjpeg&quot;

	&quot;gocv.io/x/gocv&quot;
)

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

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

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

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

	// start capturing
	go mjpegCapture()

	fmt.Println(&quot;Capturing. Point your browser to &quot; + host)

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

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

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

		buf, _ := gocv.IMEncode(&quot;.jpg&quot;, 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(&quot;/camera&quot;, stream)
// Then push new JPEG frames to the connected clients using stream.UpdateJPEG().
package mjpeg

import (
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

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

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

// 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(&quot;Stream:&quot;, r.RemoteAddr, &quot;connected&quot;)
	w.Header().Add(&quot;Content-Type&quot;, &quot;multipart/x-mixed-replace;boundary=&quot;+boundaryWord)

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

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

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

// 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) &lt; 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 &lt;- s.frame:
		default:
		}
	}
	s.lock.Unlock()
}

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

My output is as below:

在对象中放大MJPEG

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.

答案1

得分: 0

我在go代码中找到了一个答案,它使用以下方式进行图像调整大小:

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

代码如下:

	// 解码图像(从PNG到image.Image):
	src, _ := j.Decode(bytes.NewReader(jpeg))

	// 设置所需的大小:
	dst := image.NewRGBA(image.Rect(0, 0, src.Bounds().Max.X/3, src.Bounds().Max.Y/3))

	// 调整大小:
	draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)

	buf := new(bytes.Buffer)

	// 编码到`buf`:
	j.Encode(buf, dst, nil)

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

因此,我的完整流式代码如下:

// 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)
	}

	// 解码图像(从PNG到image.Image):
	src, _ := j.Decode(bytes.NewReader(jpeg))

	// 设置所需的大小:
	dst := image.NewRGBA(image.Rect(0, 0, src.Bounds().Max.X/3, src.Bounds().Max.Y/3))

	// 调整大小:
	draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)

	buf := new(bytes.Buffer)

	// 编码到`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 {
		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,
	}
}

我的输出如下:

在对象中放大MJPEG

英文:

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

&quot;image/jpeg&quot;
&quot;golang.org/x/image/draw&quot;

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(&quot;/camera&quot;, stream)
// Then push new JPEG frames to the connected clients using stream.UpdateJPEG().
package mjpeg

import (
	&quot;bytes&quot;
	&quot;fmt&quot;
	&quot;image&quot;
	j &quot;image/jpeg&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;sync&quot;
	&quot;time&quot;

	&quot;golang.org/x/image/draw&quot;
)

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

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

// 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(&quot;Stream:&quot;, r.RemoteAddr, &quot;connected&quot;)
	w.Header().Add(&quot;Content-Type&quot;, &quot;multipart/x-mixed-replace;boundary=&quot;+boundaryWord)

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

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

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

// 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) &lt; 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 &lt;- s.frame:
		default:
		}
	}
	s.lock.Unlock()
}

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

Any my output became:

在对象中放大MJPEG

huangapple
  • 本文由 发表于 2022年6月11日 18:42:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/72583642.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定