OpenCV3計算機視覺Python語言實現(二):處理文件、攝像頭和圖形用戶界面

  • 2.1 基本I/O腳本
    • 2.1.1 讀/寫圖像文件
    • 2.1.2 圖像和原始字節之間的轉換
    • 2.1.3使用numpy.array()訪問圖像數據
    • 2.1.4 視頻文件的讀寫
    • 2.1.5 捕獲攝像頭的幀
    • 2.1.6 在窗口顯示圖像
    • 2.1.7 在窗口顯示攝像頭幀
  • 2.2 Cameo項目(人臉跟蹤和圖像處理)
  • 2.3 Cameo-面向對象設計

安裝

從網址:http://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv
下載opencv-whl文件

pip install wheel
pip install ****.whl

  大多數常用的OpenCV函數都在cv2模塊中。可能會遇到其他基於cv2.cv模塊的,這些都是傳統版本,Python模塊被成爲cv2並不是表示該模塊針對OpenCV2.x.x版本的,而是因爲該模塊引入了一個更好的API接口,它們採用了面向對象的編程方式,這與以前的cv模塊有所不同,以前的cv模塊更多采用面向過程化的編程方式。

2.1 基本的I/O腳本

2.1.1 讀/寫圖像文件

  OpenCV的imread()函數和imwrite()函數能支持各種靜態圖像文件。不管哪種格式,每個像素都會有一個值,但不同格式的表示像素的方式不同。如:通過numpy數組來創建一個黑色的正方形圖像:

img=numpy.zeros((4,2),dtype=numpy.uint8)
# dtype=uint8表示每個像素都由一個8位整數來表示,即每個像素值的範圍是0~255

cv2.cvtColor函數:將該圖像轉換成BGR模式或者RGB模式

import cv2
imgBGR=cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
print imgBGR,imgBGR.shape
# img.shape():返回HxWxC.img爲4行2列的二維數組。imgBGR.shape=(4,2,3),4頁*2行*3列,其中每一個行的3列分別代表BGR值或BGR值.

取值img[0,0,0]或img[0][0]:第一個值表示y座標或行,0表示頂部;第二個值表示像素的x座標或者列;第三個值表示顏色通道

# 圖像的讀取、顯示、保存
import cv2
img=cv2.imread('lena.jpg')
# 讀取第二個參數表示以什麼方式讀入,將圖像以灰度圖像讀入:cv2.imread('./lena.jpg',cv2.IMREAD_GRAYSCALE)第二個參數省略時,默認Opencv以BGR的格式讀入
cv2.imshow('lena_img',img)
cv2.waitKey(0)
# cv2.waitKey(0)是一個鍵盤綁定函數。需要指出的是它的時間尺度是毫秒級。函數等待特定的幾毫秒,看是否有鍵盤輸入。特定的幾毫秒之內,如果按下任意鍵,這個函數會返回按鍵的ASCII碼,程序會繼續運行。如果沒有鍵盤輸入,返回爲-1,如果我們設置這個函數的參數爲0,那麼會無限期的等待鍵盤輸入。它也可以被用來檢測特定鍵是都被按下。
cv2.destoryAllWindows()
# 關閉所有打開的窗口。也可以通過cv2.destoryWindows(windowsName)來關閉指定的窗口
cv2.imwrite('lena2.png',img)
# imwrite()要求圖像爲BGR或者灰度格式

2.1.2 圖像和原始字節之間的轉換:bytearray()函數

  若一幅圖像的通道有8位,則可將其顯示轉換成標準的一維Python bytearray格式:

import numpy as np
byteArray=bytearray(image)
print len(byteArray) # 輸出爲HxWxC

# 由字節轉換成圖像
grayImage=np.array(byteArray).reshape(height,width)
bgrImage=np.array(byteArray).reshape(height,width,3)

# 隨機生成字節
import os
randomByteArray=bytearray(os.urandom(120000))
flatNumpyArray=numpy.array(randomByteArray) # OpenCV圖像是.array類型的二維或者三維數組,因此轉換成array"

bgrImage=flatNumpyArray.reshape(200,200,3)
cv2.imshow("bgrImage",bgrImage)

2.1.3使用numpy.array()訪問圖像數據

訪問數組中特定位置的值使用np.array()提供的item(),設置特定位置的值:itemset()

import cv2
import numpy as np

lenaImage = cv2.imread('lena.jpg')
print lenaImage.item(150, 120, 0)// 訪問特定位置
lenaImage.itemset((150, 120, 0), 255) // 設置特定位置的值

通過循環處理數組的效率很低,採用索引的方式來解決這個問題:

import cv2
import numpy as np

img = cv2.imread('lena.jpg') img[:,:,1]=0 
img[:,:,1] = 0 // 將所有的G通道設置爲0
img[0:100,0:100,:] // 得到圖像從左上角開始100*100的區域三個通道值
img[0,0]=[0,0,0] // 將黑色BGR值賦給圖像第0行第0列的三通道值

其他屬性

print img.shape // 返回圖像H,W,C的數組
print img.size // 返回圖像像素的大小
print img.dtype // 得到圖像的數據類型(通常爲一個無符號整數類型的變量和該類型佔的位數,比如uint8)

2.1.4 視頻文件的讀寫

OpenCV提供了VideoCapture和VideoWriter類來支持各種視頻的讀寫。在達到視頻文件末尾之前,VideoCapture都會通過read()來獲取新的一幀,每一幀都是基於BGR格式的圖像;
可將一幅圖像傳遞給VideoWriter類的write()函數,該函數會將這副圖像加到VideoWriter類所指向的文件。

import cv2

videoCapture = cv2.VideoCapture('meipai.mp4')
fps = videoCapture.get(cv2.CAP_PROP_FPS)  # 幀速率,fps幀/秒
size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), \
        int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
print size  # (854,480)注意此時的size爲WxH
videoWriter = cv2.VideoWriter('meipai.avi', cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, size)

success, frame = videoCapture.read()
print frame.shape  # (480,854,3) 此時爲讀取到的每一張圖片大小HxWxC
while success:  # success爲bool型,當讀到末尾的時候爲空
    videoWriter.write(frame)
    success, frame = videoCapture.read()

videoWriter類的構造函數需要指定視頻編解碼器。編解碼器根據系統的不同而不同,一下是常用的一些選項:

- cv2.VideoWriter_fourcc('I','4','2','0'):該選項是一個未壓縮的YUV顏色編碼,是4:2:0色度子採樣。這種編碼有很好的兼容性,但會產生較大的文件,擴展名爲avi
- cv2.VideoWriter_fourcc('P','I','M','1'):該選項是MPEG-1編碼類型,擴展名爲avi
- cv2.VideoWriter_fourcc('X', 'V', 'I', 'D'):MPEG-4編碼,如果希望得到的視頻大小爲平均值,推薦使用此選項,擴展名avi
- cv2.VideoWriter_fourcc('T', 'H', 'E', 'O'):該選項是Ogg Vorbis,擴展名是avi
- cv2.VideoWriter_fourcc('F', 'L', 'V', '1'):該選項是一個Flash視頻,擴展名爲.flv

2.1.5 捕獲攝像頭的幀

import cv2

cameraCapture = cv2.VideoCapture(0)
size = (int(cameraCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), \
        int(cameraCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
fps = 30  # 假定幀速率爲30
videoWriter = cv2.VideoWriter("MyOutputVideo.avi", \
                              cv2.VideoWriter_fourcc("I", "4", "2", "0"), fps, size)
success, frame = cameraCapture.read()
numFrameRemaining = 10 * fps - 1
while success and numFrameRemaining > 0:
    videoWriter.write(frame)
    success, frame = cameraCapture.read()
    numFrameRemaining -= 1
cameraCapture.release()

2.1.6 在窗口顯示圖像

2.1.7 在窗口顯示攝像頭幀

import cv2

clicked = False

def onMouse(event, x, y, flags, param):
    global clicked
    if event == cv2.EVENT_LBUTTONUP:
        clicked = True

cameraCapture = cv2.VideoCapture(0)
cv2.namedWindow("MyWindow") #指定窗口名
cv2.setMouseCallback("MyWindow", onMouse)#獲取鼠標輸入

print "Showing camera feed. Click window or press any key to stop."

success, frame = cameraCapture.read()

while success and not clicked and cv2.waitKey(1) == -1:#沒達到停止條件時
    cv2.imshow("MyWindow", frame)
    success, frame = cameraCapture.read()
cv2.destroyAllWindows()
cameraCapture.release()
  • cv2.waitKey():獲取鍵盤輸入,參數爲等待鍵觸發的時間,單位毫秒,其返回值爲-1(表示沒有被鍵盤按下)或ASCII碼。
  • setMouseCallback()獲取鼠標輸入

opencv的窗口函數和waitKey函數相互以阿里。Opencv的窗口只有在調用waitKey函數時纔會更新,waitkey函數只有在opencv窗口成爲活動窗口時才能捕獲輸入信息。

在一些系統中,waitKey()的返回值可能比ASCII碼的值更大(在Linux系統中,如果Opencv使用GTK作爲後端的GUI庫,就會出現一個衆所周知的bug

在所有的系統中,可以通過讀取返回值的最後一個字節來保證只提取ASCII碼,具體代碼如下:

keycode = cv2.waitKey(1)
if keycode != -1:
    keycode &= 0xFF

2.2-2.3 cameo項目(人臉跟蹤和圖像處理)

1.manager.py

manager.py
# -*-coding:utf-8-*-
import cv2
from cv2 import *
import numpy
import time


class CaptureManager(object):
    def __init__(self, capture, previewWindowManager=None, \
                 shouldMirrorPreview=False):
        self.previewWindowManager = previewWindowManager
        self.shouldMirrorPreview = shouldMirrorPreview
        # 注意大多數成員(member)變量爲非公有變量,這類變量名前會加一個下劃線進行標識。
        self._capture = capture
        self._channel = 0
        self._enteredFrame = False
        self._frame = None
        self._imageFilename = None
        self._videoFilename = None
        self._videoEncoding = None
        self._videoWriter = None

        self._startTime = None
        self._framesElapsed = long(0)
        self._fpsEstimate = None

    # 關於@符號的含義可以參考鏈接:https://www.zhihu.com/question/26930016
    @property
    def channel(self):
        return self._channel

    @channel.setter
    def channel(self, value):
        if self._channel != value:
            self._channel = value
            self._frame = None

    @property
    def frame(self):
        if self._enteredFrame and self._frame is None:
            _, self._frame = self._capture.retrieve()
        return self._frame

    @property
    def isWritingImage(self):
        return self._imageFilename is not None

    @property
    def isWritingVideo(self):
        return self._videoFilename is not None

    def enterFrame(self):
        """Capture the next frame,if any."""
        # But first ,check that any previous frame was exited.
        assert not self._enteredFrame, 'previous enteredFrame() had no matching exitFrame()'
        if self._capture is not None:
            self._enteredFrame = self._capture.retrieve()

    def exitFrame(self):
        """Draw to the window,write to files,Release the frame."""
        # Check whether ant grabbed frame is retrievable.
        # The getter may retrieve and cache the frame
        if self._frame is None:
            self._enteredFrame = False
            return
        # Update the FPS estimate and related variables
        if self._framesElapsed == 0:
            self._startTime = time.time()
        else:
            timeElapsed = time.time() - self._startTime
            self._fpsEstimate = int(self._framesElapsed / timeElapsed)
        self._framesElapsed += 1

        # Draw to the Window,if any.
        if self.previewWindowManager is not None:
            if self.shouldMirrorPreview:
                mirroredFrame = numpy.fliplr(self._frame).copy()
                self.previewWindowManager.show(mirroredFrame)
            else:
                self.previewWindowManager.show(self._frame)

        # Write to the image file,if any.
        if self.isWritingImage:
            cv2.imwrite(self._imageFilename, self._frame)
            self._imageFilename = None

        # Write to the video file,if any.
        self._writeVideoFrame()

        # Release the Frame
        self._frame = None
        self._enteredFrame = False

    def writeImage(self, filename):
        """write the next exited frame to an image frame"""
        self._imageFilename = filename

    def startWritingVideo(self, filename, encoding=cv2.VideoWriter_fourcc("I", "4", "2", "0")):
        """start writing exited frames to a video file"""
        self._videoFilename = filename
        self._videoEncoding = encoding
        print self._videoEncoding

    def stopWritingVideo(self):
        """Stop writing exited frames to a video file"""
        self._imageFilename = None
        self._videoEncoding = None
        self._videoWriter = None

    def _writeVideoFrame(self):
        if not self.isWritingVideo:
            return
        if self._videoWriter is None:
            fps = self._capture.get(cv2.CAP_PROP_FPS)
            if fps == 0.0:
                # the capture fps is unknow ,so ues an estimate.
                if self._framesElapsed < 20:
                    # wait until more frames elapse so that the estimate is stable
                    return
                else:
                    fps = self._fpsEstimate
            size = (int(self._capture.get(cv2.CAP_PROP_FRAME_WIDTH)), \
                    int(self._capture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
            # self._videoWriter = cv2.VideoWriter(self._videoFilename, self._videoEncoding, fps, size)
            print "self._videoEncoding:",self._videoEncoding
            self._videoWriter = cv2.VideoWriter(self._videoFilename, self._videoEncoding, fps, size)
            print self._frame
            self._videoWriter.write(self._frame)


class WindowManager(object):
    def __init__(self, windowName, keypressCallback=None):
        self.keypressCallback = keypressCallback
        self._windowName = windowName
        self._isWindowCreated = False

    @property
    def isWindowCreated(self):
        return self._isWindowCreated

    def createdWindow(self):
        cv2.namedWindow(self._windowName)
        self._isWindowCreated = True

    def show(self, frame):
        cv2.imshow(self._windowName, frame)
        # cv2.waitKey(1)

    def destroyWindow(self):
        cv2.destroyWindow(self._windowName)
        self._isWindowCreated = False

    def processEvents(self):
        keycode = cv2.waitKey(1)
        if self.keypressCallback is not None and keycode != -1:
            # Discard any non-ASCII info encoded by GTK
            keycode &= 0xFF
            self.keypressCallback(keycode)

2.cameo.py


import cv2
from managers import WindowManager, CaptureManager
import filters


class Cameo(object):
    def __init__(self):
        self._windowManger = WindowManager('Cameo', self.onKeypress)
        self._captureManger = CaptureManager(cv2.VideoCapture(0), self._windowManger, True)
        self._curveFilter=filters.BlurFilter()

    def run(self):
        """Run the main loop"""
        self._windowManger.createdWindow()
        while self._windowManger.isWindowCreated:
            self._captureManger.enterFrame()
            frame = self._captureManger.frame

            filters.strokeEdges(frame,frame)
            self._curveFilter.apply(frame,frame)

            self._captureManger.exitFrame()
            self._windowManger.processEvents()

    def onKeypress(self, keycode):
        """Handle a keypress.
        space   -> Take a screenshot
        tab     -> Start/stop recoding a screenshot
        escape  -> Quit
        """
        if keycode == 32:  # space
            self._captureManger.writeImage("./screenshot.png")
        elif keycode == 9:  # tab
            if not self._captureManger.isWritingVideo:
                self._captureManger.startWritingVideo('./screenshot.avi')
            else:
                self._captureManger.stopWritingVideo()
        elif keycode == 27:  # escape
            self._windowManger.destroyWindow()


if __name__ == '__main__':
    Cameo().run()

這裏出現一個問題,錄製的視頻大小不爲0,但是時長總是0.不知道是什麼原因?每一幀都被後一幀覆蓋掉了?還不懂,還望懂的小夥伴不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章