テレビ会議用のイフェクターをSpout + OpenCV + dlibで作る（その１）

SpoutはZoom等のテレビ会議システムからカメラとして選択できるので、Webカメラ→Spout(Pythonでゴニョゴニョ）→Zoomなどとすれば、在宅勤務用時に、顔をそのまま出したくないが、表情を見せたい時など、顔にイフェクトをかけたり、逆に顔以外の背景を消したりなど簡単にできます。
　「簡単に」と書きましたが、実はオリジナルの記事が古いので、まともに動かすのは各モジュールのバージョンの相性が悪いと動かないので、その塩梅がノウハウです。
 
　先ず、Spout本家ははこちら、https://spout.zeal.co/
　こちらから、Spoutをインストールします。

　次に、以下からSpout for Python をダウンロードします。

https://github.com/spiraltechnica/Spout-for-Python

このSpout-for-Python-master内のExample内の各コードが動作すれば、Libs内のSpoutSDK.pydがそのまま使えるということになりますが、動作しなければ、自分でbuildする必要があります。

 Python用にbuildする場合は以下のサイトが参考になります。上のオリジナルに従うと、
Python以外の他のライブラリをすべてbuildしますので、えらく時間がかかります。

https://rusin.work/vjing/tools/spout-for-python/


　その場合の動くバージョンの組み合わせの一つは以下の通りです。これはすべてのプラットフォーム
で動くかどうかは不明です。実際手持ちのPCで動く場合と動く場合があります。特にopencvなどがまともに動かない場合があります。
　動作バージョンのPythonフォルダを動かないほうのPCコピーすれば動きます。

 Python   3.74 
 PyOpenGL 3.15
 pygame   1.18.2
 opencv_python 4.2.0.34

ここで、buildして、SpoutSDK.dll ができ、それをSpoutSDK.pyd とリネームしたものが
Pythonからimport SpoutSDK で利用できます。

 WebCamを備えたPCで以下のコードが動けばひとまずオーケーです。
つまり、SpoutのDEMOフォルダ内のSpoutReceiveでWebCamからの映像が受信できる
かチェックしてください。受信できていれば、ZoomなどからSpoutをカメラとして
選択すると、テレビ会議にその映像を送ることができます。

　これにどのようにしてイフェクトをかけるかは次のポストで紹介します。

```
import sys
sys.path.append('../Library')
import cv2
import numpy as np
import SpoutSDK
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *

def main():

    # window details
    width = 640 
    height = 480 
    display = (width,height)
    
    # window setup
    pygame.init() 
    pygame.display.set_caption('Spout Python Sender')
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
    pygame.display.gl_set_attribute(pygame.GL_ALPHA_SIZE, 8)

    cap = cv2.VideoCapture(0)
    if cap.isOpened() is False:
        raise("IO Error")
    cap.set(cv2.CAP_PROP_FPS, 30)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

    cap.set(3, width)
    cap.set(4, height)

      # OpenGL init
    glMatrixMode(GL_PROJECTION)
    glOrtho(0,width,height,0,1,-1)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    glDisable(GL_DEPTH_TEST)
    glClearColor(0.0,0.0,0.0,0.0)
    glEnable(GL_TEXTURE_2D)
   
    # init spout sender
    spoutSender = SpoutSDK.SpoutSender()
    spoutSenderWidth = width
    spoutSenderHeight = height
    # Its signature in c++ looks like this: bool CreateSender(const char *Sendername, unsigned int width, unsigned int height, DWORD dwFormat = 0);
    spoutSender.CreateSender('Spout for Python Webcam Sender Example', width, height, 0)
    
    # create texture id for use with Spout
    senderTextureID = glGenTextures(1)

    # initalise our sender texture
    glBindTexture(GL_TEXTURE_2D, senderTextureID)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glBindTexture(GL_TEXTURE_2D, 0)

    # loop
    while True:

        for event in pygame.event.get():
           if event.type == pygame.QUIT:
               pygame.quit()
               quit()
        
        ret, frame = cap.read() #read camera image
        #img = cv2.imread('image.png') # if use the image file
        frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB) #BGR-->RGB
        h, w = frame.shape[:2]
        glBindTexture(GL_TEXTURE_2D, senderTextureID)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, frame)

        spoutSender.SendTexture(senderTextureID, GL_TEXTURE_2D, spoutSenderWidth, spoutSenderHeight, False, 0)
        #glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        # Clear screen
        glClear(GL_COLOR_BUFFER_BIT  | GL_DEPTH_BUFFER_BIT )
        # reset the drawing perspective
        glLoadIdentity()

        # Draw texture to screen
        glBegin(GL_QUADS)
        ##
        glTexCoord(0,0)        
        glVertex2f(0,0)
        #
        glTexCoord(1,0)
        glVertex2f(width,0)
        #
        glTexCoord(1,1)
        glVertex2f(width,height)
        #
        glTexCoord(0,1)
        glVertex2f(0,height)
        ##
        glEnd()
        
        pygame.display.flip()
        # unbind our sender texture
        glBindTexture(GL_TEXTURE_2D, 0)
      #  pygame.time.wait(10)

if __name__ == '__main__':
    main()


```