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()

2017/07/24

安裝 OpenCV 3

OpenCV Open Source Computer Vision Library 是一個跨平台的電腦視覺庫,由 Intel 發起並參與開發,由於 Intel 為了推動需要更高速運算的應用,增加硬體的銷售,因此發展了這個機器視覺的運算函式庫,以BSD授權條款授權發行,可以在商業和研究領域中免費使用。OpenCV可用於開發 real time 的圖像處理、電腦視覺以及特徵識別程式。

OpenCV 雖然是以 C++ 寫成,但同時提供了 python 及 java 的 bindings,也因為 python,我們可以用更短的程式碼就完成一些基本的視覺應用,以下紀錄如何在 RPi 3 以及 MacOS 中安裝 OpenCV。

Raspberry Pi 3

RPi 的 camera 是使用 CSI 介面,參考 Raspberry Pi相機模組開箱文 將 RPi 的 camera 裝好。

以 raspi-config 指令 enable camera,然後 reboot。

sudo raspi-config

vcgencmd 是用來查詢一些系統參數的指令,可以用 vcgencmd 測試 camera 狀態。

$ vcgencmd get_camera
supported=1 detected=1
vcgencmd version
vcgencmd get_mem arm
vcgencmd get_mem gpu

安裝 OpenCV 3,必須先更新 RPi,安裝一些基本的工具及 library

sudo apt-get update
sudo apt-get upgrade
sudo apt-get -y install build-essential cmake pkg-config
sudo apt-get -y install cmake-curses-gui htop swig

# load various image file formats from disk
sudo apt-get -y install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev

#sudo apt-get -y install build-essential cmake cmake-curses-gui pkg-config libpng12-0 libpng12-dev libpng++-dev libpng3 libpnglite-dev zlib1g-dbg zlib1g zlib1g-dev pngtools libtiff5-dev libtiff5 libtiffxx0c2 libtiff-tools libeigen3-dev

#sudo apt-get -y install libjpeg8 libjpeg8-dev libjpeg8-dbg libjpeg-progs swig libv4l-0 libv4l-dev python-numpy

# read various video file formats from disk
sudo apt-get -y install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
sudo apt-get -y install libxvidcore-dev libx264-dev

# for module: highgui
sudo apt-get -y install libgtk2.0-dev

# matrix operations
sudo apt-get -y install libatlas-base-dev gfortran

# python
sudo apt-get -y install python2.7-dev python3-dev python-numpy python3-numpy

sudo apt-get -y install doxygen

如果需要 tesseract,不安裝也沒關係,可以直接用 pytesseract:

sudo apt-get install -y tesseract-ocr libtesseract-dev libleptonica-dev

安裝 python library

wget https://bootstrap.pypa.io/get-pip.py
sudo python get-pip.py

# for numerical processing
sudo pip install numpy

下載並安裝 OpenCV 3.1

wget -O opencv-3.1.0.zip https://github.com/Itseez/opencv/archive/3.1.0.zip

wget -O opencv_contrib-3.1.0.zip https://github.com/opencv/opencv_contrib/archive/3.1.0.zip

unzip opencv-3.1.0.zip
unzip opencv_contrib-3.1.0.zip

因為 Opencv + Tesseract 編譯會發生問題,所以要把 OpenCV 偵測 Tesseract library 的部分關閉,未來直接用 pytesseract 就可以做 OCR,不用透過 OpenCV 呼叫 tesseract。

#修改 /opt/opencv_contrib-3.1.0/modules/text/FindTesseract.cmake

#增加最後一行
set(Tesseract_FOUND 0)

編譯 opencv

cd opencv-3.1.0/
mkdir build
cd build

# setup build with cmake 或是以 ccmake ../ 用介面設定 build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D INSTALL_C_EXAMPLES=OFF \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D OPENCV_EXTRA_MODULES_PATH=~/opencv/opencv_contrib-3.1.0/modules \
    -D BUILD_EXAMPLES=ON ..

如果不直接用 cmake,也可以用 ccmake gui 設定 cmake 的選項

ccmake ../

# 按 c 產生全新設定,決定細項設定後,修改完後,按 c 設定新的選項,然後再按下 g 即可產生編譯用的設定檔案。

如果 cmake 時會出現這個錯誤

CMake Error at samples/gpu/CMakeLists.txt:100 (list):
  list sub-command REMOVE_ITEM requires list to be present.

參考這裡的解法,要將 INSTALLCEXAMPLES=OFF 設定為 OFF。

# 這個步驟會要做很久,大概是 80 分鐘,如果一直發生問題,就改用 make 去掉 -j4,但會需要 3~4 hrs
# 如果 make -j4 出現 error,可以再用 make -j4 或是改用 make -j2 多試幾次看看,如果錯誤沒有出現在同一個地方,,可以這樣繼續編譯
make -j2

也可以用這樣的方式重複 10 次 make

for i in {1..10}; do make -j2; done

或是用 shell script

#!/bin/bash
for i in {1..10}
do
    make -j2
    if [ $? -ne 0 ]; then
        echo "Try again";
    else
        break;
    fi
done
sudo make install
sudo ldconfig

在 python 的 dist-packages 目錄中,看到 cv2.so 就成功了。

ls -l /usr/local/lib/python2.7/dist-packages/

以 python 測試 cv2

$ python
>>> import cv2
>>> cv2.__version__
'3.1.0'

測試拍照及錄影

raspistill -o t.jpg
raspivid -o t.h264

要讓 OpenCV 使用 camera 必須安裝V4L2套件,在 RPi 必須要先載入 video driver for cv video capture method,camera 的程式才會有作用。

可以直接編譯

cd ~/opencv/
wget http://linuxtv.org/downloads/v4l-utils/v4l-utils-1.6.2.tar.bz2
tar xfvj v4l-utils-1.6.2.tar.bz2

sudo apt-get -y install autoconf gettext libtool libjpeg-dev

cd v4l-utils-1.6.2
autoreconf -vfi
./configure
make
sudo make install

或是用這樣的方式安裝

# 增加sources.list
$ sudo vim /etc/apt/sources.list
於sources.list中寫入以下資訊
deb http://www.linux-projects.org/listing/uv4l_repo/raspbian/ wheezy main

# 加入GPG key
$ sudo wget http://www.linux-projects.org/listing/uv4l_repo/lrkey.asc ~/
$ sudo apt-key add ./lrkey.asc

# 再更新一次系統
$ sudo apt-get update && sudo apt-get upgrade

# 安裝V4L2套件
$ sudo apt-get install uv4l uv4l-raspicam
sudo modprobe bcm2835-v4l2

在modules文件中寫入以下資訊,下次開機就會自動載入這個 module

$ sudo vim /etc/modules
bcm2835-v4l2

測試 /dev/video0

v4l2-ctl --list-ctrls --device /dev/video0

Mac OS El Caption

用類似 RPi 的方式編譯,因為 OpenCV 3.2 會遇到 freetype 編譯錯誤,還不知道怎麼解決,目前只能使用 OpenCV 3.1。另外 python 整合的部分也沒有裝好。所以就不用這樣的方式,改用 macport 直接安裝 opencv +python27。

#安裝 macport, xcode

sudo xcodebuild -license
sudo port install cmake +gui

sudo port install libgphoto2
sudo port install jpeg libpng tiff openexr
sudo port install eigen tbb eigen3
sudo port install py27-numpy

unzip opencv-3.1.0.zip
unzip opencv_contrib-3.1.0.zip

cd opencv-3.1.0
mkdir build
cd build

cmake -D CMAKE_BUILD_TYPE=Release \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D OPENCV_EXTRA_MODULES_PATH=~/project/opencv/opencv_contrib-3.1.0/modules \
    -D BUILD_opencv_python2=ON \
    -D BUILD_opencv_python3=OFF \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D INSTALL_C_EXAMPLES=OFF \
    -D BUILD_EXAMPLES=ON \
    -D WITH_EIGEN=OFF \
    -D PYTHON2_PACKAGES_PATH=/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages \
    -D PYTHON2_LIBRARY=/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin \
    -D PYTHON2_INCLUDE_DIR=/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/ \
    ..

make -j4

sudo make install

改用 macport 直接安裝的方式

# 安裝 xcode
xcode-select -install

sudo port selfupdate

sudo port install py27-tkinter
sudo port install cmake +gui
sudo port install py27-scipy

# 設定 python
sudo port install python_select
sudo port slect python python27

# 編譯 opencv
sudo port install opencv +python27 +openni

可以在編譯 opencv 的 Porfile 中查看一些資料,目前是用 3.1.0 版,也已經包含了 opencv_contrib modules,如果真的需要調整編譯的過程,可以設定 macport 的 local repository,複製這個 Portfile,修改編譯的過程。

/opt/local/var/macports/sources/rsync.macports.org/release/tarballs/ports/graphics/opencv/Portfile

測試 1: 打開 1.jpg 顯示在視窗畫面中

test.cpp: 打開 1.jpg 顯示在視窗畫面中

#include <opencv2/opencv.hpp>
using namespace cv;

int main()
{
        Mat img=imread("1.jpg");
        imshow("result",img);
        // 6s後視窗自動關閉
        waitKey(6000);
}

編譯與執行

# 在 mac 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` -stdlib=libc++ test.cpp -o test

# 在 RPi 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` test.cpp -o test

./test

一樣的程式碼,改用 python 寫:test.py

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

import cv2
import time

img = cv2.imread('1.jpg');
cv2.imshow("result",img);
cv2.waitKey()

直接用 python 就可以執行,在 Mac 及 RPi 都一樣。

python test.py

測試 2: 載入圖片, 並用 MORPH_RECT 腐蝕操作

用圖片中暗色的部分,腐蝕高亮的部分。

test2.cpp

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

int main(   )
{
    //載入原圖
    Mat srcImage = imread("1.jpg");
    //顯示原圖
    imshow("source", srcImage);
    //進行腐蝕操作
    Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
    Mat dstImage;
    erode(srcImage, dstImage, element);
    //顯示效果圖
    imshow("result", dstImage);
    waitKey(0);

    return 0;
}

編譯+執行

# 在 mac 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` -stdlib=libc++  test2.cpp -o test2

# 在 RPi 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` test2.cpp -o test2

./test2

test2.py

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

import cv2
import time

# 中文字型
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"SimSun.ttc", size=14)

img = cv2.imread('1.jpg');
# grayscale image
#img = cv2.imread('1.jpg', 0);
#img = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
#腐蝕圖像  
eroded = cv2.erode(img,kernel)

#顯示腐蝕後的圖片
cv2.imshow("result", eroded);
cv2.waitKey(0)
cv2.destroyAllWindows()

編譯+執行

python test2.py

測試 3: blur 影像模糊

test3.cpp

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;

int main( )
{
    Mat srcImage=imread("1.jpg");
    imshow( "source", srcImage );

    Mat dstImage;
    blur( srcImage, dstImage, Size(7, 7));

    imshow( "result" ,dstImage );

    waitKey( 0 );
}

編譯+執行

# 在 mac 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` -stdlib=libc++ test3.cpp -o test3
./test3

# 在 RPi 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` test3.cpp -o test3

./test3

tes3.py

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

import cv2
import time

img = cv2.imread('1.jpg');

blur = cv2.blur(img,(7,7))

cv2.imshow("result", blur);
cv2.waitKey(0)
cv2.destroyAllWindows()

測試 4: canny 邊緣檢測

test4.cpp

#include <opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;

int main( )
{
    Mat srcImage = imread("1.jpg");
    imshow("source", srcImage);     //顯示原始圖
    Mat dstImage,edge,grayImage;    //參數定義

    //建立與src同類別型和大小的矩陣(dst)
    dstImage.create( srcImage.size(), srcImage.type() );

    //將原圖像轉換為灰度圖像
    //此句程式碼的OpenCV2版為:
    //cvtColor( srcImage, grayImage, CV_BGR2GRAY );
    //此句程式碼的OpenCV3版為:
    cvtColor( srcImage, grayImage, COLOR_BGR2GRAY );

    // 使用 3x3核心來降噪
    blur( grayImage, edge, Size(3,3) );

    // 執行Canny算子
    Canny( edge, edge, 3, 9,3 );

    imshow("result", edge);

    waitKey(0);

    return 0;
}

編譯+執行

# 在 mac 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` -stdlib=libc++ test4.cpp -o test4
./test4

# 在 RPi 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` test4.cpp -o test4

./test4

test4.py

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

import cv2
import time

# grayscale image
img = cv2.imread('1.jpg', 0);

# 用高斯平滑處理原圖像降噪
img = cv2.GaussianBlur(img,(3,3),0)
# Canny只能處理 grayscale image, 指定最大和最小閾值,其中apertureSize默認為3
canny = cv2.Canny(img, 3, 9)

cv2.imshow("result", canny);
cv2.waitKey(0)
cv2.destroyAllWindows()

test5: 播放 avi 影片

test5.cpp

#include <opencv2/opencv.hpp>
using namespace cv;

//-----------------------------------【main( )函數】--------------------------------------------
//      描述:控制臺應用程式的入口函數,我們的程式從這里開始
//-------------------------------------------------------------------------------------------------
int main( )
{
    VideoCapture capture("1.avi");

    while(1)
    {
        Mat frame;//定義一個Mat變數, 儲存現在的 frame
        capture>>frame;  //讀取現在的 frame
        if (!frame.empty()) {
            imshow("result",frame);
        } else {
            break;
        }
        waitKey(30);  // delay 30ms
    }
    return 0;
}

編譯+執行

# 在 mac 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` -stdlib=libc++ test5.cpp -o test5
./test5

# 在 RPi 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` test5.cpp -o test5

./test5

test5.py

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

import cv2
import time

cap = cv2.VideoCapture('1.avi');

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

    if ret == True:
        ## grayscale avi
        #gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        #cv2.imshow('frame', gray)

        ## normal avi
        cv2.imshow('frame', frame)

        if cv2.waitKey(30) & 0xFF == ord('q'):
            break

    else:
        break

cap.release()
cv2.destroyAllWindows()

test6: 使用 camera

test6.cpp,只有將 VideoCapture capture(0); 換成 camera index 就可以了。

#include <opencv2/opencv.hpp>
using namespace cv;

int main( )
{
    VideoCapture capture(0);
    while(1)
    {
        Mat frame, dst;
        capture>>frame;

        if (!frame.empty()) {
            imshow("result", frame);
        } else {
            break;
        }
        waitKey(30);  //delay 30ms
    }
    return 0;
}

編譯+執行

# 在 mac 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` -stdlib=libc++ test6.cpp -o test6
./test6

# 在 RPi 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` test6.cpp -o test6

./test6

test6.py

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

import cv2
import time

cap = cv2.VideoCapture(0);

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

    if ret == True:
        ## grayscale avi
        #gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        #cv2.imshow('frame', gray)

        ## normal avi
        cv2.imshow('frame', frame)

        if cv2.waitKey(30) & 0xFF == ord('q'):
            break

    else:
        break

cap.release()
cv2.destroyAllWindows()

test7: 使用 camera 加上畫面旋轉

上面的 test6 在 mac 的 camera 角度不對,畫面必須逆時針旋轉一下,才回變成正面。

#include <opencv2/opencv.hpp>
using namespace cv;

void rotate_90n(cv::Mat const &src, cv::Mat &dst, int angle)
{
     CV_Assert(angle % 90 == 0 && angle <= 360 && angle >= -360);
     if(angle == 270 || angle == -90){
        // Rotate clockwise 270 degrees
        cv::transpose(src, dst);
        cv::flip(dst, dst, 0);
    }else if(angle == 180 || angle == -180){
        // Rotate clockwise 180 degrees
        cv::flip(src, dst, -1);
    }else if(angle == 90 || angle == -270){
        // Rotate clockwise 90 degrees
        cv::transpose(src, dst);
        cv::flip(dst, dst, 1);
    }else if(angle == 360 || angle == 0 || angle == -360){
        if(src.data != dst.data){
            src.copyTo(dst);
        }
    }
}

int main( )
{
    VideoCapture capture(0);
    while(1)
    {
        Mat frame, dst;
        capture>>frame;

        if (!frame.empty()) {

            rotate_90n(frame, dst, -90);
            imshow("result", dst);
        } else {
            break;
        }
        waitKey(30);  //delay 30ms
    }
    return 0;
}

編譯+執行

# 在 mac 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` -stdlib=libc++ test7.cpp -o test7
./test7

# 在 RPi 編譯
g++ -ggdb `pkg-config --cflags --libs opencv` test7.cpp -o test7

./test7

test7.py

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

import cv2
import time

def rot90(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

cap = cv2.VideoCapture(0);

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

    if ret == True:
        ## grayscale avi
        #gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        #cv2.imshow('frame', gray)

        ## normal avi
        dst = rot90(frame, -90)
        cv2.imshow('frame', dst)

        if cv2.waitKey(30) & 0xFF == ord('q'):
            break

    else:
        break

cap.release()
cv2.destroyAllWindows()

References

Install OpenCV-Python in Windows

Computer Vision with Raspberry Pi and the Camera Pi module

使用 vcgencmd 指令查看 Raspberry Pi 的 CPU 溫度、運行速度與電壓等資訊

在 Raspberry Pi 上面安裝 OpenCV 函式庫

安裝 OPENCV 紀錄

Install guide: Raspberry Pi 3 + Raspbian Jessie + OpenCV 3

[翻译]Python 2.7 和 Python 3+ 的OpenCV 3.0 安装教程

[Raspberry Pi] 解決 Raspberry Pi 找不到 /dev/video0

OpenCV on Raspberry Pi - Using Java(6)- 使用 OpenCV 拍攝照片(Camera Module)


Installing OpenCV in Mac OSx tutorial

macOS: Install OpenCV 3 and Python 2.7

Mac下安装OpenCV3.0—包含opencv_contrib模块

Undefined freetype symbols when building openCV 3.2.0


How to compile OpenCV sample code ?

【OpenCV】安裝在Mac及XCode筆記

VideoCapture.open(0) won't recognize pi cam

Rotate image by 90, 180 or 270 degrees

2017/07/17

使用 Tsung 測試 websocket

tsung是erlang開發的多協議分佈式負載測試工具,它能用來壓力測試HTTP, WebDAV, SOAP, PostgreSQL, MySQL, LDAP, webscoket 和 Jabber/XMPP 的 server。

安裝 Tsung

在 mac 安裝 tsung 可使用 macport

sudo port install tsung

安裝成功後,以 -v 測試

$ tsung -v
Tsung version 1.6.0

在這個路徑下面,有一些 perl script,接下來可以利用這些 script 產生報表

$ $ ls -m /opt/local/lib/tsung/bin/
log2tsung.pl, tsung-rrd.pl, tsung_percentile.pl, tsung_stats.pl

範例的路徑

/opt/local/share/doc/tsung/examples

我們可以將 websocket.xml 範例複製出來修改。

cp /opt/local/share/doc/tsung/examples/websocket.xml .

撰寫 test scenario

修改從範例複製的 websocket.xml

<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/opt/local/share/tsung/tsung-1.0.dtd" []>
<tsung loglevel="notice" version="1.0">
  <clients>
    <client host="localhost" use_controller_vm="true" maxusers="1000" />
  </clients>

  <servers>
    <server host="127.0.0.1" port="9000" type="tcp" />
  </servers>

  <load>
    <!-- phase 1: 0.1 usr per second in 10 minutes, max: 1000 -->
    <arrivalphase phase="1" duration="10" unit="minute">
      <users maxnumber="1000" arrivalrate="0.1" unit="second" />
    </arrivalphase>

    <!-- phase 2: 1 new user every second in 10 minutes -->
    <!--
    <arrivalphase phase="2" duration="10" unit="minute">
      <users interarrival="1" unit="second"></users>
    </arrivalphase>
    -->

    <!-- phase 3: 1 new usr every 0.1 second in 10 minutes -->
    <!--
    <arrivalphase phase="3" duration="10" unit="minute">
      <users interarrival="0.1" unit="second"></users>
    </arrivalphase>
    -->

    <!-- phase 4: 10 usrs per second in 10 minutes, the same as phase 3, max: 100 -->
    <!--
    <arrivalphase phase="4" duration="10" unit="minute">
      <users maxnumber="100" arrivalrate="10" unit="second"></users>
    </arrivalphase>
    -->
  </load>

  <options>
    <option name="file_server" id='userdb' value="usr.csv"/>
  </options>

  <sessions>
    <session name="websocket" probability="100" type="ts_websocket">
        <setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
          <var name="usrid"/>
          <var name="pwd"/>
        </setdynvars>

        <request subst="true">
             <websocket type="connect" path="/msgsrv/ws"></websocket>
        </request>

        <thinktime min="2" max="5" random="true"></thinktime>

        <request subst="true">

            <websocket type="message">
{
  "task":1,
  "action":1,
  "serial":"01-01-%%_usrid%%",
  "rdata":{
    "userid":"%%_usrid%%",
    "userpwd":"%%_pwd%%"
  }
}
            </websocket>
        </request>

        <thinktime min="3" max="10" random="true"></thinktime>

        <request>
            <websocket type="message" >
{
  "task":3,
  "action":2,
  "serial":"03-02-%%_usrid%%",
  "rdata":{
    "areaseq":1
  }
}
            </websocket>
        </request>

        <thinktime value="30"></thinktime>

        <request>
            <websocket type="close"></websocket>
        </request>
    </session>
  </sessions>
</tsung>

以下說明每個部分的設定:

load 的部分是新連線產生的速度,下面有四個 phases,分別有不同的連線產生的速度

<load>
    <!-- phase 1: 0.1 usr per second in 10 minutes, max: 1000 -->
    <arrivalphase phase="1" duration="10" unit="minute">
      <users maxnumber="1000" arrivalrate="0.1" unit="second" />
    </arrivalphase>

    <!-- phase 2: 1 new user every second in 10 minutes -->
    <arrivalphase phase="2" duration="10" unit="minute">
      <users interarrival="1" unit="second"></users>
    </arrivalphase>

    <!-- phase 3: 1 new usr every 0.1 second in 10 minutes -->
    <arrivalphase phase="3" duration="10" unit="minute">
      <users interarrival="0.1" unit="second"></users>
    </arrivalphase>

    <!-- phase 4: 10 usrs per second in 10 minutes, the same as phase 3, max: 100 -->
    <arrivalphase phase="4" duration="10" unit="minute">
      <users maxnumber="100" arrivalrate="10" unit="second"></users>
    </arrivalphase>
  </load>

option 是定義一個外部 csv 檔案的 id,給後面參考使用

  <options>
    <option name="file_server" id='userdb' value="usr.csv"/>
  </options>

session 的一開始,先取得 userdb 對應的 csv 檔案,並取得兩欄資料,對應到 usrid 及 pwd 兩個變數。後面在 request 的地方,可以用 %%_usrid%% 參考到 csv 裡面的變數。

<session name="websocket" probability="100" type="ts_websocket">
        <setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter">
          <var name="usrid"/>
          <var name="pwd"/>
        </setdynvars>

        <request subst="true">
             <websocket type="connect" path="/msgsrv/ws"></websocket>
        </request>

        <thinktime min="2" max="5" random="true"></thinktime>

        <request subst="true">

            <websocket type="message">
{
  "task":1,
  "action":1,
  "serial":"01-01-%%_usrid%%",
  "rdata":{
    "userid":"%%_usrid%%",
    "userpwd":"%%_pwd%%"
  }
}
            </websocket>
        </request>

        <thinktime min="3" max="10" random="true"></thinktime>
</session>

usr.csv 裡面就放兩欄,分別是 usrid 及 pwd

u0001;u0001pwd
u0002;u0002pwd
u0003;u0003pwd

進行 tsung 測試

啟動 tsung

tsung -f websocket.xml -l log start

產生的報告會放在 log 目錄下面,一個日期 timestamp的目錄中

tail -f log/20170324-1531/tsung.log

可以用 tsung_stats.pl 產生 html 的 report

mkdir report

cd report

/opt/local/lib/tsung/bin/tsung_stats.pl --stats ../log/20170324-1531/tsung.log chromium graph.html

最後可以看到一些統計報表

References

Install tsung centos 7

installing tsung in centos

CentOS下安裝 Tsung-壓力測試工具

CentOS壓力測試工具Tsung安裝和圖形報表生成

詳解CentOS下Tsung環境的搭建到跑通第一個測試用例

Load Testing using Tsung

tsung的使用筆記

Tsung’s documentation

tsung測試openfire時從CSV文件讀取user信息

2017/07/03

使用 vim Plugins 改造 vim 為開發的 IDE

在使用 linux 一定知道要用 vim,vim 提供了很多 plugins 讓我們可以調整 vim,而一個大型的 programming IDE,核心也是從文字編輯器開始,延伸其他的功能,合併成一個完整的 IDE。

這麼多 plugins,如果要瞭解內容跟細節,還是一個一個慢慢安裝,閱讀文件才會比較清楚該 plugin 能做什麼事情,不過也要花時間慢慢去調整出適合自己的工作環境。

利用 vundle 快速設定 vim

ref: Configuring Vim as an IDE

Vundle是vim plugin的管理工具,利用 vundle 可以快速將很多 plugins 一次安裝起來。

首先將以下文件內容寫入 ~/.vimrc

syntax on

set nocompatible
set smartindent
set shiftwidth=4
set backspace=indent,eol,start
set ruler
set number
set showcmd
set incsearch
set hlsearch
set hls
set ic
set pastetoggle=<F12>
set enc=utf8

set mouse=a

filetype off 

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim



"-------------- PLUGINS STARTS -----------------
call vundle#begin()

Plugin 'VundleVim/Vundle.vim'
Plugin 'vim-airline/vim-airline'
Plugin 'vim-airline/vim-airline-themes'
Plugin 'altercation/vim-colors-solarized'
Plugin 'scrooloose/nerdtree'
Plugin 'jistr/vim-nerdtree-tabs'
Plugin 'scrooloose/syntastic'
Plugin 'xolox/vim-misc'
Plugin 'xolox/vim-easytags'
Plugin 'majutsushi/tagbar'
Plugin 'ctrlpvim/ctrlp.vim'
Plugin 'vim-scripts/a.vim'
Plugin 'airblade/vim-gitgutter'
Plugin 'tpope/vim-fugitive'
Plugin 'Raimondi/delimitMate'
Plugin 'christoomey/vim-tmux-navigator'
Plugin 'jez/vim-c0'
Plugin 'jez/vim-ispc'
Plugin 'kchmck/vim-coffee-script'
Plugin 'flazz/vim-colorschemes'

call vundle#end()  
"-------------- PLUGINS END --------------------
filetype plugin indent on



"----- GENERAL SETTINGS-------
set laststatus=2
let g:airline_powerline_fonts = 1
let g:airline_detect_paste=1
let g:airline#extensions#tabline#enabled = 1
let g:airline_theme='solarized'
set background=dark
let g:solarized_termcolors=256
colorscheme solarized


"---------NERD-TREE SETTINGS----------
nmap <silent> <leader>t :NERDTreeTabsToggle<CR>
let g:nerdtree_tabs_open_on_console_startup = 1


"-------- SYNTASTIC SETTINGS---------
let g:syntastic_error_symbol = '✘'
let g:syntastic_warning_symbol = "▲"

augroup mySyntastic
    au!
    au FileType tex let b:syntastic_mode = "passive"
augroup END


"-------- TAGS SETTINGS --------------------------------
let g:easytags_events = ['BufReadPost', 'BufWritePost']
let g:easytags_async = 1
let g:easytags_dynamic_files = 2
let g:easytags_resolve_links = 1
let g:easytags_suppress_ctags_warning = 1
let g:tagbar_autoclose=2

nmap <silent> <leader>b :TagbarToggle<CR>
"autocmd BufEnter * nested :call tagbar#autoopen(0)

"---------GIT SETTINGS--------------
hi clear SignColumn
let g:airline#extensions#hunks#non_zero_only = 1


"----------DELIMITEMATE SETTINGS-----------------
let delimitMate_expand_cr = 1
augroup mydelimitMate
    au!
    au FileType markdown let b:delimitMate_nesting_quotes = ["`"]
    au FileType tex let b:delimitMate_quotes = ""
    au FileType tex let b:delimitMate_matchpairs = "(:),[:],{:},`:'"
    au FileType python let b:delimitMate_nesting_quotes = ['"', "'"]
augroup END

"-----------TMUX SETTINGS--------------
let g:tmux_navigator_save_on_switch = 2

以 git 安裝 vundle

git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim

在 CentOS 要安裝 ctags

yum install ctags

如果是 Debian 則是

sudo apt-get install exuberant-ctags

安裝字型 Menlo-for-Powerline

git clone https://github.com/abertsch/Menlo-for-Powerline.git

mkdir ~/.fonts
cd Menlo-for-Powerline/
cp "Menlo for Powerline.ttf" ~/.fonts/
fc-cache -vf ~/.fonts

安裝 color solarized

cd ~/.vim/bundle
git clone git://github.com/altercation/vim-colors-solarized.git

最後在 console 執行,花一點時間,就可以安裝 .vimrc 指定的所有 plugins

vim +PluginInstall +qall

透過以上方式,就安裝了

  1. Vundle
  2. vim-airline
  3. vim-airline-themes
  4. vim-colors-solarized
  5. nerdtree
  6. vim-nerdtree-tabs
  7. syntastic
  8. vim-misc
  9. vim-easytags
  10. ctrlp.vim
  11. a.vim
  12. vim-gitgutter
  13. vim-fugitive
  14. delimitMate
  15. vim-tmux-navigator
  16. vim-colorschemes

NERDTree

ref: 上古神器vim插件:你真的學會用NERDTree了嗎?

NERDTree 是安裝後進入 vim,馬上就會看到的介面,他是一個檔案管理員的功能。

使用 NERDTree 搭配 nerdtree-tabs,感覺就比較接近一般的 IDE。

NERDTree 的指令有這些,但其實只要用滑鼠,點擊目錄跟檔案,也可以打開檔案

?: 快速幫助文檔
o: 打開一個目錄或者打開文件,創建的是buffer,也可以用來打開書籤
go: 打開一個文件,但是光標仍然留在NERDTree,創建的是buffer
t: 打開一個文件,創建的是Tab,對書籤同樣生效
T: 打開一個文件,但是光標仍然留在NERDTree,創建的是Tab,對書籤同樣生效
i: 水平分割創建文件的窗口,創建的是buffer
gi: 水平分割創建文件的窗口,但是光標仍然留在NERDTree
s: 垂直分割創建文件的窗口,創建的是buffer
gs: 和gi,go類似
x: 收起當前打開的目錄
X: 收起所有打開的目錄
e: 以文件管理的方式打開選中的目錄
D: 刪除書籤
P: 大寫,跳轉到當前根路徑
p: 小寫,跳轉到光標所在的上一級路徑
K: 跳轉到第一個子路徑
J: 跳轉到最後一個子路徑
<C-j>和<C-k>: 在同級目錄和文件間移動,忽略子目錄和子文件
C: 將根路徑設置為光標所在的目錄
u: 設置上級目錄為根路徑
U: 設置上級目錄為跟路徑,但是維持原來目錄打開的狀態
r: 刷新光標所在的目錄
R: 刷新當前根路徑
I: 顯示或者不顯示隱藏文件
f: 打開和關閉文件過濾器
q: 關閉NERDTree
A: 全屏顯示NERDTree,或者關閉全屏

Java, Scala, Python...

以 java 開發為例,最基本就是要在編輯 .java 檔案時,填寫 System. 的時候,就要自動出現該 package 可以填寫的 package or class。

在 .vimrc plugins 區塊,call vundle#end() 的前面加上

" java import
Plugin 'javaimp.vim'
" java auto complete
Plugin 'javacomplete'

.vimrc 最後面加上 javacomplete 的設定

" ========== omnifunc:javacomplete 自動補全功能 =========
" 設定此行在 java 檔案中,就可按(ctrl + x) + (ctrl + o) 自動補全
setlocal omnifunc=javacomplete#Complete
" 當檔案為副檔名為 java 動作
" mode的狀態下,按"."會替換成以下指令,換言之,與ide相同當按"."會自動補全
autocmd Filetype java,jsp set omnifunc=javacomplete#Complete
autocmd Filetype java,jsp set completefunc=javacomplete#CompleteParamsInf
autocmd Filetype java,jsp inoremap <buffer> . .<C-X><C-O><C-P><DOWN>
" 設定額外 include 的 classpath
" let b:classpath="/opt/apache-tomcat-8.0.30/lib/*"

" ========== omnifunc:javacomplete2 自動補全功能 =========
autocmd FileType java setlocal omnifunc=javacomplete#Complete
let g:JavaComplete_MavenRepositoryDisable = 1
let g:JavaComplete_UseFQN = 1
let g:JavaComplete_ClosingBrace = 1
let g:JavaComplete_JavaviDebug = 1
let g:JavaComplete_ImportDefault = 0

再安裝一次

vim +PluginInstall +qall

編輯 .java 就可以出現這樣的功能

References

Scala development in Vim

Coding Scala with Vim

vim plugin 推薦 (For python and java )

用 Vim 寫 JAVA - 環境建立 與 Eclim

Coding Java with Vim, 打造自己的工作環境

vim 使用 javacomplete2 自動補齊功能,只有 root 權限能完全工作問題

vim plugin 推薦 (For python and java )