First implementation
This commit is contained in:
81
camera/camera.go
Normal file
81
camera/camera.go
Normal file
@ -0,0 +1,81 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"github.com/cyrilix/robocar-base/mqttdevice"
|
||||
"gocv.io/x/gocv"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type VideoSource interface {
|
||||
Read(m *gocv.Mat) bool
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type OpencvCameraPart struct {
|
||||
vc VideoSource
|
||||
pub mqttdevice.Publisher
|
||||
topic string
|
||||
publishFrequency int
|
||||
muImgBuffered sync.Mutex
|
||||
imgBuffered *gocv.Mat
|
||||
}
|
||||
|
||||
func New(topic string, publisher mqttdevice.Publisher, publishFrequency int, videoProperties map[gocv.VideoCaptureProperties]float64) *OpencvCameraPart {
|
||||
log.Printf("Run camera part")
|
||||
|
||||
vc, err := gocv.OpenVideoCapture(0)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to open video device: %v", err)
|
||||
}
|
||||
for k, v := range videoProperties {
|
||||
vc.Set(k, v)
|
||||
}
|
||||
|
||||
img := gocv.NewMat()
|
||||
o := OpencvCameraPart{
|
||||
vc: vc,
|
||||
pub: publisher,
|
||||
topic: topic,
|
||||
publishFrequency: publishFrequency,
|
||||
imgBuffered: &img,
|
||||
}
|
||||
return &o
|
||||
}
|
||||
|
||||
func (o *OpencvCameraPart) Start() error {
|
||||
log.Printf("start camera")
|
||||
ticker := time.NewTicker(1 * time.Second / time.Duration(o.publishFrequency))
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
go o.publishFrame()
|
||||
<-ticker.C
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OpencvCameraPart) Stop() {
|
||||
log.Print("close video device")
|
||||
if err := o.vc.Close(); err != nil {
|
||||
log.Printf("unexpected error while VideoCapture is closed: %v", err)
|
||||
}
|
||||
if err := o.imgBuffered.Close(); err != nil {
|
||||
log.Printf("unexpected error while VideoCapture is closed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OpencvCameraPart) publishFrame() {
|
||||
o.muImgBuffered.Lock()
|
||||
defer o.muImgBuffered.Unlock()
|
||||
|
||||
o.vc.Read(o.imgBuffered)
|
||||
img, err := gocv.IMEncode(gocv.JPEGFileExt, *o.imgBuffered)
|
||||
if err != nil {
|
||||
log.Printf("unable to convert image to jpeg: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
o.pub.Publish(o.topic, img)
|
||||
}
|
57
camera/camera_test.go
Normal file
57
camera/camera_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/cyrilix/robocar-base/testtools"
|
||||
"gocv.io/x/gocv"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fakeVideoSource struct {
|
||||
io.Closer
|
||||
}
|
||||
|
||||
func (f fakeVideoSource) Read(dest *gocv.Mat) bool {
|
||||
img := gocv.IMRead("testdata/img.jpg", gocv.IMReadUnchanged)
|
||||
if img.Total() == 0 {
|
||||
log.Print("image read is empty")
|
||||
return false
|
||||
}
|
||||
img.CopyTo(dest)
|
||||
return true
|
||||
}
|
||||
|
||||
func TestOpencvCameraPart(t *testing.T) {
|
||||
p := testtools.NewFakePublisher()
|
||||
const topic = "topic/test/camera"
|
||||
imgBuffer := gocv.NewMat()
|
||||
|
||||
part := OpencvCameraPart{
|
||||
vc: fakeVideoSource{},
|
||||
pub: p,
|
||||
topic: topic,
|
||||
publishFrequency: 1000,
|
||||
imgBuffered: &imgBuffer,
|
||||
}
|
||||
|
||||
go part.Start()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
img := p.PublishedEvent(topic)
|
||||
if img == nil {
|
||||
t.Fatalf("event %s has not been published", topic)
|
||||
}
|
||||
content, err := img.ByteSliceValue()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
_, err = jpeg.Decode(bytes.NewReader(content))
|
||||
if err != nil {
|
||||
t.Errorf("image published can't be decoded: %v", err)
|
||||
}
|
||||
}
|
BIN
camera/testdata/img.jpg
vendored
Normal file
BIN
camera/testdata/img.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
Reference in New Issue
Block a user