OpenCVとnampy(ndarray)で画像処理

Pythonの醍醐味といえば、OpenCVなどの強力なライブラリーが使えることだ。
過去に自力でかなりひーこら言いながら実装したフーリエ変換(FFT)や射影変換があっという間にできてしまった。
OpenCVは中間データフォーマットとしてndarrayを使うので、ndarrayで直接操作もできる。

まず普通に開く。

    arrayImageColor = cv2.imread(filename, cv2.IMREAD_COLOR) ## cv2 returns ndarray of BGR orientation

f:id:nobu_macsuzuki:20210101024108p:plain
arrayImageColorは(512,512,3)のndarrayになる。

これをグレースケールに変換。

    arrayImageGray = cv2.cvtColor(arrayImageColor, cv2.COLOR_BGR2GRAY)

f:id:nobu_macsuzuki:20210101024210p:plain
arrayImageGrayは(512,512)のシングルチャネルのndarrayになる。

カラーの方を射影変換。

    offsetPerspective = 100
    (heightImageColor, widthImageColor) = arrayImageColor.shape[:2]
    arrayRectangleSource = np.array([[0, 0], [0, heightImageColor], [widthImageColor, heightImageColor], [widthImageColor, 0]], dtype=np.float32)
    arrayRectangleTarget = np.array([[offsetPerspective, offsetPerspective], [0, heightImageColor - offsetPerspective], [widthImageColor, heightImageColor - offsetPerspective], [widthImageColor - offsetPerspective, offsetPerspective]], dtype=np.float32)
    arrayMatrixPerspective = cv2.getPerspectiveTransform(arrayRectangleSource, arrayRectangleTarget)
    arrrayImagePerspective = cv2.warpPerspective(arrayImageColor, arrayMatrixPerspective, (widthImageColor, heightImageColor))

f:id:nobu_macsuzuki:20210101024335p:plain

グレーの方はFFT

    arrayFFTed = np.fft.fft2(arrayImageGray)
    arrayShiftedFFTed = np.fft.fftshift(arrayFFTed)

    arrayPowerSpectrumFFTRealPart = 20 * np.log(np.absolute(arrayShiftedFFTed))

    arrayRevertedShiftedFFTed = np.fft.fftshift(arrayShiftedFFTed)
    arrayInvertFFTed = np.fft.ifft2(arrayRevertedShiftedFFTed).real
    arrayBufferUint8 = arrayInvertFFTed.astype(np.uint8)

FFTのパワースペクトラムは、こうすると(512,512)のuint8のグレースケールとして表示可能なndarrayに変換できる。
ここでは最小を0に、最大を255に正規化しているが、クリップするだけでもいける。

    arrayBuffer = np.copy(arrayPowerSpectrumFFTRealPart)
    min = np.min(arrayBuffer)
    arrayBuffer[:,:] -= min
    max = np.max(arrayBuffer)
    arrayBuffer[:,:] /= max
    arrayBuffer[:,:] *= 255
    arrayBufferUint8 = arrayBuffer.astype(np.uint8)

f:id:nobu_macsuzuki:20210101024843p:plain

ここからがOpenCVの真骨頂、顔と目の認識。

    pathCascadeEye = os.path.join(pathCascade, 'haarcascade_eye.xml')
    pathCascadeFrontFace = os.path.join(pathCascade,'haarcascade_frontalface_default.xml')

    cascadeClassifierFace = cv2.CascadeClassifier(pathCascadeFrontFace) # it instatiates cascade classifier object
    cascadeClassifierEye = cv2.CascadeClassifier(pathCascadeEye)

    arrayFaces = cascadeClassifierFace.detectMultiScale(arrayImageGray) # it returns the list of ([tartX, startY, width, height]

    colorFace = (255, 0, 0) # color is tuple in CV
    colorEye = (0, 255, 0)
    LineThickness = 2

    for (facePositionX, facePositionY, faceWidth, faceHeight) in arrayFaces:
        cv2.rectangle(arrayImageColor, (facePositionX, facePositionY), (facePositionX + faceWidth, facePositionY + faceHeight), colorFace, LineThickness)
        arrayBufferFace = arrayImageColor[facePositionY: facePositionY + faceHeight, facePositionX: facePositionX + faceWidth]
        arrayBufferFaceGray = arrayImageGray[facePositionY: facePositionY + faceHeight, facePositionX: facePositionX + faceWidth]
        arrayEyes = cascadeClassifierEye.detectMultiScale(arrayBufferFaceGray)
        print(arrayEyes)
        for (eyePositionX, eyePositionY, eyeWidth, eyeHeight) in arrayEyes:
            cv2.rectangle(arrayBufferFace, (eyePositionX, eyePositionY), (eyePositionX + eyeWidth, eyePositionY + eyeHeight), colorEye, LineThickness)

f:id:nobu_macsuzuki:20210101025059p:plain

お次はRGBチャネル別のヒストグラム

[f:id:nobu_macsuzuki:20210101025258p:plain]    colors = ('b','g','r') #matlibplot has the color index, https://matplotlib.org/3.1.0/api/colors_api.html#module-matplotlib.colors
    for (indexColor, color) in enumerate(colors): # it provides list of object with index, in this case [(0, 'b'), (1, 'g'), (2, 'r')] 
        arrayHistogram = cv2.calcHist([arrayImageColor],[indexColor],None,[256],[0,256]) # it retunrs list of channel histogram
        plt.plot(arrayHistogram, color = color)
        plt.xlim([0,256])

f:id:nobu_macsuzuki:20210101025325p:plain

自作したときには、FFTとかライブラリだけで200-300行はあったし、画像を開いてFFTに流せるようにバイト列に変換してうんぬんだって書かねばならない。
射影変換も同様で、affine変換(線形変換)は簡単なのだが、射影変換はひーこらひーこらいいながら書いた。
それが上記の通り、どれも数行 - 十数行で書けてしまうのがすごい、うーむ、恐ろしい生産性。

ちなみに画像の表示はこのようにする。

    cv2.imshow('Color image',arrayImageColor)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

一行目が表示、二行目が入力待ちで、キー入力があると3行目でウィンドウを閉じる。

github.com