Golang实现服务器端图像流缩放功能探讨
Golang实现服务器端图像流缩放功能探讨
使用 gocv 将图像流式传输到我的 HTML5 页面中的 object 元素。
页面如下:
<!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>
我的 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("Error opening capture device: %v\n", deviceID)
return
}
defer webcam.Close()
// 创建 mjpeg 流
stream = mjpeg.NewStream()
// 开始捕获
go mjpegCapture()
fmt.Println("Capturing. Point your browser to " + 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("Device closed: %v\n", deviceID)
return
}
if img.Empty() {
continue
}
buf, _ := gocv.IMEncode(".jpg", img)
stream.UpdateJPEG(buf.GetBytes())
buf.Close()
}
}
opencv/mjpeg 中的流式传输函数是:
// Package 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("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 将新的 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 跳过正在休眠的流以丢弃帧。
// 这可能需要进一步考虑。
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,
}
}
我的输出如下:

我的问题是:
- 如何使流式传输的图像适应所选的
object元素尺寸? - 是否有办法将其流式传输到
video元素?我尝试过但失败了。
注意:由于我不确定解决方案是在 go 端还是在 html5/JavaScript 端,所以我在这里发布。
更多关于Golang实现服务器端图像流缩放功能探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
嗨 @hyousef! 我不确定你是否能用HTML解决,但对于图像缩放,请使用libvips。 它更快且成本更低 🙂
我正在使用libvips处理图像。试试 https://github.com/davidbyttow/govips!
更多关于Golang实现服务器端图像流缩放功能探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在将相机接收到的帧作为字节数组添加之前 如果添加边界词和内容类型,它将会正常工作
fmt.Println("Capturing. Open http://" + host)
// start http server
http.HandleFunc("/video", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=frame")
data := ""
for {
/* fmt.Println("Frame ID: ", frame_id)
*/mutex.Lock()
data = "--frame\r\n Content-Type: image/jpeg\r\n\r\n" + string(frame) + "\r\n\r\n"
mutex.Unlock()
time.Sleep(33 * time.Millisecond)
w.Write([]byte(data))
}
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("index.html")
t.Execute(w, "index")
})
要实现服务器端图像流缩放以适应HTML object元素的尺寸,可以通过在Go端对图像进行实时缩放处理。以下是修改后的mjpegCapture函数,添加了图像缩放功能:
func mjpegCapture() {
img := gocv.NewMat()
defer img.Close()
// 目标尺寸 - 根据你的object元素尺寸设置
targetWidth := 300
targetHeight := 200
for {
if ok := webcam.Read(&img); !ok {
fmt.Printf("Device closed: %v\n", deviceID)
return
}
if img.Empty() {
continue
}
// 创建目标Mat用于缩放
resized := gocv.NewMat()
defer resized.Close()
// 缩放图像到目标尺寸
gocv.Resize(img, &resized, image.Pt(targetWidth, targetHeight), 0, 0, gocv.InterpolationLinear)
// 编码为JPEG
buf, _ := gocv.IMEncode(".jpg", resized)
stream.UpdateJPEG(buf.GetBytes())
buf.Close()
}
}
对于流式传输到video元素,需要将MJPEG流转换为浏览器video元素支持的格式。以下是两种方法:
方法1:使用Canvas绘制MJPEG(HTML/JavaScript端):
<!DOCTYPE html>
<html>
<body>
<canvas id="videoCanvas" width="300" height="200"></canvas>
<script>
const canvas = document.getElementById('videoCanvas');
const ctx = canvas.getContext('2d');
const img = new Image();
// 创建图像流连接
const streamUrl = 'http://localhost:8080/camera';
const eventSource = new EventSource(streamUrl);
eventSource.onmessage = function(event) {
img.src = 'data:image/jpeg;base64,' + btoa(event.data);
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
};
</script>
</body>
</html>
方法2:使用WebRTC实现真正的video元素流(需要更复杂的服务器端实现):
// 需要安装Pion WebRTC库
// go get github.com/pion/webrtc/v3
package main
import (
"github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/media"
"github.com/pion/webrtc/v3/pkg/media/ivfwriter"
)
func setupWebRTCStream() {
// 创建PeerConnection
config := webrtc.Configuration{}
peerConnection, err := webrtc.NewPeerConnection(config)
// 添加视频轨道
videoTrack, err := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: "video/VP8"},
"video",
"stream",
)
peerConnection.AddTrack(videoTrack)
// 这里需要实现从gocv Mat到VP8帧的转换
// 可以使用libvpx或ffmpeg进行编码
}
对于纯MJPEG流,video元素支持有限。更可靠的方法是使用MediaSource Extensions API:
<!DOCTYPE html>
<html>
<body>
<video id="videoPlayer" width="300" height="200" autoplay></video>
<script>
const video = document.getElementById('videoPlayer');
const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', () => {
const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
// 从服务器获取流数据
fetch('http://localhost:8080/camera')
.then(response => response.arrayBuffer())
.then(data => {
sourceBuffer.appendBuffer(data);
video.play();
});
});
}
</script>
</body>
</html>
注意:要将MJPEG转换为MP4/H.264流,需要在Go端实现转码逻辑,可以使用C绑定调用FFmpeg或使用纯Go的编码器如codec/vpx。

