From 584ba2c1e85c626e90bbcfa6931faf8998c5ba84 Mon Sep 17 00:00:00 2001 From: shuaiqing Date: Fri, 4 Jun 2021 17:12:30 +0800 Subject: [PATCH] :rocket: [vis] add the real-time visualization --- Readme.md | 4 +- apps/vis/vis_client.py | 54 ++++ apps/vis/vis_server.py | 19 ++ config/vis/o3d_scene.yml | 39 +++ doc/assets/vis_client.png | Bin 0 -> 68250 bytes doc/assets/vis_server.png | Bin 0 -> 53723 bytes doc/quickstart.md | 13 +- doc/realtime_visualization.md | 74 +++++ easymocap/config/__init__.py | 9 + easymocap/config/baseconfig.py | 54 ++++ easymocap/config/vis_socket.py | 65 ++++ easymocap/config/yacs.py | 501 ++++++++++++++++++++++++++++++ easymocap/mytools/utils.py | 12 +- easymocap/mytools/vis_base.py | 66 ++-- easymocap/socket/base.py | 80 +++++ easymocap/socket/base_client.py | 25 ++ easymocap/socket/o3d.py | 114 +++++++ easymocap/socket/utils.py | 23 ++ easymocap/visualize/geometry.py | 54 +++- easymocap/visualize/o3dwrapper.py | 45 +++ easymocap/visualize/skelmodel.py | 59 +++- 21 files changed, 1258 insertions(+), 52 deletions(-) create mode 100644 apps/vis/vis_client.py create mode 100644 apps/vis/vis_server.py create mode 100644 config/vis/o3d_scene.yml create mode 100644 doc/assets/vis_client.png create mode 100644 doc/assets/vis_server.png create mode 100644 doc/realtime_visualization.md create mode 100644 easymocap/config/__init__.py create mode 100644 easymocap/config/baseconfig.py create mode 100644 easymocap/config/vis_socket.py create mode 100644 easymocap/config/yacs.py create mode 100644 easymocap/socket/base.py create mode 100644 easymocap/socket/base_client.py create mode 100644 easymocap/socket/o3d.py create mode 100644 easymocap/socket/utils.py create mode 100644 easymocap/visualize/o3dwrapper.py diff --git a/Readme.md b/Readme.md index a66e044..9595633 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,7 @@ * @Date: 2021-01-13 20:32:12 * @Author: Qing Shuai * @LastEditors: Qing Shuai - * @LastEditTime: 2021-04-14 16:00:04 + * @LastEditTime: 2021-06-04 17:12:01 * @FilePath: /EasyMocapRelease/Readme.md --> @@ -74,9 +74,11 @@ This project is used by many other projects: - [Pose guided synchronization](./doc/todo.md) (comming soon) - [Annotator](apps/calibration/Readme.md): a simple GUI annotator based on OpenCV - [Exporting of multiple data formats(bvh, asf/amc, ...)](./doc/02_output.md) +- [Real-time visualization](./doc/realtime_visualization.md) ## Updates +- 06/04/2021: The **real-time 3D visualization** part is released! - 04/12/2021: Mirrored-Human part is released. We also release the calibration tool and the annotator. ## Installation diff --git a/apps/vis/vis_client.py b/apps/vis/vis_client.py new file mode 100644 index 0000000..08e12e3 --- /dev/null +++ b/apps/vis/vis_client.py @@ -0,0 +1,54 @@ +''' + @ Date: 2021-05-24 18:57:48 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 16:43:00 + @ FilePath: /EasyMocapRelease/apps/vis/vis_client.py +''' +import socket +import time +from easymocap.socket.base_client import BaseSocketClient +import os + +def send_rand(client): + import numpy as np + for _ in range(1000): + k3d = np.random.rand(25, 4) + data = [ + { + 'id': 0, + 'keypoints3d': k3d + } + ] + client.send(data) + time.sleep(0.005) + client.close() + +def send_dir(client, path): + from os.path import join + from glob import glob + from tqdm import tqdm + from easymocap.mytools.reader import read_keypoints3d + results = sorted(glob(join(path, '*.json'))) + for result in tqdm(results): + data = read_keypoints3d(result) + client.send(data) + time.sleep(0.005) + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--host', type=str, default='auto') + parser.add_argument('--port', type=int, default=9999) + parser.add_argument('--path', type=str, default=None) + parser.add_argument('--debug', action='store_true') + args = parser.parse_args() + + if args.host == 'auto': + args.host = socket.gethostname() + client = BaseSocketClient(args.host, args.port) + + if args.path is not None and os.path.isdir(args.path): + send_dir(client, args.path) + else: + send_rand(client) \ No newline at end of file diff --git a/apps/vis/vis_server.py b/apps/vis/vis_server.py new file mode 100644 index 0000000..b1d5d54 --- /dev/null +++ b/apps/vis/vis_server.py @@ -0,0 +1,19 @@ +''' + @ Date: 2021-05-24 18:51:58 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 17:00:15 + @ FilePath: /EasyMocapRelease/apps/vis/vis_server.py +''' +# socket server for 3D visualization +from easymocap.socket.o3d import VisOpen3DSocket +from easymocap.config.vis_socket import Config + +def main(cfg): + server = VisOpen3DSocket(cfg.host, cfg.port, cfg) + while True: + server.update() + +if __name__ == "__main__": + cfg = Config.load_from_args() + main(cfg) \ No newline at end of file diff --git a/config/vis/o3d_scene.yml b/config/vis/o3d_scene.yml new file mode 100644 index 0000000..5973639 --- /dev/null +++ b/config/vis/o3d_scene.yml @@ -0,0 +1,39 @@ +host: 'auto' +port: 9999 + +width: 1920 +height: 1080 + +max_human: 5 +track: True +block: True # block visualization or not, True for visualize each frame, False in realtime applications +debug: False +write: False +out: 'none' + +body_model: + module: "easymocap.visualize.skelmodel.SkelModel" + args: + body_type: "body25" + joint_radius: 0.02 + gender: "neutral" + model_type: "smpl" + +scene: + "easymocap.visualize.o3dwrapper.create_coord": + camera: [0, 0, 0] + radius: 1 + "easymocap.visualize.o3dwrapper.create_bbox": + min_bound: [-3, -3, 0] + max_bound: [3, 3, 2] + flip: False + "easymocap.visualize.o3dwrapper.create_ground": + center: [0, 0, 0] + xdir: [1, 0, 0] + ydir: [0, 1, 0] + step: 1 + xrange: 3 + yrange: 3 + white: [1., 1., 1.] + black: [0.,0.,0.] + two_sides: True \ No newline at end of file diff --git a/doc/assets/vis_client.png b/doc/assets/vis_client.png new file mode 100644 index 0000000000000000000000000000000000000000..3e1d791d53a3826dfbb41a92094b345e48cf7424 GIT binary patch literal 68250 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�L_5`zOQ`IC``x3(meM(sFFrto|F{ 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 literal 0 HcmV?d00001 diff --git a/doc/assets/vis_server.png b/doc/assets/vis_server.png new file mode 100644 index 0000000000000000000000000000000000000000..2ca08c3e9aaaa856bab9a3acfb2d989b207e11d9 GIT binary patch literal 53723 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%0DMS*eLv(3ajpKa+IV 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 ahh+P14@};(v9u%CTth`ixkT~qlm7>=&#CMH literal 0 HcmV?d00001 diff --git a/doc/quickstart.md b/doc/quickstart.md index a15215d..c50455b 100644 --- a/doc/quickstart.md +++ b/doc/quickstart.md @@ -2,7 +2,7 @@ * @Date: 2021-04-02 11:53:16 * @Author: Qing Shuai * @LastEditors: Qing Shuai - * @LastEditTime: 2021-04-13 16:56:19 + * @LastEditTime: 2021-05-27 20:15:52 * @FilePath: /EasyMocapRelease/doc/quickstart.md --> # Quick Start @@ -15,11 +15,15 @@ We provide an example multiview dataset[[dropbox](https://www.dropbox.com/s/24mb data=path/to/data out=path/to/output # 0. extract the video to images -python3 scripts/preprocess/extract_video.py ${data} +python3 scripts/preprocess/extract_video.py ${data} --handface # 2.1 example for SMPL reconstruction -python3 apps/demo/mv1p.py ${data} --out ${out} --vis_det --vis_repro --undis --sub_vis 1 7 13 19 --vis_smpl +python3 apps/demo/mv1p.py ${data} --out ${out}/smpl --vis_det --vis_repro --undis --sub_vis 1 7 13 19 --vis_smpl # 2.2 example for SMPL-X reconstruction -python3 apps/demo/mv1p.py ${data} --out ${out} --vis_det --vis_repro --undis --sub_vis 1 7 13 19 --body bodyhandface --model smplx --gender male --vis_smpl +python3 apps/demo/mv1p.py ${data} --out ${out}/smplx --vis_det --vis_repro --undis --sub_vis 1 7 13 19 --body bodyhandface --model smplx --gender male --vis_smpl +# 2.3 example for MANO reconstruction +# MANO model is required for this part +python3 apps/demo/mv1p.py ${data} --out ${out}/manol --vis_det --vis_repro --undis --sub_vis 1 7 13 19 --body handl --model manol --gender male --vis_smpl +python3 apps/demo/mv1p.py ${data} --out ${out}/manor --vis_det --vis_repro --undis --sub_vis 1 7 13 19 --body handr --model manor --gender male --vis_smpl ``` # Demo On Your Dataset @@ -70,6 +74,7 @@ The output flags: - `--vis_repro`: visualize the reprojection - `--sub_vis`: use to specify the views to visualize. If not set, the code will use all views - `--vis_smpl`: use to render the SMPL mesh to images. +- `--write_smpl_full`: use to write the full poses of the SMPL parameters ### 3. Output diff --git a/doc/realtime_visualization.md b/doc/realtime_visualization.md new file mode 100644 index 0000000..7e144a2 --- /dev/null +++ b/doc/realtime_visualization.md @@ -0,0 +1,74 @@ + +# EasyMoCap -> Real-time Visualization + +We are the first one to release a real-time visualization tool for both skeletons and SMPL/SMPL+H/SMPL-X/MANO models. + +## Install + +Please install `EasyMocap` first. This part requires `Open3D==0.9.0`: + +```bash +python3 -m pip install open3d==0.9.0 +``` + +## Open the server +Before any visualization, you should run a server: + +```bash +python3 apps/vis/vis_server.py --cfg config/vis/o3d_scene.yml host port +``` + +This step will open the visualization window: + +![](./assets/vis_server.png) + +You can alternate the viewpoints free. The configuration file `config/vis/o3d_scene.yml` defines the scene and other properties. In the default setting, we define the xyz-axis in the origin, the bounding box of the scene and a chessboard in the ground. + +## Send the data + +If you are success to open the server, you can visualize your 3D data anywhere. We provide an example code: + +```bash +python3 apps/vis/vis_client.py --path --host --port +``` + +Take the `zju-ls-feng` results as example, you can show the skeleton in the main window: + +![](./assets/vis_client.png) + +## Embed this feature to your code + +To add this visualization to your other code, you can follow these steps: + +```bash +# 1. import the base client +from easymocap.socket.base_client import BaseSocketClient +# 2. set the ip address and port +client = BaseSocketClient(host, port) +# 3. send the data +client.send(data) +``` + +The format of data is: +```python +data = [ + { + 'id': 0, + 'keypoints3d': numpy.ndarray # (nJoints, 4) , (x, y, z, c) for each joint + }, + { + 'id': 1, + 'keypoints3d': numpy.ndarray # (nJoints, 4) + } +] +``` + +## Define your scene + +In the configuration file, we main define the `body_model` and `scene`. You can replace them for your data. \ No newline at end of file diff --git a/easymocap/config/__init__.py b/easymocap/config/__init__.py new file mode 100644 index 0000000..13bb1f1 --- /dev/null +++ b/easymocap/config/__init__.py @@ -0,0 +1,9 @@ +''' + @ Date: 2021-06-04 13:58:01 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 13:58:43 + @ FilePath: /EasyMocap/easymocap/config/__init__.py +''' +from .baseconfig import Config +from .baseconfig import load_object \ No newline at end of file diff --git a/easymocap/config/baseconfig.py b/easymocap/config/baseconfig.py new file mode 100644 index 0000000..0cbb601 --- /dev/null +++ b/easymocap/config/baseconfig.py @@ -0,0 +1,54 @@ +''' + @ Date: 2021-05-28 14:18:20 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 15:43:31 + @ FilePath: /EasyMocap/easymocap/config/baseconfig.py +''' +from .yacs import CfgNode as CN + +class Config: + @classmethod + def load_from_args(cls): + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--cfg', type=str, default='config/vis/base.yml') + parser.add_argument("opts", default=None, nargs=argparse.REMAINDER) + args = parser.parse_args() + return cls.load(filename=args.cfg, opts=args.opts) + + @classmethod + def load(cls, filename=None, opts=[]) -> CN: + cfg = CN() + cfg = cls.init(cfg) + if filename is not None: + cfg.merge_from_file(filename) + if len(opts) > 0: + cfg.merge_from_list(opts) + cls.parse(cfg) + cls.print(cfg) + return cfg + + @staticmethod + def init(cfg): + return cfg + + @staticmethod + def parse(cfg): + pass + + @staticmethod + def print(cfg): + print('[Info] --------------') + print('[Info] Configuration:') + print('[Info] --------------') + print(cfg) + +import importlib +def load_object(module_name, module_args): + module_path = '.'.join(module_name.split('.')[:-1]) + # scene_module = importlib.import_module(cfg.scene_module) + module = importlib.import_module(module_path) + name = module_name.split('.')[-1] + obj = getattr(module, name)(**module_args) + return obj \ No newline at end of file diff --git a/easymocap/config/vis_socket.py b/easymocap/config/vis_socket.py new file mode 100644 index 0000000..8cfed19 --- /dev/null +++ b/easymocap/config/vis_socket.py @@ -0,0 +1,65 @@ +''' + @ Date: 2021-05-30 11:17:18 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 15:44:56 + @ FilePath: /EasyMocap/easymocap/config/vis_socket.py +''' +from .baseconfig import CN +from .baseconfig import Config as BaseConfig +import socket +import numpy as np + +class Config(BaseConfig): + @staticmethod + def init(cfg): + # input and output + cfg.host = 'auto' + cfg.port = 9999 + cfg.width = 1920 + cfg.height = 1080 + + cfg.body = 'body25' + cfg.max_human = 5 + cfg.track = True + cfg.block = True # block visualization or not, True for visualize each frame, False in realtime applications + cfg.debug = False + cfg.write = False + cfg.out = '/' + # scene: + cfg.scene_module = "easymocap.visualize.o3dwrapper" + cfg.scene = CN() + cfg.extra = CN() + # skel + cfg.skel = CN() + cfg.skel.joint_radius = 0.02 + # camera + cfg.camera = CN() + cfg.camera.phi = 0 + cfg.camera.theta = -90 + 60 + cfg.camera.cx = 0. + cfg.camera.cy = 0. + cfg.camera.cz = 6. + cfg.camera.set_camera = False + cfg.camera.camera_pose = [] + return cfg + + @staticmethod + def parse(cfg): + if cfg.host == 'auto': + cfg.host = socket.gethostname() + if cfg.camera.set_camera: + pass + else:# use default camera + # theta, phi = cfg.camera.theta, cfg.camera.phi + theta, phi = np.deg2rad(cfg.camera.theta), np.deg2rad(cfg.camera.phi) + cx, cy, cz = cfg.camera.cx, cfg.camera.cy, cfg.camera.cz + st, ct = np.sin(theta), np.cos(theta) + sp, cp = np.sin(phi), np.cos(phi) + dist = 6 + camera_pose = np.array([ + [cp, -st*sp, ct*sp, cx], + [sp, st*cp, -ct*cp, cy], + [0., ct, st, cz], + [0.0, 0.0, 0.0, 1.0]]) + cfg.camera.camera_pose = camera_pose.tolist() \ No newline at end of file diff --git a/easymocap/config/yacs.py b/easymocap/config/yacs.py new file mode 100644 index 0000000..930f87b --- /dev/null +++ b/easymocap/config/yacs.py @@ -0,0 +1,501 @@ +# Copyright (c) 2018-present, Facebook, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################## + +"""YACS -- Yet Another Configuration System is designed to be a simple +configuration management system for academic and industrial research +projects. +See README.md for usage and examples. +""" + +import copy +import io +import logging +import os +from ast import literal_eval + +import yaml + + +# Flag for py2 and py3 compatibility to use when separate code paths are necessary +# When _PY2 is False, we assume Python 3 is in use +_PY2 = False + +# Filename extensions for loading configs from files +_YAML_EXTS = {"", ".yaml", ".yml"} +_PY_EXTS = {".py"} + +# py2 and py3 compatibility for checking file object type +# We simply use this to infer py2 vs py3 +try: + _FILE_TYPES = (file, io.IOBase) + _PY2 = True +except NameError: + _FILE_TYPES = (io.IOBase,) + +# CfgNodes can only contain a limited set of valid types +_VALID_TYPES = {tuple, list, str, int, float, bool} +# py2 allow for str and unicode +if _PY2: + _VALID_TYPES = _VALID_TYPES.union({unicode}) # noqa: F821 + +# Utilities for importing modules from file paths +if _PY2: + # imp is available in both py2 and py3 for now, but is deprecated in py3 + import imp +else: + import importlib.util + +logger = logging.getLogger(__name__) + + +class CfgNode(dict): + """ + CfgNode represents an internal node in the configuration tree. It's a simple + dict-like container that allows for attribute-based access to keys. + """ + + IMMUTABLE = "__immutable__" + DEPRECATED_KEYS = "__deprecated_keys__" + RENAMED_KEYS = "__renamed_keys__" + + def __init__(self, init_dict=None, key_list=None): + # Recursively convert nested dictionaries in init_dict into CfgNodes + init_dict = {} if init_dict is None else init_dict + key_list = [] if key_list is None else key_list + for k, v in init_dict.items(): + if type(v) is dict: + # Convert dict to CfgNode + init_dict[k] = CfgNode(v, key_list=key_list + [k]) + else: + # Check for valid leaf type or nested CfgNode + _assert_with_logging( + _valid_type(v, allow_cfg_node=True), + "Key {} with value {} is not a valid type; valid types: {}".format( + ".".join(key_list + [k]), type(v), _VALID_TYPES + ), + ) + super(CfgNode, self).__init__(init_dict) + # Manage if the CfgNode is frozen or not + self.__dict__[CfgNode.IMMUTABLE] = False + # Deprecated options + # If an option is removed from the code and you don't want to break existing + # yaml configs, you can add the full config key as a string to the set below. + self.__dict__[CfgNode.DEPRECATED_KEYS] = set() + # Renamed options + # If you rename a config option, record the mapping from the old name to the new + # name in the dictionary below. Optionally, if the type also changed, you can + # make the value a tuple that specifies first the renamed key and then + # instructions for how to edit the config file. + self.__dict__[CfgNode.RENAMED_KEYS] = { + # 'EXAMPLE.OLD.KEY': 'EXAMPLE.NEW.KEY', # Dummy example to follow + # 'EXAMPLE.OLD.KEY': ( # A more complex example to follow + # 'EXAMPLE.NEW.KEY', + # "Also convert to a tuple, e.g., 'foo' -> ('foo',) or " + # + "'foo:bar' -> ('foo', 'bar')" + # ), + } + + def __getattr__(self, name): + if name in self: + return self[name] + else: + raise AttributeError(name) + + def __setattr__(self, name, value): + if self.is_frozen(): + raise AttributeError( + "Attempted to set {} to {}, but CfgNode is immutable".format( + name, value + ) + ) + + _assert_with_logging( + name not in self.__dict__, + "Invalid attempt to modify internal CfgNode state: {}".format(name), + ) + _assert_with_logging( + _valid_type(value, allow_cfg_node=True), + "Invalid type {} for key {}; valid types = {}".format( + type(value), name, _VALID_TYPES + ), + ) + + self[name] = value + + def __str__(self): + def _indent(s_, num_spaces): + s = s_.split("\n") + if len(s) == 1: + return s_ + first = s.pop(0) + s = [(num_spaces * " ") + line for line in s] + s = "\n".join(s) + s = first + "\n" + s + return s + + r = "" + s = [] + for k, v in self.items(): + seperator = "\n" if isinstance(v, CfgNode) else " " + attr_str = "{}:{}{}".format(str(k), seperator, str(v)) + attr_str = _indent(attr_str, 4) + s.append(attr_str) + r += "\n".join(s) + return r + + def __repr__(self): + return "{}({})".format(self.__class__.__name__, super(CfgNode, self).__repr__()) + + def dump(self): + """Dump to a string.""" + self_as_dict = _to_dict(self) + return yaml.safe_dump(self_as_dict) + + def merge_from_file(self, cfg_filename): + """Load a yaml config file and merge it this CfgNode.""" + with open(cfg_filename, "r") as f: + cfg = load_cfg(f) + if 'parent' in cfg.keys(): + if cfg.parent != 'none': + print('[Config] merge from parent file: {}'.format(cfg.parent)) + self.merge_from_file(cfg.parent) + self.merge_from_other_cfg(cfg) + + def merge_from_other_cfg(self, cfg_other): + """Merge `cfg_other` into this CfgNode.""" + _merge_a_into_b(cfg_other, self, self, []) + + def merge_from_list(self, cfg_list): + """Merge config (keys, values) in a list (e.g., from command line) into + this CfgNode. For example, `cfg_list = ['FOO.BAR', 0.5]`. + """ + _assert_with_logging( + len(cfg_list) % 2 == 0, + "Override list has odd length: {}; it must be a list of pairs".format( + cfg_list + ), + ) + root = self + for full_key, v in zip(cfg_list[0::2], cfg_list[1::2]): + if root.key_is_deprecated(full_key): + continue + if root.key_is_renamed(full_key): + root.raise_key_rename_error(full_key) + key_list = full_key.split(".") + d = self + for subkey in key_list[:-1]: + _assert_with_logging( + subkey in d, "Non-existent key: {}".format(full_key) + ) + d = d[subkey] + subkey = key_list[-1] + _assert_with_logging(subkey in d, "Non-existent key: {}".format(full_key)) + value = _decode_cfg_value(v) + value = _check_and_coerce_cfg_value_type(value, d[subkey], subkey, full_key) + d[subkey] = value + + def freeze(self): + """Make this CfgNode and all of its children immutable.""" + self._immutable(True) + + def defrost(self): + """Make this CfgNode and all of its children mutable.""" + self._immutable(False) + + def is_frozen(self): + """Return mutability.""" + return self.__dict__[CfgNode.IMMUTABLE] + + def _immutable(self, is_immutable): + """Set immutability to is_immutable and recursively apply the setting + to all nested CfgNodes. + """ + self.__dict__[CfgNode.IMMUTABLE] = is_immutable + # Recursively set immutable state + for v in self.__dict__.values(): + if isinstance(v, CfgNode): + v._immutable(is_immutable) + for v in self.values(): + if isinstance(v, CfgNode): + v._immutable(is_immutable) + + def clone(self): + """Recursively copy this CfgNode.""" + return copy.deepcopy(self) + + def register_deprecated_key(self, key): + """Register key (e.g. `FOO.BAR`) a deprecated option. When merging deprecated + keys a warning is generated and the key is ignored. + """ + _assert_with_logging( + key not in self.__dict__[CfgNode.DEPRECATED_KEYS], + "key {} is already registered as a deprecated key".format(key), + ) + self.__dict__[CfgNode.DEPRECATED_KEYS].add(key) + + def register_renamed_key(self, old_name, new_name, message=None): + """Register a key as having been renamed from `old_name` to `new_name`. + When merging a renamed key, an exception is thrown alerting to user to + the fact that the key has been renamed. + """ + _assert_with_logging( + old_name not in self.__dict__[CfgNode.RENAMED_KEYS], + "key {} is already registered as a renamed cfg key".format(old_name), + ) + value = new_name + if message: + value = (new_name, message) + self.__dict__[CfgNode.RENAMED_KEYS][old_name] = value + + def key_is_deprecated(self, full_key): + """Test if a key is deprecated.""" + if full_key in self.__dict__[CfgNode.DEPRECATED_KEYS]: + logger.warning("Deprecated config key (ignoring): {}".format(full_key)) + return True + return False + + def key_is_renamed(self, full_key): + """Test if a key is renamed.""" + return full_key in self.__dict__[CfgNode.RENAMED_KEYS] + + def raise_key_rename_error(self, full_key): + new_key = self.__dict__[CfgNode.RENAMED_KEYS][full_key] + if isinstance(new_key, tuple): + msg = " Note: " + new_key[1] + new_key = new_key[0] + else: + msg = "" + raise KeyError( + "Key {} was renamed to {}; please update your config.{}".format( + full_key, new_key, msg + ) + ) + + +def load_cfg(cfg_file_obj_or_str): + """Load a cfg. Supports loading from: + - A file object backed by a YAML file + - A file object backed by a Python source file that exports an attribute + "cfg" that is either a dict or a CfgNode + - A string that can be parsed as valid YAML + """ + _assert_with_logging( + isinstance(cfg_file_obj_or_str, _FILE_TYPES + (str,)), + "Expected first argument to be of type {} or {}, but it was {}".format( + _FILE_TYPES, str, type(cfg_file_obj_or_str) + ), + ) + if isinstance(cfg_file_obj_or_str, str): + return _load_cfg_from_yaml_str(cfg_file_obj_or_str) + elif isinstance(cfg_file_obj_or_str, _FILE_TYPES): + return _load_cfg_from_file(cfg_file_obj_or_str) + else: + raise NotImplementedError("Impossible to reach here (unless there's a bug)") + + +def _load_cfg_from_file(file_obj): + """Load a config from a YAML file or a Python source file.""" + _, file_extension = os.path.splitext(file_obj.name) + if file_extension in _YAML_EXTS: + return _load_cfg_from_yaml_str(file_obj.read()) + elif file_extension in _PY_EXTS: + return _load_cfg_py_source(file_obj.name) + else: + raise Exception( + "Attempt to load from an unsupported file type {}; " + "only {} are supported".format(file_obj, _YAML_EXTS.union(_PY_EXTS)) + ) + + +def _load_cfg_from_yaml_str(str_obj): + """Load a config from a YAML string encoding.""" + cfg_as_dict = yaml.safe_load(str_obj) + return CfgNode(cfg_as_dict) + + +def _load_cfg_py_source(filename): + """Load a config from a Python source file.""" + module = _load_module_from_file("yacs.config.override", filename) + _assert_with_logging( + hasattr(module, "cfg"), + "Python module from file {} must have 'cfg' attr".format(filename), + ) + VALID_ATTR_TYPES = {dict, CfgNode} + _assert_with_logging( + type(module.cfg) in VALID_ATTR_TYPES, + "Imported module 'cfg' attr must be in {} but is {} instead".format( + VALID_ATTR_TYPES, type(module.cfg) + ), + ) + if type(module.cfg) is dict: + return CfgNode(module.cfg) + else: + return module.cfg + + +def _to_dict(cfg_node): + """Recursively convert all CfgNode objects to dict objects.""" + + def convert_to_dict(cfg_node, key_list): + if not isinstance(cfg_node, CfgNode): + _assert_with_logging( + _valid_type(cfg_node), + "Key {} with value {} is not a valid type; valid types: {}".format( + ".".join(key_list), type(cfg_node), _VALID_TYPES + ), + ) + return cfg_node + else: + cfg_dict = dict(cfg_node) + for k, v in cfg_dict.items(): + cfg_dict[k] = convert_to_dict(v, key_list + [k]) + return cfg_dict + + return convert_to_dict(cfg_node, []) + + +def _valid_type(value, allow_cfg_node=False): + return (type(value) in _VALID_TYPES) or (allow_cfg_node and type(value) == CfgNode) + + +def _merge_a_into_b(a, b, root, key_list): + """Merge config dictionary a into config dictionary b, clobbering the + options in b whenever they are also specified in a. + """ + _assert_with_logging( + isinstance(a, CfgNode), + "`a` (cur type {}) must be an instance of {}".format(type(a), CfgNode), + ) + _assert_with_logging( + isinstance(b, CfgNode), + "`b` (cur type {}) must be an instance of {}".format(type(b), CfgNode), + ) + + for k, v_ in a.items(): + full_key = ".".join(key_list + [k]) + # a must specify keys that are in b + if k not in b: + if root.key_is_deprecated(full_key): + continue + elif root.key_is_renamed(full_key): + root.raise_key_rename_error(full_key) + else: + v = copy.deepcopy(v_) + v = _decode_cfg_value(v) + b.update({k: v}) + else: + v = copy.deepcopy(v_) + v = _decode_cfg_value(v) + v = _check_and_coerce_cfg_value_type(v, b[k], k, full_key) + + # Recursively merge dicts + if isinstance(v, CfgNode): + try: + _merge_a_into_b(v, b[k], root, key_list + [k]) + except BaseException: + raise + else: + b[k] = v + + +def _decode_cfg_value(v): + """Decodes a raw config value (e.g., from a yaml config files or command + line argument) into a Python object. + """ + # Configs parsed from raw yaml will contain dictionary keys that need to be + # converted to CfgNode objects + if isinstance(v, dict): + return CfgNode(v) + # All remaining processing is only applied to strings + if not isinstance(v, str): + return v + # Try to interpret `v` as a: + # string, number, tuple, list, dict, boolean, or None + try: + v = literal_eval(v) + # The following two excepts allow v to pass through when it represents a + # string. + # + # Longer explanation: + # The type of v is always a string (before calling literal_eval), but + # sometimes it *represents* a string and other times a data structure, like + # a list. In the case that v represents a string, what we got back from the + # yaml parser is 'foo' *without quotes* (so, not '"foo"'). literal_eval is + # ok with '"foo"', but will raise a ValueError if given 'foo'. In other + # cases, like paths (v = 'foo/bar' and not v = '"foo/bar"'), literal_eval + # will raise a SyntaxError. + except ValueError: + pass + except SyntaxError: + pass + return v + + +def _check_and_coerce_cfg_value_type(replacement, original, key, full_key): + """Checks that `replacement`, which is intended to replace `original` is of + the right type. The type is correct if it matches exactly or is one of a few + cases in which the type can be easily coerced. + """ + original_type = type(original) + replacement_type = type(replacement) + + # The types must match (with some exceptions) + if replacement_type == original_type: + return replacement + + # Cast replacement from from_type to to_type if the replacement and original + # types match from_type and to_type + def conditional_cast(from_type, to_type): + if replacement_type == from_type and original_type == to_type: + return True, to_type(replacement) + else: + return False, None + + # Conditionally casts + # list <-> tuple + casts = [(tuple, list), (list, tuple), (int, float), (float, int)] + # For py2: allow converting from str (bytes) to a unicode string + try: + casts.append((str, unicode)) # noqa: F821 + except Exception: + pass + + for (from_type, to_type) in casts: + converted, converted_value = conditional_cast(from_type, to_type) + if converted: + return converted_value + + raise ValueError( + "Type mismatch ({} vs. {}) with values ({} vs. {}) for config " + "key: {}".format( + original_type, replacement_type, original, replacement, full_key + ) + ) + + +def _assert_with_logging(cond, msg): + if not cond: + logger.debug(msg) + assert cond, msg + + +def _load_module_from_file(name, filename): + if _PY2: + module = imp.load_source(name, filename) + else: + spec = importlib.util.spec_from_file_location(name, filename) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module \ No newline at end of file diff --git a/easymocap/mytools/utils.py b/easymocap/mytools/utils.py index e469eb4..339c54a 100644 --- a/easymocap/mytools/utils.py +++ b/easymocap/mytools/utils.py @@ -2,8 +2,8 @@ @ Date: 2021-01-15 11:12:00 @ Author: Qing Shuai @ LastEditors: Qing Shuai - @ LastEditTime: 2021-03-08 21:07:48 - @ FilePath: /EasyMocap/code/mytools/utils.py + @ LastEditTime: 2021-05-27 14:55:40 + @ FilePath: /EasyMocap/easymocap/mytools/utils.py ''' import time import tabulate @@ -30,4 +30,10 @@ class Timer: end = time.time() Timer.records[self.name].append((end-self.start)*1000) if not self.silent: - print('-> [{:20s}]: {:5.1f}ms'.format(self.name, (end-self.start)*1000)) + t = (end - self.start)*1000 + if t > 10000: + print('-> [{:20s}]: {:5.1f}s'.format(self.name, t/1000)) + elif t > 1e3*60*60: + print('-> [{:20s}]: {:5.1f}min'.format(self.name, t/1e3/60)) + else: + print('-> [{:20s}]: {:5.1f}ms'.format(self.name, (end-self.start)*1000)) diff --git a/easymocap/mytools/vis_base.py b/easymocap/mytools/vis_base.py index d43847c..e1b4da3 100644 --- a/easymocap/mytools/vis_base.py +++ b/easymocap/mytools/vis_base.py @@ -2,7 +2,7 @@ @ Date: 2020-11-28 17:23:04 @ Author: Qing Shuai @ LastEditors: Qing Shuai - @ LastEditTime: 2021-03-28 22:19:34 + @ LastEditTime: 2021-06-03 22:31:31 @ FilePath: /EasyMocap/easymocap/mytools/vis_base.py ''' import cv2 @@ -57,17 +57,26 @@ def get_rgb(index): col = tuple([int(c*255) for c in col[::-1]]) return col -def plot_point(img, x, y, r, col, pid=-1): - cv2.circle(img, (int(x+0.5), int(y+0.5)), r, col, -1) +def get_rgb_01(index): + col = get_rgb(index) + return [i*1./255 for i in col[:3]] + +def plot_point(img, x, y, r, col, pid=-1, font_scale=-1, circle_type=-1): + cv2.circle(img, (int(x+0.5), int(y+0.5)), r, col, circle_type) + if font_scale == -1: + font_scale = img.shape[0]/4000 if pid != -1: - cv2.putText(img, '{}'.format(pid), (int(x+0.5), int(y+0.5)), cv2.FONT_HERSHEY_SIMPLEX, 1, col, 2) + cv2.putText(img, '{}'.format(pid), (int(x+0.5), int(y+0.5)), cv2.FONT_HERSHEY_SIMPLEX, font_scale, col, 1) def plot_line(img, pt1, pt2, lw, col): cv2.line(img, (int(pt1[0]+0.5), int(pt1[1]+0.5)), (int(pt2[0]+0.5), int(pt2[1]+0.5)), col, lw) -def plot_cross(img, x, y, col, width=10, lw=2): +def plot_cross(img, x, y, col, width=-1, lw=-1): + if lw == -1: + lw = int(round(img.shape[0]/1000)) + width = lw * 5 cv2.line(img, (int(x-width), int(y)), (int(x+width), int(y)), col, lw) cv2.line(img, (int(x), int(y-width)), (int(x), int(y+width)), col, lw) @@ -82,7 +91,8 @@ def plot_bbox(img, bbox, pid, vis_id=True): lw = max(img.shape[0]//300, 2) cv2.rectangle(img, (x1, y1), (x2, y2), color, lw) if vis_id: - cv2.putText(img, '{}'.format(pid), (x1, y1+20), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2) + font_scale = img.shape[0]/1000 + cv2.putText(img, '{}'.format(pid), (x1, y1+int(25*font_scale)), cv2.FONT_HERSHEY_SIMPLEX, font_scale, color, 2) def plot_keypoints(img, points, pid, config, vis_conf=False, use_limb_color=True, lw=2): for ii, (i, j) in enumerate(config['kintree']): @@ -108,33 +118,44 @@ def plot_keypoints(img, points, pid, config, vis_conf=False, use_limb_color=True def plot_points2d(img, points2d, lines, lw=4, col=(0, 255, 0), putText=True): # 将2d点画上去 + if points2d.shape[1] == 2: + points2d = np.hstack([points2d, np.ones((points2d.shape[0], 1))]) for i, (x, y, v) in enumerate(points2d): if v < 0.01: continue c = col plot_cross(img, x, y, width=10, col=c, lw=lw) if putText: - cv2.putText(img, '{}'.format(i), (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 1, c, 2) + font_scale = img.shape[0]/2000 + cv2.putText(img, '{}'.format(i), (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, font_scale, c, 2) for i, j in lines: if points2d[i][2] < 0.01 or points2d[j][2] < 0.01: continue - plot_line(img, points2d[i], points2d[j], 2, (255, 255, 255)) + plot_line(img, points2d[i], points2d[j], 2, col) -def merge(images, row=-1, col=-1, resize=False, ret_range=False): - if row == -1 and col == -1: +row_col_ = { + 2: (2, 1), + 7: (2, 4), + 8: (2, 4), + 9: (3, 3), + 26: (4, 7) +} +def get_row_col(l): + if l in row_col_.keys(): + return row_col_[l] + else: from math import sqrt - row = int(sqrt(len(images)) + 0.5) - col = int(len(images)/ row + 0.5) + row = int(sqrt(l) + 0.5) + col = int(l/ row + 0.5) + if row*col col: row, col = col, row - if len(images) == 8: - # basketball 场景 - row, col = 2, 4 - images = [images[i] for i in [0, 1, 2, 3, 7, 6, 5, 4]] - if len(images) == 7: - row, col = 3, 3 - elif len(images) == 2: - row, col = 2, 1 + return row, col + +def merge(images, row=-1, col=-1, resize=False, ret_range=False, **kwargs): + if row == -1 and col == -1: + row, col = get_row_col(len(images)) height = images[0].shape[0] width = images[0].shape[1] ret_img = np.zeros((height * row, width * col, images[0].shape[2]), dtype=np.uint8) + 255 @@ -149,8 +170,9 @@ def merge(images, row=-1, col=-1, resize=False, ret_range=False): ret_img[height * i: height * (i+1), width * j: width * (j+1)] = img ranges.append((width*j, height*i, width*(j+1), height*(i+1))) if resize: - scale = min(1000/ret_img.shape[0], 1800/ret_img.shape[1]) - while ret_img.shape[0] > 2000: + min_height = 3000 + if ret_img.shape[0] > min_height: + scale = min_height/ret_img.shape[0] ret_img = cv2.resize(ret_img, None, fx=scale, fy=scale) if ret_range: return ret_img, ranges diff --git a/easymocap/socket/base.py b/easymocap/socket/base.py new file mode 100644 index 0000000..dd0dd77 --- /dev/null +++ b/easymocap/socket/base.py @@ -0,0 +1,80 @@ +''' + @ Date: 2021-05-25 11:14:48 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-02 13:00:35 + @ FilePath: /EasyMocap/easymocap/socket/base.py +''' +import socket +import time +from threading import Thread +from queue import Queue + +def log(x): + from datetime import datetime + time_now = datetime.now().strftime("%m-%d-%H:%M:%S.%f ") + print(time_now + x) + +class BaseSocket: + def __init__(self, host, port, debug=False) -> None: + # 创建 socket 对象 + print('[Info] server start') + serversocket = socket.socket( + socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind((host, port)) + serversocket.listen(1) + self.serversocket = serversocket + self.t = Thread(target=self.run) + self.t.start() + self.queue = Queue() + self.debug = debug + self.disconnect = False + + @staticmethod + def recvLine(sock): + flag = True + result = b'' + while not result.endswith(b'\n'): + res = sock.recv(1) + if not res: + flag = False + break + result += res + return flag, result.strip().decode('ascii') + + @staticmethod + def recvAll(sock, l): + l = int(l) + result = b'' + while (len(result) < l): + t = sock.recv(l - len(result)) + result += t + return result.decode('ascii') + + def run(self): + while True: + clientsocket, addr = self.serversocket.accept() + print("[Info] Connect: %s" % str(addr)) + while True: + flag, l = self.recvLine(clientsocket) + if not flag: + print("[Info] Disonnect: %s" % str(addr)) + break + data = self.recvAll(clientsocket, l) + if self.debug:log('[Info] Recv data') + self.queue.put(data) + clientsocket.close() + + def update(self): + time.sleep(1) + while not self.queue.empty(): + log('update') + data = self.queue.get() + self.main(data) + + def main(self, datas): + print(datas) + + def __del__(self): + self.serversocket.close() + self.t.join() \ No newline at end of file diff --git a/easymocap/socket/base_client.py b/easymocap/socket/base_client.py new file mode 100644 index 0000000..f4983cf --- /dev/null +++ b/easymocap/socket/base_client.py @@ -0,0 +1,25 @@ +''' + @ Date: 2021-05-25 13:39:07 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 16:43:39 + @ FilePath: /EasyMocapRelease/easymocap/socket/base_client.py +''' +import socket +from .utils import encode_detect + +class BaseSocketClient: + def __init__(self, host, port) -> None: + if host == 'auto': + host = socket.gethostname() + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + self.s = s + + def send(self, data): + val = encode_detect(data) + self.s.send(bytes('{}\n'.format(len(val)), 'ascii')) + self.s.sendall(val) + + def close(self): + self.s.close() \ No newline at end of file diff --git a/easymocap/socket/o3d.py b/easymocap/socket/o3d.py new file mode 100644 index 0000000..52cfb15 --- /dev/null +++ b/easymocap/socket/o3d.py @@ -0,0 +1,114 @@ +''' + @ Date: 2021-05-25 11:15:53 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 17:06:17 + @ FilePath: /EasyMocapRelease/easymocap/socket/o3d.py +''' +import open3d as o3d +from ..config import load_object +from ..visualize.o3dwrapper import Vector3dVector, create_mesh, load_mesh +from ..mytools import Timer +from ..mytools.vis_base import get_rgb_01 +from .base import BaseSocket, log +import json +import numpy as np +from os.path import join +import os + +class VisOpen3DSocket(BaseSocket): + def __init__(self, host, port, cfg) -> None: + # output + self.write = cfg.write + self.out = cfg.out + if self.write: + print('[Info] capture the screen to {}'.format(self.out)) + os.makedirs(self.out, exist_ok=True) + # scene + vis = o3d.visualization.Visualizer() + vis.create_window(window_name='Visualizer', width=cfg.width, height=cfg.height) + self.vis = vis + # load the scene + for key, mesh_args in cfg.scene.items(): + mesh = load_object(key, mesh_args) + self.vis.add_geometry(mesh) + for key, val in cfg.extra.items(): + mesh = load_mesh(val["path"]) + trans = np.array(val['transform']).reshape(4, 4) + mesh.transform(trans) + self.vis.add_geometry(mesh) + # create vis => update => super() init + super().__init__(host, port, debug=cfg.debug) + self.block = cfg.block + self.body_model = load_object(cfg.body_model.module, cfg.body_model.args) + zero_params = self.body_model.init_params(1) + self.max_human = cfg.max_human + self.track = cfg.track + self.camera_pose = cfg.camera.camera_pose + self.init_camera(cfg.camera.camera_pose) + self.zero_vertices = Vector3dVector(np.zeros((self.body_model.nVertices, 3))) + + self.vertices, self.meshes = [], [] + for i in range(self.max_human): + self.add_human(zero_params) + + self.count = 0 + + def add_human(self, zero_params): + vertices = self.body_model(zero_params)[0] + self.vertices.append(vertices) + mesh = create_mesh(vertices=vertices, faces=self.body_model.faces) + self.meshes.append(mesh) + self.vis.add_geometry(mesh) + self.init_camera(self.camera_pose) + + def init_camera(self, camera_pose): + ctr = self.vis.get_view_control() + init_param = ctr.convert_to_pinhole_camera_parameters() + # init_param.intrinsic.set_intrinsics(init_param.intrinsic.width, init_param.intrinsic.height, fx, fy, cx, cy) + init_param.extrinsic = np.array(camera_pose) + ctr.convert_from_pinhole_camera_parameters(init_param) + + def main(self, datas): + if self.debug:log('[Info] Load data {}'.format(self.count)) + datas = json.loads(datas) + with Timer('forward'): + for i, data in enumerate(datas): + if i >= len(self.meshes): + print('[Error] the number of human exceeds!') + self.add_human(np.array(data['keypoints3d'])) + vertices = self.body_model(np.array(data['keypoints3d'])) + self.vertices[i] = Vector3dVector(vertices[0]) + for i in range(len(datas), len(self.meshes)): + self.vertices[i] = self.zero_vertices + # Open3D will lock the thread here + with Timer('set vertices'): + for i in range(len(self.vertices)): + self.meshes[i].vertices = self.vertices[i] + if i < len(datas) and self.track: + col = get_rgb_01(datas[i]['id']) + self.meshes[i].paint_uniform_color(col) + + def update(self): + if not self.queue.empty(): + if self.debug:log('Update' + str(self.queue.qsize())) + datas = self.queue.get() + if not self.block: + while self.queue.qsize() > 0: + datas = self.queue.get() + self.main(datas) + with Timer('update geometry'): + for mesh in self.meshes: + mesh.compute_triangle_normals() + self.vis.update_geometry(mesh) + self.vis.poll_events() + self.vis.update_renderer() + if self.write: + outname = join(self.out, '{:06d}.jpg'.format(self.count)) + with Timer('capture'): + self.vis.capture_screen_image(outname) + self.count += 1 + else: + with Timer('update renderer', True): + self.vis.poll_events() + self.vis.update_renderer() \ No newline at end of file diff --git a/easymocap/socket/utils.py b/easymocap/socket/utils.py new file mode 100644 index 0000000..af020ae --- /dev/null +++ b/easymocap/socket/utils.py @@ -0,0 +1,23 @@ +''' + @ Date: 2021-05-24 20:07:34 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-06-04 16:29:35 + @ FilePath: /EasyMocapRelease/media/qing/Project/mirror/EasyMocap/easymocap/socket/utils.py +''' +import cv2 +import numpy as np +from ..mytools.file_utils import write_common_results + +def encode_detect(data): + res = write_common_results(None, data, ['keypoints3d']) + res = res.replace('\r', '').replace('\n', '').replace(' ', '') + return res.encode('ascii') + +def encode_image(image): + fourcc = [int(cv2.IMWRITE_JPEG_QUALITY), 90] + #frame을 binary 형태로 변환 jpg로 decoding + result, img_encode = cv2.imencode('.jpg', image, fourcc) + data = np.array(img_encode) # numpy array로 안바꿔주면 ERROR + stringData = data.tostring() + return stringData \ No newline at end of file diff --git a/easymocap/visualize/geometry.py b/easymocap/visualize/geometry.py index 0118716..afa84a4 100644 --- a/easymocap/visualize/geometry.py +++ b/easymocap/visualize/geometry.py @@ -2,12 +2,13 @@ @ Date: 2021-01-17 22:44:34 @ Author: Qing Shuai @ LastEditors: Qing Shuai - @ LastEditTime: 2021-04-03 20:00:02 - @ FilePath: /EasyMocap/code/visualize/geometry.py + @ LastEditTime: 2021-05-25 14:01:24 + @ FilePath: /EasyMocap/easymocap/visualize/geometry.py ''' import numpy as np import cv2 import numpy as np +from tqdm import tqdm def create_ground( center=[0, 0, 0], xdir=[1, 0, 0], ydir=[0, 1, 0], # 位置 @@ -19,15 +20,15 @@ def create_ground( center = np.array(center) xdir = np.array(xdir) ydir = np.array(ydir) - print(center, xdir, ydir) + print('[Vis Info] {}, x: {}, y: {}'.format(center, xdir, ydir)) xdir = xdir * step ydir = ydir * step vertls, trils, colls = [],[],[] cnt = 0 min_x = -xrange if two_sides else 0 min_y = -yrange if two_sides else 0 - for i in range(min_x, xrange+1): - for j in range(min_y, yrange+1): + for i in range(min_x, xrange): + for j in range(min_y, yrange): point0 = center + i*xdir + j*ydir point1 = center + (i+1)*xdir + j*ydir point2 = center + (i+1)*xdir + (j+1)*ydir @@ -107,3 +108,46 @@ def create_cameras(cameras): 'vertices': vertices, 'faces': triangles, 'name': 'camera_{}'.format(nv), 'vid': nv }) return meshes + +import os +current_dir = os.path.dirname(os.path.realpath(__file__)) + +def create_cameras_texture(cameras, imgnames, scale=5e-3): + import trimesh + import pyrender + from PIL import Image + from os.path import join + cam_path = join(current_dir, 'objs', 'background.obj') + meshes = [] + for nv, (key, camera) in enumerate(tqdm(cameras.items(), desc='loading images')): + cam_trimesh = trimesh.load(cam_path, process=False) + vert = np.asarray(cam_trimesh.vertices) + K, R, T = camera['K'], camera['R'], camera['T'] + img = Image.open(imgnames[nv]) + height, width = img.height, img.width + vert[:, 0] *= width + vert[:, 1] *= height + vert[:, 2] *= 0 + vert[:, 0] -= vert[:, 0]*0.5 + vert[:, 1] -= vert[:, 1]*0.5 + vert[:, 1] = - vert[:, 1] + vert[:, :2] *= scale + # vert[:, 2] = 1 + cam_trimesh.vertices = (vert - T.T) @ R + cam_trimesh.visual.material.image = img + cam_mesh = pyrender.Mesh.from_trimesh(cam_trimesh, smooth=True) + meshes.append(cam_mesh) + return meshes + +def create_mesh_pyrender(vert, faces, col): + import trimesh + import pyrender + mesh = trimesh.Trimesh(vert, faces, process=False) + material = pyrender.MetallicRoughnessMaterial( + metallicFactor=0.0, + alphaMode='OPAQUE', + baseColorFactor=col) + mesh = pyrender.Mesh.from_trimesh( + mesh, + material=material) + return mesh \ No newline at end of file diff --git a/easymocap/visualize/o3dwrapper.py b/easymocap/visualize/o3dwrapper.py new file mode 100644 index 0000000..48f80f9 --- /dev/null +++ b/easymocap/visualize/o3dwrapper.py @@ -0,0 +1,45 @@ +''' + @ Date: 2021-04-25 15:52:01 + @ Author: Qing Shuai + @ LastEditors: Qing Shuai + @ LastEditTime: 2021-05-25 11:48:49 + @ FilePath: /EasyMocap/easymocap/visualize/o3dwrapper.py +''' +import open3d as o3d +import numpy as np + +Vector3dVector = o3d.utility.Vector3dVector +Vector3iVector = o3d.utility.Vector3iVector +Vector2iVector = o3d.utility.Vector2iVector +TriangleMesh = o3d.geometry.TriangleMesh +load_mesh = o3d.io.read_triangle_mesh + +def create_mesh(vertices, faces, colors=None, **kwargs): + mesh = TriangleMesh() + mesh.vertices = Vector3dVector(vertices) + mesh.triangles = Vector3iVector(faces) + if colors is not None: + mesh.vertex_colors = Vector3dVector(colors) + else: + mesh.paint_uniform_color([1., 0.8, 0.8]) + mesh.compute_vertex_normals() + return mesh + +def create_ground(**kwargs): + from .geometry import create_ground as create_ground_ + ground = create_ground_(**kwargs) + return create_mesh(**ground) + +def create_coord(camera = [0,0,0], radius=1): + camera_frame = TriangleMesh.create_coordinate_frame( + size=radius, origin=camera) + return camera_frame + +def create_bbox(min_bound=(-3., -3., 0), max_bound=(3., 3., 2), flip=False): + if flip: + min_bound_ = min_bound.copy() + max_bound_ = max_bound.copy() + min_bound = [min_bound_[0], -max_bound_[1], -max_bound_[2]] + max_bound = [max_bound_[0], -min_bound_[1], -min_bound_[2]] + bbox = o3d.geometry.AxisAlignedBoundingBox(min_bound, max_bound) + return bbox diff --git a/easymocap/visualize/skelmodel.py b/easymocap/visualize/skelmodel.py index 0ad9dc1..de4090f 100644 --- a/easymocap/visualize/skelmodel.py +++ b/easymocap/visualize/skelmodel.py @@ -2,13 +2,14 @@ @ Date: 2021-01-17 21:38:19 @ Author: Qing Shuai @ LastEditors: Qing Shuai - @ LastEditTime: 2021-01-22 23:08:18 - @ FilePath: /EasyMocap/code/visualize/skelmodel.py + @ LastEditTime: 2021-06-04 15:52:49 + @ FilePath: /EasyMocap/easymocap/visualize/skelmodel.py ''' import numpy as np import cv2 from os.path import join import os +from ..dataset.config import CONFIG def calTransformation(v_i, v_j, r, adaptr=False, ratio=10): """ from to vertices to T @@ -27,7 +28,7 @@ def calTransformation(v_i, v_j, r, adaptr=False, ratio=10): rotdir = rotdir * np.arccos(np.dot(direc, xaxis)) rotmat, _ = cv2.Rodrigues(rotdir) # set the minimal radius for the finger and face - shrink = max(length/ratio, 0.005) + shrink = min(max(length/ratio, 0.005), 0.05) eigval = np.array([[length/2/r, 0, 0], [0, shrink, 0], [0, 0, shrink]]) T = np.eye(4) T[:3,:3] = rotmat @ eigval @ rotmat.T @@ -35,33 +36,36 @@ def calTransformation(v_i, v_j, r, adaptr=False, ratio=10): return T, r, length class SkelModel: - def __init__(self, nJoints, kintree) -> None: - self.nJoints = nJoints - self.kintree = kintree + def __init__(self, nJoints=None, kintree=None, body_type=None, joint_radius=0.02, **kwargs) -> None: + if nJoints is not None: + self.nJoints = nJoints + self.kintree = kintree + else: + config = CONFIG[body_type] + self.nJoints = config['nJoints'] + self.kintree = config['kintree'] cur_dir = os.path.dirname(__file__) faces = np.loadtxt(join(cur_dir, 'sphere_faces_20.txt'), dtype=np.int) self.vertices = np.loadtxt(join(cur_dir, 'sphere_vertices_20.txt')) # compose faces faces_all = [] - for nj in range(nJoints): + for nj in range(self.nJoints): faces_all.append(faces + nj*self.vertices.shape[0]) - for nk in range(len(kintree)): - faces_all.append(faces + nJoints*self.vertices.shape[0] + nk*self.vertices.shape[0]) + for nk in range(len(self.kintree)): + faces_all.append(faces + self.nJoints*self.vertices.shape[0] + nk*self.vertices.shape[0]) self.faces = np.vstack(faces_all) + self.nVertices = self.vertices.shape[0] * self.nJoints + self.vertices.shape[0] * len(self.kintree) + self.joint_radius = joint_radius - def __call__(self, keypoints3d, id=0, return_verts=True, return_tensor=False): + def __call__(self, keypoints3d, id=0, return_verts=True, return_tensor=False, **kwargs): vertices_all = [] - r = 0.02 + r = self.joint_radius # joints for nj in range(self.nJoints): - if nj > 25: - r_ = r * 0.4 - else: - r_ = r if keypoints3d[nj, -1] < 0.01: vertices_all.append(self.vertices*0.001) continue - vertices_all.append(self.vertices*r_ + keypoints3d[nj:nj+1, :3]) + vertices_all.append(self.vertices*r + keypoints3d[nj:nj+1, :3]) # limb for nk, (i, j) in enumerate(self.kintree): if keypoints3d[i][-1] < 0.1 or keypoints3d[j][-1] < 0.1: @@ -74,4 +78,25 @@ class SkelModel: vertices = self.vertices @ T[:3, :3].T + T[:3, 3:].T vertices_all.append(vertices) vertices = np.vstack(vertices_all) - return vertices[None, :, :] \ No newline at end of file + return vertices[None, :, :] + + def init_params(self, nFrames): + return np.zeros((self.nJoints, 4)) + +class SMPLSKEL: + def __init__(self, model_type, gender, body_type) -> None: + from ..smplmodel import load_model + config = CONFIG[body_type] + self.smpl_model = load_model(gender, model_type=model_type, skel_type=body_type) + self.body_model = SkelModel(config['nJoints'], config['kintree']) + + def __call__(self, return_verts=True, **kwargs): + keypoints3d = self.smpl_model(return_verts=False, return_tensor=False, **kwargs) + if not return_verts: + return keypoints3d + else: + verts = self.body_model(return_verts=True, return_tensor=False, keypoints3d=keypoints3d[0]) + return verts + + def init_params(self, nFrames): + return np.zeros((self.body_model.nJoints, 4)) \ No newline at end of file