9 Commits
1.3 ... 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
11 changed files with 362 additions and 65 deletions

View File

@@ -11,17 +11,19 @@ or
### Requirements ### 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 ### 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) - [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.3/cam2ip-1.3-RPi.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.3/cam2ip-1.3-RPi3.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.3/cam2ip-1.3.zip) - [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 ### Installation
@@ -48,6 +50,8 @@ Usage of ./cam2ip:
Camera index Camera index
-nowebgl -nowebgl
Disable WebGL drawing of images (html handler) Disable WebGL drawing of images (html handler)
-rotate int
Rotate image, valid values are 90, 180, 270
-video-file string -video-file string
Use video file instead of camera Use video file instead of camera
``` ```

View File

@@ -12,7 +12,7 @@ import (
const ( const (
name = "cam2ip" name = "cam2ip"
version = "1.3" version = "1.4"
) )
func main() { func main() {
@@ -22,6 +22,7 @@ func main() {
flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds") flag.IntVar(&srv.Delay, "delay", 10, "Delay between frames, in milliseconds")
flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width") flag.Float64Var(&srv.FrameWidth, "width", 640, "Frame width")
flag.Float64Var(&srv.FrameHeight, "height", 480, "Frame height") 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.BoolVar(&srv.NoWebGL, "nowebgl", false, "Disable WebGL drawing of images (html handler)")
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address") 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.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled")
@@ -48,7 +49,7 @@ func main() {
} }
if srv.FileName != "" { if srv.FileName != "" {
vid, err := video.New(srv.FileName) vid, err := video.New(video.Options{srv.FileName, srv.Rotate})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error()) fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1) os.Exit(1)
@@ -56,7 +57,7 @@ func main() {
srv.Reader = vid srv.Reader = vid
} else { } else {
cam, err := camera.New(srv.Index) cam, err := camera.New(camera.Options{srv.Index, srv.Rotate})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error()) fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1) os.Exit(1)

View File

@@ -1,3 +1,5 @@
// +build !cv3
// Package camera. // Package camera.
package camera package camera
@@ -5,64 +7,31 @@ import (
"fmt" "fmt"
"image" "image"
"github.com/disintegration/imaging"
"github.com/lazywei/go-opencv/opencv" "github.com/lazywei/go-opencv/opencv"
) )
// Property identifiers. // Options.
const ( type Options struct {
PropPosMsec = iota Index int
PropPosFrames Rotate int
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. // Camera represents camera.
type Camera struct { type Camera struct {
opts Options
camera *opencv.Capture camera *opencv.Capture
frame *opencv.IplImage
} }
// New returns new Camera for given camera index. // New returns new Camera for given camera index.
func New(index int) (camera *Camera, err error) { func New(opts Options) (camera *Camera, err error) {
camera = &Camera{} camera = &Camera{}
camera.opts = opts
camera.camera = opencv.NewCameraCapture(index) camera.camera = opencv.NewCameraCapture(opts.Index)
if camera.camera == nil { 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 return
@@ -71,8 +40,26 @@ func New(index int) (camera *Camera, err error) {
// Read reads next frame from camera and returns image. // Read reads next frame from camera and returns image.
func (c *Camera) Read() (img image.Image, err error) { func (c *Camera) Read() (img image.Image, err error) {
if c.camera.GrabFrame() { if c.camera.GrabFrame() {
frame := c.camera.RetrieveFrame(1) c.frame = c.camera.RetrieveFrame(1)
img = frame.ToImage()
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 { } else {
err = fmt.Errorf("camera: can not grab frame") err = fmt.Errorf("camera: can not grab frame")
} }
@@ -86,8 +73,8 @@ func (c *Camera) GetProperty(id int) float64 {
} }
// SetProperty sets a camera property. // SetProperty sets a camera property.
func (c *Camera) SetProperty(id int, value float64) int { func (c *Camera) SetProperty(id int, value float64) {
return c.camera.SetProperty(id, value) c.camera.SetProperty(id, value)
} }
// Close closes camera. // Close closes camera.
@@ -97,6 +84,7 @@ func (c *Camera) Close() (err error) {
return return
} }
c.frame.Release()
c.camera.Release() c.camera.Release()
c.camera = nil c.camera = nil
return 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

@@ -1,9 +1,10 @@
// +build !jpeg
// Package encoder. // Package encoder.
package encoder package encoder
import ( import (
"image" "image"
//"image/jpeg"
"io" "io"
jpeg "github.com/antonini/golibjpegturbo" jpeg "github.com/antonini/golibjpegturbo"

30
encoder/encode_jpeg.go Normal file
View File

@@ -0,0 +1,30 @@
// +build jpeg
// Package encoder.
package encoder
import (
"image"
"image/jpeg"
"io"
)
// 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

@@ -2,6 +2,7 @@
CHROOT="/usr/x86_64-pc-linux-gnu-static" CHROOT="/usr/x86_64-pc-linux-gnu-static"
MINGW="/usr/i686-w64-mingw32" MINGW="/usr/i686-w64-mingw32"
MINGW64="/usr/x86_64-w64-mingw32"
RPI="/usr/armv6j-hardfloat-linux-gnueabi" RPI="/usr/armv6j-hardfloat-linux-gnueabi"
RPI3="/usr/armv7a-hardfloat-linux-gnueabi" RPI3="/usr/armv7a-hardfloat-linux-gnueabi"
@@ -11,9 +12,9 @@ LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \ PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \ PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \ 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 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="/usr/bin/i686-w64-mingw32-pkg-config" \
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \ PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$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++" \ 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 -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="/usr/bin/armv6j-hardfloat-linux-gnueabi-pkg-config" \
PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \ PKG_CONFIG_PATH="$RPI/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \ PKG_CONFIG_LIBDIR="$RPI/usr/lib/pkgconfig" \

View File

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

View File

@@ -1,3 +1,5 @@
// +build !cv3
// Package video. // Package video.
package video package video
@@ -5,21 +7,31 @@ import (
"fmt" "fmt"
"image" "image"
"github.com/disintegration/imaging"
"github.com/lazywei/go-opencv/opencv" "github.com/lazywei/go-opencv/opencv"
) )
// Options.
type Options struct {
Filename string
Rotate int
}
// Video represents video. // Video represents video.
type Video struct { type Video struct {
opts Options
video *opencv.Capture video *opencv.Capture
frame *opencv.IplImage
} }
// New returns new Video for given path. // New returns new Video for given path.
func New(filename string) (video *Video, err error) { func New(opts Options) (video *Video, err error) {
video = &Video{} video = &Video{}
video.opts = opts
video.video = opencv.NewFileCapture(filename) video.video = opencv.NewFileCapture(opts.Filename)
if video.video == nil { if video.video == nil {
err = fmt.Errorf("video: can not open video %s", filename) err = fmt.Errorf("video: can not open video %s", opts.Filename)
} }
return return
@@ -28,8 +40,25 @@ func New(filename string) (video *Video, err error) {
// Read reads next frame from video and returns image. // Read reads next frame from video and returns image.
func (v *Video) Read() (img image.Image, err error) { func (v *Video) Read() (img image.Image, err error) {
if v.video.GrabFrame() { if v.video.GrabFrame() {
frame := v.video.RetrieveFrame(1) v.frame = v.video.RetrieveFrame(1)
img = frame.ToImage() 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 { } else {
err = fmt.Errorf("video: can not grab frame") err = fmt.Errorf("video: can not grab frame")
} }
@@ -44,6 +73,7 @@ func (v *Video) Close() (err error) {
return return
} }
v.frame.Release()
v.video.Release() v.video.Release()
v.video = nil v.video = nil
return 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
}