2017年7月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年7月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年7月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年7月3日

使用 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 )