Open ryouchinsa opened 1 year ago
Thanks for your work. I'll make a PR to update the main script with your code. Let me know if you want to do that yourself.
Thanks for reviewing the script. We created a pull request. https://github.com/ultralytics/JSON2YOLO/pull/46
For those who are still looking for a script to convert coco keypoint json file to yolo8 pose format, I am sharing the script that I have written for yolo-pose detection task.
import argparse
import json
import os
import shutil
from collections import defaultdict
from pathlib import Path
import numpy as np
from tqdm import tqdm
parser = argparse.ArgumentParser()
parser.add_argument(
"--coco_json_path",
default="/home/hodor/dev/data/coco_test/person_keypoints_train2017.json",
type=str,
help="input: coco format(json)",
)
parser.add_argument(
"--yolo_save_root_dir",
default="/home/hodor/dev/data/coco_test/yolo/",
type=str,
help="specify where to save the output dir of labels",
)
def convert_bbox_to_yolo(size, box):
dw = 1.0 / (size[0])
dh = 1.0 / (size[1])
x = box[0] + box[2] / 2.0
y = box[1] + box[3] / 2.0
w = box[2]
h = box[3]
# The round function determines the number of decimal places in (xmin, ymin, xmax, ymax)
x = round(x * dw, 6)
w = round(w * dw, 6)
y = round(y * dh, 6)
h = round(h * dh, 6)
return (x, y, w, h)
def convert_keypoints2_list(keypoints, img_width, img_height):
xiaoshu = 10 ** 6
arry_x = np.zeros([17, 1])
num_1 = 0
for x in keypoints[0:51:3]:
arry_x[num_1, 0] = int((x / img_width) * xiaoshu) / xiaoshu
num_1 += 1
arry_y = np.zeros([17, 1])
num_2 = 0
for y in keypoints[1:51:3]:
arry_y[num_2, 0] = int((y / img_height) * xiaoshu) / xiaoshu
num_2 += 1
arry_v = np.zeros([17, 1])
num_3 = 0
for v in keypoints[2:51:3]:
arry_v[num_3, 0] = v
num_3 += 1
list_1 = []
num_4 = 0
for i in range(17):
list_1.append(float(arry_x[num_4]))
list_1.append(float(arry_y[num_4]))
list_1.append(float(arry_v[num_4]))
num_4 += 1
return list_1
def main(root_dir, ana_txt_save_path_txt, json_file):
xiaoshu = 10 ** 6
data = json.load(open(json_file, "r"))
# if not os.path.exists(ana_txt_save_path_txt):
# os.makedirs(ana_txt_save_path_txt)
id_map = (
{}
) # The ids of the coco dataset are not continuous! Remap and output again!
with open(os.path.join(root_dir, "classes.txt"), "w") as f:
for i, category in enumerate(data["categories"]):
f.write(f"{category['name']}\n")
id_map[category["id"]] = i
fn = Path(ana_txt_save_path_txt)
images = {"%g" % x["id"]: x for x in data["images"]}
# Create image-annotations dict
imgToAnns = defaultdict(list)
for ann in data["annotations"]:
imgToAnns[ann["image_id"]].append(ann)
list_file = open(os.path.join(root_dir, "train.txt"), "w")
# Write labels file
for img_id, anns in tqdm(imgToAnns.items(), desc=f"Annotations {json_file}"):
img = images["%g" % img_id]
h, w, f = img["height"], img["width"], img["file_name"]
bboxes = []
segments = []
for ann in anns:
# if ann['iscrowd']:
# continue
# The COCO box format is [top left x, top left y, width, height]
box = np.array(ann["bbox"], dtype=np.float64)
box[:2] += box[2:] / 2 # xy top-left corner to center
box[[0, 2]] /= w # normalize x
box[[1, 3]] /= h # normalize y
if box[2] <= 0 or box[3] <= 0: # if w <= 0 and h <= 0
continue
# keypoints
keypoints = ann["keypoints"]
keypoints_list = convert_keypoints2_list(keypoints, w, h)
# conver_keypoins2_list(keypoints,w,h)
# print(keypoints)
cls = id_map[ann["category_id"]]
# cls = coco80[ann['category_id'] - 1] if cls91to80 else ann['category_id'] - 1 # class
box = [cls] + box.tolist() + keypoints_list
if box not in bboxes:
bboxes.append(box)
# Segments
# if use_segments:
# if len(ann['segmentation']) > 1:
# s = merge_multi_segment(ann['segmentation'])
# s = (np.concatenate(s, axis=0) / np.array([w, h])).reshape(-1).tolist()
# else:
# s = [j for i in ann['segmentation'] for j in i] # all segments concatenated
# s = (np.array(s).reshape(-1, 2) / np.array([w, h])).reshape(-1).tolist()
# s = [cls] + s
# if s not in segments:
# segments.append(s)
# Write
# head, tail = os.path.splitext(filename)
# ana_txt_name = head + ".txt"
# replace_txt_name = os.path.join(ana_txt_save_path_txt, ana_txt_name)
with open((fn / f).with_suffix(".txt"), "w") as file:
for i in range(len(bboxes)):
line = (*(bboxes[i]),) # cls, box,keypoins
file.write(("%g " * len(line)).rstrip() % line + "\n")
list_file.write("./images/train/%s\n" % (f))
list_file.close()
if __name__ == "__main__":
args = parser.parse_args()
print("Parsing and creating directories...")
ROOT_DIR = args.yolo_save_root_dir
COCO_JSON_FILE = args.coco_json_path
YOLO_ANNO_TXT_SAVE_PATH = ROOT_DIR+"/labels/train/"
print(ROOT_DIR,COCO_JSON_FILE, YOLO_ANNO_TXT_SAVE_PATH)
# try:
# os.makedirs(YOLO_ANNO_TXT_SAVE_PATH, exist_ok=True)
# except:
# print("Permission error!")
main(ROOT_DIR, YOLO_ANNO_TXT_SAVE_PATH, COCO_JSON_FILE)
## USAGE
# python coco_keypointjson2yolo.py --coco_json_path /home/hodor/dev/data/coco_test/person_keypoints_train2017.json --yolo_save_root_dir /home/hodor/dev/data/coco_test/yolo/
It seems like your code only works for converting 17 keypoints from the coco format. Maybe you should make that clear in case someone tried to use it for different number of keypoints(like I did which left me frustrated for the past 8 hours).
I have modified @kaplansinan 's code for converting coco dataset to yolo. I added a feature in which you can define the number of keypoints on your dataset.
import argparse
import json
import os
import shutil
from collections import defaultdict
from pathlib import Path
import numpy as np
from tqdm import tqdm
parser = argparse.ArgumentParser()
numOfKpts = 21 #replace this with the number of keypoints inside the dataset
kptsInfo = numOfKpts * 3
parser.add_argument(
"--coco_json_path",
default="/home/hodor/dev/data/coco_test/person_keypoints_train2077.json",
type=str,
help="input: coco format(json)",
)
parser.add_argument(
"--yolo_save_root_dir",
default="/home/hodor/dev/data/coco_test/yolo/",
type=str,
help="specify where to save the output dir of labels",
)
def convert_bbox_to_yolo(size, box):
dw = 1.0 / (size[0])
dh = 1.0 / (size[1])
x = box[0] + box[2] / 2.0
y = box[1] + box[3] / 2.0
w = box[2]
h = box[3]
# The round function determines the number of decimal places in (xmin, ymin, xmax, ymax)
x = round(x * dw, 6)
w = round(w * dw, 6)
y = round(y * dh, 6)
h = round(h * dh, 6)
return (x, y, w, h)
def convert_keypoints2_list(keypoints, img_width, img_height):
xiaoshu = 10 ** 6
arry_x = np.zeros([numOfKpts, 1])
num_1 = 0
for x in keypoints[0:kptsInfo:3]:
arry_x[num_1, 0] = int((x / img_width) * xiaoshu) / xiaoshu
num_1 += 1
arry_y = np.zeros([numOfKpts, 1])
num_2 = 0
for y in keypoints[1:kptsInfo:3]:
arry_y[num_2, 0] = int((y / img_height) * xiaoshu) / xiaoshu
num_2 += 1
arry_v = np.zeros([numOfKpts, 1])
num_3 = 0
for v in keypoints[2:kptsInfo:3]:
arry_v[num_3, 0] = v
num_3 += 1
list_1 = []
num_4 = 0
for i in range(numOfKpts):
list_1.append(float(arry_x[num_4]))
list_1.append(float(arry_y[num_4]))
list_1.append(float(arry_v[num_4]))
num_4 += 1
return list_1
def main(root_dir, ana_txt_save_path_txt, json_file):
xiaoshu = 10 ** 6
data = json.load(open(json_file, "r"))
# if not os.path.exists(ana_txt_save_path_txt):
# os.makedirs(ana_txt_save_path_txt)
id_map = (
{}
) # The ids of the coco dataset are not continuous! Remap and output again!
with open(os.path.join(root_dir, "classes.txt"), "w") as f:
for i, category in enumerate(data["categories"]):
f.write(f"{category['name']}\n")
id_map[category["id"]] = i
fn = Path(ana_txt_save_path_txt)
images = {"%g" % x["id"]: x for x in data["images"]}
# Create image-annotations dict
imgToAnns = defaultdict(list)
for ann in data["annotations"]:
imgToAnns[ann["image_id"]].append(ann)
list_file = open(os.path.join(root_dir, "train.txt"), "w")
# Write labels file
for img_id, anns in tqdm(imgToAnns.items(), desc=f"Annotations {json_file}"):
img = images["%g" % img_id]
h, w, f = img["height"], img["width"], img["file_name"]
bboxes = []
segments = []
for ann in anns:
# if ann['iscrowd']:
# continue
# The COCO box format is [top left x, top left y, width, height]
box = np.array(ann["bbox"], dtype=np.float64)
box[:2] += box[2:] / 2 # xy top-left corner to center
box[[0, 2]] /= w # normalize x
box[[1, 3]] /= h # normalize y
if box[2] <= 0 or box[3] <= 0: # if w <= 0 and h <= 0
continue
# keypoints
keypoints = ann["keypoints"]
keypoints_list = convert_keypoints2_list(keypoints, w, h)
# conver_keypoins2_list(keypoints,w,h)
# print(keypoints)
cls = id_map[ann["category_id"]]
# cls = coco80[ann['category_id'] - 1] if cls91to80 else ann['category_id'] - 1 # class
box = [cls] + box.tolist() + keypoints_list
if box not in bboxes:
bboxes.append(box)
# Segments
# if use_segments:
# if len(ann['segmentation']) > 1:
# s = merge_multi_segment(ann['segmentation'])
# s = (np.concatenate(s, axis=0) / np.array([w, h])).reshape(-1).tolist()
# else:
# s = [j for i in ann['segmentation'] for j in i] # all segments concatenated
# s = (np.array(s).reshape(-1, 2) / np.array([w, h])).reshape(-1).tolist()
# s = [cls] + s
# if s not in segments:
# segments.append(s)
# Write
# head, tail = os.path.splitext(filename)
# ana_txt_name = head + ".txt"
# replace_txt_name = os.path.join(ana_txt_save_path_txt, ana_txt_name)
with open((fn / f).with_suffix(".txt"), "w") as file:
for i in range(len(bboxes)):
line = (*(bboxes[i]),) # cls, box,keypoins
file.write(("%g " * len(line)).rstrip() % line + "\n")
list_file.write("./images/train/%s\n" % (f))
list_file.close()
if __name__ == "__main__":
args = parser.parse_args()
print("Parsing and creating directories...")
ROOT_DIR = args.yolo_save_root_dir
COCO_JSON_FILE = args.coco_json_path
YOLO_ANNO_TXT_SAVE_PATH = ROOT_DIR+"/labels/train/"
print(ROOT_DIR,COCO_JSON_FILE, YOLO_ANNO_TXT_SAVE_PATH)
# try:
# os.makedirs(YOLO_ANNO_TXT_SAVE_PATH, exist_ok=True)
# except:
# print("Permission error!")
main(ROOT_DIR, YOLO_ANNO_TXT_SAVE_PATH, COCO_JSON_FILE)
## USAGE
# replace the value of the variable "numOfKpts" with the number of keypoints on your dataset
# python coco_keypointjson2yolo_v02.py --coco_json_path /home/hodor/dev/data/coco_test/person_keypoints_train2077.json --yolo_save_root_dir /home/hodor/dev/data/coco_test/yolo/
Many thanks to @kaplansinan for the base code
It's great to see the improvements you've made to the script for converting COCO keypoints to YOLO format. Allowing users to define the number of keypoints in the dataset is a useful addition and will make it more flexible for different use cases. Thank you for sharing the updated script and the clear usage instructions! Your collaboration and contributions are appreciated. Keep up the good work!
We updated the general_json2yolo.py script so that the RLE mask with holes are converted to YOLO segmentation format.
We believe that this script would be beneficial for your company and users. Could you review the script before making a PR?
@ryouchinsa thank you for sharing the updated script! We appreciate your contribution and willingness to improve the tool. I will review the script and provide feedback as soon as possible. Your effort is valuable to the YOLO community and our users.
I am trying to convert coco formate Pose estimation dataset into yolo8-pose. but no script is working
I'm sorry to hear you're having trouble with the conversion. π For converting a COCO format pose estimation dataset into YOLOv8 format, you might need to ensure your script handles the keypoints correctly. Here's a basic outline you can follow or adjust your script against:
(x_center / image_width, y_center / image_height, width / image_width, height / image_height)
.If this aligns with your approach and the issue persists, please share a snippet of your script or the error you're encountering. This will help pinpoint the problem more effectively. π
For more detailed assistance, you might find the Ultralytics Docs helpful: https://docs.ultralytics.com.
I'm sorry to hear you're experiencing difficulties with converting your COCO pose estimation dataset for YOLOv8 pose. Our goal is to support our community the best we can, and feedback like yours is crucial for us. π
Converting from COCO keypoints format to YOLOv8 pose does require handling the data carefully. Although we strive to provide comprehensive solutions, gaps can indeed occur. However, we definitely do not intend to drive users towards paid solutions through lack of support.
Using yolo-nas-pose can indeed be a great alternative if it suits your project's requirements directly. Also, I'd recommend checking the latest scripts and documentation on our repository or Ultralytics Docs for potentially updated tools or guidance.
If you're open to it, could you share which scripts you've tried and what issues you encountered? This information could help us improve or guide you more effectively.
Thank you for your feedback, and we're here to help!
Hi @mycodeDev786 , if you could share the COCO pose file with us, we can fix our script so that our script can convert your COCO file to the YOLO pose format.
HI @wesboyt, it is not true. We are not driving users to paid solutions. Here, to convert the COCO file to YOLO pose format, we can support without paid solutions.
Hi @mycodeDev786 , if you could share the COCO pose file with us, we can fix our script so that our script can convert your COCO file to the YOLO pose format.
HI @wesboyt, it is not true. We are not driving users to paid solutions. Here, to convert the COCO file to YOLO pose format, we can support without paid solutions.
hi there thanks for the response i am uploading the JSON file of keypoints as coco formate of 14 keypoints. i want to use it for pose estimation [ExLPose_train_WL.json](https://githu@ryouchinsa .com/ultralytics/JSON2YOLO/files/15125856/ExLPose_train_WL.json) @ryouchinsa
Hi @mycodeDev786 , if you could share the COCO pose file with us, we can fix our script so that our script can convert your COCO file to the YOLO pose format.
HI @wesboyt, it is not true. We are not driving users to paid solutions. Here, to convert the COCO file to YOLO pose format, we can support without paid solutions.
here is the file ExLPose_train_WL.json
@ryouchinsa
Hi @mycodeDev786, thanks for sharing the COCO file.
We updated the general_json2yolo.py so that your ExLPose_train_WL.json is correctly converted to YOLOv8 pose format.
In the JSON2YOLO directory, run the script.
python general_json2yolo.py
Hi @mycodeDev786, thanks for sharing the COCO file.
We updated the general_json2yolo.py so that your ExLPose_train_WL.json is correctly converted to YOLOv8 pose format.
In the JSON2YOLO directory, run the script.
python general_json2yolo.py
Thanks, it works for me now. I am working on pose estimation in low light conditions and want to optimize yolov8 for this purpose but i can't find any documentation about cutomizing yolov8-pose. can you guide me? thanks again ....... @ryouchinsa
Hi @mycodeDev786, this is a document how to train the pose model. https://docs.ultralytics.com/tasks/pose
For the low light conditions, the augmentation parameter hsv_v might be useful.
hsv_v modifies the value (brightness) of the image by a fraction, helping the model to perform well under various lighting conditions. https://github.com/ultralytics/ultralytics/issues/8308
@ryouchinsa
Hi, thank you for your work, it really helped me! I would like to add usage of 133 keypoints for coco-wholebody pose detection to your script. Feel free to incorporate this into your PR, if you want π . Hoping it helps any lost souls too
Hi @Gnoot01, great work! please add your code and submit the new PR by yourself. My PR is not accepted yet.
Hey @Gnoot01 , I added some code to your repo to handle the different use cases (17, 23 and 133 keypoints). I opened a pull request on your repo. Thanks for the codebase!
We improved the script so that it converts the COCO keypoints format to YOLOv8 format. https://github.com/ryouchinsa/Rectlabel-support/blob/master/general_json2yolo.py
The function show_kpt_shape_flip_idx() shows the kpt_shape and flip_idx for the yaml file. Copy the 2 lines and paste to your yaml file. Please let us know your opinion.