15 Commits
1.2 ... 1.4

Author SHA1 Message Date
Milan Nikolic
c9d77f03e7 Add rotate option 2018-10-10 04:32:00 +02:00
Milan Nikolic
37b19fcfe8 Update README.md 2018-09-08 20:17:32 +02:00
Milan Nikolic
5006c48690 Update README.md 2018-09-08 20:16:18 +02:00
Milan Nikolic
d340fa2dc3 Allow use of native image/jpeg 2018-07-17 14:51:18 +02:00
Milan Nikolic
fa5233255e Fix cv3 build 2018-07-02 17:31:12 +02:00
Milan Nikolic
ac555cbf7b Update README.md 2018-04-17 13:56:23 +02:00
Milan Nikolic
e1f03b55a1 Reuse frame 2018-03-14 14:33:01 +01:00
Milan Nikolic
3c0c949f31 Add support for OpenCV 3 2018-03-14 13:45:38 +01:00
Milan Nikolic
8a740337ab Update README.md 2018-01-29 21:39:17 +01:00
Milan Nikolic
cbc7d21b23 Shorten options 2018-01-27 03:50:50 +01:00
Milan Nikolic
45ca5bdedb New release 2018-01-27 03:28:09 +01:00
Milan Nikolic
173deebc88 Add video file reader 2018-01-27 03:22:33 +01:00
Milan Nikolic
7392d8d9b8 WebGL is now default 2018-01-27 01:59:46 +01:00
Milan Nikolic
1c9dfcb84c Move encoder 2018-01-27 01:47:04 +01:00
Milan Nikolic
f65a7cf1aa Fix golibjpegturbo missing repo 2018-01-10 11:39:04 +01:00
18 changed files with 574 additions and 127 deletions

View File

@@ -11,17 +11,19 @@ or
### Requirements
* [OpenCV 2.x](http://opencv.org/)
* [OpenCV](http://opencv.org/) (default is version 2.x via [go-opencv](https://github.com/lazywei/go-opencv), use `-tags cv3` for [gocv](https://github.com/hybridgroup/gocv))
* [libjpeg-turbo](https://www.libjpeg-turbo.org/) (use `-tags jpeg` for native image/jpeg, but note that CPU usage will be much higher)
### Download
Binaries are compiled with static OpenCV library:
Binaries are compiled with static OpenCV/libjpeg-turbo libraries, they should just work:
- [Android 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2-android.tar.gz)
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2-64bit.tar.gz)
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2-RPi.tar.gz)
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.2/cam2ip-1.2.zip)
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.4/cam2ip-1.4-64bit.tar.gz)
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.4/cam2ip-1.4-RPi.tar.gz)
- [RPi3 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.4/cam2ip-1.4-RPi3.tar.gz)
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.4/cam2ip-1.4.zip)
- [Windows 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.4/cam2ip-1.4-64bit.zip)
### Installation
@@ -38,16 +40,20 @@ Usage of ./cam2ip:
Bind address (default ":56000")
-delay int
Delay between frames, in milliseconds (default 10)
-frame-height float
-height float
Frame height (default 480)
-frame-width float
-width float
Frame width (default 640)
-htpasswd-file string
Path to htpasswd file, if empty auth is disabled
-index int
Camera index
-webgl
Use WebGL to draw images
-nowebgl
Disable WebGL drawing of images (html handler)
-rotate int
Rotate image, valid values are 90, 180, 270
-video-file string
Use video file instead of camera
```
### Handlers

View File

@@ -7,11 +7,12 @@ import (
"github.com/gen2brain/cam2ip/camera"
"github.com/gen2brain/cam2ip/server"
"github.com/gen2brain/cam2ip/video"
)
const (
name = "cam2ip"
version = "1.0"
version = "1.4"
)
func main() {
@@ -19,11 +20,13 @@ func main() {
flag.IntVar(&srv.Index, "index", 0, "Camera index")
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
flag.Float64Var(&srv.FrameWidth, "frame-width", 640, "Frame width")
flag.Float64Var(&srv.FrameHeight, "frame-height", 480, "Frame height")
flag.BoolVar(&srv.WebGL, "webgl", false, "Use WebGL to draw images")
flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width")
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height")
flag.IntVar(&srv.Rotate, "rotate", 0, "Rotate image, valid values are 90, 180, 270")
flag.BoolVar(&srv.NoWebGL, "nowebgl", false, "Disable WebGL drawing of images (html handler)")
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address")
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled")
flag.StringVar(&srv.FileName, "video-file", "", "Use video file instead of camera")
flag.Parse()
srv.Name = name
@@ -38,16 +41,35 @@ func main() {
}
}
srv.Camera, err = camera.NewCamera(srv.Index)
if srv.FileName != "" {
if _, err = os.Stat(srv.FileName); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
}
if srv.FileName != "" {
vid, err := video.New(video.Options{srv.FileName, srv.Rotate})
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
srv.Camera.SetProperty(camera.PropFrameWidth, srv.FrameWidth)
srv.Camera.SetProperty(camera.PropFrameHeight, srv.FrameHeight)
srv.Reader = vid
} else {
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate})
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
defer srv.Camera.Close()
cam.SetProperty(camera.PropFrameWidth, srv.FrameWidth)
cam.SetProperty(camera.PropFrameHeight, srv.FrameHeight)
srv.Reader = cam
}
defer srv.Reader.Close()
fmt.Fprintf(os.Stderr, "Listening on %s\n", srv.Bind)

View File

@@ -1,3 +1,5 @@
// +build !cv3
// Package camera.
package camera
@@ -5,66 +7,31 @@ import (
"fmt"
"image"
"github.com/disintegration/imaging"
"github.com/lazywei/go-opencv/opencv"
)
// Property identifiers.
const (
PropPosMsec = iota
PropPosFrames
PropPosAviRatio
PropFrameWidth
PropFrameHeight
PropFps
PropFourcc
PropFrameCount
PropFormat
PropMode
PropBrightness
PropContrast
PropSaturation
PropHue
PropGain
PropExposure
PropConvertRgb
PropWhiteBalanceU
PropRectification
PropMonocrome
PropSharpness
PropAutoExposure
PropGamma
PropTemperature
PropTrigger
PropTriggerDelay
PropWhiteBalanceV
PropZoom
PropFocus
PropGuid
PropIsoSpeed
PropMaxDc1394
PropBacklight
PropPan
PropTilt
PropRoll
PropIris
PropSettings
PropBuffersize
)
// Options.
type Options struct {
Index int
Rotate int
}
// Camera represents camera.
type Camera struct {
Index int
opts Options
camera *opencv.Capture
frame *opencv.IplImage
}
// NewCamera returns new Camera for given camera index.
func NewCamera(index int) (camera *Camera, err error) {
// New returns new Camera for given camera index.
func New(opts Options) (camera *Camera, err error) {
camera = &Camera{}
camera.Index = index
camera.opts = opts
camera.camera = opencv.NewCameraCapture(index)
camera.camera = opencv.NewCameraCapture(opts.Index)
if camera.camera == nil {
err = fmt.Errorf("camera: can not open camera %d", index)
err = fmt.Errorf("camera: can not open camera %d", opts.Index)
}
return
@@ -73,8 +40,26 @@ func NewCamera(index int) (camera *Camera, err error) {
// Read reads next frame from camera and returns image.
func (c *Camera) Read() (img image.Image, err error) {
if c.camera.GrabFrame() {
frame := c.camera.RetrieveFrame(1)
img = frame.ToImage()
c.frame = c.camera.RetrieveFrame(1)
if c.frame == nil {
err = fmt.Errorf("camera: can not grab frame")
return
}
img = c.frame.ToImage()
if c.opts.Rotate == 0 {
return
}
switch c.opts.Rotate {
case 90:
img = imaging.Rotate90(img)
case 180:
img = imaging.Rotate180(img)
case 270:
img = imaging.Rotate270(img)
}
} else {
err = fmt.Errorf("camera: can not grab frame")
}
@@ -88,8 +73,8 @@ func (c *Camera) GetProperty(id int) float64 {
}
// SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) int {
return c.camera.SetProperty(id, value)
func (c *Camera) SetProperty(id int, value float64) {
c.camera.SetProperty(id, value)
}
// Close closes camera.
@@ -99,6 +84,7 @@ func (c *Camera) Close() (err error) {
return
}
c.frame.Release()
c.camera.Release()
c.camera = nil
return

44
camera/camera_const.go Normal file
View File

@@ -0,0 +1,44 @@
package camera
// Property identifiers.
const (
PropPosMsec = iota
PropPosFrames
PropPosAviRatio
PropFrameWidth
PropFrameHeight
PropFps
PropFourcc
PropFrameCount
PropFormat
PropMode
PropBrightness
PropContrast
PropSaturation
PropHue
PropGain
PropExposure
PropConvertRgb
PropWhiteBalanceU
PropRectification
PropMonocrome
PropSharpness
PropAutoExposure
PropGamma
PropTemperature
PropTrigger
PropTriggerDelay
PropWhiteBalanceV
PropZoom
PropFocus
PropGuid
PropIsoSpeed
PropMaxDc1394
PropBacklight
PropPan
PropTilt
PropRoll
PropIris
PropSettings
PropBuffersize
)

99
camera/camera_cv3.go Normal file
View File

@@ -0,0 +1,99 @@
// +build cv3
// Package camera.
package camera
import (
"fmt"
"image"
"github.com/disintegration/imaging"
"gocv.io/x/gocv"
)
// Options.
type Options struct {
Index int
Rotate int
}
// Camera represents camera.
type Camera struct {
opts Options
camera *gocv.VideoCapture
frame *gocv.Mat
}
// New returns new Camera for given camera index.
func New(opts Options) (camera *Camera, err error) {
camera = &Camera{}
camera.opts = opts
mat := gocv.NewMat()
camera.frame = &mat
camera.camera, err = gocv.VideoCaptureDevice(opts.Index)
if err != nil {
err = fmt.Errorf("camera: can not open camera %d: %s", opts.Index, err.Error())
}
return
}
// Read reads next frame from camera and returns image.
func (c *Camera) Read() (img image.Image, err error) {
ok := c.camera.Read(c.frame)
if !ok {
err = fmt.Errorf("camera: can not grab frame")
return
}
img, e := c.frame.ToImage()
if e != nil {
err = fmt.Errorf("camera: %v", e)
return
}
if c.frame == nil {
err = fmt.Errorf("camera: can not grab frame")
return
}
if c.opts.Rotate == 0 {
return
}
switch c.opts.Rotate {
case 90:
img = imaging.Rotate90(img)
case 180:
img = imaging.Rotate180(img)
case 270:
img = imaging.Rotate270(img)
}
return
}
// GetProperty returns the specified camera property.
func (c *Camera) GetProperty(id int) float64 {
return c.camera.Get(gocv.VideoCaptureProperties(id))
}
// SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) {
c.camera.Set(gocv.VideoCaptureProperties(id), value)
}
// Close closes camera.
func (c *Camera) Close() (err error) {
if c.camera == nil {
err = fmt.Errorf("camera: camera is not opened")
return
}
c.frame.Close()
err = c.camera.Close()
c.camera = nil
return
}

View File

@@ -11,7 +11,7 @@ import (
)
func TestCamera(t *testing.T) {
camera, err := NewCamera(1)
camera, err := New(1)
if err != nil {
t.Fatal(err)
}

31
encoder/encode.go Normal file
View File

@@ -0,0 +1,31 @@
// +build !jpeg
// Package encoder.
package encoder
import (
"image"
"io"
jpeg "github.com/antonini/golibjpegturbo"
)
// New returns a new Encoder.
func New(w io.Writer) *Encoder {
return &Encoder{w}
}
// Encoder struct.
type Encoder struct {
w io.Writer
}
// Encode encodes image to JPEG.
func (e Encoder) Encode(img image.Image) error {
err := jpeg.Encode(e.w, img, &jpeg.Options{Quality: 75})
if err != nil {
return err
}
return nil
}

View File

@@ -1,15 +1,16 @@
package camera
// +build jpeg
// Package encoder.
package encoder
import (
"image"
//"image/jpeg"
"image/jpeg"
"io"
jpeg "github.com/kjk/golibjpegturbo"
)
// NewEncoder returns a new Encoder.
func NewEncoder(w io.Writer) *Encoder {
// New returns a new Encoder.
func New(w io.Writer) *Encoder {
return &Encoder{w}
}

View File

@@ -13,7 +13,7 @@ type HTML struct {
}
// NewHTML returns new HTML handler.
func NewHTML(bind string, width, height float64, gl bool) *HTML {
func NewHTML(bind string, width, height float64, nogl bool) *HTML {
h := &HTML{}
b := strings.Split(bind, ":")
@@ -21,9 +21,9 @@ func NewHTML(bind string, width, height float64, gl bool) *HTML {
bind = "127.0.0.1" + bind
}
tpl := html
if gl {
tpl = htmlWebGL
tpl := htmlWebGL
if nogl {
tpl = html
}
tpl = strings.Replace(tpl, "{BIND}", bind, -1)

View File

@@ -4,17 +4,18 @@ import (
"log"
"net/http"
"github.com/gen2brain/cam2ip/camera"
"github.com/gen2brain/cam2ip/encoder"
"github.com/gen2brain/cam2ip/reader"
)
// JPEG handler.
type JPEG struct {
camera *camera.Camera
reader reader.ImageReader
}
// NewJPEG returns new JPEG handler.
func NewJPEG(camera *camera.Camera) *JPEG {
return &JPEG{camera}
func NewJPEG(reader reader.ImageReader) *JPEG {
return &JPEG{reader}
}
// ServeHTTP handles requests on incoming connections.
@@ -28,15 +29,13 @@ func (j *JPEG) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "no-store, no-cache")
w.Header().Add("Content-Type", "image/jpeg")
img, err := j.camera.Read()
img, err := j.reader.Read()
if err != nil {
log.Printf("jpeg: read: %v", err)
return
}
enc := camera.NewEncoder(w)
err = enc.Encode(img)
err = encoder.New(w).Encode(img)
if err != nil {
log.Printf("jpeg: encode: %v", err)
return

View File

@@ -9,18 +9,19 @@ import (
"net/textproto"
"time"
"github.com/gen2brain/cam2ip/camera"
"github.com/gen2brain/cam2ip/encoder"
"github.com/gen2brain/cam2ip/reader"
)
// MJPEG handler.
type MJPEG struct {
camera *camera.Camera
reader reader.ImageReader
delay int
}
// NewMJPEG returns new MJPEG handler.
func NewMJPEG(camera *camera.Camera, delay int) *MJPEG {
return &MJPEG{camera, delay}
func NewMJPEG(reader reader.ImageReader, delay int) *MJPEG {
return &MJPEG{reader, delay}
}
// ServeHTTP handles requests on incoming connections.
@@ -55,15 +56,13 @@ loop:
continue
}
img, err := m.camera.Read()
img, err := m.reader.Read()
if err != nil {
log.Printf("mjpeg: read: %v", err)
continue
}
enc := camera.NewEncoder(partWriter)
err = enc.Encode(img)
err = encoder.New(partWriter).Encode(img)
if err != nil {
log.Printf("mjpeg: encode: %v", err)
continue

View File

@@ -8,34 +8,34 @@ import (
"golang.org/x/net/websocket"
"github.com/gen2brain/cam2ip/camera"
"github.com/gen2brain/cam2ip/encoder"
"github.com/gen2brain/cam2ip/reader"
)
// Socket handler.
type Socket struct {
camera *camera.Camera
reader reader.ImageReader
delay int
}
// NewSocket returns new socket handler.
func NewSocket(camera *camera.Camera, delay int) websocket.Handler {
s := &Socket{camera, delay}
func NewSocket(reader reader.ImageReader, delay int) websocket.Handler {
s := &Socket{reader, delay}
return websocket.Handler(s.write)
}
// write writes images to socket
func (s *Socket) write(ws *websocket.Conn) {
for {
img, err := s.camera.Read()
img, err := s.reader.Read()
if err != nil {
log.Printf("socket: read: %v", err)
continue
break
}
w := new(bytes.Buffer)
enc := camera.NewEncoder(w)
err = enc.Encode(img)
err = encoder.New(w).Encode(img)
if err != nil {
log.Printf("socket: encode: %v", err)
continue

View File

@@ -2,8 +2,9 @@
CHROOT="/usr/x86_64-pc-linux-gnu-static"
MINGW="/usr/i686-w64-mingw32"
MINGW64="/usr/x86_64-w64-mingw32"
RPI="/usr/armv6j-hardfloat-linux-gnueabi"
ANDROID="/opt/android-toolchain-arm7"
RPI3="/usr/armv7a-hardfloat-linux-gnueabi"
mkdir -p build
@@ -11,9 +12,9 @@ LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \
CGO_CFLAGS="-I$CHROOT/usr/include" \
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v -x -o build/cam2ip.linux.amd64 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
@@ -22,6 +23,14 @@ CGO_CFLAGS="-I$MINGW/usr/include" \
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -v -x -o build/cam2ip.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
PKG_CONFIG="/usr/bin/x86_64-w64-mingw32-pkg-config" \
PKG_CONFIG_PATH="$MINGW64/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$MINGW64/usr/lib/pkgconfig" \
CGO_LDFLAGS="-L$MINGW64/usr/lib" \
CGO_CFLAGS="-I$MINGW64/usr/include" \
CC="x86_64-w64-mingw32-gcc" CXX="x86_64-w64-mingw32-g++" \
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -v -x -o build/cam2ip.exe.amd64 -ldflags "-linkmode external -s -w '-extldflags=-static'" github.com/gen2brain/cam2ip
PKG_CONFIG="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
@@ -30,11 +39,10 @@ CGO_CFLAGS="-I$RPI/usr/include" \
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -v -x -o build/cam2ip.linux.arm -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
PATH="$PATH:$ANDROID/bin" \
PKG_CONFIG="$ANDROID/bin/arm-linux-androideabi-pkg-config" \
PKG_CONFIG_PATH="$ANDROID/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$ANDROID/lib/pkgconfig" \
CGO_LDFLAGS="-L$ANDROID/lib" \
CGO_CFLAGS="-I$ANDROID/include" \
CC="arm-linux-androideabi-gcc" CXX="arm-linux-androideabi-g++" \
CGO_ENABLED=1 GOOS=android GOARCH=arm go build -v -x -o build/cam2ip.android.arm -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
PKG_CONFIG="/usr/bin/armv7a-hardfloat-linux-gnueabi-pkg-config" \
PKG_CONFIG_PATH="$RPI3/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$RPI3/usr/lib/pkgconfig" \
CGO_LDFLAGS="-L$RPI3/usr/lib" \
CGO_CFLAGS="-I$RPI3/usr/include" \
CC="armv7a-hardfloat-linux-gnueabi-gcc" CXX="armv7a-hardfloat-linux-gnueabi-g++" \
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -v -x -o build/cam2ip.linux.arm7 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip

15
reader/reader.go Normal file
View File

@@ -0,0 +1,15 @@
// Package reader.
package reader
import (
"image"
)
// ImageReader interface
type ImageReader interface {
// Read reads next frame from camera/video and returns image.
Read() (img image.Image, err error)
// Close closes camera/video.
Close() error
}

View File

@@ -8,8 +8,8 @@ import (
"github.com/abbot/go-http-auth"
"github.com/gen2brain/cam2ip/camera"
"github.com/gen2brain/cam2ip/handlers"
"github.com/gen2brain/cam2ip/reader"
)
// Server struct.
@@ -22,11 +22,17 @@ type Server struct {
Index int
Delay int
FrameWidth float64
FrameHeight float64
WebGL bool
Camera *camera.Camera
Rotate int
NoWebGL bool
FileName string
Reader reader.ImageReader
}
// NewServer returns new Server.
@@ -43,11 +49,11 @@ func (s *Server) ListenAndServe() error {
basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd))
}
http.Handle("/html", newAuthHandler(handlers.NewHTML(s.Bind, s.FrameWidth, s.FrameHeight, s.WebGL), basic))
http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Camera), basic))
http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Camera, s.Delay), basic))
http.Handle("/html", newAuthHandler(handlers.NewHTML(s.Bind, s.FrameWidth, s.FrameHeight, s.NoWebGL), basic))
http.Handle("/jpeg", newAuthHandler(handlers.NewJPEG(s.Reader), basic))
http.Handle("/mjpeg", newAuthHandler(handlers.NewMJPEG(s.Reader, s.Delay), basic))
http.Handle("/socket", handlers.NewSocket(s.Camera, s.Delay))
http.Handle("/socket", handlers.NewSocket(s.Reader, s.Delay))
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)

80
video/video.go Normal file
View File

@@ -0,0 +1,80 @@
// +build !cv3
// Package video.
package video
import (
"fmt"
"image"
"github.com/disintegration/imaging"
"github.com/lazywei/go-opencv/opencv"
)
// Options.
type Options struct {
Filename string
Rotate int
}
// Video represents video.
type Video struct {
opts Options
video *opencv.Capture
frame *opencv.IplImage
}
// New returns new Video for given path.
func New(opts Options) (video *Video, err error) {
video = &Video{}
video.opts = opts
video.video = opencv.NewFileCapture(opts.Filename)
if video.video == nil {
err = fmt.Errorf("video: can not open video %s", opts.Filename)
}
return
}
// Read reads next frame from video and returns image.
func (v *Video) Read() (img image.Image, err error) {
if v.video.GrabFrame() {
v.frame = v.video.RetrieveFrame(1)
if v.frame == nil {
err = fmt.Errorf("video: can not grab frame")
return
}
img = v.frame.ToImage()
if v.opts.Rotate == 0 {
return
}
switch v.opts.Rotate {
case 90:
img = imaging.Rotate90(img)
case 180:
img = imaging.Rotate180(img)
case 270:
img = imaging.Rotate270(img)
}
} else {
err = fmt.Errorf("video: can not grab frame")
}
return
}
// Close closes video.
func (v *Video) Close() (err error) {
if v.video == nil {
err = fmt.Errorf("video: video is not opened")
return
}
v.frame.Release()
v.video.Release()
v.video = nil
return
}

89
video/video_cv3.go Normal file
View File

@@ -0,0 +1,89 @@
// +build cv3
// Package video.
package video
import (
"fmt"
"image"
"github.com/disintegration/imaging"
"gocv.io/x/gocv"
)
// Options.
type Options struct {
Filename string
Rotate int
}
// Video represents video.
type Video struct {
opts Options
video *gocv.VideoCapture
frame *gocv.Mat
}
// New returns new Video for given path.
func New(opts Options) (video *Video, err error) {
video = &Video{}
video.opts = opts
mat := gocv.NewMat()
video.frame = &mat
video.video, err = gocv.VideoCaptureFile(opts.Filename)
if err != nil {
err = fmt.Errorf("video: can not open video %s: %s", opts.Filename, err.Error())
}
return
}
// Read reads next frame from video and returns image.
func (v *Video) Read() (img image.Image, err error) {
ok := v.video.Read(v.frame)
if !ok {
err = fmt.Errorf("video: can not grab frame")
return
}
if v.frame == nil {
err = fmt.Errorf("video: can not grab frame")
return
}
img, e := v.frame.ToImage()
if e != nil {
err = fmt.Errorf("video: %v", e)
return
}
if v.opts.Rotate == 0 {
return
}
switch v.opts.Rotate {
case 90:
img = imaging.Rotate90(img)
case 180:
img = imaging.Rotate180(img)
case 270:
img = imaging.Rotate270(img)
}
return
}
// Close closes video.
func (v *Video) Close() (err error) {
if v.video == nil {
err = fmt.Errorf("video: video is not opened")
return
}
v.frame.Close()
err = v.video.Close()
v.video = nil
return
}

62
video/video_test.go Normal file
View File

@@ -0,0 +1,62 @@
package video
import (
"fmt"
"image/jpeg"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
)
func TestVideo(t *testing.T) {
video, err := New("test.mp4")
if err != nil {
t.Fatal(err)
}
defer video.Close()
tmpdir, err := ioutil.TempDir(os.TempDir(), "cam2ip")
if err != nil {
t.Error(err)
}
defer os.RemoveAll(tmpdir)
var i int
var n int = 10
timeout := time.After(time.Duration(n) * time.Second)
for {
select {
case <-timeout:
//fmt.Printf("Fps: %d\n", i/n)
return
default:
i += 1
img, err := video.Read()
if err != nil {
t.Error(err)
}
file, err := os.Create(filepath.Join(tmpdir, fmt.Sprintf("%03d.jpg", i)))
if err != nil {
t.Error(err)
}
err = jpeg.Encode(file, img, &jpeg.Options{Quality: 75})
if err != nil {
t.Error(err)
}
err = file.Close()
if err != nil {
t.Error(err)
}
}
}
}