{
  "id": 255016,
  "title": "新人踩坑总结 CowBoy Outfits Detection",
  "url": "/competitions/cowboyoutfits/discussion/255016",
  "author_name": "",
  "post_date": "2021-07-25T03:02:14.300005900Z",
  "votes": 5,
  "comment_count": 8,
  "views": 0,
  "content": "<p>目标检测这块由于课上只讲了个ssd的实现，所以如果想用别的模型基本要套框架，有一些额外的学习成本。不同的框架，不同的模型(rcnn,yolo,ssd)好像需要的数据格式也不太一样，大家对这些数据格式可能也不太了解。因此我想开个帖来汇总一下大家遇到的一些问题，大家相互探讨来节省下学习及试错成本。</p>\n<p>首先从Joshua Z. Zhang的starter kit开始。</p>\n<ol>\n<li><p>create_submission里面用的bbox是xyxy_abs，但本次提交的格式得是xywh_abs，如果大家是从这个kernel开始的话，得把create_submission里面bbox这行改一下。加几个epoch提交到codalab大概能得到30+的mAP。<br>\n这点刚好在另一个帖里有提<br>\n<a href=\"https://www.kaggle.com/c/cowboyoutfits/discussion/254656\" target=\"_blank\">https://www.kaggle.com/c/cowboyoutfits/discussion/254656</a></p></li>\n<li><p>autoGluon的文档比较简略，如果想做数据增强或是修改模型都好像不太方便（也可能是我不太了解..）。我试了下把预训练模型改成yolo3_darknet53_coco，loss就变成nan了，感觉格式可能不太对。换成center_net_resnet50_v1b_coco可以跑，kaggle kernel上训练了一个小时大概也是30+mAP的样子。</p></li>\n</ol>\n<p>json相关</p>\n<ol>\n<li><p>train-valid-split. 和以往给的表格型数据不同，这次给的input里面用的json格式。我由于不太了解相关的操作，所以是把json转成pd dataframe做的，切完再转回json。具体用到json.load，pd.json_normalize，df.json，json.dump这些函数。<br>\n<a href=\"https://www.kaggle.com/c/cowboyoutfits/discussion/254354\" target=\"_blank\">https://www.kaggle.com/c/cowboyoutfits/discussion/254354</a> 里面Joshua Z. Zhang给了段代码做train-valid-split（虽然我还没仔细看）</p></li>\n<li><p>json格式不支持float32的写入。做完预测，把结果从tensor形式拿出来，直接用to_list来存比较好，用numpy array最后用json.dump会报错写不了float32格式。</p></li>\n<li><p>提交格式。和上一条相关。虽然把tensor的结果存成字符串是可以用json.dump写到json文件里面的，但是codalab计算的时候是不能识别字符串（带引号）的，最后会导致提交失败。</p></li>\n</ol>\n<p>这是目前踩过的一些小坑。</p>\n<p>目前进展：搭了个有简单数据增强的faster rcnn作为baseline，基本没调参mAP 55。这次换个模型还挺花时间的，想寻求用yolo做的同学组个队相互学习下。</p>\n<p>UPDATE:<br>\n感觉cocoeval出了问题，还没搞明白问题在哪<br>\n<a href=\"https://www.kaggle.com/yichaohan/how-does-the-coco-eval-work?scriptVersionId=69921958\" target=\"_blank\">https://www.kaggle.com/yichaohan/how-does-the-coco-eval-work?scriptVersionId=69921958</a></p>\n<p>UPDATE:<br>\n捣腾半天应该是跟image_id相关，没细看cocoeval源代码，但是觉得非常奇怪。建议大家把原始数据reindex一下，不然交叉验证可能不准。不知道最后评估会不会有类似问题。</p>\n<p>FINAL UPDATE:<br>\n跌跌撞撞最后拿了第六。很多问题最后也没弄明白，最后一天准备测试下ensemble model，然后却发现dev phase过了就不能提交valid results了，最后无奈盲选了一波。好在运气不错有一个提交拿到了56+的成绩，准备新开个新帖分享下solutions以及赛后总结。</p>",
  "messages": [
    {
      "id": "1399209",
      "postDate": "07/25/2021 03:02:14",
      "content": "<p>目标检测这块由于课上只讲了个ssd的实现，所以如果想用别的模型基本要套框架，有一些额外的学习成本。不同的框架，不同的模型(rcnn,yolo,ssd)好像需要的数据格式也不太一样，大家对这些数据格式可能也不太了解。因此我想开个帖来汇总一下大家遇到的一些问题，大家相互探讨来节省下学习及试错成本。</p>\n<p>首先从Joshua Z. Zhang的starter kit开始。</p>\n<ol>\n<li><p>create_submission里面用的bbox是xyxy_abs，但本次提交的格式得是xywh_abs，如果大家是从这个kernel开始的话，得把create_submission里面bbox这行改一下。加几个epoch提交到codalab大概能得到30+的mAP。<br>\n这点刚好在另一个帖里有提<br>\n<a href=\"https://www.kaggle.com/c/cowboyoutfits/discussion/254656\" target=\"_blank\">https://www.kaggle.com/c/cowboyoutfits/discussion/254656</a></p></li>\n<li><p>autoGluon的文档比较简略，如果想做数据增强或是修改模型都好像不太方便（也可能是我不太了解..）。我试了下把预训练模型改成yolo3_darknet53_coco，loss就变成nan了，感觉格式可能不太对。换成center_net_resnet50_v1b_coco可以跑，kaggle kernel上训练了一个小时大概也是30+mAP的样子。</p></li>\n</ol>\n<p>json相关</p>\n<ol>\n<li><p>train-valid-split. 和以往给的表格型数据不同，这次给的input里面用的json格式。我由于不太了解相关的操作，所以是把json转成pd dataframe做的，切完再转回json。具体用到json.load，pd.json_normalize，df.json，json.dump这些函数。<br>\n<a href=\"https://www.kaggle.com/c/cowboyoutfits/discussion/254354\" target=\"_blank\">https://www.kaggle.com/c/cowboyoutfits/discussion/254354</a> 里面Joshua Z. Zhang给了段代码做train-valid-split（虽然我还没仔细看）</p></li>\n<li><p>json格式不支持float32的写入。做完预测，把结果从tensor形式拿出来，直接用to_list来存比较好，用numpy array最后用json.dump会报错写不了float32格式。</p></li>\n<li><p>提交格式。和上一条相关。虽然把tensor的结果存成字符串是可以用json.dump写到json文件里面的，但是codalab计算的时候是不能识别字符串（带引号）的，最后会导致提交失败。</p></li>\n</ol>\n<p>这是目前踩过的一些小坑。</p>\n<p>目前进展：搭了个有简单数据增强的faster rcnn作为baseline，基本没调参mAP 55。这次换个模型还挺花时间的，想寻求用yolo做的同学组个队相互学习下。</p>\n<p>UPDATE:<br>\n感觉cocoeval出了问题，还没搞明白问题在哪<br>\n<a href=\"https://www.kaggle.com/yichaohan/how-does-the-coco-eval-work?scriptVersionId=69921958\" target=\"_blank\">https://www.kaggle.com/yichaohan/how-does-the-coco-eval-work?scriptVersionId=69921958</a></p>\n<p>UPDATE:<br>\n捣腾半天应该是跟image_id相关，没细看cocoeval源代码，但是觉得非常奇怪。建议大家把原始数据reindex一下，不然交叉验证可能不准。不知道最后评估会不会有类似问题。</p>\n<p>FINAL UPDATE:<br>\n跌跌撞撞最后拿了第六。很多问题最后也没弄明白，最后一天准备测试下ensemble model，然后却发现dev phase过了就不能提交valid results了，最后无奈盲选了一波。好在运气不错有一个提交拿到了56+的成绩，准备新开个新帖分享下solutions以及赛后总结。</p>",
      "rawMarkdown": "目标检测这块由于课上只讲了个ssd的实现，所以如果想用别的模型基本要套框架，有一些额外的学习成本。不同的框架，不同的模型(rcnn,yolo,ssd)好像需要的数据格式也不太一样，大家对这些数据格式可能也不太了解。因此我想开个帖来汇总一下大家遇到的一些问题，大家相互探讨来节省下学习及试错成本。\n\n首先从Joshua Z. Zhang的starter kit开始。\n1. create_submission里面用的bbox是xyxy_abs，但本次提交的格式得是xywh_abs，如果大家是从这个kernel开始的话，得把create_submission里面bbox这行改一下。加几个epoch提交到codalab大概能得到30+的mAP。\n这点刚好在另一个帖里有提\nhttps://www.kaggle.com/c/cowboyoutfits/discussion/254656\n\n2. autoGluon的文档比较简略，如果想做数据增强或是修改模型都好像不太方便（也可能是我不太了解..）。我试了下把预训练模型改成yolo3_darknet53_coco，loss就变成nan了，感觉格式可能不太对。换成center_net_resnet50_v1b_coco可以跑，kaggle kernel上训练了一个小时大概也是30+mAP的样子。\n\njson相关\n3. train-valid-split. 和以往给的表格型数据不同，这次给的input里面用的json格式。我由于不太了解相关的操作，所以是把json转成pd dataframe做的，切完再转回json。具体用到json.load，pd.json_normalize，df.json，json.dump这些函数。\nhttps://www.kaggle.com/c/cowboyoutfits/discussion/254354 里面Joshua Z. Zhang给了段代码做train-valid-split（虽然我还没仔细看）\n\n4. json格式不支持float32的写入。做完预测，把结果从tensor形式拿出来，直接用to_list来存比较好，用numpy array最后用json.dump会报错写不了float32格式。\n5. 提交格式。和上一条相关。虽然把tensor的结果存成字符串是可以用json.dump写到json文件里面的，但是codalab计算的时候是不能识别字符串（带引号）的，最后会导致提交失败。\n\n这是目前踩过的一些小坑。\n\n目前进展：搭了个有简单数据增强的faster rcnn作为baseline，基本没调参mAP 55。这次换个模型还挺花时间的，想寻求用yolo做的同学组个队相互学习下。\n\n\nUPDATE:\n感觉cocoeval出了问题，还没搞明白问题在哪\nhttps://www.kaggle.com/yichaohan/how-does-the-coco-eval-work?scriptVersionId=69921958\n\nUPDATE:\n捣腾半天应该是跟image_id相关，没细看cocoeval源代码，但是觉得非常奇怪。建议大家把原始数据reindex一下，不然交叉验证可能不准。不知道最后评估会不会有类似问题。\n\nFINAL UPDATE:\n跌跌撞撞最后拿了第六。很多问题最后也没弄明白，最后一天准备测试下ensemble model，然后却发现dev phase过了就不能提交valid results了，最后无奈盲选了一波。好在运气不错有一个提交拿到了56+的成绩，准备新开个新帖分享下solutions以及赛后总结。",
      "votes": null
    },
    {
      "id": "1399296",
      "postDate": "07/25/2021 06:57:37",
      "content": "<p>我想问一下，你的验证集是怎么划分的啊，我尝试了很多划分方法，但是我本地验证集和codalab的结果都大概能差到20%</p>",
      "rawMarkdown": "我想问一下，你的验证集是怎么划分的啊，我尝试了很多划分方法，但是我本地验证集和codalab的结果都大概能差到20%",
      "votes": null
    },
    {
      "id": "1399312",
      "postDate": "07/25/2021 07:08:16",
      "content": "<p>我魔改了一下划分的代码，现在是直接划分train和valid了。</p>\n<pre><code>import random\nimport copy\nimport json\nfrom pycocotools.coco import COCO\n\ndef create_subset(c, cats, test_n=180):\n    new_coco = {}\n    new_coco['info'] = {\"description\": \"CowboySuit\",\n                        \"url\": \"http://github.com/dmlc/gluon-cv\",\n                        \"version\": \"1.0\",\"year\": 2021,\n                        \"contributor\": \"GluonCV/AutoGluon\",\n                        \"date_created\": \"2021/07/01\"}\n    new_coco[\"licenses\"]: [\n        {\"url\": \"http://creativecommons.org/licenses/by/2.0/\",\"id\": 4,\"name\": \"Attribution License\"}]\n    cat_ids = c.getCatIds(cats)\n    train_img_ids = set()\n\n    test_img_ids = set()\n    for cat in cat_ids[::-1]:\n        img_ids = copy.copy(c.getImgIds(catIds=[cat]))\n        random.shuffle(img_ids)\n        tn = min(test_n, int(len(img_ids) * 0.5))\n        new_test = set(img_ids[:tn])\n        exist_test_ids = new_test.intersection(train_img_ids)\n        test_ids = new_test.difference(exist_test_ids)\n        train_ids = set(img_ids).difference(test_ids)\n        print(tn, len(img_ids), len(new_test), len(test_ids), len(train_ids))\n        train_img_ids.update(train_ids)\n        test_img_ids.update(test_ids)\n        print(len(test_img_ids))\n\n    # prune duplicates\n    dup = train_img_ids.intersection(test_img_ids)\n    train_img_ids = train_img_ids - dup\n\n    train_anno_ids = set()\n    test_anno_ids = set()\n    for cat in cat_ids:\n        train_anno_ids.update(c.getAnnIds(imgIds=list(train_img_ids), catIds=[cat]))\n        test_anno_ids.update(c.getAnnIds(imgIds=list(test_img_ids), catIds=[cat]))\n\n    assert len(train_img_ids.intersection(test_img_ids)) == 0, 'img id conflicts, {} '.format(train_img_ids.intersection(test_img_ids))\n    assert len(train_anno_ids.intersection(test_anno_ids)) == 0, 'anno id conflicts'\n    print('train img ids #:', len(train_img_ids), 'train anno #:', len(train_anno_ids))\n    print('test img ids #:', len(test_img_ids), 'test anno #:', len(test_anno_ids))\n    new_coco_test = copy.deepcopy(new_coco)\n\n    new_coco[\"images\"] = c.loadImgs(list(train_img_ids))\n    new_coco[\"annotations\"] = c.loadAnns(list(train_anno_ids))\n    for ann in new_coco[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco[\"categories\"] = c.loadCats(cat_ids)\n\n    new_coco_test[\"images\"] = c.loadImgs(list(test_img_ids))\n    new_coco_test[\"annotations\"] = c.loadAnns(list(test_anno_ids))\n    for ann in new_coco_test[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco_test[\"categories\"] = c.loadCats(cat_ids)\n    print('new train split, images:', len(new_coco[\"images\"]), 'annos:', len(new_coco[\"annotations\"]))\n    print('new test split, images:', len(new_coco_test[\"images\"]), 'annos:', len(new_coco_test[\"annotations\"]))\n    return new_coco, new_coco_test\n\ncoco = COCO('cowboydata/train.json')\n\nnc, nc_test = create_subset(coco, ['belt', 'sunglasses', 'boot', 'cowboy_hat', 'jacket', ])\n\nwith open('cowboydata/new_train.json', 'w') as f:\n    json.dump(nc, f)\n\nwith open('cowboydata/new_valid.json', 'w') as f:\n    json.dump(nc_test, f)\n</code></pre>",
      "rawMarkdown": "我魔改了一下划分的代码，现在是直接划分train和valid了。\n```\nimport random\nimport copy\nimport json\nfrom pycocotools.coco import COCO\n\ndef create_subset(c, cats, test_n=180):\n    new_coco = {}\n    new_coco['info'] = {\"description\": \"CowboySuit\",\n                        \"url\": \"http://github.com/dmlc/gluon-cv\",\n                        \"version\": \"1.0\",\"year\": 2021,\n                        \"contributor\": \"GluonCV/AutoGluon\",\n                        \"date_created\": \"2021/07/01\"}\n    new_coco[\"licenses\"]: [\n        {\"url\": \"http://creativecommons.org/licenses/by/2.0/\",\"id\": 4,\"name\": \"Attribution License\"}]\n    cat_ids = c.getCatIds(cats)\n    train_img_ids = set()\n\n    test_img_ids = set()\n    for cat in cat_ids[::-1]:\n        img_ids = copy.copy(c.getImgIds(catIds=[cat]))\n        random.shuffle(img_ids)\n        tn = min(test_n, int(len(img_ids) * 0.5))\n        new_test = set(img_ids[:tn])\n        exist_test_ids = new_test.intersection(train_img_ids)\n        test_ids = new_test.difference(exist_test_ids)\n        train_ids = set(img_ids).difference(test_ids)\n        print(tn, len(img_ids), len(new_test), len(test_ids), len(train_ids))\n        train_img_ids.update(train_ids)\n        test_img_ids.update(test_ids)\n        print(len(test_img_ids))\n\n    # prune duplicates\n    dup = train_img_ids.intersection(test_img_ids)\n    train_img_ids = train_img_ids - dup\n\n    train_anno_ids = set()\n    test_anno_ids = set()\n    for cat in cat_ids:\n        train_anno_ids.update(c.getAnnIds(imgIds=list(train_img_ids), catIds=[cat]))\n        test_anno_ids.update(c.getAnnIds(imgIds=list(test_img_ids), catIds=[cat]))\n\n    assert len(train_img_ids.intersection(test_img_ids)) == 0, 'img id conflicts, {} '.format(train_img_ids.intersection(test_img_ids))\n    assert len(train_anno_ids.intersection(test_anno_ids)) == 0, 'anno id conflicts'\n    print('train img ids #:', len(train_img_ids), 'train anno #:', len(train_anno_ids))\n    print('test img ids #:', len(test_img_ids), 'test anno #:', len(test_anno_ids))\n    new_coco_test = copy.deepcopy(new_coco)\n\n    new_coco[\"images\"] = c.loadImgs(list(train_img_ids))\n    new_coco[\"annotations\"] = c.loadAnns(list(train_anno_ids))\n    for ann in new_coco[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco[\"categories\"] = c.loadCats(cat_ids)\n\n    new_coco_test[\"images\"] = c.loadImgs(list(test_img_ids))\n    new_coco_test[\"annotations\"] = c.loadAnns(list(test_anno_ids))\n    for ann in new_coco_test[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco_test[\"categories\"] = c.loadCats(cat_ids)\n    print('new train split, images:', len(new_coco[\"images\"]), 'annos:', len(new_coco[\"annotations\"]))\n    print('new test split, images:', len(new_coco_test[\"images\"]), 'annos:', len(new_coco_test[\"annotations\"]))\n    return new_coco, new_coco_test\n\ncoco = COCO('cowboydata/train.json')\n\nnc, nc_test = create_subset(coco, ['belt', 'sunglasses', 'boot', 'cowboy_hat', 'jacket', ])\n\nwith open('cowboydata/new_train.json', 'w') as f:\n    json.dump(nc, f)\n\nwith open('cowboydata/new_valid.json', 'w') as f:\n    json.dump(nc_test, f)\n\n```",
      "votes": null
    },
    {
      "id": "1399646",
      "postDate": "07/25/2021 14:40:25",
      "content": "<p>用的StratifiedKFold，验证集20%。腰带也就5个左右。我也是本地验证集结果好不少，可能是测试集里面腰带比较多把，目前我还没仔细研究这块。</p>",
      "rawMarkdown": "用的StratifiedKFold，验证集20%。腰带也就5个左右。我也是本地验证集结果好不少，可能是测试集里面腰带比较多把，目前我还没仔细研究这块。",
      "votes": null
    },
    {
      "id": "1399881",
      "postDate": "07/25/2021 18:54:58",
      "content": "<p>想问下，如果某一个图片里面，什么检测结果都没有。你们最后是怎么处理的呢？我这边直接跳过了，总觉得不妥。主要example submission也没看到说明，是否要用nnone来添加标签。因为我设置的confidence阈值还是蛮高的，所以蛮多图片都是空的，😹大家又是怎么设置这个的呢？</p>",
      "rawMarkdown": "想问下，如果某一个图片里面，什么检测结果都没有。你们最后是怎么处理的呢？我这边直接跳过了，总觉得不妥。主要example submission也没看到说明，是否要用nnone来添加标签。因为我设置的confidence阈值还是蛮高的，所以蛮多图片都是空的，😹大家又是怎么设置这个的呢？",
      "votes": null
    },
    {
      "id": "1400141",
      "postDate": "07/26/2021 05:04:01",
      "content": "<p>我刚开始也担心这个，后来提交发现空着似乎没有问题，没报错。XD<br>\nconfidence=0.1就行了，我的理解是mAP计算的时候会自动略过score低于于0.5的object<br>\nYolov5的<a href=\"https://github.com/ultralytics/yolov5/issues/1952\" target=\"_blank\">issue</a>里有人问test时conf为什么设成0.001，作者回答是计算mAP的常数</p>",
      "rawMarkdown": "我刚开始也担心这个，后来提交发现空着似乎没有问题，没报错。XD\nconfidence=0.1就行了，我的理解是mAP计算的时候会自动略过score低于于0.5的object\nYolov5的[issue](https://github.com/ultralytics/yolov5/issues/1952)里有人问test时conf为什么设成0.001，作者回答是计算mAP的常数",
      "votes": null
    },
    {
      "id": "1400156",
      "postDate": "07/26/2021 05:33:00",
      "content": "<p>yolo是都可以设置的，默认在inference时候是0.25，我设置的confidence数值还是蛮高的0.5以上了，可以查看我分享的notebook。在最后我提交了3次结果，也有对conf进行调整。但是还是有很多地方不明白。</p>",
      "rawMarkdown": "yolo是都可以设置的，默认在inference时候是0.25，我设置的confidence数值还是蛮高的0.5以上了，可以查看我分享的notebook。在最后我提交了3次结果，也有对conf进行调整。但是还是有很多地方不明白。",
      "votes": null
    },
    {
      "id": "1400160",
      "postDate": "07/26/2021 05:39:47",
      "content": "<p>刚看到提到说json格式书写问题，之前我也遇到了。我的做法是先转成pandas，然后再输出成json，感觉会稍微方便一点，也可以直观通过pandas来查看结果。 😸</p>\n<p><code>output_pd = pd.DataFrame(output_data)</code><br>\n<code>output_pd.to_json('answer.json',orient='records')</code></p>",
      "rawMarkdown": "刚看到提到说json格式书写问题，之前我也遇到了。我的做法是先转成pandas，然后再输出成json，感觉会稍微方便一点，也可以直观通过pandas来查看结果。 😸\n\n`output_pd = pd.DataFrame(output_data)`\n`output_pd.to_json('answer.json',orient='records')`",
      "votes": null
    },
    {
      "id": "1402459",
      "postDate": "07/28/2021 08:08:41",
      "content": "<h2>分享通过 pycocotools 方便处理 train.json</h2>\n<p>感谢分享，要把一个base版本做出来，除了课上的内容，不少知识需要一点点去自己补充，大家分享下都能加快进展了~</p>\n<p>其实对于训练数据中的 train.json 的处理，由于它用的是coco的格式，所有可以用 pycocotools 中 COCO 这个类之间读取<br>\npip install pycocotools 后<br>\nfrom pycocotools.coco import COCO 即可使用，对于处理 image，category，box 信息都非常方便：</p>\n<p>--   给出一些实例代码  -- </p>\n<h3>通过 train.json 文件路径创建 coco 实例</h3>\n<p>coco = COCO(annotation_file=CFG.TRAIN_JSON_PATH)</p>\n<h3>获取图片总数、生成图片id</h3>\n<p>len(coco.imgs.keys())<br>\nids = list(sorted(coco.imgs.keys()))</p>\n<h3>获取分类信息 dict</h3>\n<p>coco_id2cat = {v['id']: v['name'] for k,v in coco.cats.items()}</p>\n<h3>通过 coco 获取第一张图片，它的类别，box 信息，然后画出来</h3>\n<p>\"\"\" （探索代码，写的比较丑陋)  \"\"\"</p>\n<h3>通过图片 id， 获取图片文件路径</h3>\n<p>id1 = ids[0]<br>\nimg1_fname = coco.loadImgs(id1)[0]['file_name']<br>\nimg1_path = os.path.join(CFG.IMG_DIR, img1_fname)</p>\n<h3>获取图片对于的 box 边框等 annotation 信息</h3>\n<p>anno1_ids = coco.getAnnIds(imgIds=id1)<br>\ntarget1 = coco.loadAnns(anno_id1)<br>\n\"\"\"<br>\ntarget1 信息<br>\n[{'id': 5614,<br>\n  'image_id': 1051329269795162,<br>\n  'freebase_id': '/m/017ftj',<br>\n  'category_id': 1034,<br>\n  'iscrowd': False,<br>\n  'bbox': [647.68, 188.83, 152.32, 57.61],<br>\n  'area': 8775.11}]<br>\n\"\"\"</p>\n<h3>画出边框</h3>\n<p>\"\"\" 画目标类别名称的 font 需要自己用一个字体ttf文件创建下，不然ImageDraw原版的字太小看不清 \"\"\"<br>\nfont_size = 50<br>\nfont = ImageFont.truetype(\"font/Arial Italic.ttf\", size=font_size)</p>\n<p>img1 = Image.open(img1_path).convert(\"RGB\")<br>\ndraw = ImageDraw.Draw(img1)<br>\nfor target in target1:<br>\n    x, y, w, h = target['bbox']<br>\n    x2, y2 = x + w, y + h<br>\n    draw.rectangle((int(x), int(y), int(x2), int(y2)), width=5)<br>\n    draw.text((x, y - font_size), coco_id2cat[target['category_id']], fill=(255, 0, 0), font=font)</p>\n<p>plt.imshow(img1)</p>",
      "rawMarkdown": "## 分享通过 pycocotools 方便处理 train.json \n感谢分享，要把一个base版本做出来，除了课上的内容，不少知识需要一点点去自己补充，大家分享下都能加快进展了~\n\n其实对于训练数据中的 train.json 的处理，由于它用的是coco的格式，所有可以用 pycocotools 中 COCO 这个类之间读取\npip install pycocotools 后\nfrom pycocotools.coco import COCO 即可使用，对于处理 image，category，box 信息都非常方便：\n\n--   给出一些实例代码  -- \n\n### 通过 train.json 文件路径创建 coco 实例\ncoco = COCO(annotation_file=CFG.TRAIN_JSON_PATH)\n\n### 获取图片总数、生成图片id\nlen(coco.imgs.keys())\nids = list(sorted(coco.imgs.keys()))\n\n### 获取分类信息 dict\ncoco_id2cat = {v['id']: v['name'] for k,v in coco.cats.items()}\n\n### 通过 coco 获取第一张图片，它的类别，box 信息，然后画出来 \n\"\"\" （探索代码，写的比较丑陋)  \"\"\"\n### 通过图片 id， 获取图片文件路径\nid1 = ids[0]\nimg1_fname = coco.loadImgs(id1)[0]['file_name']\nimg1_path = os.path.join(CFG.IMG_DIR, img1_fname)\n\n### 获取图片对于的 box 边框等 annotation 信息\nanno1_ids = coco.getAnnIds(imgIds=id1)\ntarget1 = coco.loadAnns(anno_id1)\n\"\"\"\ntarget1 信息\n[{'id': 5614,\n  'image_id': 1051329269795162,\n  'freebase_id': '/m/017ftj',\n  'category_id': 1034,\n  'iscrowd': False,\n  'bbox': [647.68, 188.83, 152.32, 57.61],\n  'area': 8775.11}]\n\"\"\"\n\n### 画出边框\n\n\"\"\" 画目标类别名称的 font 需要自己用一个字体ttf文件创建下，不然ImageDraw原版的字太小看不清 \"\"\"\nfont_size = 50\nfont = ImageFont.truetype(\"font/Arial Italic.ttf\", size=font_size)\n\nimg1 = Image.open(img1_path).convert(\"RGB\")\ndraw = ImageDraw.Draw(img1)\nfor target in target1:\n    x, y, w, h = target['bbox']\n    x2, y2 = x + w, y + h\n    draw.rectangle((int(x), int(y), int(x2), int(y2)), width=5)\n    draw.text((x, y - font_size), coco_id2cat[target['category_id']], fill=(255, 0, 0), font=font)\n\nplt.imshow(img1)",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 1399296,
      "author_name": "nekokiku",
      "author_url": "",
      "post_date": "07/25/2021 06:57:37",
      "content": "<p>我想问一下，你的验证集是怎么划分的啊，我尝试了很多划分方法，但是我本地验证集和codalab的结果都大概能差到20%</p>",
      "votes": null,
      "replies": [
        {
          "id": 1399646,
          "author_name": "yichaohan",
          "author_url": "",
          "post_date": "07/25/2021 14:40:25",
          "content": "<p>用的StratifiedKFold，验证集20%。腰带也就5个左右。我也是本地验证集结果好不少，可能是测试集里面腰带比较多把，目前我还没仔细研究这块。</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 1399312,
      "author_name": "nekokiku",
      "author_url": "",
      "post_date": "07/25/2021 07:08:16",
      "content": "<p>我魔改了一下划分的代码，现在是直接划分train和valid了。</p>\n<pre><code>import random\nimport copy\nimport json\nfrom pycocotools.coco import COCO\n\ndef create_subset(c, cats, test_n=180):\n    new_coco = {}\n    new_coco['info'] = {\"description\": \"CowboySuit\",\n                        \"url\": \"http://github.com/dmlc/gluon-cv\",\n                        \"version\": \"1.0\",\"year\": 2021,\n                        \"contributor\": \"GluonCV/AutoGluon\",\n                        \"date_created\": \"2021/07/01\"}\n    new_coco[\"licenses\"]: [\n        {\"url\": \"http://creativecommons.org/licenses/by/2.0/\",\"id\": 4,\"name\": \"Attribution License\"}]\n    cat_ids = c.getCatIds(cats)\n    train_img_ids = set()\n\n    test_img_ids = set()\n    for cat in cat_ids[::-1]:\n        img_ids = copy.copy(c.getImgIds(catIds=[cat]))\n        random.shuffle(img_ids)\n        tn = min(test_n, int(len(img_ids) * 0.5))\n        new_test = set(img_ids[:tn])\n        exist_test_ids = new_test.intersection(train_img_ids)\n        test_ids = new_test.difference(exist_test_ids)\n        train_ids = set(img_ids).difference(test_ids)\n        print(tn, len(img_ids), len(new_test), len(test_ids), len(train_ids))\n        train_img_ids.update(train_ids)\n        test_img_ids.update(test_ids)\n        print(len(test_img_ids))\n\n    # prune duplicates\n    dup = train_img_ids.intersection(test_img_ids)\n    train_img_ids = train_img_ids - dup\n\n    train_anno_ids = set()\n    test_anno_ids = set()\n    for cat in cat_ids:\n        train_anno_ids.update(c.getAnnIds(imgIds=list(train_img_ids), catIds=[cat]))\n        test_anno_ids.update(c.getAnnIds(imgIds=list(test_img_ids), catIds=[cat]))\n\n    assert len(train_img_ids.intersection(test_img_ids)) == 0, 'img id conflicts, {} '.format(train_img_ids.intersection(test_img_ids))\n    assert len(train_anno_ids.intersection(test_anno_ids)) == 0, 'anno id conflicts'\n    print('train img ids #:', len(train_img_ids), 'train anno #:', len(train_anno_ids))\n    print('test img ids #:', len(test_img_ids), 'test anno #:', len(test_anno_ids))\n    new_coco_test = copy.deepcopy(new_coco)\n\n    new_coco[\"images\"] = c.loadImgs(list(train_img_ids))\n    new_coco[\"annotations\"] = c.loadAnns(list(train_anno_ids))\n    for ann in new_coco[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco[\"categories\"] = c.loadCats(cat_ids)\n\n    new_coco_test[\"images\"] = c.loadImgs(list(test_img_ids))\n    new_coco_test[\"annotations\"] = c.loadAnns(list(test_anno_ids))\n    for ann in new_coco_test[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco_test[\"categories\"] = c.loadCats(cat_ids)\n    print('new train split, images:', len(new_coco[\"images\"]), 'annos:', len(new_coco[\"annotations\"]))\n    print('new test split, images:', len(new_coco_test[\"images\"]), 'annos:', len(new_coco_test[\"annotations\"]))\n    return new_coco, new_coco_test\n\ncoco = COCO('cowboydata/train.json')\n\nnc, nc_test = create_subset(coco, ['belt', 'sunglasses', 'boot', 'cowboy_hat', 'jacket', ])\n\nwith open('cowboydata/new_train.json', 'w') as f:\n    json.dump(nc, f)\n\nwith open('cowboydata/new_valid.json', 'w') as f:\n    json.dump(nc_test, f)\n</code></pre>",
      "votes": null,
      "replies": []
    },
    {
      "id": 1399881,
      "author_name": "sheepwang",
      "author_url": "",
      "post_date": "07/25/2021 18:54:58",
      "content": "<p>想问下，如果某一个图片里面，什么检测结果都没有。你们最后是怎么处理的呢？我这边直接跳过了，总觉得不妥。主要example submission也没看到说明，是否要用nnone来添加标签。因为我设置的confidence阈值还是蛮高的，所以蛮多图片都是空的，😹大家又是怎么设置这个的呢？</p>",
      "votes": null,
      "replies": [
        {
          "id": 1400141,
          "author_name": "snowclem",
          "author_url": "",
          "post_date": "07/26/2021 05:04:01",
          "content": "<p>我刚开始也担心这个，后来提交发现空着似乎没有问题，没报错。XD<br>\nconfidence=0.1就行了，我的理解是mAP计算的时候会自动略过score低于于0.5的object<br>\nYolov5的<a href=\"https://github.com/ultralytics/yolov5/issues/1952\" target=\"_blank\">issue</a>里有人问test时conf为什么设成0.001，作者回答是计算mAP的常数</p>",
          "votes": null,
          "replies": []
        },
        {
          "id": 1400156,
          "author_name": "sheepwang",
          "author_url": "",
          "post_date": "07/26/2021 05:33:00",
          "content": "<p>yolo是都可以设置的，默认在inference时候是0.25，我设置的confidence数值还是蛮高的0.5以上了，可以查看我分享的notebook。在最后我提交了3次结果，也有对conf进行调整。但是还是有很多地方不明白。</p>",
          "votes": null,
          "replies": []
        }
      ]
    },
    {
      "id": 1400160,
      "author_name": "sheepwang",
      "author_url": "",
      "post_date": "07/26/2021 05:39:47",
      "content": "<p>刚看到提到说json格式书写问题，之前我也遇到了。我的做法是先转成pandas，然后再输出成json，感觉会稍微方便一点，也可以直观通过pandas来查看结果。 😸</p>\n<p><code>output_pd = pd.DataFrame(output_data)</code><br>\n<code>output_pd.to_json('answer.json',orient='records')</code></p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 1402459,
      "author_name": "niangaopx",
      "author_url": "",
      "post_date": "07/28/2021 08:08:41",
      "content": "<h2>分享通过 pycocotools 方便处理 train.json</h2>\n<p>感谢分享，要把一个base版本做出来，除了课上的内容，不少知识需要一点点去自己补充，大家分享下都能加快进展了~</p>\n<p>其实对于训练数据中的 train.json 的处理，由于它用的是coco的格式，所有可以用 pycocotools 中 COCO 这个类之间读取<br>\npip install pycocotools 后<br>\nfrom pycocotools.coco import COCO 即可使用，对于处理 image，category，box 信息都非常方便：</p>\n<p>--   给出一些实例代码  -- </p>\n<h3>通过 train.json 文件路径创建 coco 实例</h3>\n<p>coco = COCO(annotation_file=CFG.TRAIN_JSON_PATH)</p>\n<h3>获取图片总数、生成图片id</h3>\n<p>len(coco.imgs.keys())<br>\nids = list(sorted(coco.imgs.keys()))</p>\n<h3>获取分类信息 dict</h3>\n<p>coco_id2cat = {v['id']: v['name'] for k,v in coco.cats.items()}</p>\n<h3>通过 coco 获取第一张图片，它的类别，box 信息，然后画出来</h3>\n<p>\"\"\" （探索代码，写的比较丑陋)  \"\"\"</p>\n<h3>通过图片 id， 获取图片文件路径</h3>\n<p>id1 = ids[0]<br>\nimg1_fname = coco.loadImgs(id1)[0]['file_name']<br>\nimg1_path = os.path.join(CFG.IMG_DIR, img1_fname)</p>\n<h3>获取图片对于的 box 边框等 annotation 信息</h3>\n<p>anno1_ids = coco.getAnnIds(imgIds=id1)<br>\ntarget1 = coco.loadAnns(anno_id1)<br>\n\"\"\"<br>\ntarget1 信息<br>\n[{'id': 5614,<br>\n  'image_id': 1051329269795162,<br>\n  'freebase_id': '/m/017ftj',<br>\n  'category_id': 1034,<br>\n  'iscrowd': False,<br>\n  'bbox': [647.68, 188.83, 152.32, 57.61],<br>\n  'area': 8775.11}]<br>\n\"\"\"</p>\n<h3>画出边框</h3>\n<p>\"\"\" 画目标类别名称的 font 需要自己用一个字体ttf文件创建下，不然ImageDraw原版的字太小看不清 \"\"\"<br>\nfont_size = 50<br>\nfont = ImageFont.truetype(\"font/Arial Italic.ttf\", size=font_size)</p>\n<p>img1 = Image.open(img1_path).convert(\"RGB\")<br>\ndraw = ImageDraw.Draw(img1)<br>\nfor target in target1:<br>\n    x, y, w, h = target['bbox']<br>\n    x2, y2 = x + w, y + h<br>\n    draw.rectangle((int(x), int(y), int(x2), int(y2)), width=5)<br>\n    draw.text((x, y - font_size), coco_id2cat[target['category_id']], fill=(255, 0, 0), font=font)</p>\n<p>plt.imshow(img1)</p>",
      "votes": null,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "1399209": "目标检测这块由于课上只讲了个ssd的实现，所以如果想用别的模型基本要套框架，有一些额外的学习成本。不同的框架，不同的模型(rcnn,yolo,ssd)好像需要的数据格式也不太一样，大家对这些数据格式可能也不太了解。因此我想开个帖来汇总一下大家遇到的一些问题，大家相互探讨来节省下学习及试错成本。\n\n首先从Joshua Z. Zhang的starter kit开始。\n1. create_submission里面用的bbox是xyxy_abs，但本次提交的格式得是xywh_abs，如果大家是从这个kernel开始的话，得把create_submission里面bbox这行改一下。加几个epoch提交到codalab大概能得到30+的mAP。\n这点刚好在另一个帖里有提\nhttps://www.kaggle.com/c/cowboyoutfits/discussion/254656\n\n2. autoGluon的文档比较简略，如果想做数据增强或是修改模型都好像不太方便（也可能是我不太了解..）。我试了下把预训练模型改成yolo3_darknet53_coco，loss就变成nan了，感觉格式可能不太对。换成center_net_resnet50_v1b_coco可以跑，kaggle kernel上训练了一个小时大概也是30+mAP的样子。\n\njson相关\n3. train-valid-split. 和以往给的表格型数据不同，这次给的input里面用的json格式。我由于不太了解相关的操作，所以是把json转成pd dataframe做的，切完再转回json。具体用到json.load，pd.json_normalize，df.json，json.dump这些函数。\nhttps://www.kaggle.com/c/cowboyoutfits/discussion/254354 里面Joshua Z. Zhang给了段代码做train-valid-split（虽然我还没仔细看）\n\n4. json格式不支持float32的写入。做完预测，把结果从tensor形式拿出来，直接用to_list来存比较好，用numpy array最后用json.dump会报错写不了float32格式。\n5. 提交格式。和上一条相关。虽然把tensor的结果存成字符串是可以用json.dump写到json文件里面的，但是codalab计算的时候是不能识别字符串（带引号）的，最后会导致提交失败。\n\n这是目前踩过的一些小坑。\n\n目前进展：搭了个有简单数据增强的faster rcnn作为baseline，基本没调参mAP 55。这次换个模型还挺花时间的，想寻求用yolo做的同学组个队相互学习下。\n\n\nUPDATE:\n感觉cocoeval出了问题，还没搞明白问题在哪\nhttps://www.kaggle.com/yichaohan/how-does-the-coco-eval-work?scriptVersionId=69921958\n\nUPDATE:\n捣腾半天应该是跟image_id相关，没细看cocoeval源代码，但是觉得非常奇怪。建议大家把原始数据reindex一下，不然交叉验证可能不准。不知道最后评估会不会有类似问题。\n\nFINAL UPDATE:\n跌跌撞撞最后拿了第六。很多问题最后也没弄明白，最后一天准备测试下ensemble model，然后却发现dev phase过了就不能提交valid results了，最后无奈盲选了一波。好在运气不错有一个提交拿到了56+的成绩，准备新开个新帖分享下solutions以及赛后总结。",
    "1399296": "我想问一下，你的验证集是怎么划分的啊，我尝试了很多划分方法，但是我本地验证集和codalab的结果都大概能差到20%",
    "1399312": "我魔改了一下划分的代码，现在是直接划分train和valid了。\n```\nimport random\nimport copy\nimport json\nfrom pycocotools.coco import COCO\n\ndef create_subset(c, cats, test_n=180):\n    new_coco = {}\n    new_coco['info'] = {\"description\": \"CowboySuit\",\n                        \"url\": \"http://github.com/dmlc/gluon-cv\",\n                        \"version\": \"1.0\",\"year\": 2021,\n                        \"contributor\": \"GluonCV/AutoGluon\",\n                        \"date_created\": \"2021/07/01\"}\n    new_coco[\"licenses\"]: [\n        {\"url\": \"http://creativecommons.org/licenses/by/2.0/\",\"id\": 4,\"name\": \"Attribution License\"}]\n    cat_ids = c.getCatIds(cats)\n    train_img_ids = set()\n\n    test_img_ids = set()\n    for cat in cat_ids[::-1]:\n        img_ids = copy.copy(c.getImgIds(catIds=[cat]))\n        random.shuffle(img_ids)\n        tn = min(test_n, int(len(img_ids) * 0.5))\n        new_test = set(img_ids[:tn])\n        exist_test_ids = new_test.intersection(train_img_ids)\n        test_ids = new_test.difference(exist_test_ids)\n        train_ids = set(img_ids).difference(test_ids)\n        print(tn, len(img_ids), len(new_test), len(test_ids), len(train_ids))\n        train_img_ids.update(train_ids)\n        test_img_ids.update(test_ids)\n        print(len(test_img_ids))\n\n    # prune duplicates\n    dup = train_img_ids.intersection(test_img_ids)\n    train_img_ids = train_img_ids - dup\n\n    train_anno_ids = set()\n    test_anno_ids = set()\n    for cat in cat_ids:\n        train_anno_ids.update(c.getAnnIds(imgIds=list(train_img_ids), catIds=[cat]))\n        test_anno_ids.update(c.getAnnIds(imgIds=list(test_img_ids), catIds=[cat]))\n\n    assert len(train_img_ids.intersection(test_img_ids)) == 0, 'img id conflicts, {} '.format(train_img_ids.intersection(test_img_ids))\n    assert len(train_anno_ids.intersection(test_anno_ids)) == 0, 'anno id conflicts'\n    print('train img ids #:', len(train_img_ids), 'train anno #:', len(train_anno_ids))\n    print('test img ids #:', len(test_img_ids), 'test anno #:', len(test_anno_ids))\n    new_coco_test = copy.deepcopy(new_coco)\n\n    new_coco[\"images\"] = c.loadImgs(list(train_img_ids))\n    new_coco[\"annotations\"] = c.loadAnns(list(train_anno_ids))\n    for ann in new_coco[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco[\"categories\"] = c.loadCats(cat_ids)\n\n    new_coco_test[\"images\"] = c.loadImgs(list(test_img_ids))\n    new_coco_test[\"annotations\"] = c.loadAnns(list(test_anno_ids))\n    for ann in new_coco_test[\"annotations\"]:\n        ann.pop('segmentation', None)\n    new_coco_test[\"categories\"] = c.loadCats(cat_ids)\n    print('new train split, images:', len(new_coco[\"images\"]), 'annos:', len(new_coco[\"annotations\"]))\n    print('new test split, images:', len(new_coco_test[\"images\"]), 'annos:', len(new_coco_test[\"annotations\"]))\n    return new_coco, new_coco_test\n\ncoco = COCO('cowboydata/train.json')\n\nnc, nc_test = create_subset(coco, ['belt', 'sunglasses', 'boot', 'cowboy_hat', 'jacket', ])\n\nwith open('cowboydata/new_train.json', 'w') as f:\n    json.dump(nc, f)\n\nwith open('cowboydata/new_valid.json', 'w') as f:\n    json.dump(nc_test, f)\n\n```",
    "1399646": "用的StratifiedKFold，验证集20%。腰带也就5个左右。我也是本地验证集结果好不少，可能是测试集里面腰带比较多把，目前我还没仔细研究这块。",
    "1399881": "想问下，如果某一个图片里面，什么检测结果都没有。你们最后是怎么处理的呢？我这边直接跳过了，总觉得不妥。主要example submission也没看到说明，是否要用nnone来添加标签。因为我设置的confidence阈值还是蛮高的，所以蛮多图片都是空的，😹大家又是怎么设置这个的呢？",
    "1400141": "我刚开始也担心这个，后来提交发现空着似乎没有问题，没报错。XD\nconfidence=0.1就行了，我的理解是mAP计算的时候会自动略过score低于于0.5的object\nYolov5的[issue](https://github.com/ultralytics/yolov5/issues/1952)里有人问test时conf为什么设成0.001，作者回答是计算mAP的常数",
    "1400156": "yolo是都可以设置的，默认在inference时候是0.25，我设置的confidence数值还是蛮高的0.5以上了，可以查看我分享的notebook。在最后我提交了3次结果，也有对conf进行调整。但是还是有很多地方不明白。",
    "1400160": "刚看到提到说json格式书写问题，之前我也遇到了。我的做法是先转成pandas，然后再输出成json，感觉会稍微方便一点，也可以直观通过pandas来查看结果。 😸\n\n`output_pd = pd.DataFrame(output_data)`\n`output_pd.to_json('answer.json',orient='records')`",
    "1402459": "## 分享通过 pycocotools 方便处理 train.json \n感谢分享，要把一个base版本做出来，除了课上的内容，不少知识需要一点点去自己补充，大家分享下都能加快进展了~\n\n其实对于训练数据中的 train.json 的处理，由于它用的是coco的格式，所有可以用 pycocotools 中 COCO 这个类之间读取\npip install pycocotools 后\nfrom pycocotools.coco import COCO 即可使用，对于处理 image，category，box 信息都非常方便：\n\n--   给出一些实例代码  -- \n\n### 通过 train.json 文件路径创建 coco 实例\ncoco = COCO(annotation_file=CFG.TRAIN_JSON_PATH)\n\n### 获取图片总数、生成图片id\nlen(coco.imgs.keys())\nids = list(sorted(coco.imgs.keys()))\n\n### 获取分类信息 dict\ncoco_id2cat = {v['id']: v['name'] for k,v in coco.cats.items()}\n\n### 通过 coco 获取第一张图片，它的类别，box 信息，然后画出来 \n\"\"\" （探索代码，写的比较丑陋)  \"\"\"\n### 通过图片 id， 获取图片文件路径\nid1 = ids[0]\nimg1_fname = coco.loadImgs(id1)[0]['file_name']\nimg1_path = os.path.join(CFG.IMG_DIR, img1_fname)\n\n### 获取图片对于的 box 边框等 annotation 信息\nanno1_ids = coco.getAnnIds(imgIds=id1)\ntarget1 = coco.loadAnns(anno_id1)\n\"\"\"\ntarget1 信息\n[{'id': 5614,\n  'image_id': 1051329269795162,\n  'freebase_id': '/m/017ftj',\n  'category_id': 1034,\n  'iscrowd': False,\n  'bbox': [647.68, 188.83, 152.32, 57.61],\n  'area': 8775.11}]\n\"\"\"\n\n### 画出边框\n\n\"\"\" 画目标类别名称的 font 需要自己用一个字体ttf文件创建下，不然ImageDraw原版的字太小看不清 \"\"\"\nfont_size = 50\nfont = ImageFont.truetype(\"font/Arial Italic.ttf\", size=font_size)\n\nimg1 = Image.open(img1_path).convert(\"RGB\")\ndraw = ImageDraw.Draw(img1)\nfor target in target1:\n    x, y, w, h = target['bbox']\n    x2, y2 = x + w, y + h\n    draw.rectangle((int(x), int(y), int(x2), int(y2)), width=5)\n    draw.text((x, y - font_size), coco_id2cat[target['category_id']], fill=(255, 0, 0), font=font)\n\nplt.imshow(img1)"
  },
  "source": "meta"
}