1. 准备数据集

  • 这里使用开源的BCCD血细胞图像数据集,包含364张图像和364个xml标注
  • xml标注
  • 图像
  • 某张图像

    上图为血细胞图像,其中最多包含3种目标对象,分别是红细胞,白细胞和血小板

    上图为图像对应的xml标注,主要信息有目标的类别,标注框的位置坐标
    这里将364张图像拆分为训练集300张,测试集64张,分别存放在iamges和images_testdata路径下
    对应的xml标注分别存放在annotations和Annotations_testdata路径下
    其中0-338为训练集,339-410为测试集
    至此,数据集和xml标注准备完成

2. 数据读取和观察分析

import os
import numpy as np
import xml.etree.ElementTree as ET
from lxml import etree
CELL_NAMES = ['RBC', 'WBC', 'Platelets']
os.chdir("E:/cell_detection_count/project")# 返回一个字典,将类别转为数字
# {'RBC': 0, 'WBC': 1, 'Platelets': 2}
def get_cell_names():cell_category2id = {}for i, item in enumerate(CELL_NAMES):cell_category2id[item] = ireturn cell_category2id# 获得数据集列表  (读取xml文件)
def get_annotations(cname2cid, datadir):# 获取全部xml文件名filenames = os.listdir(datadir)records = []  # 保存对象具体信息# 遍历全部xml文件for fname in filenames:# 保存该xml文件路径+文件名fpath = os.path.join(datadir, fname)# 生成对应xml文件的图像路径img_file = os.path.join("BCCD_Dataset-master/BCCD/JPEGImages/", fname.split(".")[0] + ".jpg")tree = etree.parse(datadir + "/" + fname)  # 生成解析xml对象objs = tree.xpath("//object")  # 获取当前图像中全部血细胞对象im_w = float(tree.xpath('//size')[0].xpath('width')[0].text)  # 获取图像宽高im_h = float(tree.xpath('//size')[0].xpath('height')[0].text)gt_bbox = np.zeros((len(objs), 4), dtype=np.float32)  # 生成保存对象的数组gt_class = np.zeros((len(objs),), dtype=np.int32)  # 生成保存对象类别的数组is_crowd = np.zeros((len(objs),), dtype=np.int32)  # 生成difficult = np.zeros((len(objs),), dtype=np.int32)for i, obj in enumerate(objs):  # 遍历全部对象cname = obj.xpath('.//name')[0].text  # 获取对象类别名称gt_class[i] = cname2cid[cname]  # 类别名转为数值并添加到数组_difficult = int(obj.xpath('.//difficult')[0].text)x1 = float(obj.xpath('.//bndbox')[0].xpath('./xmin')[0].text)  # 获取对象框的左上角和右下角的坐标y1 = float(obj.xpath('.//bndbox')[0].xpath('./ymin')[0].text)x2 = float(obj.xpath('.//bndbox')[0].xpath('./xmax')[0].text)y2 = float(obj.xpath('.//bndbox')[0].xpath('./ymax')[0].text)x1 = max(0, x1)y1 = max(0, y1)x2 = min(im_w - 1, x2)y2 = min(im_h - 1, y2)# 这里使用xywh格式来表示目标物体真实框gt_bbox[i] = [(x1 + x2) / 2.0, (y1 + y2) / 2.0, x2 - x1 + 1., y2 - y1 + 1.]is_crowd[i] = 0difficult[i] = _difficult# 保存当前图像具体信息的字典voc_rec = {'im_file': img_file,'h': im_h,'w': im_w,'is_crowd': is_crowd,'gt_class': gt_class,'gt_bbox': gt_bbox,'gt_poly': [],'difficult': difficult}# 若该图像包含对象至少一个则将其追加到列表if len(objs) != 0:records.append(voc_rec)return recordstrain_path = 'BCCD_Dataset-master/BCCD/Annotations'# 读取图像数据
import cv2# 对于一般的检测任务来说,一张图片上往往会有多个目标物体
# 设置参数MAX_NUM = 50, 即一张图片最多取50个真实框;如果真实
# 框的数目少于50个,则将不足部分的gt_bbox, gt_class和gt_score的各项数值全设置为0
def get_bbox(gt_bbox, gt_class):MAX_NUM = 50gt_bbox2 = np.zeros((MAX_NUM, 4))gt_class2 = np.zeros((MAX_NUM,))for i in range(len(gt_bbox)):gt_bbox2[i, :] = gt_bbox[i, :]gt_class2[i] = gt_class[i]if i >= MAX_NUM:breakreturn gt_bbox2, gt_class2def get_img_data_from_file(record):im_file = record['im_file']h = record['h']w = record['w']is_crowd = record['is_crowd']gt_class = record['gt_class']gt_bbox = record['gt_bbox']difficult = record['difficult']img = cv2.imread(im_file)img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)gt_boxes, gt_labels = get_bbox(gt_bbox, gt_class)# gt_bbox 用相对值gt_boxes[:, 0] = gt_boxes[:, 0] / float(w)gt_boxes[:, 1] = gt_boxes[:, 1] / float(h)gt_boxes[:, 2] = gt_boxes[:, 2] / float(w)gt_boxes[:, 3] = gt_boxes[:, 3] / float(h)return img, gt_boxes, gt_labels, (h, w)def rectangle_draw(img, gt_boxes, gt_class, size):"""#给图像中的细胞绘制矩形框和文字标注"""# 将位置相对值转为绝对值gt_boxes[:, 0] = gt_boxes[:, 0] * size[1]gt_boxes[:, 1] = gt_boxes[:, 1] * size[0]gt_boxes[:, 2] = gt_boxes[:, 2] * size[1]gt_boxes[:, 3] = gt_boxes[:, 3] * size[0]font = cv2.FONT_HERSHEY_SIMPLEXfor i, g_b in enumerate(gt_boxes):font_x_y = (int(g_b[0]), int(g_b[1]))  #字体显示位置坐标x1 = int(g_b[0]) - int(g_b[2] / 2)y1 = int(g_b[1]) - int(g_b[3] / 2)x2 = int(g_b[0]) + int(g_b[2] / 2)y2 = int(g_b[1]) + int(g_b[3] / 2)if gt_class[i] == 0:color = (0,0,255)cv2.putText(img, "RBC", font_x_y, font, 0.5, color, 1, cv2.LINE_AA)elif gt_class[i] == 1:color = (255,0,0)cv2.putText(img, "WBC", font_x_y, font, 0.5, color, 1, cv2.LINE_AA)else:color = (0,255,0)cv2.putText(img, "Platelets", font_x_y, font, 0.5, color, 1, cv2.LINE_AA)cv2.rectangle(img, (x1,y1), (x2,y2), color)def circle_draw(img, gt_boxes, gt_class, size):import math"""#给图像中的细胞绘制圆形框和文字标注"""# 将位置相对值转为绝对值gt_boxes[:, 0] = gt_boxes[:, 0] * size[1]gt_boxes[:, 1] = gt_boxes[:, 1] * size[0]gt_boxes[:, 2] = gt_boxes[:, 2] * size[1]gt_boxes[:, 3] = gt_boxes[:, 3] * size[0]font = cv2.FONT_HERSHEY_SIMPLEXfor i, g_b in enumerate(gt_boxes):font_x_y = (int(g_b[0]), int(g_b[1]))  # 字体显示位置坐标x = int(g_b[0])y = int(g_b[1])w = int(g_b[2])h = int(g_b[3])r = int(math.sqrt(w*w + h*h)/3)if gt_class[i] == 0:color = (0, 0, 255)cv2.putText(img, "RBC", font_x_y, font, 0.5, color, 1, cv2.LINE_AA)elif gt_class[i] == 1:color = (255, 0, 0)cv2.putText(img, "WBC", font_x_y, font, 0.5, color, 1, cv2.LINE_AA)else:color = (0, 255, 0)cv2.putText(img, "Platelets", font_x_y, font, 0.5, color, 1, cv2.LINE_AA)cv2.circle(img, (x,y), (r), color)if __name__ == '__main__':cname2cid = get_cell_names()records = get_annotations(cname2cid,train_path)import cv2import matplotlib.pyplot as pltimg, gt_boxes, gt_labels, scales = get_img_data_from_file(records[300])print(gt_boxes)img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)cv2.imshow("src", img)circle_draw(img, gt_boxes, gt_labels, scales)cv2.imshow("dst", img)cv2.waitKey()

这里将xml文件中的标注信息读取出来存放到列表中(records),并且在原图上绘制出标注框如下图

这里由于细胞大多为圆形,绘制矩形框并不美观,我们重新绘制圆形框,圆形框的坐标变换由以下公式:
圆心 = 矩形框中心
半径 = 矩形框对角线 / 3
这一公式由实验得出,能较为美观的绘制细胞标注框,如下图

  • 对细胞大小进行聚类,这里为查看细胞大小分布情况,对细胞的宽高进行了聚类。
import read_xml
import numpy as np
import sklearn#对全部位置框聚类可视化  查看位置框大小分布
cname = read_xml.get_cell_names()
traindir = read_xml.train_path
records = read_xml.get_annotations(cname, traindir)#boxes用来保存全部位置框的宽高信息
boxes = []
for r in records:boxes.extend(r['gt_bbox'])#转成np数组
boxes = np.array(boxes)
#取后两列,即宽高信息
boxes = boxes[:, 2:4]  #形状为(4888,2)tmp = np.array(boxes)#全部位置框的宽高比  形状为(4888,1)
ratio = tmp[:,0]/tmp[:,1]
ratio = np.array(ratio).reshape(-1, 1)from sklearn.cluster import KMeans
# 正式定义kmeans模型  生成5个聚簇
model1 = KMeans(n_clusters=5)
# 跑模型
model1.fit(boxes)
# 需要知道每个类别有哪些参数
C_i = model1.predict(boxes)   #全部类别的簇标号, 形状为(4888,)#聚类中心的坐标
centers = model1.cluster_centers_
#坐标转为int型
centers = centers.astype('int32')count = [0,0,0,0,0]
for i in C_i:count[int(i)] += 1
print(count)  #统计各个簇有多少样本sums = centers[:, 0] + centers[:, 1]
centers_sorted = np.append(centers.T, [sums], axis = 0)
centers_sorted = centers_sorted.T
print(centers_sorted)
print('-'*30)
centers_sorted = centers_sorted[centers_sorted[:,2].argsort()]
print(centers_sorted)  #5个簇中心宽高大小排序#绘图显示聚类情况
from matplotlib import pyplot as plt
plt.figure(figsize=(18,4),dpi=80)
ax1 = plt.subplot(121)
#绘制样本点
plt.scatter(boxes[:, 0], boxes[:, 1], s=1)
#绘制聚类中心点
plt.scatter(centers[:, 0], centers[:, 1], s=50)
plt.title("细胞宽高信息聚类图", fontsize=16)
plt.xlabel("细胞宽度")
plt.ylabel("细 \n胞 \n高 \n 度 \n\n", rotation=0)
plt.legend(["非聚簇中心细胞", "聚簇中心细胞"])#绘制直方图统计
ax2 = plt.subplot(122)
plt.bar(["聚簇1"+str(centers[0]),"聚簇2"+str(centers[1]),"聚簇3"+str(centers[2]),"聚簇4"+str(centers[3]),"聚簇5"+str(centers[4])], count)
plt.title("聚簇细胞数目直方图", fontsize=16)
plt.xlabel("聚簇中心细胞[宽,高]")
plt.ylabel("细 \n胞 \n数 \n 量 \n\n", rotation=0)
plt.show()
  • 结果如下图

    图像显示,绝大多数细胞的宽高聚集在100-200之间。
    从作图可以看出,由少数细胞的宽高在30左右,观察原图像不难分析,这些细胞正是体积较小的血小板如下图
  • 每幅图像包含的细胞数量均值
import read_xml
import numpy as npcname = read_xml.get_cell_names()
traindir = read_xml.train_path
records = read_xml.get_annotations(cname, traindir)number = []for cell in records:number.append(len(cell["gt_class"]))sum = 0
for i in number:sum += i
avg = sum / 364
print(avg)


结果显示,每幅图像平均包含13.4个细胞对象

  • 每幅图像包含的三种细胞数量统计
import read_xml
import numpy as np
from collections import Counter
import matplotlib.pyplot as pltcname = read_xml.get_cell_names()
traindir = read_xml.train_path
records = read_xml.get_annotations(cname, traindir)rbc_number = []
wbc_number = []
platelets_number = []for cell in records:num_sequence = cell["gt_class"]count = Counter(num_sequence)rbc_number.append(count[0])wbc_number.append(count[1])platelets_number.append(count[2])counter_rbc = Counter(rbc_number)
counter_wbc = Counter(wbc_number)
counter_platelets = Counter(platelets_number)rbc_x = []
rbc_y = []
for k, v in counter_rbc.items():rbc_x.append(k)
rbc_x.sort()
for i in rbc_x:rbc_y.append(counter_rbc[i])wbc_x = []
wbc_y = []
for k, v in counter_wbc.items():wbc_x.append(k)
wbc_x.sort()
for i in wbc_x:wbc_y.append(counter_wbc[i])platelets_x = []
platelets_y = []
for k, v in counter_platelets.items():platelets_x.append(k)
platelets_x.sort()
for i in platelets_x:platelets_y.append(counter_platelets[i])fig = plt.figure(figsize=(14,4))#指定绘图域,figsize指定当前绘图域大小
ax1=fig.add_subplot(1,3,1)
ax1.bar(rbc_x, rbc_y, color="red", label="图像数量")
ax1.set_xlabel("红细胞数量")
ax1.set_ylabel("数据集对应图像数量")
ax1.set_title("红细胞数量统计")
plt.legend()
ax2=fig.add_subplot(1,3,2)
ax2.bar(wbc_x, wbc_y, color="#4D4DFF", label="图像数量")
ax2.set_xlabel("白细胞数量")
ax2.set_ylabel("数据集对应图像数量")
ax2.set_title("白细胞数量统计")
plt.legend()
ax3=fig.add_subplot(1,3,3)
ax3.bar(platelets_x, platelets_y, color="#ADEAEA", label="图像数量")
ax3.set_xlabel("血小板数量")
ax3.set_ylabel("数据集对应图像数量")
ax3.set_title("血小板数量统计")
plt.legend()
plt.show()

  • 每幅图像包含的全部细胞数量统计


可以看出,大多数图像包含的细胞数量在10-25之间,此外也有部分图像(不到60张)包含细胞较多,在28-30个之间
还发现由极少数图像仅包含5个以下的细胞,我们将其找出来观察一下
过滤后发现序号为
67
115
126
129
130
133
193
204
217
218
226
235
236
269
285
290
291
311的细胞包含细胞个数小于5 我们观察几张如下图



如上图所示,根据标注对原图绘制圆形标注框后发现,确实仅包含极少数细胞对象值得考虑的是以下这种图像

虽然标注中只给出一个白细胞,但我个人观察,图中是存在不少红细胞,但是很多粘合在一起,且清晰度很低,但源数据集中没有对其进行标注。这里为了保证数据集的质量,将上述这种样本一共18个从数据集中剔除。剩余共346个样本。这样,前300个为训练集,测试集减少到46个样本。

2. xml标注转换成csv格式

  • tensorflow的api在制作训练需要的.record格式文件时需要传入的标注是csv格式,所以这里需要将xml文件转为csv文件,这里使用xml_to_csv.py程序处理,这里编写一个bat批处理文件(windows系统)执行该程序
  • xml_to_csv.py
import os
import glob
import pandas as pd
import xml.etree.ElementTree as ETdef xml_to_csv(path):xml_list = []for xml_file in glob.glob(path + '/*.xml'):print(xml_file)tree = ET.parse(xml_file)root = tree.getroot()for member in root.findall('object'):try:value = (root.find('filename').text,int(root.find('size')[0].text),int(root.find('size')[1].text),member[0].text,int(member[4][0].text),int(member[4][1].text),int(member[4][2].text),int(member[4][3].text))except ValueError:value = (root.find('filename').text,int(root.find('size')[0].text),int(root.find('size')[1].text),member[0].text,int(member[4][1][0].text),int(member[4][1][1].text),int(member[4][1][2].text),int(member[4][1][3].text))xml_list.append(value)column_name = ['filename', 'width', 'height', 'class', 'xmin', 'ymin', 'xmax', 'ymax']xml_df = pd.DataFrame(xml_list, columns=column_name)return xml_dfdef main():#'annotation'参数是xml标注文件的路径, 'label.csv'参数是生成的csv文件名,这里根据实际情况修改参数image_path = os.path.join(os.getcwd(), 'annotations')xml_df = xml_to_csv(image_path)xml_df.to_csv('labels.csv', index=None)print('Successfully converted xml to csv.')main()
  • xml_to_csv.bat
python xml_to_csv.py
pause

编写号bat文件后双击该文件即可执行转换程序,需要注意的是这里训练集和测试集需要分别转换,修改py文件中对应参数即可

  • 转换后的cvs文件(训练集)如下图:
  • 最后生成的训练集cvs标注为
  • 测试集csv标注为

    至此,csv标注转换完成

3. 生成tfrecord格式文件

这里使用generate_tfrecord.py生成训练集和测试集的tfrecord格式文件,这是一种二进制文件,用于tensorflow的api需要接受这种类型的数据文件

  • 这里需要将row_label 改成自己的标注描述 我们这里是红细胞,白细胞和血小板一共3个目标类别
"""
Usage:# From tensorflow/models/# Create train data:python generate_tfrecord.py --csv_input=data/train_labels.csv  --output_path=train.record# Create test data:python generate_tfrecord.py --csv_input=data/test_labels.csv  --output_path=test.record
"""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_importimport os
import io
import pandas as pd
import tensorflow as tffrom PIL import Image
from object_detection.utils import dataset_util
from collections import namedtuple, OrderedDictflags = tf.app.flags
flags.DEFINE_string('csv_input', '', 'Path to the CSV input')
flags.DEFINE_string('output_path', '', 'Path to output TFRecord')
flags.DEFINE_string('images_dir', 'images', 'Path to output TFRecord')
FLAGS = flags.FLAGS#这里需要将row_label 改成自己的标注描述  我们这里是红细胞,白细胞和血小板一共3个目标类别
# TO-DO replace this with label map
def class_text_to_int(row_label):if row_label == 'RBC':return 1elif row_label == 'WBC':return 2elif row_label == 'Platelets':return 3else:Nonedef split(df, group):data = namedtuple('data', ['filename', 'object'])gb = df.groupby(group)return [data(filename, gb.get_group(x)) for filename, x in zip(gb.groups.keys(), gb.groups)]def create_tf_example(group, path):with tf.gfile.GFile(os.path.join(path, '{}'.format(group.filename)), 'rb') as fid:encoded_jpg = fid.read()encoded_jpg_io = io.BytesIO(encoded_jpg)image = Image.open(encoded_jpg_io)width, height = image.sizefilename = group.filename.encode('utf8')image_format = b'jpg'xmins = []xmaxs = []ymins = []ymaxs = []classes_text = []classes = []for index, row in group.object.iterrows():xmins.append(row['xmin'] / width)xmaxs.append(row['xmax'] / width)ymins.append(row['ymin'] / height)ymaxs.append(row['ymax'] / height)classes_text.append(row['class'].encode('utf8'))classes.append(class_text_to_int(row['class']))tf_example = tf.train.Example(features=tf.train.Features(feature={'image/height': dataset_util.int64_feature(height),'image/width': dataset_util.int64_feature(width),'image/filename': dataset_util.bytes_feature(filename),'image/source_id': dataset_util.bytes_feature(filename),'image/encoded': dataset_util.bytes_feature(encoded_jpg),'image/format': dataset_util.bytes_feature(image_format),'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),'image/object/class/text': dataset_util.bytes_list_feature(classes_text),'image/object/class/label': dataset_util.int64_list_feature(classes),}))return tf_exampledef main(_):writer = tf.python_io.TFRecordWriter(FLAGS.output_path)path = os.path.join(os.getcwd(), FLAGS.images_dir)examples = pd.read_csv(FLAGS.csv_input)grouped = split(examples, 'filename')for group in grouped:tf_example = create_tf_example(group, path)writer.write(tf_example.SerializeToString())writer.close()output_path = os.path.join(os.getcwd(), FLAGS.output_path)print('Successfully created the TFRecords: {}'.format(output_path))if __name__ == '__main__':tf.app.run()

-同样编写一个bat文件来执行这个程序

  • generate_tfrecord.bat
python generate_tfrecord.py --csv_input=test_labels.csv --output_path=test.record --images_dir=images_testdata
pause

这里传入了三个参数,cvs_input是测试集csv标注文件的路径名,output_path是生成的record文件的路径名,images_dir是测试集的图像数据的路径名
运行结束后会在指定的路径生成一个record文件,这个文件将用于模型训练

  • 同样需要注意的是这里训练集也要生成对应的record文件,操作步骤一致,只需要更改bat文件中的参数即可如下所示:
python generate_tfrecord.py --csv_input=labels.csv --output_path=train.record --images_dir=images
pause

这里运行结束后会生成两个record文件如下:


这两个record文件集成了图像和标注数据,将输入到模型进行训练
至此,record文件准备完成

4. 准备.pbtxt文件

这里需要创建一个.pbtxt文件,用于类别序号和类别名称的对应,如下所示,此在,这里的对应关系需要和步骤3中的row_label保持一致

5. 准备一个谷歌的预训练模型

这里使用的是谷歌在coco数据集上预训练的模型

下载好模型压缩文件后解压,将后缀.ckpt的三个文件放在项目当前路径下的某个目录,这里放在training目录下

6. 修改配置文件

  • 在object_detection/samples/configs路径下找到和第五步中的模型匹配的配置文件,这里是下图中的配置文件
  • 将其放在项目路径下
  • 修改以下几个参数
  • num_classes: 3 目标种类的个数,这里是三种细胞目标
  • batch_size: 16 批量大小
  • num_steps: 200 训练步数
  • fine_tune_checkpoint: “./training/model.ckpt” 预训练模型的路径
  • 验证集的样本数量

7.训练模型

运行train.py训练模型, 这里编写一个批处理文件执行

  • train.py
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# 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.
# ==============================================================================r"""Training executable for detection models.This executable is used to train DetectionModels. There are two ways of
configuring the training job:1) A single pipeline_pb2.TrainEvalPipelineConfig configuration file
can be specified by --pipeline_config_path.Example usage:./train \--logtostderr \--train_dir=path/to/train_dir \--pipeline_config_path=pipeline_config.pbtxt2) Three configuration files can be provided: a model_pb2.DetectionModel
configuration file to define what type of DetectionModel is being trained, an
input_reader_pb2.InputReader file to specify what training data will be used and
a train_pb2.TrainConfig file to configure training parameters.Example usage:./train \--logtostderr \--train_dir=path/to/train_dir \--model_config_path=model_config.pbtxt \--train_config_path=train_config.pbtxt \--input_config_path=train_input_config.pbtxt
"""import functools
import json
import os
import tensorflow as tffrom object_detection.builders import dataset_builder
from object_detection.builders import graph_rewriter_builder
from object_detection.builders import model_builder
from object_detection.legacy import trainer
from object_detection.utils import config_utiltf.logging.set_verbosity(tf.logging.INFO)flags = tf.app.flags
flags.DEFINE_string('master', '', 'Name of the TensorFlow master to use.')
flags.DEFINE_integer('task', 0, 'task id')
flags.DEFINE_integer('num_clones', 1, 'Number of clones to deploy per worker.')
flags.DEFINE_boolean('clone_on_cpu', False,'Force clones to be deployed on CPU.  Note that even if ''set to False (allowing ops to run on gpu), some ops may ''still be run on the CPU if they have no GPU kernel.')
flags.DEFINE_integer('worker_replicas', 1, 'Number of worker+trainer ''replicas.')
flags.DEFINE_integer('ps_tasks', 0,'Number of parameter server tasks. If None, does not use ''a parameter server.')
flags.DEFINE_string('train_dir', '','Directory to save the checkpoints and training summaries.')flags.DEFINE_string('pipeline_config_path', '','Path to a pipeline_pb2.TrainEvalPipelineConfig config ''file. If provided, other configs are ignored')flags.DEFINE_string('train_config_path', '','Path to a train_pb2.TrainConfig config file.')
flags.DEFINE_string('input_config_path', '','Path to an input_reader_pb2.InputReader config file.')
flags.DEFINE_string('model_config_path', '','Path to a model_pb2.DetectionModel config file.')FLAGS = flags.FLAGS@tf.contrib.framework.deprecated(None, 'Use object_detection/model_main.py.')
def main(_):assert FLAGS.train_dir, '`train_dir` is missing.'if FLAGS.task == 0: tf.gfile.MakeDirs(FLAGS.train_dir)if FLAGS.pipeline_config_path:configs = config_util.get_configs_from_pipeline_file(FLAGS.pipeline_config_path)if FLAGS.task == 0:tf.gfile.Copy(FLAGS.pipeline_config_path,os.path.join(FLAGS.train_dir, 'pipeline.config'),overwrite=True)else:configs = config_util.get_configs_from_multiple_files(model_config_path=FLAGS.model_config_path,train_config_path=FLAGS.train_config_path,train_input_config_path=FLAGS.input_config_path)if FLAGS.task == 0:for name, config in [('model.config', FLAGS.model_config_path),('train.config', FLAGS.train_config_path),('input.config', FLAGS.input_config_path)]:tf.gfile.Copy(config, os.path.join(FLAGS.train_dir, name),overwrite=True)model_config = configs['model']train_config = configs['train_config']input_config = configs['train_input_config']model_fn = functools.partial(model_builder.build,model_config=model_config,is_training=True)def get_next(config):return dataset_builder.make_initializable_iterator(dataset_builder.build(config)).get_next()create_input_dict_fn = functools.partial(get_next, input_config)env = json.loads(os.environ.get('TF_CONFIG', '{}'))cluster_data = env.get('cluster', None)cluster = tf.train.ClusterSpec(cluster_data) if cluster_data else Nonetask_data = env.get('task', None) or {'type': 'master', 'index': 0}task_info = type('TaskSpec', (object,), task_data)# Parameters for a single worker.ps_tasks = 0worker_replicas = 1worker_job_name = 'lonely_worker'task = 0is_chief = Truemaster = ''if cluster_data and 'worker' in cluster_data:# Number of total worker replicas include "worker"s and the "master".worker_replicas = len(cluster_data['worker']) + 1if cluster_data and 'ps' in cluster_data:ps_tasks = len(cluster_data['ps'])if worker_replicas > 1 and ps_tasks < 1:raise ValueError('At least 1 ps task is needed for distributed training.')if worker_replicas >= 1 and ps_tasks > 0:# Set up distributed training.server = tf.train.Server(tf.train.ClusterSpec(cluster), protocol='grpc',job_name=task_info.type,task_index=task_info.index)if task_info.type == 'ps':server.join()returnworker_job_name = '%s/task:%d' % (task_info.type, task_info.index)task = task_info.indexis_chief = (task_info.type == 'master')master = server.targetgraph_rewriter_fn = Noneif 'graph_rewriter_config' in configs:graph_rewriter_fn = graph_rewriter_builder.build(configs['graph_rewriter_config'], is_training=True)trainer.train(create_input_dict_fn,model_fn,train_config,master,task,FLAGS.num_clones,worker_replicas,FLAGS.clone_on_cpu,ps_tasks,worker_job_name,is_chief,FLAGS.train_dir,graph_hook_fn=graph_rewriter_fn)if __name__ == '__main__':tf.app.run()
  • train.bat (批处理文件)
python train.py --logtostderr --pipeline_config_path=ssd_mobilenet_v1_coco.config --train_dir=train_dir/
pause

第二个参数是指定模型的配置文件,第三个参数是指定模型保存路径

  • 直接双击train.bat即可开始训练模型,运行结果如下图所示

    这里使用的环境是cpu版的tensorflow1.13.1,所以训练速度很慢,一轮大概需要十几秒钟
    训练完200轮之后损失降低到了5左右

8. 模型导出

  • 训练结束后可以将模型导出,这里将训练生成的.ckpt文件转成.pb文件,方便后续调用模型
  • 直接运行export_inference_graph.py文件即可导出模型
  • 这里同样编写一个批处理文件来运行导出程序
  • export_inference_graph.py
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# 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.
# ==============================================================================r"""Tool to export an object detection model for inference.Prepares an object detection tensorflow graph for inference using model
configuration and a trained checkpoint. Outputs inference
graph, associated checkpoint files, a frozen inference graph and a
SavedModel (https://tensorflow.github.io/serving/serving_basic.html).The inference graph contains one of three input nodes depending on the user
specified option.* `image_tensor`: Accepts a uint8 4-D tensor of shape [None, None, None, 3]* `encoded_image_string_tensor`: Accepts a 1-D string tensor of shape [None]containing encoded PNG or JPEG images. Image resolutions are expected to bethe same if more than 1 image is provided.* `tf_example`: Accepts a 1-D string tensor of shape [None] containingserialized TFExample protos. Image resolutions are expected to be the sameif more than 1 image is provided.and the following output nodes returned by the model.postprocess(..):* `num_detections`: Outputs float32 tensors of the form [batch]that specifies the number of valid boxes per image in the batch.* `detection_boxes`: Outputs float32 tensors of the form[batch, num_boxes, 4] containing detected boxes.* `detection_scores`: Outputs float32 tensors of the form[batch, num_boxes] containing class scores for the detections.* `detection_classes`: Outputs float32 tensors of the form[batch, num_boxes] containing classes for the detections.* `detection_masks`: Outputs float32 tensors of the form[batch, num_boxes, mask_height, mask_width] containing predicted instancemasks for each box if its present in the dictionary of postprocessedtensors returned by the model.Notes:* This tool uses `use_moving_averages` from eval_config to decide whichweights to freeze.Example Usage:
--------------
python export_inference_graph \--input_type image_tensor \--pipeline_config_path path/to/ssd_inception_v2.config \--trained_checkpoint_prefix path/to/model.ckpt \--output_directory path/to/exported_model_directoryThe expected output would be in the directory
path/to/exported_model_directory (which is created if it does not exist)
with contents:- inference_graph.pbtxt- model.ckpt.data-00000-of-00001- model.ckpt.info- model.ckpt.meta- frozen_inference_graph.pb+ saved_model (a directory)Config overrides (see the `config_override` flag) are text protobufs
(also of type pipeline_pb2.TrainEvalPipelineConfig) which are used to override
certain fields in the provided pipeline_config_path.  These are useful for
making small changes to the inference graph that differ from the training or
eval config.Example Usage (in which we change the second stage post-processing score
threshold to be 0.5):python export_inference_graph \--input_type image_tensor \--pipeline_config_path path/to/ssd_inception_v2.config \--trained_checkpoint_prefix path/to/model.ckpt \--output_directory path/to/exported_model_directory \--config_override " \model{ \faster_rcnn { \second_stage_post_processing { \batch_non_max_suppression { \score_threshold: 0.5 \} \} \} \}"
"""
import tensorflow as tf
from google.protobuf import text_format
from object_detection import exporter
from object_detection.protos import pipeline_pb2slim = tf.contrib.slim
flags = tf.app.flagsflags.DEFINE_string('input_type', 'image_tensor', 'Type of input node. Can be ''one of [`image_tensor`, `encoded_image_string_tensor`, ''`tf_example`]')
flags.DEFINE_string('input_shape', None,'If input_type is `image_tensor`, this can explicitly set ''the shape of this input tensor to a fixed size. The ''dimensions are to be provided as a comma-separated list ''of integers. A value of -1 can be used for unknown ''dimensions. If not specified, for an `image_tensor, the ''default shape will be partially specified as ''`[None, None, None, 3]`.')
flags.DEFINE_string('pipeline_config_path', None,'Path to a pipeline_pb2.TrainEvalPipelineConfig config ''file.')
flags.DEFINE_string('trained_checkpoint_prefix', None,'Path to trained checkpoint, typically of the form ''path/to/model.ckpt')
flags.DEFINE_string('output_directory', None, 'Path to write outputs.')
flags.DEFINE_string('config_override', '','pipeline_pb2.TrainEvalPipelineConfig ''text proto to override pipeline_config_path.')
flags.DEFINE_boolean('write_inference_graph', False,'If true, writes inference graph to disk.')
tf.app.flags.mark_flag_as_required('pipeline_config_path')
tf.app.flags.mark_flag_as_required('trained_checkpoint_prefix')
tf.app.flags.mark_flag_as_required('output_directory')
FLAGS = flags.FLAGSdef main(_):pipeline_config = pipeline_pb2.TrainEvalPipelineConfig()with tf.gfile.GFile(FLAGS.pipeline_config_path, 'r') as f:text_format.Merge(f.read(), pipeline_config)text_format.Merge(FLAGS.config_override, pipeline_config)if FLAGS.input_shape:input_shape = [int(dim) if dim != '-1' else Nonefor dim in FLAGS.input_shape.split(',')]else:input_shape = Noneexporter.export_inference_graph(FLAGS.input_type, pipeline_config, FLAGS.trained_checkpoint_prefix,FLAGS.output_directory, input_shape=input_shape,write_inference_graph=FLAGS.write_inference_graph)if __name__ == '__main__':tf.app.run()
  • export_inference_graph.bat
python export_inference_graph.py ^
--input_type image_tensor ^
--pipeline_config_path ssd_mobilenet_v1_coco.config ^
--trained_checkpoint_prefix train_dir/model.ckpt-201 ^
--output_directory models
pause

批处理文件传入几个参数,第二个参数是模型配置文件,第三个参数是训练模型生成的.ckpt文件,第四个参数是导出模型的路径
直接双击该批处理文件即可导出模型,运行结束后生成的模型pb文件如下图

9. 利用训练好的模型进行血细胞检测

  • object_detection_tutorial.ipynb
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import os
import six.moves.urllib as urllib
import sys
import tarfile
import tensorflow as tf
import zipfilefrom distutils.version import StrictVersion
from collections import defaultdict
from io import StringIO
from matplotlib import pyplot as plt
from PIL import Image# This is needed since the notebook is stored in the object_detection folder.
sys.path.append("..")
from object_detection.utils import ops as utils_ops# tf版本需要大于1.9
if StrictVersion(tf.__version__) < StrictVersion('1.9.0'):raise ImportError('Please upgrade your TensorFlow installation to v1.9.* or later!')from utils import label_map_utilfrom utils import visualization_utils as vis_util# pb模型存放位置
PATH_TO_FROZEN_GRAPH = 'models/frozen_inference_graph.pb'# coco数据集的label映射文件
PATH_TO_LABELS = 'label_map.pbtxt'# 载入训练好的pd模型
detection_graph = tf.Graph()
with detection_graph.as_default():od_graph_def = tf.GraphDef()with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid:serialized_graph = fid.read()od_graph_def.ParseFromString(serialized_graph)tf.import_graph_def(od_graph_def, name='')# 得到一个保存编号和类别描述映射关系的列表
category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True)
print(category_index)# 把图片数据变成3维的数据,定义数据类型为uint8
def load_image_into_numpy_array(image):(im_width, im_height) = image.sizereturn np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8)# 目标检测
def run_inference_for_single_image(image, graph):with graph.as_default():with tf.Session() as sess:# 获得图中所有opops = tf.get_default_graph().get_operations()# 获得输出op的名字all_tensor_names = {output.name for op in ops for output in op.outputs}tensor_dict = {}for key in ['num_detections', 'detection_boxes', 'detection_scores','detection_classes', 'detection_masks']:tensor_name = key + ':0'# 如果tensor_name在all_tensor_names中if tensor_name in all_tensor_names:# 则获取到该tensortensor_dict[key] = tf.get_default_graph().get_tensor_by_name(tensor_name)# 图片输入的tensorimage_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0')# 传入图片运行模型获得结果output_dict = sess.run(tensor_dict,feed_dict={image_tensor: image})# 所有的结果都是float32类型的,有些数据需要做数据格式转换# 检测到目标的数量output_dict['num_detections'] = int(output_dict['num_detections'][0])# 目标的类型output_dict['detection_classes'] = output_dict['detection_classes'][0].astype(np.uint8)# 预测框坐标output_dict['detection_boxes'] = output_dict['detection_boxes'][0]# 预测框置信度output_dict['detection_scores'] = output_dict['detection_scores'][0]return output_dict#获取细胞计数结果
def get_detection_number(output_dict):select = np.where(output_dict["detection_scores"]>0.3)classes = output_dict["detection_classes"][select]r = 0w = 0p = 0for i in list(classes):if i == 1:r += 1elif i == 2:w += 1else:p += 1cls_num = {"RBC":r, "WBC":w, "Platelets":p, "Total":r+w+p}return cls_num#在原图上绘制检测框和标注名称以及计数结果
import cv2
import math
def draw_boxes_label_on_image(image, boxes, classes, scores, cls_num):select = np.where(output_dict["detection_scores"]>0.3)boxes = boxes[select]classes = classes[select]scores = scores[select]bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)boxes_color = [0, (0,0,255), (255,0,0), (0,255,0)]boxes_text = ["", "RBC", "WBC", "Platelets"]font = cv2.FONT_HERSHEY_SIMPLEXcls_num_str = "RBC:" + str(cls_num["RBC"]) + " WBC:" + str(cls_num["WBC"]) + \" Platelets:" + str(cls_num["Platelets"]) + " Totla:" + str(cls_num["Total"])cv2.putText(bgr, cls_num_str, (25,25), font, 1,\(0,0,0), 1, cv2.LINE_AA)for i in range(len(classes)):xmin = int(boxes[i][1]*640)ymin = int(boxes[i][0]*480)xmax = int(boxes[i][3]*640)ymax = int(boxes[i][2]*480)w = xmax - xminh = ymax - ymincenter_x = int((xmin+xmax)/2)center_y = int((ymin+ymax)/2)radius = int(math.sqrt(w*w + h*h)/3)cv2.circle(bgr, (center_x,center_y), (radius), boxes_color[classes[i]])cv2.putText(bgr, boxes_text[classes[i]], (center_x,center_y), font, 0.5,\boxes_color[classes[i]], 1, cv2.LINE_AA)rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)return bgrfor root,dirs,files in os.walk('test_images/'):for image_path in files:# 读取图片image = Image.open(os.path.join(root,image_path))# 把图片数据变成3维的数据,定义数据类型为uint8image_np = load_image_into_numpy_array(image)# 增加一个维度,数据变成: [1, None, None, 3]image_np_expanded = np.expand_dims(image_np, axis=0)# 目标检测  获取检测结果字典output_dict = run_inference_for_single_image(image_np_expanded, detection_graph)#获取各种细胞计数cls_num = get_detection_number(output_dict)print(cls_num)image_labeled = draw_boxes_label_on_image(image_np,output_dict['detection_boxes'],output_dict['detection_classes'],output_dict['detection_scores'],cls_num)cv2.imshow("dst", image_labeled)cv2.waitKey()

在测试图像路径下存放几张(这里只测试一张)血细胞图像即可进行测试如下图

测试结果如下:

可以看出,检测效果还不错,基本将红细胞和白细胞较为准确的框选和标注了,详细对比下真实标注如下:

对比发现,检测结果总数和真实标注总数一致,均为18个细胞。但是没有检测到两个血小板,多检测出了两个红细胞。这两个红细胞和真实标注对比实质是多检测出三个,然后真实标注中有一个红细胞漏检,多检测出的两个红细胞处在较为边缘的位置如下图

而漏检的那一个红细胞是被另一个红细胞遮挡住了一部分如下图:

此外,白细胞因其特征较为明显被精准的检测到
显然,两个血小板全没有被检测到,究其原因不难想象,其体积相比其他两种细胞过小,且在整张图像中占比很小,往往一张图像中只有一个或两个血小板,我们开始统计的全部数据中三种细胞的数量占比如下图:

可以发现,血小板的数量实在太少,并且有近一半的图像中没有血小板对象,所以血小板较难检测到。而数量同样很少的白细胞却精确的被检测到了,这不难理解,目标检测的难点往往在于小物体,而白细胞体积较大,且几乎每张图像中都至少包含一个白细胞对象。所以我们的重点研究任务将聚集在血小板的检测上。

10. 评估模型

这里评估模型的标准我们采用目标检测最主流的mAP评估
评估方法在eval.py中

  • eval.py
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# 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.
# ==============================================================================
r"""Evaluation executable for detection models.This executable is used to evaluate DetectionModels. There are two ways of
configuring the eval job.1) A single pipeline_pb2.TrainEvalPipelineConfig file maybe specified instead.
In this mode, the --eval_training_data flag may be given to force the pipeline
to evaluate on training data instead.Example usage:./eval \--logtostderr \--checkpoint_dir=path/to/checkpoint_dir \--eval_dir=path/to/eval_dir \--pipeline_config_path=pipeline_config.pbtxt2) Three configuration files may be provided: a model_pb2.DetectionModel
configuration file to define what type of DetectionModel is being evaluated, an
input_reader_pb2.InputReader file to specify what data the model is evaluating
and an eval_pb2.EvalConfig file to configure evaluation parameters.Example usage:./eval \--logtostderr \--checkpoint_dir=path/to/checkpoint_dir \--eval_dir=path/to/eval_dir \--eval_config_path=eval_config.pbtxt \--model_config_path=model_config.pbtxt \--input_config_path=eval_input_config.pbtxt
"""
import functools
import os
import tensorflow as tffrom object_detection.builders import dataset_builder
from object_detection.builders import graph_rewriter_builder
from object_detection.builders import model_builder
from object_detection.legacy import evaluator
from object_detection.utils import config_util
from object_detection.utils import label_map_utiltf.logging.set_verbosity(tf.logging.INFO)flags = tf.app.flags
flags.DEFINE_boolean('eval_training_data', False,'If training data should be evaluated for this job.')
flags.DEFINE_string('checkpoint_dir', '','Directory containing checkpoints to evaluate, typically ''set to `train_dir` used in the training job.')
flags.DEFINE_string('eval_dir', '', 'Directory to write eval summaries to.')
flags.DEFINE_string('pipeline_config_path', '','Path to a pipeline_pb2.TrainEvalPipelineConfig config ''file. If provided, other configs are ignored')
flags.DEFINE_string('eval_config_path', '','Path to an eval_pb2.EvalConfig config file.')
flags.DEFINE_string('input_config_path', '','Path to an input_reader_pb2.InputReader config file.')
flags.DEFINE_string('model_config_path', '','Path to a model_pb2.DetectionModel config file.')
flags.DEFINE_boolean('run_once', False, 'Option to only run a single pass of ''evaluation. Overrides the `max_evals` parameter in the ''provided config.')
FLAGS = flags.FLAGS@tf.contrib.framework.deprecated(None, 'Use object_detection/model_main.py.')
def main(unused_argv):assert FLAGS.checkpoint_dir, '`checkpoint_dir` is missing.'assert FLAGS.eval_dir, '`eval_dir` is missing.'tf.gfile.MakeDirs(FLAGS.eval_dir)if FLAGS.pipeline_config_path:configs = config_util.get_configs_from_pipeline_file(FLAGS.pipeline_config_path)tf.gfile.Copy(FLAGS.pipeline_config_path,os.path.join(FLAGS.eval_dir, 'pipeline.config'),overwrite=True)else:configs = config_util.get_configs_from_multiple_files(model_config_path=FLAGS.model_config_path,eval_config_path=FLAGS.eval_config_path,eval_input_config_path=FLAGS.input_config_path)for name, config in [('model.config', FLAGS.model_config_path),('eval.config', FLAGS.eval_config_path),('input.config', FLAGS.input_config_path)]:tf.gfile.Copy(config, os.path.join(FLAGS.eval_dir, name), overwrite=True)model_config = configs['model']eval_config = configs['eval_config']input_config = configs['eval_input_config']if FLAGS.eval_training_data:input_config = configs['train_input_config']model_fn = functools.partial(model_builder.build, model_config=model_config, is_training=False)def get_next(config):return dataset_builder.make_initializable_iterator(dataset_builder.build(config)).get_next()create_input_dict_fn = functools.partial(get_next, input_config)categories = label_map_util.create_categories_from_labelmap(input_config.label_map_path)if FLAGS.run_once:eval_config.max_evals = 1graph_rewriter_fn = Noneif 'graph_rewriter_config' in configs:graph_rewriter_fn = graph_rewriter_builder.build(configs['graph_rewriter_config'], is_training=False)evaluator.evaluate(create_input_dict_fn,model_fn,eval_config,categories,FLAGS.checkpoint_dir,FLAGS.eval_dir,graph_hook_fn=graph_rewriter_fn)if __name__ == '__main__':tf.app.run()

这里同样编写一个批处理文件来执行评估程序如下

  • eval.bat
python eval.py --logtostderr --pipeline_config_path=ssd_mobilenet_v1_coco.config --eval_dir=eval_dir/ --checkpoint_dir=train_dir/
pause

这里传入四个参数,第一个固定,第二个是模型配置文件,第三个是评估结果的保存路径,第四个是模型训练的检查点保存路径。评估结果以tensorboard日志文件形式保存如下

评估程序运行结束后如下所示结果

输出信息显示,最终目标分类损失为3.417399, 预测框回归损失为1.131326
血小板在交并比阈值为0.5的情况下AP为0.045508 这个结果太糟糕了,原因上面分析过
红细胞在交并比阈值为0.5的情况下AP为0.883228, 这个结果很不错
白细胞在交并比阈值为0.5的情况下AP为0.937231, 这个结果非常不错
显然,受血小板AP的拖累,mAP值仅为0.621989

tensorflow目标检测API实现血细胞图像识别和计数相关推荐

  1. 用 TensorFlow 目标检测 API 发现皮卡丘!

    在 TensorFlow 众多功能和工具中,有一个名为 TensorFlow 目标检测 API 的组件.这个库的功能正如它的名字,是用来训练神经网络检测视频帧中目标的能力,比如,一副图像. 需要查看我 ...

  2. 谷歌更新TensorFlow目标检测API

    去年谷歌发布了TensorFlow目标检测API[7],从那以后陆续添加了很多新特性,比如Neural Architecture Search[1]的模型学习,支持实例分割[8],在谷歌的超大数据集O ...

  3. tensorflow目标检测API实现

    tensorflow目标检测API实现(windows环境) 1下载Anaconda(https://repo.continuum.io/archive/Anaconda3-4.2.0-Windows ...

  4. tensorflow 目标检测API及jupyter notebook 虚拟环境配置

    环境 tensorflow == 2.8.0 win10 or linux 概要 目标检测项目的主要步骤如下: 搭建虚拟环境 采集图像并打标 训练 预测 模型的保存和转换 调优 项目部署 1. 搭建虚 ...

  5. Python 数据科学入门教程:TensorFlow 目标检测

    TensorFlow 目标检测 原文:TensorFlow Object Detection 译者:飞龙 协议:CC BY-NC-SA 4.0 一.引言 你好,欢迎阅读 TensorFlow 目标检测 ...

  6. 深度学习实战(七)——目标检测API训练自己的数据集(R-FCN数据集制作+训练+测试)

    TensorFlow提供的网络结构的预训练权重:https://cloud.tencent.com/developer/article/1006123 将voc数据集转换成.tfrecord格式供te ...

  7. TF2目标检测API

    作者|Ivan Ralašić 编译|VK 来源|Analytics Vidhya Tensorflow目标检测API(TF OD API)刚刚变得更好.最近,Google发布了tf od api的新 ...

  8. 【小应用】使用TensorFlow目标检测模型和OpenCV分析足球比赛

    文章目录 一.引言 二.步骤概述 三.深入探讨主要步骤 四.结论和参考   本文是翻墙阅读其他博客的翻译 一.引言   作为数据科学家,我们有机会对足球视频剪辑做一些分析,使用深度学习和opencv可 ...

  9. 实践操作:六步教你如何用开源框架Tensorflow对象检测API构建一个玩具检测器

    TensorFlow对象检测API是一个建立在TensorFlow之上的开源框架,可以轻松构建,训练和部署对象检测模型. 到目前为止,API的性能给我留下了深刻的印象.在这篇文章中,我将API的对象设 ...

  10. 如何使用TensorFlow对象检测API播放Quidditch

    by Bharath Raj 巴拉斯·拉吉(Bharath Raj) 如何使用TensorFlow对象检测API播放Quidditch (How to play Quidditch using the ...

最新文章

  1. awk2.0 — awk分隔符
  2. 一、nginx基本模块以及模块配置
  3. 内核同步机制——完成量
  4. linux卸载dev中的设备,Linux /dev 常见特殊设备介绍与应用
  5. pfSense DMZ配置
  6. 教程:创建简单的 ETL 包
  7. 使用URI设计松散耦合的Metro应用程序
  8. Android之使用ACTION_USAGE_ACCESS_SETTINGS权限检测手机多少天没有未使用其它APP
  9. 为《31天成为IT服务达人》征求正式名字
  10. 无限法则无法连接服务器内容,无限法则链接不了服务器
  11. Android Studio插件安装
  12. 计算机网络实验报告3-tcp,计算机网络实验报告3-TCP.doc
  13. 《健康报》携手健康之路帮助医院医生免费搭建微官网
  14. Android 实现点击按钮弹出日期选择器与时间选择器
  15. 《穿越计算机的迷雾》第一版说明
  16. 红外解码--基于1838红外接收头
  17. Hi3516A开发--电阻分压阻值计算
  18. 全国省市县区乡镇级别的矢量文件(百度网盘可下载)
  19. 操作系统 文件管理实验报告
  20. 用photoShop简单提取他人的签名

热门文章

  1. Boost电路硬件设计实例
  2. java对大陆身份证号码验证
  3. shell脚本的逻辑判断
  4. 多个excel工作簿合并_无需VBA代码,1分钟合并多个工作簿至一个工作簿!
  5. 计算机窗口的PPT,PPT幻灯片基础入门01-认识窗口界面
  6. 《赖氏经典英语语法》第五集
  7. Android 4.1 Netd详细分析(一)概述与应用实例
  8. php直播平台原理,php直播平台源码的直播带货平台有何技巧,别说没告诉你
  9. StyleGAN3 笔记
  10. mysql两种事务管理器_MyBatis事务管理的两种方式