项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义
人种这一概念在维基百科中的定义是:一个根据文化、种族、地理和历史等因素将人类划分成大规模的有差异的群体的社会概念。由于各种地理环境,如大山、沙漠、河流和海洋的隔离,不同的古代人类群体在不同的自然环境下发生了独特的生物进化过程。根据不同的人种分类学,人类能够被划分为3到200个群体。本文中对人种的划分是基于Fu的结论",即将人种划分为7个被广泛接受的群体,即非洲人、南亚人、东亚人、高加索人、印度人、阿拉伯人和拉丁美洲人,这些群体覆盖了世界人口的85%。不同的人种之间有两种差异:个体物理差异和文化行为差异。个体物理上的差异包括皮肤颜色、虹膜颜色、头发颜色、身高和颅面部等。而文化行为差异包括语言、服饰、头饰等。通常,“人种”一词用于描述不同群体之间的物理特征,而“种族”一词用于描述不同群体之间的文化特征。
本文主要针对人脸图像上存在的不同群体间的个体物理差异进行分析研究,因此,文中所用的相关概念都是用“人种”一词。
人种分类,即对通过照相机等设备采集到的人脸图像上的人种相关信息进行特征提取,然后采用一定的算法进行分析,最终确定该人脸图像所对应的对象所属人种类别的过程。随着科技的发展,导致不同人种形成的主要因素—地理阻隔,已经几乎不再存在,整个世界呈现出一体化景象。但是,在世界走向融合的进程中,总会发生不同种族之间或者他们各自所拥有的文化之间的冲突。全球化的发展需要引导各种族的融合,同时需要监督管理融合过程中发生的不和谐的情况。在这种背景下,人种分类的智能化研究工作在若干个方面都体现出了极大的意义。
改进CARAFE特征图上采样的YOLO的人种图像分类系统_哔哩哔哩_bilibili
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集RaceDatasets。
下面是一个简单的方法是使用Python脚本,该脚本读取分类图片文件,然后将其转换为所需的格式。
import os
import shutil
import random
# 指定输入和输出文件夹的路径
input_dir = 'train'
output_dir = 'output'
# 确保输出文件夹存在
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 遍历输入文件夹中的所有子文件夹
for subdir in os.listdir(input_dir):
input_subdir_path = os.path.join(input_dir, subdir)
# 确保它是一个子文件夹
if os.path.isdir(input_subdir_path):
output_subdir_path = os.path.join(output_dir, subdir)
# 在输出文件夹中创建同名的子文件夹
if not os.path.exists(output_subdir_path):
os.makedirs(output_subdir_path)
# 获取所有文件的列表
files = [f for f in os.listdir(input_subdir_path) if os.path.isfile(os.path.join(input_subdir_path, f))]
# 随机选择四分之一的文件
files_to_move = random.sample(files, len(files) // 4)
# 移动文件
for file_to_move in files_to_move:
src_path = os.path.join(input_subdir_path, file_to_move)
dest_path = os.path.join(output_subdir_path, file_to_move)
shutil.move(src_path, dest_path)
print("任务完成!")
我们需要将数据集整理为以下结构:
-----dataset
-----dataset
|-----train
| |-----class1
| |-----class2
| |-----.......
|
|-----valid
| |-----class1
| |-----class2
| |-----.......
|
|-----test
| |-----class1
| |-----class2
| |-----.......
Epoch gpu_mem box obj cls labels img_size
1/200 20.8G 0.01576 0.01955 0.007536 22 1280: 100%|██████████| 849/849 [14:42<00:00, 1.04s/it]
Class Images Labels P R [email protected] [email protected]:.95: 100%|██████████| 213/213 [01:14<00:00, 2.87it/s]
all 3395 17314 0.994 0.957 0.0957 0.0843
Epoch gpu_mem box obj cls labels img_size
2/200 20.8G 0.01578 0.01923 0.007006 22 1280: 100%|██████████| 849/849 [14:44<00:00, 1.04s/it]
Class Images Labels P R [email protected] [email protected]:.95: 100%|██████████| 213/213 [01:12<00:00, 2.95it/s]
all 3395 17314 0.996 0.956 0.0957 0.0845
Epoch gpu_mem box obj cls labels img_size
3/200 20.8G 0.01561 0.0191 0.006895 27 1280: 100%|██████████| 849/849 [10:56<00:00, 1.29it/s]
Class Images Labels P R [email protected] [email protected]:.95: 100%|███████ | 187/213 [00:52<00:00, 4.04it/s]
all 3395 17314 0.996 0.957 0.0957 0.0845
def export_formats():
# YOLOv5 export formats
x = [
['PyTorch', '-', '.pt', True, True],
['TorchScript', 'torchscript', '.torchscript', True, True],
['ONNX', 'onnx', '.onnx', True, True],
['OpenVINO', 'openvino', '_openvino_model', True, False],
['TensorRT', 'engine', '.engine', False, True],
['CoreML', 'coreml', '.mlmodel', True, False],
['TensorFlow SavedModel', 'saved_model', '_saved_model', True, True],
['TensorFlow GraphDef', 'pb', '.pb', True, True],
['TensorFlow Lite', 'tflite', '.tflite', True, False],
['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False],
['TensorFlow.js', 'tfjs', '_web_model', False, False],
['PaddlePaddle', 'paddle', '_paddle_model', True, True],]
return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU'])
def try_export(inner_func):
# YOLOv5 export decorator, i..e @try_export
inner_args = get_default_args(inner_func)
def outer_func(*args, **kwargs):
prefix = inner_args['prefix']
try:
with Profile() as dt:
f, model = inner_func(*args, **kwargs)
LOGGER.info(f'{prefix} export success ✅ {dt.t:.1f}s, saved as {f} ({file_size(f):.1f} MB)')
return f, model
except Exception as e:
LOGGER.info(f'{prefix} export failure ❌ {dt.t:.1f}s: {e}')
return None, None
return outer_func
@try_export
def export_torchscript(model, im, file, optimize, prefix=colorstr('TorchScript:')):
# YOLOv5 TorchScript model export
LOGGER.info(f'\n{prefix} starting export with torch {torch.__version__}...')
f = file.with_suffix('.torchscript')
ts = torch.jit.trace(model, im, strict=False)
d = {'shape': im.shape, 'stride': int(max(model.stride)), 'names': model.names}
extra_files = {'config.txt': json.dumps(d)} # torch._C.ExtraFilesMap()
if optimize: # https://pytorch.org/tutorials/recipes/mobile_interpreter.html
optimize_for_mobile(ts)._save_for_lite_interpreter(str(f), _extra_files=extra_files)
else:
ts.save(str(f), _extra_files=extra_files)
return f, None
@try_export
def export_onnx(model, im, file, opset, dynamic, simplify, prefix=colorstr('ONNX:')):
# YOLOv5 ONNX export
check_requirements('onnx>=1.12.0')
import onnx
LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
f = file.with_suffix('.onnx')
output_names = ['output0', 'output1'] if isinstance(model, SegmentationModel) else ['output0']
if dynamic:
dynamic = {'images': {0: 'batch', 2: 'height', 3: 'width'}} # shape(1,3,640,640)
if isinstance(model, SegmentationModel):
dynamic['output0'] = {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
dynamic['output1'] = {0: 'batch', 2: 'mask_height', 3: 'mask_width'} # shape(1,32,160,160)
elif isinstance(model, DetectionModel):
dynamic['output0'] = {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
torch.onnx.export(
model.cpu() if dynamic else model, # --dynamic only compatible with cpu
im.cpu() if dynamic else im,
f,
verbose=False,
opset_version=opset,
do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
input_names=['images'],
output_names=output_names,
dynamic_axes=dynamic or None)
# Checks
model_onnx = onnx.load(f) # load onnx model
onnx.checker.check_model(model_onnx) # check onnx model
# Metadata
d = {'stride': int(max(model.stride)), 'names': model.names}
for k, v in d.items():
meta = model_onnx.metadata_props.add()
meta.key, meta.value = k, str(v)
onnx.save(model_onnx, f)
# Simplify
if simplify:
try:
cuda = torch.cuda.is_available()
check_requirements(('onnxruntime-gpu' if cuda else 'onnxruntime', 'onnx-simplifier>=0.4.1'))
import onnxsim
LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
model_onnx, check = onnxsim.simplify(model_onnx)
export.py是一个用于将YOLOv5 PyTorch模型导出为其他格式的程序文件。该文件定义了一些函数和装饰器,用于导出模型到不同的格式,如TorchScript、ONNX、OpenVINO等。该文件还包含了一些辅助函数和全局变量。
文件中定义的函数包括:
文件中还定义了一些全局变量和常量,如FILE、ROOT、MACOS等。
使用该文件可以通过命令行参数指定要导出的模型文件和导出的格式,然后调用相应的导出函数进行导出。导出成功后,会输出相应的日志信息。
此外,文件中还包含了一些关于使用导出模型进行推理的示例代码和TensorFlow.js的使用说明。
class YOLOv5Trainer:
def __init__(self, hyp, opt, device, callbacks):
self.hyp = hyp
self.opt = opt
self.device = device
self.callbacks = callbacks
self.save_dir = Path(opt.save_dir)
self.epochs = opt.epochs
self.batch_size = opt.batch_size
self.weights = opt.weights
self.single_cls = opt.single_cls
self.evolve = opt.evolve
self.data = opt.data
self.cfg = opt.cfg
self.resume = opt.resume
self.noval = opt.noval
self.nosave = opt.nosave
self.workers = opt.workers
self.freeze = opt.freeze
self.w = self.save_dir / 'weights'
self.last = self.w / 'last.pt'
self.best = self.w / 'best.pt'
self.plots = not self.evolve and not opt.noplots
self.cuda = self.device.type != 'cpu'
self.init_seeds(opt.seed + 1 + RANK, deterministic=True)
self.data_dict = None
self.loggers = None
self.train_path = None
self.val_path = None
self.nc = None
self.names = None
self.is_coco = None
self.model = None
self.optimizer = None
self.scheduler = None
self.ema = None
self.best_fitness = 0.0
self.start_epoch = 0
def init_seeds(self, seed, deterministic=True):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
if deterministic:
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
def train(self):
self.callbacks.run('on_pretrain_routine_start')
(self.w.parent if self.evolve else self.w).mkdir(parents=True, exist_ok=True)
if isinstance(self.hyp, str):
with open(self.hyp, errors='ignore') as f:
self.hyp = yaml.safe_load(f)
LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in self.hyp.items()))
self.opt.hyp = self.hyp.copy()
if not self.evolve:
yaml_save(self.save_dir / 'hyp.yaml', self.hyp)
yaml_save(self.save_dir / 'opt.yaml', vars(self.opt))
self.data_dict = self.check_dataset(self.data)
self.train_path, self.val_path = self.data_dict['train'], self.data_dict['val']
self.nc = 1 if self.single_cls else int(self.data_dict['nc'])
self.names = {0: 'item'} if self.single_cls and len(self.data_dict['names']) != 1 else self.data_dict['names']
self.is_coco = isinstance(self.val_path, str) and self.val_path.endswith('coco/val2017.txt')
self.check_suffix(self.weights, '.pt')
pretrained = self.weights.endswith('.pt')
if pretrained:
self.weights = self.attempt_download(self.weights)
ckpt = torch.load(self.weights, map_location='cpu')
self.model = Model(self.cfg or ckpt['model'].yaml, ch=3, nc=self.nc, anchors=self.hyp.get('anchors')).to(self.device)
exclude = ['anchor'] if (self.cfg or self.hyp.get('anchors')) and not self.resume else []
csd = ckpt['model'].float().state_dict()
csd = self.intersect_dicts(csd, self.model.state_dict(), exclude=exclude)
self.model.load_state_dict(csd, strict=False)
LOGGER.info(f'Transferred {len(csd)}/{len(self.model.state_dict())} items from {self.weights}')
else:
self.model = Model(self.cfg, ch=3, nc=self.nc, anchors=self.hyp.get('anchors')).to(self.device)
self.amp = self.check_amp(self.model)
freeze = [f'model.{x}.' for x in (self.freeze if len(self.freeze) > 1 else range(self.freeze[0]))]
for k, v in self.model.named_parameters():
v.requires_grad = True
if any(x in k for x in freeze):
LOGGER.info(f'freezing {k}')
v.requires_grad = False
gs = max(int(self.model.stride.max()), 32)
self.imgsz = self.check_img_size(self.opt.imgsz, gs, floor=gs * 2)
if RANK == -1 and self.batch_size == -1:
self.batch_size = self.check_train_batch_size(self.model, self.imgsz, self.amp)
self.loggers.on_params_update({'batch_size': self.batch_size})
self.hyp['weight_decay'] *= self.batch_size * self.accumulate / self.nbs
self.optimizer = self.smart_optimizer(self.model, self.opt.optimizer, self.hyp['lr0'], self.hyp['momentum'], self.hyp['weight_decay'])
if self.opt.cos_lr:
lf = self.one_cycle(1, self.hyp['lrf'], self.epochs)
else:
lf = lambda x: (1 - x / self.epochs) * (1.0 - self.hyp['lrf']) + self.hyp['lrf']
self.scheduler = lr_scheduler.LambdaLR(self.optimizer, lr_lambda=lf)
self.ema = ModelEMA(self.model) if RANK in {-1, 0} else None
if pretrained:
if self.resume:
self.best_fitness, self.start_epoch, self.epochs = self.smart_resume(ckpt, self.optimizer, self.ema, self.weights, self.epochs, self.resume)
del ckpt, csd
if self.cuda and RANK == -1 and torch.cuda.device_count() > 1:
LOGGER.warning(
'WARNING ⚠️ DP not recommended, use torch.distributed.run for best DDP Multi-GPU results.\n'
'See Multi-GPU Tutorial at https://docs.ultralytics.com/yolov5/tutorials/multi_gpu_training to get started.'
)
self.model = torch.nn.DataParallel(self.model)
if self.opt.sync_bn and self.cuda and RANK != -1:
self.model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(self.model).to(self.device)
self.loggers = Loggers(self.save_dir, self.weights, self.opt, self.hyp, LOGGER)
for k in methods(self.loggers):
self.callbacks.register_action(k, callback=getattr(self.loggers, k))
self.loggers.remote_dataset = self.data_dict
if self.resume:
self.weights, self.epochs, self.hyp, self.batch_size = self.opt.weights, self.opt.epochs, self.opt.hyp, self.opt.batch_size
self.loggers.on_pretrain_routine_start()
train.py是一个用于训练YOLOv5模型的程序文件。它可以在自定义数据集上训练模型,并支持单GPU和多GPU的训练。
程序的使用方法如下:
python train.py --data coco128.yaml --weights yolov5s.pt --img 640
python train.py --data coco128.yaml --weights '' --cfg yolov5s.yaml --img 640
python -m torch.distributed.run --nproc_per_node 4 --master_port 1 train.py --data coco128.yaml --weights yolov5s.pt --img 640 --device 0,1,2,3
程序会自动下载YOLOv5模型和数据集,并根据参数进行训练。训练过程中会输出训练日志和进度条。训练完成后,会保存训练权重和日志文件。
程序的主要功能包括:
程序还支持多GPU训练、分布式训练和模型权重的加载和保存。
class ImageClassifier:
def __init__(self, weights, source, data, imgsz, device, view_img, save_txt, nosave, augment, visualize, update, project, name, exist_ok, half, dnn, vid_stride):
self.weights = weights
self.source = source
self.data = data
self.imgsz = imgsz
self.device = device
self.view_img = view_img
self.save_txt = save_txt
self.nosave = nosave
self.augment = augment
self.visualize = visualize
self.update = update
self.project = project
self.name = name
self.exist_ok = exist_ok
self.half = half
self.dnn = dnn
self.vid_stride = vid_stride
def run(self):
source = str(self.source)
save_img = not self.nosave and not source.endswith('.txt') # save inference images
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
screenshot = source.lower().startswith('screen')
if is_url and is_file:
source = check_file(source) # download
# Directories
save_dir = increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok) # increment run
(save_dir / 'labels' if self.save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Load model
device = select_device(self.device)
model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data, fp16=self.half)
stride, names, pt = model.stride, model.names, model.pt
imgsz = check_img_size(self.imgsz, s=stride) # check image size
# Dataloader
bs = 1 # batch_size
if webcam:
view_img = check_imshow(warn=True)
dataset = LoadStreams(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]), vid_stride=self.vid_stride)
bs = len(dataset)
elif screenshot:
dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
else:
dataset = LoadImages(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]), vid_stride=self.vid_stride)
vid_path, vid_writer = [None] * bs, [None] * bs
# Run inference
model.warmup(imgsz=(1 if pt else bs, 3, *imgsz)) # warmup
seen, windows, dt = 0, [], (Profile(), Profile(), Profile())
for path, im, im0s, vid_cap, s in dataset:
with dt[0]:
im = torch.Tensor(im).to(model.device)
im = im.half() if model.fp16 else im.float() # uint8 to fp16/32
if len(im.shape) == 3:
im = im[None] # expand for batch dim
# Inference
with dt[1]:
results = model(im)
# Post-process
with dt[2]:
pred = F.softmax(results, dim=1) # probabilities
# Process predictions
for i, prob in enumerate(pred): # per image
seen += 1
if webcam: # batch_size >= 1
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f'{i}: '
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
p = Path(p) # to Path
save_path = str(save_dir / p.name) # im.jpg
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # im.txt
s += '%gx%g ' % im.shape[2:] # print string
annotator = Annotator(im0, example=str(names), pil=True)
# Print results
top5i = prob.argsort(0, descending=True)[:5].tolist() # top 5 indices
s += f"{', '.join(f'{names[j]} {prob[j]:.2f}' for j in top5i)}, "
# Write results
text = '\n'.join(f'{prob[j]:.2f} {names[j]}' for j in top5i)
classname = names[top5i[0]]
ui.printf('人种分类为:' + str(classname))
if save_img or view_img: # Add bbox to image
annotator.text((32, 32), text, txt_color=(255, 255, 255))
if self.save_txt: # Write to file
with open(f'{txt_path}.txt', 'a') as f:
f.write(text + '\n')
# Stream results
im0 = annotator.result()
ui.showimg(im0)
QApplication.processEvents()
if view_img:
if platform.system() == 'Linux' and p not in windows:
windows.append(p)
cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO) # allow window resize (Linux)
cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
cv2.imshow(str(p), im0)
cv2.waitKey(1) # 1 millisecond
# Save results (image
ui.py是一个使用PyQt5实现的图形用户界面程序。该程序的主要功能是使用YOLOv5模型对图像或视频进行目标检测和分类。程序通过读取命令行参数来设置模型路径、输入源、数据集路径、推理尺寸、设备、是否显示结果等参数。程序使用PyTorch和OpenCV库来加载模型、进行推理和处理图像。推理结果可以保存为图片或文本文件,并可以在界面上显示出来。
class YOLOv5Validator:
def __init__(self, weights, data, batch_size=32, imgsz=640, conf_thres=0.001, iou_thres=0.6, max_det=300,
device='', workers=8, single_cls=False, augment=False, verbose=False, save_txt=False,
save_hybrid=False, save_conf=False, save_json=False, project=ROOT / 'runs/val', name='exp',
exist_ok=False, half=True, dnn=False, model=None, dataloader=None, save_dir=Path(''),
plots=True, callbacks=Callbacks(), compute_loss=None):
self.weights = weights
self.data = data
self.batch_size = batch_size
self.imgsz = imgsz
self.conf_thres = conf_thres
self.iou_thres = iou_thres
self.max_det = max_det
self.device = device
self.workers = workers
self.single_cls = single_cls
self.augment = augment
self.verbose = verbose
self.save_txt = save_txt
self.save_hybrid = save_hybrid
self.save_conf = save_conf
self.save_json = save_json
self.project = project
self.name = name
self.exist_ok = exist_ok
self.half = half
self.dnn = dnn
self.model = model
self.dataloader = dataloader
self.save_dir = save_dir
self.plots = plots
self.callbacks = callbacks
self.compute_loss = compute_loss
def run(self):
# Initialize/load model and set device
training = self.model is not None
if training: # called by train.py
device, pt, jit, engine = next(self.model.parameters()).device, True, False, False # get model device, PyTorch model
half &= device.type != 'cpu' # half precision only supported on CUDA
self.model.half() if half else self.model.float()
else: # called directly
device = select_device(self.device, batch_size=self.batch_size)
# Directories
self.save_dir = increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok) # increment run
(self.save_dir / 'labels' if self.save_txt else self.save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Load model
self.model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data, fp16=self.half)
stride, pt, jit, engine = self.model.stride, self.model.pt, self.model.jit, self.model.engine
self.imgsz = check_img_size(self.imgsz, s=stride) # check image size
self.half = self.model.fp16 # FP16 supported on limited backends with CUDA
if engine:
self.batch_size = self.model.batch_size
else:
device = self.model.device
if not (pt or jit):
self.batch_size = 1 # export.py models default to batch-size 1
LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{self.imgsz},{self.imgsz}) for non-PyTorch models')
# Data
self.data = check_dataset(self.data) # check
# Configure
self.model.eval()
cuda = device.type != 'cpu'
is_coco = isinstance(self.data.get('val'), str) and self.data['val'].endswith(f'coco{os.sep}val2017.txt') # COCO dataset
nc = 1 if self.single_cls else int(self.data['nc']) # number of classes
iouv = torch.linspace(0.5, 0.95, 10, device=device) # iou vector for [email protected]:0.95
niou = iouv.numel()
# Dataloader
if not training:
if pt and not self.single_cls: # check --weights are trained on --data
ncm = self.model.model.nc
assert ncm == nc, f'{self.weights} ({ncm} classes) trained on different --data than what you passed ({nc} ' \
f'classes). Pass correct combination of --weights and --data that are trained together.'
self.model.warmup(imgsz=(1 if pt else self.batch_size, 3, self.imgsz, self.imgsz)) # warmup
pad, rect = (0.0, False) if self.task == 'speed' else (0.5, pt) # square inference for benchmarks
self.task = self.task if self.task in ('train', 'val', 'test') else 'val' # path to train/val/test images
self.dataloader = create_dataloader(self.data[self.task],
self.imgsz,
self.batch_size,
stride,
self.single_cls,
pad=pad,
rect=rect,
workers=self.workers,
prefix=colorstr(f'{self.task}: '))[0]
seen = 0
confusion_matrix = ConfusionMatrix(nc=nc)
names = self.model.names if hasattr(self.model, 'names') else self.model.module.names # get class names
if isinstance(names, (list, tuple)): # old format
names = dict(enumerate(names))
class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
s = ('%22s' + '%11s' * 6) % ('Class', 'Images', 'Instances', 'P', 'R', 'mAP50', 'mAP50-95')
tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
这是一个用于在检测数据集上验证训练好的YOLOv5检测模型的程序文件。它包含了一些命令行参数,可以用来指定模型权重文件、数据集文件、图像大小等。程序首先加载模型并设置设备,然后加载数据集并创建数据加载器。接下来,程序通过数据加载器迭代处理每个批次的图像和目标。对于每个批次,程序进行推理并计算损失(如果需要)。然后,程序使用非最大抑制(NMS)对预测进行后处理,并计算评估指标(如精确率、召回率、mAP等)。最后,程序保存结果(如预测结果的文本文件、JSON文件等)并绘制相关的图表。
class CARAFE(nn.Module):
def __init__(self, c, k_enc=3, k_up=5, c_mid=64, scale=2):
super(CARAFE, self).__init__()
self.scale = scale
self.comp = Conv(c, c_mid)
self.enc = Conv(c_mid, (scale*k_up)**2, k=k_enc, act=False)
self.pix_shf = nn.PixelShuffle(scale)
self.upsmp = nn.Upsample(scale_factor=scale, mode='nearest')
self.unfold = nn.Unfold(kernel_size=k_up, dilation=scale,
padding=k_up//2*scale)
def forward(self, X):
b, c, h, w = X.size()
h_, w_ = h * self.scale, w * self.scale
W = self.comp(X) # b * m * h * w
W = self.enc(W) # b * 100 * h * w
W = self.pix_shf(W) # b * 25 * h_ * w_
W = torch.softmax(W, dim=1) # b * 25 * h_ * w_
X = self.upsmp(X) # b * c * h_ * w_
X = self.unfold(X) # b * 25c * h_ * w_
X = X.view(b, c, -1, h_, w_) # b * 25 * c * h_ * w_
X = torch.einsum('bkhw,bckhw->bchw', [W, X]) # b * c * h_ * w_
return X
这是一个名为yolov5-CARAFE.py的程序文件,它实现了CARAFE模块的类。CARAFE模块是一个非官方实现,详细内容可以在论文"https://arxiv.org/abs/1905.02188"中找到。
CARAFE类的构造函数接受一些参数,包括输入和输出的通道数©,编码器的kernel大小(k_enc),上采样的kernel大小(k_up),压缩后的通道数(c_mid),以及期望的上采样比例(scale)。
forward方法是CARAFE类的前向传播函数。它接受一个输入张量X,并根据CARAFE模块的算法对其进行处理。首先,将输入张量X通过一个压缩层(self.comp)得到一个中间特征图W。然后,将W通过一个编码器层(self.enc)得到一个大小为(scalek_up)^2的特征图W。接下来,通过像素洗牌层(self.pix_shf)将W上采样到大小为h_w_的特征图。然后,对W进行softmax操作,得到一个权重矩阵W,大小为25h_w_。接着,将输入张量X通过上采样层(self.upsmp)上采样到大小为h_w_的特征图。然后,通过展开层(self.unfold)将X展开为大小为25ch_w_的特征图。接下来,将展开后的特征图X重新调整为大小为b25ch_w_的形状。最后,通过张量乘法运算(torch.einsum)将权重矩阵W与特征图X相乘,得到最终的上采样特征图X,大小为bc*h_*w_。
如果变量m是CARAFE类的实例,那么将根据变量f的值获取通道数c2,并将c2和其他参数一起传递给args列表。
class YOLOv5Classifier:
def __init__(self, weights, source, data, imgsz, device, view_img, save_txt, nosave, augment, visualize, update,
project, name, exist_ok, half, dnn, vid_stride):
self.weights = weights
self.source = source
self.data = data
self.imgsz = imgsz
self.device = device
self.view_img = view_img
self.save_txt = save_txt
self.nosave = nosave
self.augment = augment
self.visualize = visualize
self.update = update
self.project = project
self.name = name
self.exist_ok = exist_ok
self.half = half
self.dnn = dnn
self.vid_stride = vid_stride
def run(self):
source = str(self.source)
save_img = not self.nosave and not source.endswith('.txt') # save inference images
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
screenshot = source.lower().startswith('screen')
if is_url and is_file:
source = check_file(source) # download
# Directories
save_dir = increment_path(Path(self.project) / self.name, exist_ok=self.exist_ok) # increment run
(save_dir / 'labels' if self.save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Load model
device = select_device(self.device)
model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data, fp16=self.half)
stride, names, pt = model.stride, model.names, model.pt
imgsz = check_img_size(self.imgsz, s=stride) # check image size
# Dataloader
bs = 1 # batch_size
if webcam:
view_img = check_imshow(warn=True)
dataset = LoadStreams(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]),
vid_stride=self.vid_stride)
bs = len(dataset)
elif screenshot:
dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
else:
dataset = LoadImages(source, img_size=imgsz, transforms=classify_transforms(imgsz[0]),
vid_stride=self.vid_stride)
vid_path, vid_writer = [None] * bs, [None] * bs
# Run inference
model.warmup(imgsz=(1 if pt else bs, 3, *imgsz)) # warmup
seen, windows, dt = 0, [], (Profile(), Profile(), Profile())
for path, im, im0s, vid_cap, s in dataset:
with dt[0]:
im = torch.Tensor(im).to(model.device)
im = im.half() if model.fp16 else im.float() # uint8 to fp16/32
if len(im.shape) == 3:
im = im[None] # expand for batch dim
# Inference
with dt[1]:
results = model(im)
# Post-process
with dt[2]:
pred = F.softmax(results, dim=1) # probabilities
# Process predictions
for i, prob in enumerate(pred): # per image
seen += 1
if webcam: # batch_size >= 1
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f'{i}: '
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
p = Path(p) # to Path
save_path = str(save_dir / p.name) # im.jpg
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # im.txt
s += '%gx%g ' % im.shape[2:] # print string
annotator = Annotator(im0, example=str(names), pil=True)
# Print results
top5i = prob.argsort(0, descending=True)[:5].tolist() # top 5 indices
s += f"{', '.join(f'{names[j]} {prob[j]:.2f}' for j in top5i)}, "
# Write results
text = '\n'.join(f'{prob[j]:.2f} {names[j]}' for j in top
这个程序文件是一个用于YOLOv5分类推理的脚本。它可以对图像、视频、目录、URL等进行分类推理。
该脚本可以通过命令行参数指定模型文件、数据集文件、推理尺寸、设备等信息。它使用YOLOv5模型进行推理,并将结果保存到指定的目录中。可以选择是否显示结果、保存结果到文本文件、保存结果图像/视频等。
该脚本还支持一些特性,如数据增强、特征可视化、模型更新等。
通过解析命令行参数,创建模型并运行推理过程。最后,打印推理速度和保存结果的信息。
要运行该脚本,可以使用命令行参数来指定模型文件、数据源等信息。
整体功能和构架概述:
该项目是一个改进CARAFE特征图上采样的YOLO的人种图像分类系统。它包含了多个程序文件,用于训练、验证、推理和导出YOLOv5模型,以及实现图形用户界面和其他辅助功能。
以下是每个文件的功能概述:
文件路径 | 功能 |
---|---|
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\export.py | 将YOLOv5模型导出为其他格式的文件,如TorchScript、ONNX等 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\train.py | 训练YOLOv5模型的程序文件,支持单GPU和多GPU训练 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\ui.py | 使用PyQt5实现的图形用户界面程序,用于目标检测和分类推理 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\val.py | 在检测数据集上验证训练好的YOLOv5检测模型 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\yolov5-CARAFE.py | 实现CARAFE模块的类,用于特征图上采样 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\classify\predict.py | YOLOv5分类推理的脚本,用于对图像、视频等进行分类推理 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\classify\train.py | YOLOv5分类模型的训练脚本,用于训练分类模型 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\classify\val.py | YOLOv5分类模型的验证脚本,用于验证分类模型 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\models\common.py | 包含YOLOv5模型的通用函数和类,如Conv、BottleneckCSP等 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\models\experimental.py | 包含YOLOv5模型的实验性功能,如CSPDarknet53、ScaledYOLOv4等 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\models\tf.py | 包含YOLOv5模型与TensorFlow相关的函数和类 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\models\yolo.py | 包含YOLOv5模型的主要类和函数,如YOLOv5、Detect等 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\models_init_.py | 模型文件夹的初始化文件 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\segment\predict.py | YOLOv5分割推理的脚本,用于对图像、视频等进行分割推理 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\segment\train.py | YOLOv5分割模型的训练脚本,用于训练分割模型 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\segment\val.py | YOLOv5分割模型的验证脚本,用于验证分割模型 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\utils\activations.py | 包含激活函数的实现,如Mish、Swish等 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\utils\augmentations.py | 包含数据增强的实现,如随机缩放、随机翻转等 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\utils\autoanchor.py | 包含自动锚框聚类的实现 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\utils\autobatch.py | 包含自动批次大小调整的实现 |
E:\视觉项目\shop\改进CARAFE特征图上采样的YOLO的人种图像分类系统\code\utils\callbacks.py | 包含回调函数的实现,如EarlyStopping、ModelCheckpoint等 |
仅通过像素点的空间位置来决定上采样核,并没有利用到特征图的语义信息,可以看作是一种“均匀”的上采样,而且感知域通常都很小(最近邻 1x1,双线性 2x2);
Deconvolution
上采样核并不是通过像素间的距离计算,而是通过网络学出来的,但对于特征图每个位置都是应用相同的上采样核,不能捕捉到特征图内容的信息,另外引入了大量参数和计算量,尤其是当上采样核尺寸较大的时候;
Dynamic filter
对于特征图每个位置都会预测一组不同的上采样核,但是参数量和计算量更加爆炸,而且公认比较难学习;
Large receptive field:需要具有较大的感受野,这样才能更好地利用周围的信息;
Content-aware:上采样核应该和特征图的语义信息相关,基于输入内容进行上采样;
Lightweight:轻量化,不能引入过多的参数和计算量;
参考该博客提出的CARAFE 分为两个主要模块,分别是上采样核预测模块和特征重组模块。假设上采样倍率为σ \sigmaσ,给定一个形状为H × W × C H\times W\times CH×W×C的输入特征图,CARAFE首先利用上采样核预测模块预测上采样核,然后利用特征重组模块完成上采样,得到形状为σ H × σ W × C \sigma H\times \sigma W\times CσH×σW×C的输出特征图。
对于形状为H × W × C H\times W\times CH×W×C的输入特征图,首先用一个1 × 1 1\times 11×1卷积将它的通道数压缩到H × W × C m H\times W\times C_mH×W×C ,这一步的主要目的是减小后续步骤的计算量。
假设上采样核尺寸为k u p × k u p k_{up}\times k_{up}k up ×k up
(越大的上采样核意味着更大的感受野和更大的计算量),如果希望对输出特征图的每个位置使用不同的上采样核,那么需要预测的上采样核形状为σ H × σ W × k u p × k u p \sigma H\times \sigma W\times k_{up}\times k_{up}σH×σW×k up ×k up 。
对于第一步中压缩后的输入特征图,利用一个k e n c o d e r × k e n c o d e r k_{encoder}\times k_{encoder}k encoder ×k encoder 的卷积层来预测上采样核,输入通道数为C m C_mC m ,输出通道数为σ 2 k u p 2 \sigma^2 k_{up}^2σ 2 k up2 ,然后将通道维在空间维展开,得到形状为σ H × σ W × k u p 2 \sigma H\times \sigma W\times k_{up}^2σH×σW×k up2 的上采样核。
对第二步中得到的上采样核利用 softmax 进行归一化,使得卷积核权重和为 1。
对于输出特征图中的每个位置,将其映射回输入特征图,取出以之为中心的k u p × k u p k_{up}\times k_{up}k up ×k up
的区域,和预测出的该点的上采样核作点积,得到输出值。相同位置的不同通道共享同一个上采样核。
# yolov5n-CARAFE
# Parameters
nc: 4 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.25 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, C3, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3, [1024]],
[-1, 1, SPPF, [1024, 5]], # 9
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, CARAFE, [3,5]],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, C3, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, CARAFE, [3,5]],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
Epoch:训练的迭代次数。
训练损失(train/loss):训练阶段的损失函数值,理想情况下应随着时间的推移而减小。
测试损失(test/loss):测试阶段的损失函数值,有助于评估模型对新数据的泛化程度。
Top-1 Accuracy (metrics/accuracy_top1):顶部预测(概率最高的预测)正确的次数比例。
Top-5 Accuracy (metrics/accuracy_top5):正确答案位于前 5 个预测范围内的次数比例。
学习率 (lr/0):训练期间的学习率,可以作为优化策略的一部分进行调整。
让我们可视化这些指标以帮助我们进行详细分析。
import matplotlib.pyplot as plt
import seaborn as sns
# Setting the style for the plots
sns.set(style="whitegrid")
# Creating a function for plotting
def plot_performance(data, title, ylabel, metrics):
plt.figure(figsize=(12, 6))
for metric in metrics:
plt.plot(data['epoch'], data[metric], label=metric)
plt.title(title)
plt.xlabel('Epoch')
plt.ylabel(ylabel)
plt.legend()
plt.show()
# Plotting training and test losses
plot_performance(data_updated, 'Training and Test Losses Over Epochs', 'Loss',
['train/loss', 'test/loss'])
# Plotting accuracy metrics
plot_performance(data_updated, 'Top-1 and Top-5 Accuracy Over Epochs', 'Accuracy',
['metrics/accuracy_top1', 'metrics/accuracy_top5'])
# Plotting Learning Rate
plot_performance(data_updated, 'Learning Rate Over Epochs', 'Learning Rate', ['lr/0'])
训练和测试阶段的损失都呈现下降趋势,表明模型正在随着时间的推移学习并提高其预测能力。
训练和测试损失的收敛表明该模型并没有显着过度拟合,并且可能很好地推广到未见过的数据。
随着训练的进行,top-1 和 top-5 的准确率都有明显的提高,这表明模型正确分类图像的能力正在增强。
top-5 的准确度非常高,达到近乎完美的分数,这表明正确的类别几乎总是在模型做出的前 5 个预测之内。
学习率逐渐降低,这是一种常见的做法,允许模型最初对权重进行较大的更新,并随着训练的进行进行更小、更精细的更新。
应检查学习率变化对损失和准确性的影响,以确保其符合预期。下降太快可能会导致收敛不理想。
下图完整源码&数据集&环境部署视频教程&自定义UI界面
参考博客《改进CARAFE特征图上采样的YOLO的人种图像分类系统》
[1]段晓东,王存睿,刘向东,等.人脸的民族特征抽取及其识别[J].计算机科学.2010,(8).DOI:10.3969/j.issn.1002-137X.2010.08.062 .
[2]李辉,潘方芳,张敏华,等.滇西北8个民族群体面貌特征观察的聚类分析[J].复旦学报(自然科学版).2001,(5).DOI:10.3969/j.issn.0427-7104.2001.05.021 .
[3]陈守榕,邓国顺.中国15个少数民族面部形态标志点的差异[J].中国法医学杂志.1999,(2).89-97.
[4]费孝通.关于我国民族的识别问题[J].中国社会科学.1980,(1).147-162.
[5]Hou, Z.,Fu, S.,He, H..Learning Race from Face: A Survey[J].IEEE Transactions on Pattern Analysis & Machine Intelligence.2014,36(12).
[6]Ocegueda, Omar,Fang, Tianhong,Shah, Shishir K.,等.3D Face Discriminant Analysis Using Gauss-Markov Posterior Marginals[J].IEEE Transactions on Pattern Analysis & Machine Intelligence.2013,35(3).
[7]MUHAMMAD HUSSAIN,GHULAM MUHAMMAD,HATIM ABOALSAMH,等.Race Classification from Face Images Using Local Descriptors[J].International Journal of Artificial Intelligence Tools: Architectures, Languages, Algorithms.2012,21(5).
[8]Wiese,H..The role of age and ethnic group in face recognition memory: ERP evidence from a combined own-age and own-race bias study[J].Biological Psychology.2012,89(1).
[9]Michael A, Strom,Leslie A, Zebrowitz,Shunan, Zhang,等.Skin and bones: the contribution of skin tone and facial structure to racial prototypicality ratings.[J].PloS one.2012,7(7).e41193.
[10]Heo, Jingu,Savvides, Marios.Gender and Ethnicity Specific Generic Elastic Models from a Single 2D Image for Novel 2D Pose Face Synthesis and Recognition[J].IEEE Transactions on Pattern Analysis & Machine Intelligence.2012,34(12).