14 Commits
1.3 ... 1.5

Author SHA1 Message Date
Milan Nikolic
d5870110d9 Add support for native VFW implementation on Windows 2018-10-29 16:27:46 +01:00
Milan Nikolic
72f12931da Update README.md 2018-10-28 16:43:55 +01:00
Milan Nikolic
bb676e4e44 New release 2018-10-28 14:37:20 +01:00
Milan Nikolic
ec60387edf Add support for native V4L implementation on Linux/RPi 2018-10-28 14:23:04 +01:00
Milan Nikolic
0503355005 Use asm implementation of base64 on amd64 2018-10-28 13:44:47 +01:00
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
25 changed files with 1307 additions and 210 deletions

View File

@@ -11,17 +11,36 @@ or
### Requirements
* [OpenCV 2.x](http://opencv.org/)
* [libjpeg-turbo](https://www.libjpeg-turbo.org/) (use `-tags jpeg` to build without `CGo`)
On Linux/RPi native Go [V4L](https://github.com/korandiz/v4l) implementation is used to capture images.
On Windows [Video for Windows (VfW)](https://en.wikipedia.org/wiki/Video_for_Windows) framework is used over win32 API.
### Installation
go get -u github.com/gen2brain/cam2ip
### Build tags
* `cv2` - build with `OpenCV` 2.x ([go-opencv](https://github.com/lazywei/go-opencv))
* `cv3` - build with `OpenCV` 3.x ([gocv](https://github.com/hybridgroup/gocv))
* `jpeg` - build with native Go `image/jpeg` instead of `libjpeg-turbo`
### Download
Binaries are compiled with static OpenCV library:
Binaries are compiled with static OpenCV/libjpeg-turbo libraries, they should just work:
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.3/cam2ip-1.3-64bit.tar.gz)
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.3/cam2ip-1.3-RPi.tar.gz)
- [RPi3 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.3/cam2ip-1.3-RPi3.tar.gz)
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.3/cam2ip-1.3.zip)
- [Linux 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit.tar.gz)
- [Linux 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit-cv2.tar.gz)
- [RPi 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi.tar.gz)
- [RPi 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi-cv2.tar.gz)
- [RPi 32bit Static](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi-nocgo.tar.gz)
- [RPi3 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi3.tar.gz)
- [RPi3 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-RPi3-cv2.tar.gz)
- [Windows 32bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-32bit.zip)
- [Windows 32bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-32bit-cv2.zip)
- [Windows 64bit](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit.zip)
- [Windows 64bit OpenCV](https://github.com/gen2brain/cam2ip/releases/download/1.5/cam2ip-1.5-64bit-cv2.zip)
### Installation
@@ -48,6 +67,8 @@ Usage of ./cam2ip:
Camera index
-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
```

View File

@@ -7,12 +7,11 @@ import (
"github.com/gen2brain/cam2ip/camera"
"github.com/gen2brain/cam2ip/server"
"github.com/gen2brain/cam2ip/video"
)
const (
name = "cam2ip"
version = "1.3"
version = "1.5"
)
func main() {
@@ -22,10 +21,10 @@ func main() {
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
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
@@ -47,27 +46,14 @@ func main() {
}
}
if srv.FileName != "" {
vid, err := video.New(srv.FileName)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
srv.Reader = vid
} else {
cam, err := camera.New(srv.Index)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
cam.SetProperty(camera.PropFrameWidth, srv.FrameWidth)
cam.SetProperty(camera.PropFrameHeight, srv.FrameHeight)
srv.Reader = cam
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate, srv.FrameWidth, srv.FrameHeight})
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
srv.Reader = cam
defer srv.Reader.Close()
fmt.Fprintf(os.Stderr, "Listening on %s\n", srv.Bind)

80
cam2ip_cv.go Normal file
View File

@@ -0,0 +1,80 @@
// +build cv2,cv3
package main
import (
"flag"
"fmt"
"os"
"github.com/gen2brain/cam2ip/camera"
"github.com/gen2brain/cam2ip/server"
"github.com/gen2brain/cam2ip/video"
)
const (
name = "cam2ip"
version = "1.5"
)
func main() {
srv := server.NewServer()
flag.IntVar(&srv.Index, "index", 0, "Camera index")
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
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
srv.Version = version
var err error
if srv.Htpasswd != "" {
if _, err = os.Stat(srv.Htpasswd); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
}
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.Reader = vid
} else {
cam, err := camera.New(camera.Options{srv.Index, srv.Rotate, srv.FrameWidth, srv.FrameHeight})
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
srv.Reader = cam
}
defer srv.Reader.Close()
fmt.Fprintf(os.Stderr, "Listening on %s\n", srv.Bind)
err = srv.ListenAndServe()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
}

View File

@@ -1,103 +0,0 @@
// Package camera.
package camera
import (
"fmt"
"image"
"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
)
// Camera represents camera.
type Camera struct {
camera *opencv.Capture
}
// New returns new Camera for given camera index.
func New(index int) (camera *Camera, err error) {
camera = &Camera{}
camera.camera = opencv.NewCameraCapture(index)
if camera.camera == nil {
err = fmt.Errorf("camera: can not open camera %d", index)
}
return
}
// 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()
} else {
err = fmt.Errorf("camera: can not grab frame")
}
return
}
// GetProperty returns the specified camera property.
func (c *Camera) GetProperty(id int) float64 {
return c.camera.GetProperty(id)
}
// SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) int {
return c.camera.SetProperty(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.camera.Release()
c.camera = nil
return
}

46
camera/camera_const_cv.go Normal file
View File

@@ -0,0 +1,46 @@
// +build cv2 cv3
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
)

View File

@@ -0,0 +1,21 @@
// +build !cv2,!cv3
package camera
import (
"github.com/korandiz/v4l"
)
// Property identifiers.
const (
PropBrightness = v4l.CtrlBrightness
PropContrast = v4l.CtrlContrast
PropSaturation = v4l.CtrlSaturation
PropHue = v4l.CtrlHue
PropGain = v4l.CtrlGain
PropExposure = v4l.CtrlExposure
PropWhiteBalanceU = v4l.CtrlWhiteBalance
PropSharpness = v4l.CtrlSharpness
PropWhiteBalanceV = v4l.CtrlDoWhiteBalance
PropBacklight = v4l.CtrlBacklightCompensation
)

96
camera/camera_cv2.go Normal file
View File

@@ -0,0 +1,96 @@
// +build cv2,!cv3
// Package camera.
package camera
import (
"fmt"
"image"
"github.com/disintegration/imaging"
"github.com/lazywei/go-opencv/opencv"
)
// Options.
type Options struct {
Index int
Rotate int
Width float64
Height float64
}
// Camera represents camera.
type Camera struct {
opts Options
camera *opencv.Capture
frame *opencv.IplImage
}
// New returns new Camera for given camera index.
func New(opts Options) (camera *Camera, err error) {
camera = &Camera{}
camera.opts = opts
camera.camera = opencv.NewCameraCapture(opts.Index)
if camera.camera == nil {
err = fmt.Errorf("camera: can not open camera %d", opts.Index)
}
camera.SetProperty(PropFrameWidth, opts.Width)
camera.SetProperty(PropFrameHeight, opts.Height)
return
}
// Read reads next frame from camera and returns image.
func (c *Camera) Read() (img image.Image, err error) {
if c.camera.GrabFrame() {
c.frame = c.camera.RetrieveFrame(1)
if c.frame == nil {
err = fmt.Errorf("camera: can not retrieve 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")
}
return
}
// GetProperty returns the specified camera property.
func (c *Camera) GetProperty(id int) float64 {
return c.camera.GetProperty(id)
}
// SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) {
c.camera.SetProperty(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.Release()
c.camera.Release()
c.camera = nil
return
}

104
camera/camera_cv3.go Normal file
View File

@@ -0,0 +1,104 @@
// +build cv3,!cv2
// Package camera.
package camera
import (
"fmt"
"image"
"github.com/disintegration/imaging"
"gocv.io/x/gocv"
)
// Options.
type Options struct {
Index int
Rotate int
Width float64
Height float64
}
// 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())
}
camera.SetProperty(PropFrameWidth, opts.Width)
camera.SetProperty(PropFrameHeight, opts.Height)
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 retrieve 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
}

132
camera/camera_linux.go Normal file
View File

@@ -0,0 +1,132 @@
// +build !cv2,!cv3
// Package camera.
package camera
import (
"fmt"
"image"
"github.com/disintegration/imaging"
"github.com/korandiz/v4l"
"github.com/korandiz/v4l/fmt/mjpeg"
im "github.com/gen2brain/cam2ip/image"
)
// Options.
type Options struct {
Index int
Rotate int
Width float64
Height float64
}
// Camera represents camera.
type Camera struct {
opts Options
camera *v4l.Device
}
// New returns new Camera for given camera index.
func New(opts Options) (camera *Camera, err error) {
camera = &Camera{}
camera.opts = opts
devices := v4l.FindDevices()
if len(devices) < opts.Index+1 {
err = fmt.Errorf("camera: no camera at index %d", opts.Index)
return
}
camera.camera, err = v4l.Open(devices[opts.Index].Path)
if err != nil {
err = fmt.Errorf("camera: %s", err.Error())
return
}
if camera.camera == nil {
err = fmt.Errorf("camera: can not open camera %d", opts.Index)
return
}
config, err := camera.camera.GetConfig()
if err != nil {
err = fmt.Errorf("camera: %s", err.Error())
return
}
config.Format = mjpeg.FourCC
config.Width = int(opts.Width)
config.Height = int(opts.Height)
err = camera.camera.SetConfig(config)
if err != nil {
err = fmt.Errorf("camera: %s", err.Error())
return
}
err = camera.camera.TurnOn()
if err != nil {
err = fmt.Errorf("camera: %s", err.Error())
return
}
return
}
// Read reads next frame from camera and returns image.
func (c *Camera) Read() (img image.Image, err error) {
buffer, err := c.camera.Capture()
if err != nil {
err = fmt.Errorf("camera: can not grab frame: %s", err.Error())
return
}
img, err = im.NewDecoder(buffer).Decode()
if err != nil {
err = fmt.Errorf("camera: %s", err.Error())
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 {
ret, _ := c.camera.GetControl(uint32(id))
return float64(ret)
}
// SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) {
c.camera.SetControl(uint32(id), int32(value))
}
// Close closes camera.
func (c *Camera) Close() (err error) {
if c.camera == nil {
err = fmt.Errorf("camera: camera is not opened")
return
}
c.camera.TurnOff()
c.camera.Close()
c.camera = nil
return
}

View File

@@ -11,7 +11,7 @@ import (
)
func TestCamera(t *testing.T) {
camera, err := New(1)
camera, err := New(Options{0, 0, 640, 480})
if err != nil {
t.Fatal(err)
}
@@ -25,18 +25,6 @@ func TestCamera(t *testing.T) {
defer os.RemoveAll(tmpdir)
var width, height float64 = 640, 480
camera.SetProperty(PropFrameWidth, width)
camera.SetProperty(PropFrameHeight, height)
if camera.GetProperty(PropFrameWidth) != width {
t.Error("FrameWidth not correct")
}
if camera.GetProperty(PropFrameHeight) != height {
t.Error("FrameHeight not correct")
}
var i int
var n int = 10

412
camera/camera_windows.go Normal file
View File

@@ -0,0 +1,412 @@
// +build !cv2,!cv3
// Package camera.
package camera
import (
"bytes"
"fmt"
"image"
"syscall"
"unsafe"
"github.com/disintegration/imaging"
)
// Options.
type Options struct {
Index int
Rotate int
Width float64
Height float64
}
// Camera represents camera.
type Camera struct {
opts Options
camera syscall.Handle
frame *image.RGBA
hdr *videoHdr
instance syscall.Handle
className string
}
// New returns new Camera for given camera index.
func New(opts Options) (camera *Camera, err error) {
camera = &Camera{}
camera.opts = opts
camera.className = "capWindowClass"
camera.instance, err = getModuleHandle()
if err != nil {
return
}
camera.frame = image.NewRGBA(image.Rect(0, 0, int(camera.opts.Width), int(camera.opts.Height)))
go func(c *Camera) {
fn := func(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
switch msg {
case wmClose:
destroyWindow(hwnd)
case wmDestroy:
postQuitMessage(0)
default:
ret := defWindowProc(hwnd, msg, wparam, lparam)
return ret
}
return 0
}
err = registerClass(c.className, c.instance, fn)
if err != nil {
return
}
hwnd, err := createWindow(0, c.className, "", wsOverlappedWindow, cwUseDefault, cwUseDefault, int64(c.opts.Width)+100, int64(c.opts.Height)+100, 0, 0, c.instance)
if err != nil {
return
}
c.camera, err = capCreateCaptureWindow("", wsChild, 0, 0, int64(c.opts.Width), int64(c.opts.Height), hwnd, 0)
if err != nil {
return
}
ret := sendMessage(c.camera, wmCapDriverConnect, uintptr(c.opts.Index), 0)
if bool(int(ret) == 0) {
err = fmt.Errorf("camera: can not open camera %d", c.opts.Index)
return
}
var bi bitmapInfo
size := sendMessage(c.camera, wmCapGetVideoformat, 0, 0)
sendMessage(c.camera, wmCapGetVideoformat, size, uintptr(unsafe.Pointer(&bi)))
bi.BmiHeader.BiWidth = int32(c.opts.Width)
bi.BmiHeader.BiHeight = int32(c.opts.Height)
ret = sendMessage(c.camera, wmCapSetVideoformat, size, uintptr(unsafe.Pointer(&bi)))
if bool(int(ret) == 0) {
err = fmt.Errorf("camera: can not set video format")
return
}
sendMessage(c.camera, wmCapSetCallbackFrame, 0, syscall.NewCallback(c.callback))
messageLoop(c.camera)
}(camera)
return
}
// Read reads next frame from camera and returns image.
func (c *Camera) Read() (img image.Image, err error) {
ret := sendMessage(c.camera, wmCapGrabFrameNoStop, 0, 0)
if bool(int(ret) == 0) {
err = fmt.Errorf("camera: can not grab frame")
return
}
data := (*[1 << 24]uint8)(unsafe.Pointer(c.hdr.LpData))[0:c.hdr.DwBytesUsed]
r := bytes.NewReader(data)
width := int(c.opts.Width)
height := int(c.opts.Height)
// Taken from https://github.com/hotei/bmp/blob/master/bmpRGBA.go#L12
// There are 3 bytes per pixel, and each row is 4-byte aligned.
b := make([]byte, (3*width+3)&^3)
// BMP images are stored bottom-up rather than top-down.
for y := height - 1; y >= 0; y-- {
_, err = r.Read(b)
if err != nil {
err = fmt.Errorf("camera: can not retrieve frame: %v", err)
return
}
p := c.frame.Pix[y*c.frame.Stride : y*c.frame.Stride+width*4]
for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
// BMP images are stored in BGR order rather than RGB order.
p[i+0] = b[j+2]
p[i+1] = b[j+1]
p[i+2] = b[j+0]
p[i+3] = 0xFF
}
}
img = c.frame
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 0
}
// SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) {
return
}
// Close closes camera.
func (c *Camera) Close() (err error) {
sendMessage(c.camera, wmCapSetCallbackFrame, 0, 0)
unregisterClass(c.className, c.instance)
sendMessage(c.camera, wmCapDriverDisconnect, 0, 0)
destroyWindow(c.camera)
return
}
// callback function.
func (c *Camera) callback(hwvd syscall.Handle, hdr *videoHdr) uintptr {
c.hdr = hdr
return 0
}
var (
user32 = syscall.NewLazyDLL("user32.dll")
kernel32 = syscall.NewLazyDLL("kernel32.dll")
avicap32 = syscall.NewLazyDLL("avicap32.dll")
createWindowExW = user32.NewProc("CreateWindowExW")
destroyWindowW = user32.NewProc("DestroyWindow")
defWindowProcW = user32.NewProc("DefWindowProcW")
dispatchMessageW = user32.NewProc("DispatchMessageW")
translateMessageW = user32.NewProc("TranslateMessage")
getMessageW = user32.NewProc("GetMessageW")
sendMessageW = user32.NewProc("SendMessageW")
postQuitMessageW = user32.NewProc("PostQuitMessage")
registerClassExW = user32.NewProc("RegisterClassExW")
unregisterClassW = user32.NewProc("UnregisterClassW")
getModuleHandleW = kernel32.NewProc("GetModuleHandleW")
capCreateCaptureWindowW = avicap32.NewProc("capCreateCaptureWindowW")
)
const (
wmDestroy = 0x0002
wmClose = 0x0010
wmUser = 0x0400
wmCapStart = wmUser
wmCapSetCallbackFrame = wmCapStart + 5
wmCapDriverConnect = wmCapStart + 10
wmCapDriverDisconnect = wmCapStart + 11
wmCapGetVideoformat = wmCapStart + 44
wmCapSetVideoformat = wmCapStart + 45
wmCapGrabFrame = wmCapStart + 60
wmCapGrabFrameNoStop = wmCapStart + 61
wmCapStop = wmCapStart + 68
wmCapAbort = wmCapStart + 69
wsChild = 0x40000000
wsOverlappedWindow = 0x00CF0000
cwUseDefault = 0x7fffffff
)
// wndClassExW https://msdn.microsoft.com/en-us/library/windows/desktop/ms633577.aspx
type wndClassExW struct {
size uint32
style uint32
wndProc uintptr
clsExtra int32
wndExtra int32
instance syscall.Handle
icon syscall.Handle
cursor syscall.Handle
background syscall.Handle
menuName *uint16
className *uint16
iconSm syscall.Handle
}
// msgW https://msdn.microsoft.com/en-us/library/windows/desktop/ms644958.aspx
type msgW struct {
hwnd syscall.Handle
message uint32
wParam uintptr
lParam uintptr
time uint32
pt pointW
}
// https://msdn.microsoft.com/en-us/ecb0f0e1-90c2-48ab-a069-552262b49c7c
type pointW struct {
x, y int32
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx
type bitmapInfoHeader struct {
BiSize uint32
BiWidth int32
BiHeight int32
BiPlanes uint16
BiBitCount uint16
BiCompression uint32
BiSizeImage uint32
BiXPelsPerMeter int32
BiYPelsPerMeter int32
BiClrUsed uint32
BiClrImportant uint32
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd183375.aspx
type bitmapInfo struct {
BmiHeader bitmapInfoHeader
BmiColors *rgbQuad
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162938.aspx
type rgbQuad struct {
RgbBlue byte
RgbGreen byte
RgbRed byte
RgbReserved byte
}
// https://docs.microsoft.com/en-us/windows/desktop/api/vfw/ns-vfw-videohdr_tag
type videoHdr struct {
LpData *uint8
DwBufferLength uint32
DwBytesUsed uint32
DwTimeCaptured uint32
DwUser uint64
DwFlags uint32
DwReserved [4]uint64
}
// https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-getmodulehandlea
func getModuleHandle() (syscall.Handle, error) {
ret, _, err := getModuleHandleW.Call(uintptr(0))
if ret == 0 {
return 0, err
}
return syscall.Handle(ret), nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-createwindowexw
func createWindow(exStyle uint64, className, windowName string, style uint64, x, y, width, height int64,
parent, menu, instance syscall.Handle) (syscall.Handle, error) {
ret, _, err := createWindowExW.Call(uintptr(exStyle), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(windowName))), uintptr(style), uintptr(x), uintptr(y),
uintptr(width), uintptr(height), uintptr(parent), uintptr(menu), uintptr(instance), uintptr(0))
if ret == 0 {
return 0, err
}
return syscall.Handle(ret), nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-destroywindow
func destroyWindow(hwnd syscall.Handle) error {
ret, _, err := destroyWindowW.Call(uintptr(hwnd))
if ret == 0 {
return err
}
return nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-defwindowprocw
func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), uintptr(wparam), uintptr(lparam))
return uintptr(ret)
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-dispatchmessagew
func dispatchMessage(msg *msgW) {
dispatchMessageW.Call(uintptr(unsafe.Pointer(msg)))
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-translatemessage
func translateMessage(msg *msgW) {
translateMessageW.Call(uintptr(unsafe.Pointer(msg)))
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getmessagew
func getMessage(msg *msgW, hwnd syscall.Handle, msgFilterMin, msgFilterMax uint32) (bool, error) {
ret, _, err := getMessageW.Call(uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgFilterMin), uintptr(msgFilterMax))
if int32(ret) == -1 {
return false, err
}
return int32(ret) != 0, nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-sendmessage
func sendMessage(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
ret, _, _ := sendMessageW.Call(uintptr(hwnd), uintptr(msg), wparam, lparam, 0, 0)
return ret
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-postquitmessage
func postQuitMessage(exitCode int32) {
postQuitMessageW.Call(uintptr(exitCode))
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registerclassexw
func registerClass(className string, instance syscall.Handle, fn interface{}) error {
var wcx wndClassExW
wcx.size = uint32(unsafe.Sizeof(wcx))
wcx.wndProc = syscall.NewCallback(fn)
wcx.instance = instance
wcx.className = syscall.StringToUTF16Ptr(className)
ret, _, err := registerClassExW.Call(uintptr(unsafe.Pointer(&wcx)))
if ret == 0 {
return err
}
return nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-unregisterclassw
func unregisterClass(className string, instance syscall.Handle) bool {
ret, _, _ := unregisterClassW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))), uintptr(instance))
return ret != 0
}
// https://docs.microsoft.com/en-us/windows/desktop/api/vfw/nf-vfw-capcreatecapturewindoww
func capCreateCaptureWindow(lpszWindowName string, dwStyle, x, y, width, height int64, parent syscall.Handle, id int64) (syscall.Handle, error) {
ret, _, err := capCreateCaptureWindowW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpszWindowName))),
uintptr(dwStyle), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(parent), uintptr(id))
if ret == 0 {
return 0, err
}
return syscall.Handle(ret), nil
}
// messageLoop function
func messageLoop(hwnd syscall.Handle) {
for {
msg := &msgW{}
ok, _ := getMessage(msg, 0, 0, 0)
if ok {
translateMessage(msg)
dispatchMessage(msg)
} else {
break
}
}
return
}

View File

@@ -4,7 +4,7 @@ import (
"log"
"net/http"
"github.com/gen2brain/cam2ip/encoder"
"github.com/gen2brain/cam2ip/image"
"github.com/gen2brain/cam2ip/reader"
)
@@ -35,7 +35,7 @@ func (j *JPEG) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
err = encoder.New(w).Encode(img)
err = image.NewEncoder(w).Encode(img)
if err != nil {
log.Printf("jpeg: encode: %v", err)
return

View File

@@ -9,7 +9,7 @@ import (
"net/textproto"
"time"
"github.com/gen2brain/cam2ip/encoder"
"github.com/gen2brain/cam2ip/image"
"github.com/gen2brain/cam2ip/reader"
)
@@ -62,7 +62,7 @@ loop:
continue
}
err = encoder.New(partWriter).Encode(img)
err = image.NewEncoder(partWriter).Encode(img)
if err != nil {
log.Printf("mjpeg: encode: %v", err)
continue

View File

@@ -1,3 +1,5 @@
// +build !amd64
package handlers
import (
@@ -8,7 +10,7 @@ import (
"golang.org/x/net/websocket"
"github.com/gen2brain/cam2ip/encoder"
"github.com/gen2brain/cam2ip/image"
"github.com/gen2brain/cam2ip/reader"
)
@@ -35,7 +37,7 @@ func (s *Socket) write(ws *websocket.Conn) {
w := new(bytes.Buffer)
err = encoder.New(w).Encode(img)
err = image.NewEncoder(w).Encode(img)
if err != nil {
log.Printf("socket: encode: %v", err)
continue

53
handlers/socket_amd64.go Normal file
View File

@@ -0,0 +1,53 @@
package handlers
import (
"bytes"
"log"
"time"
"golang.org/x/net/websocket"
"goost.org/encoding/base64"
"github.com/gen2brain/cam2ip/image"
"github.com/gen2brain/cam2ip/reader"
)
// Socket handler.
type Socket struct {
reader reader.ImageReader
delay int
}
// NewSocket returns new socket handler.
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.reader.Read()
if err != nil {
log.Printf("socket: read: %v", err)
break
}
w := new(bytes.Buffer)
err = image.NewEncoder(w).Encode(img)
if err != nil {
log.Printf("socket: encode: %v", err)
continue
}
b64 := base64.StdEncoding.EncodeToString(w.Bytes())
_, err = ws.Write([]byte(b64))
if err != nil {
break
}
time.Sleep(time.Duration(s.delay) * time.Millisecond)
}
}

26
image/decode.go Normal file
View File

@@ -0,0 +1,26 @@
// +build !jpeg
// Package image.
package image
import (
"image"
"io"
jpeg "github.com/antonini/golibjpegturbo"
)
// NewDecoder returns a new Decoder.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r}
}
// Decoder struct.
type Decoder struct {
r io.Reader
}
// Decode decodes image from JPEG.
func (d Decoder) Decode() (image.Image, error) {
return jpeg.Decode(d.r)
}

25
image/decode_jpeg.go Normal file
View File

@@ -0,0 +1,25 @@
// +build jpeg
// Package image.
package image
import (
"image"
"image/jpeg"
"io"
)
// NewDecoder returns a new Decoder.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r}
}
// Decoder struct.
type Decoder struct {
r io.Reader
}
// Decode decodes image from JPEG.
func (d Decoder) Decode() (image.Image, error) {
return jpeg.Decode(d.r)
}

View File

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

30
image/encode_jpeg.go Normal file
View File

@@ -0,0 +1,30 @@
// +build jpeg
// Package image.
package image
import (
"image"
"image/jpeg"
"io"
)
// NewEncoder returns a new Encoder.
func NewEncoder(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

@@ -2,6 +2,7 @@
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"
RPI3="/usr/armv7a-hardfloat-linux-gnueabi"
@@ -11,8 +12,15 @@ 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_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v -x -o build/cam2ip.linux.amd64 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
CGO_CFLAGS="-I$CHROOT/usr/include" \
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags cv2 -o build/cam2ip.linux.amd64.cv2 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip
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 -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" \
@@ -20,7 +28,31 @@ PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
CGO_LDFLAGS="-L$MINGW/usr/lib" \
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
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -tags cv2 -o build/cam2ip.386.cv2.exe -ldflags "-linkmode external -s -w '-extldflags=-static'" 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" \
CGO_LDFLAGS="-L$MINGW/usr/lib" \
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 -o build/cam2ip.386.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 -tags cv2 -o build/cam2ip.amd64.cv2.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 -o build/cam2ip.amd64.exe -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" \
@@ -28,7 +60,23 @@ PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \
CGO_LDFLAGS="-L$RPI/usr/lib" \
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
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -tags cv2 -o build/cam2ip.linux.arm.cv2 -ldflags "-linkmode external -s -w" 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" \
CGO_LDFLAGS="-L$RPI/usr/lib" \
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 -o build/cam2ip.linux.arm -ldflags "-linkmode external -s -w" 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" \
CGO_LDFLAGS="-L$RPI/usr/lib" \
CGO_CFLAGS="-I$RPI/usr/include" \
CC="armv6j-hardfloat-linux-gnueabi-gcc" CXX="armv6j-hardfloat-linux-gnueabi-g++" \
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -tags jpeg -o build/cam2ip.linux.arm.nocgo -ldflags "-s -w" github.com/gen2brain/cam2ip
PKG_CONFIG="/usr/bin/armv7a-hardfloat-linux-gnueabi-pkg-config" \
PKG_CONFIG_PATH="$RPI3/usr/lib/pkgconfig" \
@@ -36,4 +84,12 @@ 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
CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -tags cv2 -o build/cam2ip.linux.arm7.cv2 -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 -o build/cam2ip.linux.arm7 -ldflags "-linkmode external -s -w" github.com/gen2brain/cam2ip

View File

@@ -26,6 +26,8 @@ type Server struct {
FrameWidth float64
FrameHeight float64
Rotate int
NoWebGL bool
FileName string

View File

@@ -1,50 +0,0 @@
// Package video.
package video
import (
"fmt"
"image"
"github.com/lazywei/go-opencv/opencv"
)
// Video represents video.
type Video struct {
video *opencv.Capture
}
// New returns new Video for given path.
func New(filename string) (video *Video, err error) {
video = &Video{}
video.video = opencv.NewFileCapture(filename)
if video.video == nil {
err = fmt.Errorf("video: can not open video %s", filename)
}
return
}
// Read reads next frame from video and returns image.
func (v *Video) Read() (img image.Image, err error) {
if v.video.GrabFrame() {
frame := v.video.RetrieveFrame(1)
img = frame.ToImage()
} 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.video.Release()
v.video = nil
return
}

80
video/video_cv2.go Normal file
View File

@@ -0,0 +1,80 @@
// +build cv2,!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 retrieve 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,!cv2
// 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 retrieve 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
}

View File

@@ -11,7 +11,7 @@ import (
)
func TestVideo(t *testing.T) {
video, err := New("test.mp4")
video, err := New(video.Options{"test.mp4", 0})
if err != nil {
t.Fatal(err)
}