zhenyueqin commited on
Commit
d32c009
ยท
verified ยท
1 Parent(s): aab969f

Force push - update all files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ examples/a.png filter=lfs diff=lfs merge=lfs -text
37
+ examples/b.png filter=lfs diff=lfs merge=lfs -text
38
+ templates/fist-back.png filter=lfs diff=lfs merge=lfs -text
39
+ templates/opened-palm.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
README.md CHANGED
@@ -1,14 +1,14 @@
1
  ---
2
- title: Handcraft
3
- emoji: ๐Ÿ“š
4
- colorFrom: gray
5
- colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 5.21.0
8
  app_file: app.py
9
  pinned: false
10
  license: cc
11
- short_description: Sourcecode of HandCraft (WACV 2025)
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: HandCraft
3
+ emoji: ๐Ÿ–๏ธ
4
+ colorFrom: pink
5
+ colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.21.0
8
  app_file: app.py
9
  pinned: false
10
  license: cc
11
+ short_description: Official sourcecode for HandCraft (WACV 2025)
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
component/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .bbox import bbox
2
+ from .skeleton import skeleton
3
+ from .control import control
component/bbox.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ultralytics import YOLO
2
+ import cv2
3
+ import numpy as np
4
+
5
+
6
+ hand_dict = {0: 'Standard', 1: 'Non-Standard'}
7
+
8
+ def bbox(image, include_standard, mask_expand_ratio):
9
+ model = YOLO('model/yolo.pt')
10
+
11
+ solution = model.predict(image)
12
+
13
+ # detect bbox
14
+ result = []
15
+ for det in solution[0]:
16
+ x1, y1, x2, y2, conf, cls = list(det.boxes.data[0])
17
+ bbox = [int(x1), int(y1), int(x2), int(y2), int(cls), float(conf), hand_dict[int(cls)]]
18
+ result.append(bbox)
19
+
20
+ print('BBox Result:', bbox, image.shape)
21
+
22
+ # draw bbox
23
+ for bbox in result:
24
+ COLOR = (0, 255, 0) if bbox[4] == 0 else (255, 0, 0)
25
+ cv2.rectangle(image, (bbox[0], bbox[1]), (bbox[2], bbox[3]), COLOR, 2)
26
+ cv2.putText(image, str(bbox[6]), (bbox[0], bbox[1]), cv2.FONT_HERSHEY_SIMPLEX, 1, COLOR, 2, cv2.LINE_AA)
27
+
28
+ # get mask
29
+ image_mask = np.zeros(image.shape, dtype=np.uint8)
30
+ for bbox in result:
31
+ # only non-standard
32
+ if not include_standard and bbox[4] == 0:
33
+ continue
34
+
35
+ # expand
36
+ bbox[0] = max(0, bbox[0] - int((bbox[2] - bbox[0]) * (mask_expand_ratio - 1) * 0.5))
37
+ bbox[1] = max(0, bbox[1] - int((bbox[3] - bbox[1]) * (mask_expand_ratio - 1) * 0.5))
38
+ bbox[2] = min(image.shape[1], bbox[2] + int((bbox[2] - bbox[0]) * (mask_expand_ratio - 1) * 0.5))
39
+ bbox[3] = min(image.shape[0], bbox[3] + int((bbox[3] - bbox[1]) * (mask_expand_ratio - 1) * 0.5))
40
+
41
+ image_mask[bbox[1]:bbox[3], bbox[0]:bbox[2]] = 255
42
+
43
+ # get bbox info
44
+ bbox_info = f'Number of hands: {len(result)}\n'
45
+ for i, hand in enumerate(result):
46
+ bbox_info += f'Hand {i}: {hand[6]} with confidence {hand[5]:.4f}.\n'
47
+
48
+ return bbox_info, image, image_mask
component/control.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import json
4
+ from datetime import datetime
5
+
6
+
7
+ def read_template(name, type):
8
+ path_template = f'templates/{name}.png'
9
+ path_keypoint = f'templates/{name}.txt'
10
+
11
+ template = cv2.cvtColor(cv2.imread(path_template), cv2.COLOR_BGR2RGB)
12
+ template_keypoint = np.loadtxt(path_keypoint, dtype=np.int32, delimiter=',')
13
+
14
+ if type == 'b':
15
+ template = cv2.flip(template, 1)
16
+ template_keypoint[:, 0] = template.shape[1] - template_keypoint[:, 0]
17
+
18
+ return template, template_keypoint
19
+
20
+
21
+ def affine(image, image_keypoint, template, template_keypoint, expand_ratio):
22
+ # pre process
23
+ template_white = np.full(template.shape, 255, dtype=np.uint8)
24
+
25
+ # resize
26
+ scale = np.linalg.norm(image_keypoint[0] - image_keypoint[2]) / np.linalg.norm(
27
+ template_keypoint[0] - template_keypoint[2])
28
+ scale *= expand_ratio
29
+ M = np.array([
30
+ [scale, 0, 0],
31
+ [0, scale, 0],
32
+ ], dtype=np.float32)
33
+ size = (int(template.shape[1] * scale), int(template.shape[0] * scale))
34
+ template_white = cv2.warpAffine(template_white, M, size)
35
+ template = cv2.warpAffine(template, M, size)
36
+ template_keypoint = template_keypoint * scale
37
+
38
+ # move
39
+ distance = image_keypoint[0] - template_keypoint[0]
40
+ M = np.array([
41
+ [1, 0, distance[0]],
42
+ [0, 1, distance[1]],
43
+ ], dtype=np.float32)
44
+ template_white = cv2.warpAffine(template_white, M, image.shape[1::-1])
45
+ template = cv2.warpAffine(template, M, image.shape[1::-1])
46
+
47
+ # rotate
48
+ v1 = template_keypoint[2] - template_keypoint[0]
49
+ v2 = image_keypoint[2] - image_keypoint[0]
50
+
51
+ angle = np.rad2deg(np.arccos(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))))
52
+ if np.cross(v1, v2) > 0:
53
+ angle = -angle
54
+
55
+ M = cv2.getRotationMatrix2D(image_keypoint[0], angle, 1)
56
+ template_white = cv2.warpAffine(template_white, M, image.shape[1::-1])
57
+ template = cv2.warpAffine(template, M, image.shape[1::-1])
58
+
59
+ return template_white, template
60
+
61
+
62
+ def control(image, image_bbox_mask, res_skeleton, expand_ratio, template_name, image_skeleton, force):
63
+ # left hand
64
+ template_white_l, template_l = np.zeros_like(image), np.zeros_like(image)
65
+ image_keypoint = np.array(json.loads(res_skeleton))[0]
66
+ if np.max(image_keypoint) < 1:
67
+ image_keypoint[:, 0], image_keypoint[:, 1] = image_keypoint[:, 1] * image.shape[1], image_keypoint[:, 0] * image.shape[0]
68
+ # check hand be detected by yolo
69
+ be_detected = False
70
+ for keypoint in image_keypoint:
71
+ if np.sum(image_bbox_mask[int(keypoint[1]), int(keypoint[0])]) > 1:
72
+ be_detected = True
73
+ break
74
+ if be_detected or force:
75
+
76
+ if np.cross(image_keypoint[2] - image_keypoint[0], image_keypoint[3] - image_keypoint[1]) > 0:
77
+ template, template_keypoint = read_template(template_name, 'a')
78
+ else:
79
+ template, template_keypoint = read_template(template_name, 'b')
80
+
81
+ template_white_l, template_l = affine(image, image_keypoint, template, template_keypoint, expand_ratio)
82
+
83
+ # right hand
84
+ template_white_r, template_r = np.zeros_like(image), np.zeros_like(image)
85
+
86
+ image_keypoint = np.array(json.loads(res_skeleton))[1]
87
+ if np.max(image_keypoint) < 1:
88
+ image_keypoint[:, 0], image_keypoint[:, 1] = image_keypoint[:, 1] * image.shape[1], image_keypoint[:, 0] * image.shape[0]
89
+ # check hand be detected by yolo
90
+ be_detected = False
91
+ for keypoint in image_keypoint:
92
+ if np.sum(image_bbox_mask[int(keypoint[1]), int(keypoint[0])]) > 1:
93
+ be_detected = True
94
+ break
95
+ if be_detected or force:
96
+
97
+ if np.cross(image_keypoint[2] - image_keypoint[0], image_keypoint[3] - image_keypoint[1]) > 0:
98
+ template, template_keypoint = read_template(template_name, 'a')
99
+ else:
100
+ template, template_keypoint = read_template(template_name, 'b')
101
+
102
+ template_white_r, template_r = affine(image, image_keypoint, template, template_keypoint, expand_ratio)
103
+
104
+
105
+ # control image
106
+ template = np.clip(template_l.astype(np.int32) + template_r.astype(np.int32), 0, 255).astype(np.uint8)
107
+ # combine image
108
+ image_combine = np.clip(image.astype(np.int32) + template, 0, 255).astype(np.uint8)
109
+ # control mask
110
+ template_white = np.clip(template_white_l.astype(np.int32) + template_white_r.astype(np.int32), 0, 255).astype(np.uint8)
111
+ # union mask
112
+ image_union_mask = np.clip(image_bbox_mask.astype(np.int32) + template_white.astype(np.int32), 0, 255).astype(np.uint8)
113
+
114
+ # visualization
115
+ image_visualization = np.clip(
116
+ template.astype(np.int32) * - 1 +
117
+ image_skeleton.astype(np.int32) * 0.6 +
118
+ image_bbox_mask.astype(np.int32) * 0.3 +
119
+ template_white.astype(np.int32) * 0.3
120
+ , 0, 255).astype(np.uint8)
121
+
122
+ return image_visualization, template, template_white, image_union_mask
123
+
component/skeleton.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mediapipe as mp
2
+ import numpy as np
3
+ import json
4
+ from datetime import datetime
5
+
6
+ def skeleton(image):
7
+ mp_pose = mp.solutions.pose.Pose()
8
+ mp_drawing = mp.solutions.drawing_utils
9
+
10
+ solution = mp_pose.process(image)
11
+
12
+ # default value
13
+ result = np.zeros((33, 2))
14
+
15
+ # detect and draw skeleton
16
+ if solution.pose_landmarks:
17
+ mp_drawing.draw_landmarks(image, solution.pose_landmarks, mp.solutions.pose.POSE_CONNECTIONS)
18
+ result = np.array([
19
+ (landmark.y, landmark.x)
20
+ for landmark in solution.pose_landmarks.landmark
21
+ ])
22
+
23
+ # serialization
24
+ skeleton_info = json.dumps(np.array([result[15:22:2], result[16:23:2]]).tolist())
25
+
26
+ return skeleton_info, image
examples/a.png ADDED

Git LFS Details

  • SHA256: 04943c614293f66d4587c354a589a6d1d74c9f74fbd7f28b215b71040e09d234
  • Pointer size: 132 Bytes
  • Size of remote file: 4.06 MB
examples/b.png ADDED

Git LFS Details

  • SHA256: 3c2ccef51878f866eced40be237d8246e7e32201773924f653ba87bfd2869c5e
  • Pointer size: 132 Bytes
  • Size of remote file: 4.38 MB
main.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ from component import bbox
4
+ from component import skeleton
5
+ from component import control
6
+
7
+ # Load example image
8
+ image_example = cv2.cvtColor(cv2.imread("examples/a.png"), cv2.COLOR_BGR2RGB)
9
+ uni_height = 800
10
+
11
+ # Create the interface with a cute theme
12
+ with gr.Blocks(theme=gr.themes.Soft()) as interface:
13
+ # Title with kawaii emojis
14
+ with gr.Row():
15
+ with gr.Column(scale=1):
16
+ gr.Markdown(f"""
17
+ <div style='text-align: center;
18
+ padding: 25px;
19
+ border-radius: 12px;
20
+ background: #f3f4f6;
21
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08),
22
+ 0 2px 4px rgba(0, 0, 0, 0.05);
23
+ margin: 20px 0;
24
+ border: 1px solid rgba(0,0,0,0.1);
25
+ width: 100%;
26
+ position: relative;
27
+ transition: transform 0.2s ease, box-shadow 0.2s ease;'>
28
+ <h1 style='margin: 0;
29
+ color: #BE830E;
30
+ font-family: "Comic Sans MS", cursive;
31
+ text-shadow: 2px 2px 4px rgba(190, 131, 14, 0.1);
32
+ letter-spacing: 1.2px;
33
+ position: relative;
34
+ z-index: 2;'>
35
+ ๐Ÿ–๏ธ HandCraft ๐Ÿ–๏ธ
36
+ </h1>
37
+ <h3 style='margin: 10px 0 0;
38
+ color: #374151;
39
+ font-family: "Comic Sans MS", cursive;
40
+ font-weight: 500;
41
+ position: relative;
42
+ z-index: 2;
43
+ line-height: 1.4;
44
+ text-shadow: 0 1px 2px rgba(0,0,0,0.05);'>
45
+ ๐Ÿพโœจ Anatomically Correct Restoration of Malformed Hands in Diffusion Generated Images โœจ๐Ÿพ
46
+ </h3>
47
+ </div>
48
+ """)
49
+
50
+ # Shared input image at the top
51
+ input_image = gr.Image(
52
+ type="numpy",
53
+ label="๐Ÿ“ธ Upload Your Image with Hands",
54
+ height=uni_height,
55
+ value=image_example
56
+ )
57
+
58
+ # Button to trigger the cascade
59
+ generate_btn = gr.Button("โœจ๐Ÿช„ Generate Control Mask ๐Ÿช„โœจ", variant="primary", size="lg")
60
+
61
+ # State variables to store intermediate results
62
+ bbox_mask_state = gr.State()
63
+ keypoints_state = gr.State()
64
+ skeleton_state = gr.State()
65
+
66
+ # Results section with tabs for each step
67
+ with gr.Tabs():
68
+ with gr.TabItem("๐Ÿพ Step 1: Malformed Hand Detection"):
69
+ with gr.Row():
70
+ with gr.Column(scale=1):
71
+ output_bbox_result = gr.Textbox(label="๐Ÿท๏ธ Number of Hands & Classification with Confidence")
72
+ include_standard = gr.Checkbox(
73
+ label="๐Ÿคฒ Include Standard Hands",
74
+ value=False
75
+ )
76
+ expand_ratio = gr.Slider(
77
+ minimum=0.5,
78
+ maximum=2,
79
+ step=0.01,
80
+ value=1,
81
+ label="๐Ÿ“ Bounding Box Expand Ratio"
82
+ )
83
+ with gr.Column(scale=2):
84
+ with gr.Row():
85
+ with gr.Column(scale=1):
86
+ output_bbox_vis = gr.Image(type="numpy", label="๐Ÿ“ฆ Bounding Box", height=uni_height)
87
+ with gr.Column(scale=1):
88
+ output_bbox_mask = gr.Image(type="numpy", label="๐ŸŽญ Bounding Box Mask", height=uni_height)
89
+
90
+ with gr.TabItem("๐Ÿ’ƒ Step 2: Body Pose Estimation"):
91
+ with gr.Row():
92
+ with gr.Column(scale=1):
93
+ output_keypoints = gr.Textbox(label="๐Ÿ“Š Key Points String")
94
+ with gr.Column(scale=2):
95
+ output_skeleton = gr.Image(type="numpy", label="๐Ÿ’ช Body Skeleton", height=uni_height)
96
+
97
+ with gr.TabItem("๐ŸŽจ Step 3: Control Image Generation"):
98
+ with gr.Row():
99
+ with gr.Column(scale=1):
100
+ hand_template = gr.Radio(
101
+ ["opened-palm", "fist-back"],
102
+ label="๐Ÿ‘‹ Hand Template",
103
+ value="opened-palm"
104
+ )
105
+ control_expand = gr.Slider(
106
+ minimum=0.5,
107
+ maximum=2,
108
+ step=0.01,
109
+ value=1,
110
+ label="๐Ÿ” Control Image Expand Ratio"
111
+ )
112
+ include_undetected = gr.Checkbox(
113
+ label="๐Ÿ”Ž Include Undetected Hand",
114
+ value=False
115
+ )
116
+ with gr.Column(scale=2):
117
+ with gr.Row():
118
+ with gr.Column():
119
+ output_viz = gr.Image(type="numpy", label="๐Ÿ‘๏ธ Visualization Image", height=300)
120
+ with gr.Column():
121
+ output_control = gr.Image(type="numpy", label="๐ŸŽฎ Control Image", height=300)
122
+ with gr.Row():
123
+ with gr.Column():
124
+ output_control_mask = gr.Image(type="numpy", label="๐ŸŽญ Control Mask", height=300)
125
+ with gr.Column():
126
+ output_union_mask = gr.Image(type="numpy", label="๐Ÿ”„ Union Mask", height=300)
127
+
128
+ with gr.TabItem("๐ŸŽ‰ Output Control Image"):
129
+ gr.Markdown("""
130
+ ### โœจ๐ŸŒˆ Control Image ๐ŸŒˆโœจ
131
+ Your hand-fixed image is ready! (๏พ‰โ—•ใƒฎโ—•)๏พ‰*:๏ฝฅ๏พŸโœง
132
+ """)
133
+ with gr.Row():
134
+ with gr.Column():
135
+ output_final_control = gr.Image(type="numpy", label="๐Ÿ‘ Fixed Hand Image", interactive=False, height=uni_height)
136
+ with gr.Column():
137
+ gr.Markdown("""
138
+ ### ๐ŸŒŸโœจ How to Use Your Control Image โœจ๐ŸŒŸ
139
+
140
+ 1. ๐Ÿช„ Take this Control Image to your favorite Stable Diffusion model
141
+ 2. ๐ŸŽ€ Apply that to the ControlNet
142
+ 3. ๐Ÿฌ Sprinkle some parameters until it looks just right!
143
+
144
+ """)
145
+
146
+ # Citation information with cute emojis
147
+ with gr.Accordion("๐Ÿ“šโœจ Citation Information โœจ๐Ÿ“š", open=False):
148
+ gr.Markdown("""
149
+ If you find this tool helpful for your research, please cite our paper: ๐Ÿ“
150
+
151
+ ```bibtex
152
+ @InProceedings{2025_wacv_handcraft,
153
+ author = {Qin, Zhenyue and Zhang, Yiqun and Liu, Yang and Campbell, Dylan},
154
+ title = {HandCraft: Anatomically Correct Restoration of Malformed Hands in Diffusion Generated Images},
155
+ booktitle = {Proceedings of the Winter Conference on Applications of Computer Vision (WACV)},
156
+ month = {February},
157
+ year = {2025},
158
+ pages = {3925-3933}
159
+ }
160
+ ```
161
+
162
+ Thank you for using HandCraft! โœจ๐Ÿ‘โœจ
163
+ """)
164
+
165
+ # Define the step functions with improved data flow
166
+ def run_step1(image, include_std, expand_r):
167
+ # Step 1: Run hand detection
168
+ bbox_result, bbox_vis, bbox_mask = bbox(image, include_std, expand_r)
169
+ return bbox_result, bbox_vis, bbox_mask, bbox_mask
170
+
171
+ def run_step2(image):
172
+ # Step 2: Run pose estimation
173
+ keypoints, skeleton_img = skeleton(image)
174
+ return keypoints, skeleton_img, keypoints, skeleton_img
175
+
176
+ def run_step3(image, bbox_mask, keypoints, control_exp, hand_tmpl, skeleton_img, include_undetect):
177
+ # Step 3: Generate control images
178
+ viz, control_img, control_mask, union_mask = control(
179
+ image, bbox_mask, keypoints, control_exp, hand_tmpl, skeleton_img, include_undetect
180
+ )
181
+ return viz, control_img, control_mask, union_mask, control_img
182
+
183
+ # Connect the Generate button to trigger all steps in sequence
184
+ generate_btn.click(
185
+ fn=run_step1,
186
+ inputs=[input_image, include_standard, expand_ratio],
187
+ outputs=[output_bbox_result, output_bbox_vis, output_bbox_mask, bbox_mask_state]
188
+ ).then(
189
+ fn=run_step2,
190
+ inputs=[input_image],
191
+ outputs=[output_keypoints, output_skeleton, keypoints_state, skeleton_state]
192
+ ).then(
193
+ fn=run_step3,
194
+ inputs=[
195
+ input_image,
196
+ bbox_mask_state,
197
+ keypoints_state,
198
+ control_expand,
199
+ hand_template,
200
+ skeleton_state,
201
+ include_undetected
202
+ ],
203
+ outputs=[output_viz, output_control, output_control_mask, output_union_mask, output_final_control]
204
+ )
205
+
206
+ # Launch the interface
207
+ interface.launch(server_name="0.0.0.0", server_port=7860, share=True)
model/yolo.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8fa7a64d136d9b9356a72ed2238fa4744833cb1dc6078fc395e39c2d664372e2
3
+ size 136682110
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.0.1
2
+ gradio==3.48.0
3
+ mediapipe==0.10.5
4
+ numpy==1.26.1
5
+ opencv_contrib_python==4.8.0.76
6
+ opencv_python==4.8.0.76
7
+ Pillow==10.1.0
8
+ ultralytics==8.0.180
9
+ accelerate==0.23.0
10
+ transformers==4.33.2
templates/fist-back.png ADDED

Git LFS Details

  • SHA256: c906a534b69de1884da5b1031e0b60d86301c078a005c7523e18cb5091e09652
  • Pointer size: 131 Bytes
  • Size of remote file: 164 kB
templates/fist-back.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ 153, 84
2
+ 87, 268
3
+ 175, 342
4
+ 270, 300
5
+ 256, 267
templates/opened-palm.png ADDED

Git LFS Details

  • SHA256: 4dd5d1765db0f5cdb73e63584378619a9d8dfb554254684227ecbb5ed06c1276
  • Pointer size: 131 Bytes
  • Size of remote file: 351 kB
templates/opened-palm.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ 414, 127
2
+ 460, 365
3
+ 362, 477
4
+ 286, 393
5
+ 646, 960