YOLOv3训练+修改


最近参加2020年(第13届)中国大学生计算机设计大赛,选择了人工智能挑战赛的赛题二,基于 CT 影像的结直肠息肉检测。
赛题要求是,设计算法,判断图像中是否存在息肉,并实现息肉的准确检测,利用矩形方框将所检测出的息肉包含在检测框内。
乍一看,是一道标准的目标检测题,再看看官方给出的数据集标注,是YOLO格式的,那就直接用YOLOv3进行训练。最后再根据题目的需求,增加了一些功能。
我们这个版本,是在c语言的YOLOv3框架上进行修改的;网上还有很多基于pytorch、tensorflow构建的YOLOv3,以后有时间也写一下。

YOLOv3训练自己的数据集

标记数据

如果是训练自己的数据集,即还未对数据集进行标注,那么推荐使用LabelImg工具进行标注,可以得到适用于PascalVOC(xml)或者YOLO(txt)格式的标注。
因为官方给出的数据集已经是YOLO格式的标注,我们可以直接用。当然,考虑到后面某个功能使用了PascalVOC格式的标注,这里给出一个从YOLO(txt)格式转换成PascalVOC(xml)格式的代码txt2xml.py
此时,原始图像放在JPEGImages目录下,YOLO(txt)格式标注放在labels目录下,PascalVOC(xml)格式标注放在Annotations目录下。

制作 yolo 需要的txt文件

这一步需要制作四个文件:train.txt、val.txt、object_train.txt、object_val.txt。
train.txt、val.txt这两个文件保存了用于训练、验证图片的名字,每行一个名字(不带后缀.jpg)。这里参考了一个别人的代码img2train.py
object_train.txt、object_val.txt这两个文件保存了用于训练、验证图片的绝对路径,每行一个路径。这里参考了一个别人的代码voc_label.py。这个代码不仅可以划分文件,还可以将PascalVOC(xml)格式标注转换成YOLO(txt)格式标注。

制作 yolo 需要的配置文件

这一步需要制作三个文件:ct.names、ct.data、ct.cfg(ct是自己定义的,因为我用的是ct数据集,所以有此定义)。
ct.names包含数据集中的种类,每行一个。注意,这个顺序代表了之后预测时的种类顺序。
ct.data包含以下几个内容

classes= 1 # 类别数
train = data/object_train.txt # obj_train.txt 路径
valid = data/object_val.txt # obj_val.txt 路径
names = data/ct.names # ct.names 路径
backup = backup/ # 建一个 backup 文件夹用于存放 weights 结果

注意,这里放的是object_train.txt和object_val.txt,是写有绝对路径的txt文本。
ct.cfg包含的是与YOLOv3训练或测试相关的配置,有几个地方需要注意

1.注意文档开头training和testing的切换;
2.直接搜索‘classes’,修改三处对应位置:
[convolutional]
filters = 3*(classes + 5) #修改filters数量
[yolo]
classes=5 #修改类别数;
3.修改max_batches = 2000 * classes

训练

首先,下载预训练权重。

wget https://pjreddie.com/media/files/darknet53.conv.74

然后,执行训练命令。

./darknet detector train ./cfg/ct.data ./cfg/ct.cfg darknet53.conv.74

增加功能

在图像上添加置信度

YOLOv3单张图像检测的结果,默认设定只包含目标类别。我们可以通过修改src/image.c文件draw_detections()函数,添加目标置信度。修改片段如下:

for(i = 0; i < num; ++i)
{
	char labelstr[4096] = {0};
	char s1[]={"  "};// 为了name与置信度之间加空格
	int class = -1;
	char possible[5];// 存放检测的置信值
	for(j = 0; j < classes; ++j)
	{
		sprintf(possible,"%.2f",dets[i].prob[j]);//置信值截取小数点后两位赋给possible
        if (dets[i].prob[j] > thresh)
        {
            if (class < 0) 
            {
                strcat(labelstr, names[j]);
				strcat(labelstr, s1); //加空格
		        strcat(labelstr, possible);//标签中加入置信值
                class = j;
            } 
            else 
            {
                strcat(labelstr, ", ");
                strcat(labelstr, names[j]);
				strcat(labelstr, s1);//加空格
		        strcat(labelstr, possible);//标签中加入置信值
            }
            printf("%s: %.0f%%\n", names[j], dets[i].prob[j]*100);
        }
    }
}	    	

单张图像检测命令

./darknet detector test ./cfg/ct.data ./cfg/ct_test.cfg ct_final.weights test.jpg

批量检测图像

首先,修改example/detector.c文件,在开头添加一个获取图片名字的函数:

char *GetFilename(char *p)
{ 
   static char name[50]={""};
   char *q = strrchr(p,'/') + 1;
   int i = 0;
   while(q[i] != '\0')
   {
       if(q[i] == '.') break;
       i++;
   }
   strncpy(name,q,i);// i是图片名的长度
   name[i] = '\0';
   return name;

}

在这里,为了对不同长度的文件名能够兼容处理,设置name数组长度为50,可以根据需要修改。
然后,替换examples/detector.c 中的test_detector函数:

void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen)
{
    list *options = read_data_cfg(datacfg);
    char *name_list = option_find_str(options, "names", "data/names.list");
    char **names = get_labels(name_list);
 
    image **alphabet = load_alphabet();
    network *net = load_network(cfgfile, weightfile, 0);
    set_batch_network(net, 1);
    srand(2222222);
    double time;
    char buff[256];
    char *input = buff;
    float nms=.45;
    int i=0;
    while(1){
        if(filename){
            strncpy(input, filename, 256);
            image im = load_image_color(input,0,0);
            image sized = letterbox_image(im, net->w, net->h);
            layer l = net->layers[net->n-1];
 
 
            float *X = sized.data;
            time=what_time_is_it_now();
            network_predict(net, X);
            printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time);
            int nboxes = 0;
            detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
            if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
                draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes);
                free_detections(dets, nboxes);
            if(outfile)
             {
                save_image(im, outfile);
             }
            else{
                save_image(im, "predictions");
#ifdef OPENCV
                cvNamedWindow("predictions", CV_WINDOW_NORMAL); 
                if(fullscreen){
                cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
                }
                show_image(im, "predictions",0);
                cvWaitKey(0);
                cvDestroyAllWindows();
#endif
            }
            free_image(im);
            free_image(sized);
            if (filename) break;
         } 
        else {
            printf("Enter Image Path: ");
            fflush(stdout);
            input = fgets(input, 256, stdin);
            if(!input) return;
            strtok(input, "\n");
   
            list *plist = get_paths(input);
            char **paths = (char **)list_to_array(plist);
             printf("Start Testing!\n");
            int m = plist->size;
            if(access("/home/lzm/data/test_folder/darknet/car_person/out",0)==-1)//"/homelzm/......"修改成自己要保存图片的的路径
            {
              if (mkdir("/home/lzm/data/test_folder/darknet/car_person/out",0777))//"/homelzm/......"修改成自己要保存图片的的路径
               {
                 printf("creat folder failed!!!");
               }
            }
            for(i = 0; i < m; ++i){
             char *path = paths[i];
             image im = load_image_color(path,0,0);
             image sized = letterbox_image(im, net->w, net->h);
        //image sized = resize_image(im, net->w, net->h);
        //image sized2 = resize_max(im, net->w);
        //image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
        //resize_network(net, sized.w, sized.h);
        layer l = net->layers[net->n-1];
 
 
        float *X = sized.data;
        time=what_time_is_it_now();
        network_predict(net, X);
        printf("Try Very Hard:");
        printf("%s: Predicted in %f seconds.\n", path, what_time_is_it_now()-time);
        int nboxes = 0;
        detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
        //printf("%d\n", nboxes);
        //if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
        if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
        draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes);
        free_detections(dets, nboxes);
        if(outfile){
            save_image(im, outfile);
        }
        else{
             
             char b[2048];
            sprintf(b,"/home/lzm/data/test_folder/darknet/car_person/out/%s",GetFilename(path));//"/homelzm/......"修改成自己要保存图片的的路径
            
            save_image(im, b);
            printf("OJBK!\n",GetFilename(path));
#ifdef OPENCV
            cvNamedWindow("predictions", CV_WINDOW_NORMAL); 
            if(fullscreen){
                cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
            }
            show_image(im, "predictions",0);
            cvWaitKey(0);
            cvDestroyAllWindows();
#endif
        }
 
        free_image(im);
        free_image(sized);
        if (filename) break;
        }
      }
    }
}

最后,在命令行输入make,更新文件。
批量检测命令,输入的路径为那些图片路径的txt。

./darknet detector test ./cfg/ct.data ./cfg/ct_test.cfg ct_final.weights

保存批量检测结果为txt文件

YOLOv3有自带的命令进行这个操作。

./darknet detector valid ./cfg/ct.data ./cfg/ct_test.cfg ct_final.weights results

计算recall

修改examples/detector.c的validate_detector_recall函数。
首先,将validate_detector_recall函数定义和调用修改如下:

void validate_detector_recall(char *datacfg, char *cfgfile, char *weightfile)
validate_detector_recall(datacfg, cfg, weights);

然后,将如下内容:

list *plist = get_paths(“data/coco_val_5k.list”);
char **paths = (char **)list_to_array(plist);

修改成

list *options = read_data_cfg(datacfg);
char *valid_images = option_find_str(options, “valid”, “data/train.list”);
list *plist = get_paths(valid_images);
char **paths = (char **)list_to_array(plist);

最后,记得make。
使用YOLOv3的命令调用方式。

./darknet detector recall ./cfg/ct.data ./cfg/ct_test.cfg ct_final.weights

计算mAP

需要用到PascalVOC(xml)格式的注释,可以用文章开始提到的代码txt2xml.py进行转换。
可以先借助py-faster-rcnn下的voc_eval.py计算出单个类别的AP,然后求平均得到mAP。
新建一个all_map.py文件用于计算mAP,这边提供了别的博主的一个例子。
如果需要重复计算mAP,需要删除生成的annots.pkl。

参考
YOLOv3:训练自己的数据(附优化与问题总结)
How to train YOLOv3 model
YOLO-V3实战(darknet)

### 关于YOLOv1与FPN结合的实现 #### YOLOv1架构概述 YOLOv1作为最早的YOLO系列版本之一,采用了一个相对简单的卷积神经网络结构来完成目标检测任务。其主要特点是将整个图像输入到单个卷积网络中,在全连接层之后直接预测边界框的位置以及类别概率[^1]。 #### FPN简介 特征金字塔网络(Feature Pyramid Networks, FPN)是一种用于多尺度物体检测的有效机制。通过自底向上的路径增强来自不同层次特征图的信息传递效率,使得模型能够更好地处理大小各异的目标对象。FPN的核心在于构建一个多级联的特征提取器,它可以从低分辨率高语义级别的深层特征逐步恢复至较高空间分辨率但较低抽象度浅层特征[^2]。 #### YOLOv1与FPN结合的可能性分析 尽管原始版YOLOv1并未设计成支持像FPN这样的复杂颈部组件,但从理论上讲,二者是可以被融合在一起工作的: - **改进后的骨干网**:为了使YOLOv1能利用上FPN的优势,可能需要先对其基础框架做一些改动,比如引入更强大的预训练权重或者增加额外的下采样操作以便生成适合接入FPN的不同尺寸特征映射。 - **集成FPN模块**:一旦有了足够的多层次特征表示形式,则可以直接按照标准方式部署FPN单元——即创建一条由粗粒度特征逐层向上补充细颗粒细节的新通路,并最终汇总这些经过加强过的特征供后续解码使用。 然而值得注意的是,由于YOLOv1本身的设计较为陈旧且缺乏现代优化手段的支持,因此即使实现了上述改造也可能难以达到最佳性能表现。对于追求高效准确率的应用场景来说,建议考虑基于更新颖的YOLO变体如YOLOv5或更高版本来进行开发工作[^3]。 ```python import torch.nn as nn class ModifiedYOLOv1(nn.Module): def __init__(self): super(ModifiedYOLOv1, self).__init__() # 假设这是修改过后的YOLOv1主干部分 self.backbone = ... # 添加FPN结构 self.fpn = FeaturePyramidNetwork(...) def forward(self, x): features = self.backbone(x) enhanced_features = self.fpn(features) return enhanced_features ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值