2017/07/31

用 socket 將 OpenCV 影像傳送到遠端 client

camera 影像以 socket 傳送到 client 的測試,目前是用 socket,將來還要改成用 websocket 處理,用以接受多個 client 連線的問題。

numpy

numpy 可以快速地將 bytearray 及 martix 進行轉換,透過這邊的程式碼,我們可以了解到,圖片就是一個二維陣列的矩陣,矩陣中每一個點,代表圖片中的一個像素點,而 OpenCV 是以 BGR 的形式儲存像素點的資料。

#!/usr/bin/python
# coding=utf-8

import cv2
import numpy
import os
import time

# 亂數產生 120000 個 bytes, 轉換為 numpy array
randomByteArray = bytearray(os.urandom(120000))
flatNumpyArray = numpy.array(randomByteArray)

# reshape 成 300x400 的矩陣並存成 gray scale 圖片
grayImage = flatNumpyArray.reshape(300,400)
cv2.imwrite('RandomGray.png', grayImage)

# reshape 成 400x100 的矩陣並存成 BGR 圖片
bgrImage = flatNumpyArray.reshape(100,400, 3)
cv2.imwrite('RandomBGR.png', bgrImage)

camera 影像以 socket 傳送到 client

  • 版本1

server.py 等待 client.py 連接,server 接收 client 的 camera 資料

server.py

import socket
import cv2
import numpy

def recvall(sock, count):
    buf = b''
    while count:
        newbuf = sock.recv(count)
        if not newbuf: return None
        buf += newbuf
        count -= len(newbuf)
    return buf

TCP_IP = "192.168.1.152"
TCP_PORT = 8002
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(True)
conn, addr = s.accept()
while 1:
    length = recvall(conn,16)
    stringData = recvall(conn, int(length))
    data = numpy.fromstring(stringData, dtype='uint8')
    decimg=cv2.imdecode(data,1)
    cv2.imshow('SERVER',decimg)
    cv2.waitKey(30)

s.close()
cv2.destroyAllWindows()

client.py

import socket
import cv2
import numpy

TCP_IP = "192.168.1.152"
TCP_PORT = 8002

sock = socket.socket()
capture = cv2.VideoCapture(0)
ret, frame = capture.read()
sock.connect((TCP_IP, TCP_PORT))
encode_param=[int(cv2.IMWRITE_JPEG_QUALITY),90]
while ret:
    result, imgencode = cv2.imencode('.jpg', frame, encode_param)
    data = numpy.array(imgencode)
    stringData = data.tostring()
    sock.send( str(len(stringData)).ljust(16));
    sock.send( stringData );

    ret, frame = capture.read()
    decimg=cv2.imdecode(data,1)
    cv2.imshow('CLIENT',decimg)
    cv2.waitKey(30)

sock.close()
cv2.destroyAllWindows()
  • 版本2

server.py 等待 client.py 連接,client 接收 server 的 camera 資料,顯示在畫面上

server2.py

import socket
import cv2
import numpy

capture = cv2.VideoCapture(0)

TCP_IP = "192.168.1.159"
TCP_PORT = 8002
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(True)

conn, addr = s.accept()

ret, frame = capture.read()
encode_param=[int(cv2.IMWRITE_JPEG_QUALITY),90]

while ret:
    result, imgencode = cv2.imencode('.jpg', frame, encode_param)
    data = numpy.array(imgencode)
    stringData = data.tostring()

    conn.send( str(len(stringData)).ljust(16));
    conn.send( stringData );

    ret, frame = capture.read()
    decimg=cv2.imdecode(data,1)
    cv2.imshow('SERVER2',decimg)
    cv2.waitKey(30)

conn.close()
cv2.destroyAllWindows()

client2.py

import socket
import cv2
import numpy

def recvall(sock, count):
    buf = b''
    while count:
        newbuf = sock.recv(count)
        if not newbuf: return None
        buf += newbuf
        count -= len(newbuf)
    return buf

TCP_IP = "192.168.1.159"
TCP_PORT = 8002

sock = socket.socket()
sock.connect((TCP_IP, TCP_PORT))

while 1:
    length = recvall(sock,16)
    stringData = recvall(sock, int(length))
    data = numpy.fromstring(stringData, dtype='uint8')
    decimg=cv2.imdecode(data,1)
    cv2.imshow('CLIENT2',decimg)
    cv2.waitKey(1)

sock.close()
cv2.destroyAllWindows()

改用 wxPython 作為 GUI

wxWidget 是一個開放原始碼且跨平台的物件工具集(widget toolkit),可用來建立基本的圖形使用者介面,先前測試時,都是以 cv2.imshow 進行畫面 preview,未來為了要製作更複雜的 GUI,所以先測試將畫面改為利用 wxPython 處理。

# mac 上要安裝 py27-wxpython
sudo port install py27-wxpython-3.0
# -*- coding: utf-8 -*-
import wx
import cv2
import time

class TestOpenCV ( wx.Frame ):
    windowWidth = 500
    windowHeight = 320

    def __init__( self, parent=None ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"單一視訊畫面", pos = wx.DefaultPosition, size = wx.Size( self.windowWidth, self.windowHeight), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )

        bSizer1 = wx.BoxSizer( wx.VERTICAL )

        self.stbmp1 = wx.StaticBitmap( self, wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer1.Add( self.stbmp1, 1, wx.ALL|wx.EXPAND, 5 )
##        self.stbmp1.SetBitmap(wx.Bitmap( u"../image/heats1-f1-02_gray_pressed.png", wx.BITMAP_TYPE_ANY ))

        self.SetSizer( bSizer1 )
        self.Layout()

        self.Centre( wx.BOTH )

    def __del__( self ):
        pass

    def scale_bitmap(self, bitmap, width, height):
        image = wx.ImageFromBitmap(bitmap)
        image = image.Scale(width, height, wx.IMAGE_QUALITY_NORMAL)
        newimg = wx.BitmapFromImage(image)
        return newimg

    def updateImage(self, bitmap):
        # 縮小圖片符合視窗的大小
        newbitmap = self.scale_bitmap(bitmap, self.windowWidth-10, self.windowHeight-30)
        self.stbmp1.SetBitmap(newbitmap)

class App(wx.App):
    """Application class."""

    def OnInit(self):
        self.frame = TestOpenCV()
        self.frame.Show()
        self.SetTopWindow(self.frame)
        self.run()
        return True

    def rot90(self, img, angle):
        if(angle == 270 or angle == -90):
            img = cv2.transpose(img)
            img = cv2.flip(img, 0)  # transpose+flip(0)=CCW
        elif (angle == 180 or angle == -180):
            img = cv2.flip(img, -1)  # transpose+flip(-1)=180
        elif (angle == 90 or angle == -270):
            img = cv2.transpose(img)
            img = cv2.flip(img, 1)  # transpose+flip(1)=CW
        elif (angle == 360 or angle == 0 or angle == -360):
            pass
        else :
            raise Exception("Unknown rotation angle({})".format(angle))
        return img

    def run(self):

        cap = cv2.VideoCapture(0);

        while True:
            ret, frame = cap.read()

            if ret == True:
                # 畫面旋轉 90度
                srcBGR = self.rot90(frame, -90)

                # wxPython 只能處理 RGB 的圖片,要從 BGR 轉 RGB
                srcRGB = cv2.cvtColor(srcBGR, cv2.COLOR_BGR2RGB)

                #print dst.shape  w=720, h=1280
                w, h = srcRGB.shape[:2]

                #dst = cv2.resize(srcRGB, (h/2,w/2), interpolation = cv2.INTER_AREA)
                #wxImage = wx.ImageFromBuffer(h/2, w/2, dst)
                wxImage = wx.ImageFromBuffer(h, w, srcRGB)
                bitmap = wx.BitmapFromImage(wxImage)

                # 更新 視窗上的圖片
                self.frame.updateImage(bitmap)

                #cv2.imshow('frame', dst)
                #if cv2.waitKey(30) & 0xFF == ord('q'):
                #    break

                # sleep 30ms
                time.sleep(0.03)

            else:
                break

        cap.release()
        cv2.destroyAllWindows()


def main():
    app = App()
    app.MainLoop()

if __name__ == '__main__':
    main()

4 則留言:

  1. 請問在虛擬區網路或外網時,嚴重的延遲該怎麼解決

    回覆刪除
  2. 這是用圖片傳遞,資料量大,網路傳輸當然會延遲,基本上是無解,要解決的話,必須要做影像壓縮。

    回覆刪除
  3. 請問後面需要";"分號嗎?
    conn.send( str(len(stringData)).ljust(16));
    conn.send( stringData );

    回覆刪除
    回覆
    1. python 可以在句末加上分號,也可以不寫

      刪除