├── .gitignore ├── README.md ├── README.zh_CN.md ├── blender ├── Alpha.fbx └── Beta.blend ├── demo ├── demo1.gif ├── demo2.gif └── results.npz ├── images ├── 1a7a853daa25f17230482437550e1d94f22252f0b02807ab105eeb6a2bd8ae30.png ├── 57480e4a863cb8f06bcb8581279a5669849d31a88ed17c6717422f707acdb0d3.png ├── 6b7e75964fd193b36ae58c94ddd99e6d234de6e085fb65d6f6691b476329b16c.png ├── Background.mp4 ├── bc3d69615afb7829359475a04e4dd024732f8a70736b7433a7aaf93888dc2be7.png └── c52b11b344f633d7d60dd2c3a4fd8af0057c2a873f5868227e5c3e3b6c27b37f.png └── src ├── characterDriven.py ├── server.py └── webcamClient.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .DS_Store 6 | patent 7 | *.blend1 8 | *.fbx 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | .gitignore 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [English](README.md)|[中](README.zh_CN.md) 2 | 3 | # Blender addon for driving character 4 | 5 | The addon drives a 3D cartoon character by transferring SMPL's pose and global translation into the skeleton of the 3D character. Poses and global translation can be obtained from RGB images using ROMP or any SMPL-based 3D pose estimation model. If the estimation model outputs pose and global position at a high speed, then you can achieve the effect of driving 3D characters in real time in Blender. 6 | 7 | ## Two Pipeline 8 | 9 | ### From Video to Animation 10 | 11 | ![image](demo/demo1.gif) 12 | 13 | Steps: 14 | 15 | 1. Modify the path of [results.npz](demo/results.npz) in [server.py](src/server.py) and start server.py in the command line. 16 | 2. Open [Beta.blend](blender/Beta.blend) and click on the triangle in the upper right corner.If a message is displayed in the lower left corner, the addon is running successfully. 17 | ![图 2](images/c52b11b344f633d7d60dd2c3a4fd8af0057c2a873f5868227e5c3e3b6c27b37f.png) 18 | 3. Go back to the Layout view and click on the small icon in the upper right to get the Texture. 19 | ![图 1](images/bc3d69615afb7829359475a04e4dd024732f8a70736b7433a7aaf93888dc2be7.png) 20 | 4. Press Ctrl+E to run the addon. At this time, the keyframe that is being transferred is displayed in the command line running server.py. 21 | ![图 4](images/1a7a853daa25f17230482437550e1d94f22252f0b02807ab105eeb6a2bd8ae30.png) 22 | 5. Press the space in Blender to view the character animation. 23 | 24 | > If you want to use your own video, you need to use [ROMP](https://github.com/Arthur151/ROMP)'s [video.sh](https://github.com/Arthur151/ROMP/blob/master/scripts/video.sh) to get a npz file. 25 | 26 | ### The Webcam Drive Character in Real-Time 27 | 28 | ![image](demo/demo2.gif) 29 | 30 | Steps: 31 | 32 | 1. Connect the camera and run ROMP's [webcam_blender.sh](https://github.com/Arthur151/ROMP/blob/master/scripts/webcam_blender.sh). 33 | 2. The rest of the steps are the same as steps 2, 3 of `From Video to Animation`, where the 3D characters in Blender are driven in real time by pressing Ctrl+E.Press A to stop it. 34 | 35 | ## Use Your Own 3D Character 36 | 37 | If you are familiar with Blender and you want to use your own 3D characters, make sure that the skeleton is exactly the same as the SMPL's skeleton and that the bones are named the same as the bones of my 3D characters (of course, you can also change the bone names in the addon). All the 24 bones in SMPL need to be named the same. There is no need to change the name of the finger bones. 38 | 39 | ![图 3](/images/6b7e75964fd193b36ae58c94ddd99e6d234de6e085fb65d6f6691b476329b16c.png) 40 | 41 | ## Use Your Own Background 42 | 43 | If you need a video background in the demo, select Compositing in the top menu bar, click Open Clip in the Movie Clip, and select your video.It is best not to use video as a background when driving in real time to prevent animations from getting too stuck. 44 | 45 | ![图 7](images/57480e4a863cb8f06bcb8581279a5669849d31a88ed17c6717422f707acdb0d3.png) 46 | 47 | ## Acknowledgement 48 | 49 | - 3D character is downloaded from [Mixamo](https://www.mixamo.com/#/) 50 | - [Blender 2.8](https://www.bilibili.com/video/BV1T4411N7GE?spm_id_from=333.999.0.0) 51 | - [Blender Manual](https://docs.blender.org/manual/en/latest/) 52 | - [Blender Python API](https://docs.blender.org/api/current/index.html) 53 | - [neuron_mocap_live-blender](https://github.com/pnmocap/neuron_mocap_live-blender) 54 | - [QuickMocap-BlenderAddon](https://github.com/vltmedia/QuickMocap-BlenderAddon) 55 | - [remote-opencv-streaming-live-video](https://github.com/rena2damas/remote-opencv-streaming-live-video) 56 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | [English](README.md)|[中](README.zh_CN.md) 2 | 3 | # 驱动 3D 人物的 Blender 插件 4 | 5 | 该插件通过传输 SMPL 的姿态和全局位置到 3D 人物的骨架中来驱动 3D 卡通人物。姿态和全局位置可以通过 ROMP 或者任意基于 SMPL 的 3D 姿态估计模型从 RGB 图像中获得。如果估计模型能够以较高速度输出姿态和全局位置,那么就能够在 Blender 中实现实时驱动 3D 人物的效果。 6 | 7 | ## 两种流程 8 | 9 | ### 从视频到动画 10 | 11 | ![image](demo/demo1.gif) 12 | 13 | 步骤: 14 | 15 | 1. 修改[server.py](src/server.py)中的[results.npz](demo/results.npz)路径,并在命令行启动 server.py 16 | 2. 打开[Beta.blend](blender/Beta.blend),并点击右上角三角,看到左下角提示,说明插件运行成功 17 | ![图 2](images/c52b11b344f633d7d60dd2c3a4fd8af0057c2a873f5868227e5c3e3b6c27b37f.png) 18 | 3. 回到 layout 视图,点击右上角小图标获取 Texture 19 | ![图 1](images/bc3d69615afb7829359475a04e4dd024732f8a70736b7433a7aaf93888dc2be7.png) 20 | 4. 按下 Ctrl+E 运行插件,此时在运行 server.py 的命令行中会提示当前正在传输的关键帧,传输结束后连接断开 21 | ![图 4](images/1a7a853daa25f17230482437550e1d94f22252f0b02807ab105eeb6a2bd8ae30.png) 22 | 5. 在 Blender 中按下空格键即可观看到人物动画 23 | 24 | > 如果你想要使用自己的视频,你需要使用 [ROMP](<(https://github.com/Arthur151/ROMP)>) 的[video.sh](https://github.com/Arthur151/ROMP/blob/master/scripts/video.sh)获取 npz 文件。 25 | 26 | ### 摄像头实时驱动3D人物 27 | 28 | ![image](demo/demo2.gif) 29 | 30 | 步骤: 31 | 32 | 1. 连接摄像头,启动 ROMP 的 [webcam_blender.sh](https://github.com/Arthur151/ROMP/blob/master/scripts/webcam_blender.sh) 33 | 2. 其余步骤与`从视频到动画`的2、3相同,按下Ctrl+E后,Blender中的3D人物便会实时驱动;按下A键便能够停下来 34 | 35 | 36 | ## 使用你自己的3D人物 37 | 38 | 如果你对Blender很熟悉,你希望使用自己的3D人物,那么你需要确保它的骨架与SMPL骨架完全一致,并且各骨头的命名也与我的3D人物的骨头命名一致(当然,你也可以修改插件中的骨头命名)。只需要SMPL的24个骨头命名保持一致即可,不需要改变手指骨头的命名。 39 | 40 | ![图 3](/images/6b7e75964fd193b36ae58c94ddd99e6d234de6e085fb65d6f6691b476329b16c.png) 41 | ## 使用你自己的动画背景 42 | 43 | 当你渲染动画时,你可能希望有一个好看点的背景。我在Compositing中设置好了添加背景的方法,你只需要点击Open Clip,选中自己的视频即可。在实时驱动时最好不要添加视频,防止动画太卡。 44 | 45 | ![图 7](images/57480e4a863cb8f06bcb8581279a5669849d31a88ed17c6717422f707acdb0d3.png) 46 | 47 | ## 致谢 48 | 49 | - [Mixamo](https://www.mixamo.com/#/):3D人物从该网站下载 50 | - [Blender 2.8](https://www.bilibili.com/video/BV1T4411N7GE?spm_id_from=333.999.0.0):第零、一、十三章的知识尤其有用 51 | - [Blender Manual](https://docs.blender.org/manual/en/latest/) 52 | - [Blender Python API](https://docs.blender.org/api/current/index.html) 53 | - [neuron_mocap_live-blender](https://github.com/pnmocap/neuron_mocap_live-blender) 54 | - [QuickMocap-BlenderAddon](https://github.com/vltmedia/QuickMocap-BlenderAddon) 55 | - [remote-opencv-streaming-live-video](https://github.com/rena2damas/remote-opencv-streaming-live-video) 56 | -------------------------------------------------------------------------------- /blender/Alpha.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/blender/Alpha.fbx -------------------------------------------------------------------------------- /blender/Beta.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/blender/Beta.blend -------------------------------------------------------------------------------- /demo/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/demo/demo1.gif -------------------------------------------------------------------------------- /demo/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/demo/demo2.gif -------------------------------------------------------------------------------- /demo/results.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/demo/results.npz -------------------------------------------------------------------------------- /images/1a7a853daa25f17230482437550e1d94f22252f0b02807ab105eeb6a2bd8ae30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/images/1a7a853daa25f17230482437550e1d94f22252f0b02807ab105eeb6a2bd8ae30.png -------------------------------------------------------------------------------- /images/57480e4a863cb8f06bcb8581279a5669849d31a88ed17c6717422f707acdb0d3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/images/57480e4a863cb8f06bcb8581279a5669849d31a88ed17c6717422f707acdb0d3.png -------------------------------------------------------------------------------- /images/6b7e75964fd193b36ae58c94ddd99e6d234de6e085fb65d6f6691b476329b16c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/images/6b7e75964fd193b36ae58c94ddd99e6d234de6e085fb65d6f6691b476329b16c.png -------------------------------------------------------------------------------- /images/Background.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/images/Background.mp4 -------------------------------------------------------------------------------- /images/bc3d69615afb7829359475a04e4dd024732f8a70736b7433a7aaf93888dc2be7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/images/bc3d69615afb7829359475a04e4dd024732f8a70736b7433a7aaf93888dc2be7.png -------------------------------------------------------------------------------- /images/c52b11b344f633d7d60dd2c3a4fd8af0057c2a873f5868227e5c3e3b6c27b37f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jianghuchengphilip/CharacterDriven-BlenderAddon/addcc33c684d15e8c13349575763ce30593e1e58/images/c52b11b344f633d7d60dd2c3a4fd8af0057c2a873f5868227e5c3e3b6c27b37f.png -------------------------------------------------------------------------------- /src/characterDriven.py: -------------------------------------------------------------------------------- 1 | from mathutils import Matrix, Vector, Quaternion 2 | from math import radians 3 | import numpy as np 4 | import bpy 5 | import json 6 | import socket 7 | 8 | bl_info = { 9 | "name": "Character Driven: Live or Offline", 10 | "author": "yanch2116", 11 | "blender": (2, 80, 0), 12 | "version": (1, 0, 0), 13 | } 14 | 15 | 16 | class CharacterDriven(bpy.types.Operator): 17 | bl_idname = 'yanch.characterdriven' 18 | bl_label = 'characterdriven' 19 | 20 | def execute(self, ctx): 21 | global mocap_timer 22 | global SMPL_Importer_ 23 | global s 24 | global pelvis_position 25 | 26 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 27 | s.connect(('127.0.0.1', 9999)) 28 | SMPL_Importer_ = SMPL_Importer(ctx) 29 | 30 | pelvis_bone = bpy.data.armatures['Armature'].bones['Pelvis'] 31 | pelvis_position = Vector(pelvis_bone.head) 32 | 33 | 34 | ctx.window_manager.modal_handler_add(self) 35 | mocap_timer = ctx.window_manager.event_timer_add( 36 | 1 / 120, window=ctx.window) 37 | 38 | return {'RUNNING_MODAL'} 39 | 40 | def modal(self, ctx, evt): 41 | if evt.type == 'TIMER': 42 | s.send(b'1') 43 | data = s.recv(4096) 44 | if data: 45 | data = json.loads(data.decode('utf-8')) 46 | mode, poses, trans, frame = data 47 | SMPL_Importer_.process_poses( 48 | mode, poses, trans, frame, pelvis_position) 49 | else: 50 | s.close() 51 | return {'FINISHED'} 52 | 53 | if evt.type == 'A': 54 | s.close() 55 | return {'FINISHED'} 56 | 57 | return {'RUNNING_MODAL'} 58 | 59 | 60 | class SMPL_Importer: 61 | 62 | def __init__(self, context): 63 | 64 | self.bone_name_from_index = { 65 | 0: 'Pelvis', 66 | 1: 'L_Hip', 67 | 2: 'R_Hip', 68 | 3: 'Spine1', 69 | 4: 'L_Knee', 70 | 5: 'R_Knee', 71 | 6: 'Spine2', 72 | 7: 'L_Ankle', 73 | 8: 'R_Ankle', 74 | 9: 'Spine3', 75 | 10: 'L_Foot', 76 | 11: 'R_Foot', 77 | 12: 'Neck', 78 | 13: 'L_Collar', 79 | 14: 'R_Collar', 80 | 15: 'Head', 81 | 16: 'L_Shoulder', 82 | 17: 'R_Shoulder', 83 | 18: 'L_Elbow', 84 | 19: 'R_Elbow', 85 | 20: 'L_Wrist', 86 | 21: 'R_Wrist', 87 | 22: 'L_Hand', 88 | 23: 'R_Hand' 89 | } 90 | 91 | def Rodrigues(self, rotvec): 92 | theta = np.linalg.norm(rotvec) 93 | r = (rotvec/theta).reshape(3, 1) if theta > 0. else rotvec 94 | cost = np.cos(theta) 95 | mat = np.asarray([[0, -r[2], r[1]], 96 | [r[2], 0, -r[0]], 97 | [-r[1], r[0], 0]]) 98 | return(cost*np.eye(3) + (1-cost)*r.dot(r.T) + np.sin(theta)*mat) 99 | 100 | def process_poses(self, mode, poses, trans, current_frame, pelvis_position): 101 | armature = bpy.data.objects['Armature'] 102 | 103 | poses = np.array(poses) 104 | trans = np.array(trans) 105 | 106 | if poses.shape[0] == 72: 107 | rod_rots = poses.reshape(24, 3) 108 | else: 109 | rod_rots = poses.reshape(26, 3) 110 | 111 | mat_rots = [self.Rodrigues(rod_rot) for rod_rot in rod_rots] 112 | 113 | bones = armature.pose.bones 114 | bones[self.bone_name_from_index[0]].location = Vector( 115 | (100*trans[1], 100*trans[2], 100*trans[0])) - pelvis_position 116 | if mode == 1: 117 | bones[self.bone_name_from_index[0]].keyframe_insert( 118 | 'location', frame=current_frame) 119 | 120 | for index, mat_rot in enumerate(mat_rots, 0): 121 | if index >= 24: 122 | continue 123 | 124 | bone = bones[self.bone_name_from_index[index]] 125 | 126 | bone_rotation = Matrix(mat_rot).to_quaternion() 127 | 128 | quat_x_90_cw = Quaternion((1.0, 0.0, 0.0), radians(-90)) 129 | quat_x_n135_cw = Quaternion((1.0, 0.0, 0.0), radians(-135)) 130 | quat_x_p45_cw = Quaternion((1.0, 0.0, 0.0), radians(45)) 131 | quat_y_90_cw = Quaternion((0.0, 1.0, 0.0), radians(-90)) 132 | quat_z_90_cw = Quaternion((0.0, 0.0, 1.0), radians(-90)) 133 | 134 | if index == 0: 135 | # Rotate pelvis so that avatar stands upright and looks along negative Y avis 136 | bone.rotation_quaternion = ( 137 | quat_x_90_cw @ quat_z_90_cw) @ bone_rotation 138 | else: 139 | bone.rotation_quaternion = bone_rotation 140 | if mode == 1: 141 | bone.keyframe_insert( 142 | 'rotation_quaternion', frame=current_frame) 143 | bpy.context.scene.frame_end = current_frame 144 | 145 | 146 | return 147 | 148 | 149 | addon_keymaps = [] 150 | 151 | 152 | def register(): 153 | bpy.utils.register_class(CharacterDriven) 154 | wm = bpy.context.window_manager 155 | kc = wm.keyconfigs.addon 156 | if kc: 157 | km = wm.keyconfigs.addon.keymaps.new( 158 | name='3D View', space_type='VIEW_3D') 159 | kmi = km.keymap_items.new( 160 | CharacterDriven.bl_idname, type='E', value='PRESS', ctrl=True) 161 | addon_keymaps.append((km, kmi)) 162 | 163 | 164 | def unregister(): 165 | bpy.utils.unregister_class(CharacterDriven) 166 | for km, kmi in addon_keymaps: 167 | km.keymap_items.remove(kmi) 168 | addon_keymaps.clear() 169 | 170 | 171 | if __name__ == "__main__": 172 | register() 173 | -------------------------------------------------------------------------------- /src/server.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import json 3 | import threading 4 | import numpy as np 5 | 6 | global mode 7 | mode = 1 8 | 9 | def getData(): 10 | # Replace Your Own npz File PATH 11 | npz_path = '../demo/results.npz' 12 | a = np.load( 13 | npz_path, allow_pickle=True)['results'][()] 14 | data = [] 15 | for key in a: 16 | temp = np.append(a[key][0]['poses'], a[key][0]['trans']) 17 | data.append(temp) 18 | data = list(data) 19 | for i in range(len(data)): 20 | data[i] = list(data[i]) 21 | return data # [N,75] N stands for the number of frames 22 | 23 | 24 | def tcplink(sock, addr): 25 | print('Accept new connection from %s:%s...' % addr) 26 | data = getData() 27 | num_frame = len(data) 28 | for frame in range(num_frame): 29 | d = sock.recv(1024) 30 | if not d: 31 | break 32 | poses = data[frame][:72] 33 | trans = data[frame][72:75] 34 | # The data is [mode,poses,trans,current_frame] 35 | send_data = json.dumps([mode, poses, trans, frame+1]).encode('utf-8') 36 | print('The current frame is {}'.format(frame+1)) 37 | sock.send(send_data) 38 | sock.close() 39 | print('Connection from %s:%s closed.' % addr) 40 | 41 | 42 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 43 | s.bind(('127.0.0.1', 9999)) 44 | s.listen(1) 45 | print('Waiting for connection...') 46 | 47 | while True: 48 | sock, addr = s.accept() 49 | t = threading.Thread(target=tcplink, args=(sock, addr)) 50 | t.start() 51 | -------------------------------------------------------------------------------- /src/webcamClient.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import json 3 | import socket 4 | import numpy as np 5 | from io import BytesIO 6 | import struct 7 | 8 | 9 | # Capture frame 10 | cap = cv2.VideoCapture(0) 11 | 12 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 | s.connect(('127.0.0.1', 9998)) 14 | 15 | while cap.isOpened(): 16 | _, frame = cap.read() 17 | dim = (512,512) 18 | frame = cv2.resize(frame, dim, interpolation=cv2.INTER_AREA) 19 | print(frame[0][0]) 20 | cv2.imshow('frame', frame) 21 | if cv2.waitKey(1) == ord('q'): 22 | break 23 | 24 | memfile = BytesIO() 25 | np.save(memfile, frame) 26 | memfile.seek(0) 27 | data = memfile.read() 28 | 29 | s.sendall(struct.pack("L", len(data)) + data) 30 | 31 | cap.release() 32 | --------------------------------------------------------------------------------