#职场大白的经验贴 #非大佬自救联盟
yolo原理,部署训练这些都老生常谈了,我下边分享工作中最麻烦占大头的数据处理工作,虽说前期做数据简单,但是上手马上整明白还是需要时间的,有问题的宝子可以留言交流哈
目录
一、数据获取
1.数据获取可以直接买个摄像头去录
这里边涉及到得问题:
1.录取的数据是不是合法(偷录小心被告)
2.摄像头分好多种,球机枪机或者其他机器录取的数据是否符合你要的数据要求(比如有的摄像头出来的图片是鱼眼效果)
2.网络爬取
爬取的数据分为图片和视频数据
图片数据可以直接写脚本爬取百度等浏览器的图片,可以多换几个搜索关键词
参考脚本片段:
class BaiduImageSpider(object):
def __init__(self):
self.json_count = 0 # 请求到的json文件数量(一个json文件包含30个图像文件)
self.url = 'https://image.baidu.com/search/acjson?tn=resultjson_com&logid=5179920884740494226&ipn=rj&ct' \
'=201326592&is=&fp=result&queryWord={' \
'}&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&hd=&latest=©right=&word={' \
'}&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&nojc=&pn={' \
'}&rn=30&gsm=1e&1635054081427= '
self.directory = r"C:\temp{}" # 存储目录 这里需要修改为自己希望保存的目录 {}不要丢
self.header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30 '
}
# 创建存储文件夹
def create_directory(self, name):
self.directory = self.directory.format(name)
# 如果目录不存在则创建
if not os.path.exists(self.directory):
os.makedirs(self.directory)
self.directory += r'{}'
# 获取图像链接
def get_image_link(self, url):
list_image_link = []
strhtml = requests.get(url, headers=self.header) # Get方式获取网页数据
jsonInfo = json.loads(strhtml.text)
for index in range(30):
list_image_link.append(jsonInfo['data'][index]['thumbURL'])
return list_image_link
# 下载图片
def save_image(self, img_link, filename):
res = requests.get(img_link, headers=self.header)
if res.status_code == 404:
print(f"图片{img_link}下载出错------->")
with open(filename, "wb") as f:
f.write(res.content)
print("存储路径:" + filename)
# 入口函数
def run(self):
searchName = input("查询内容:")
searchName_parse = parse.quote(searchName) # 编码
self.create_directory(searchName)
pic_number = 0 # 图像数量
for index in range(self.json_count):
pn = (index+1)*30
request_url = self.url.format(searchName_parse, searchName_parse, str(pn))
list_image_link = self.get_image_link(request_url)
for link in list_image_link:
pic_number += 1
self.save_image(link, self.directory.format(str(pic_number)+'.jpg'))
time.sleep(0.2) # 休眠0.2秒,防止封ip
print(searchName+"----图像下载完成--------->")
视频可以参考下边的教程去爬取
另: 如果爬取不了,也可以登录那些社交平台一个一个下,可以打开网页开发者模式找到视频的地址再去下载(例如下边图里,找到网络下的img和媒体,点击可以看到原始的图片或者视频链接)
编辑
二、数据处理
视频截取/抽帧:
收集好的图片数据可以直接肉眼删选,但是视频数据就需要抽成图片了,如果视频特别长还涉及到先把视频中的有效片段截取出来
视频截取思路:
1.根据前后帧图像变换的程度大小来截取
2.利用开源的检测模型先把有目标的部分检测出来再截取(这种对于做小众检测目标类别不太友好)
思路1的参考代码:
import cv2
import os
import subprocess
def process_video(video_path):
# 获取视频文件的名称,不包括扩展名
video_name = os.path.splitext(os.path.basename(video_path))[0]
# 获取视频所在的目录
video_dir = os.path.dirname(video_path)
# 创建与视频同名的输出文件夹
output_dir = os.path.join(video_dir, video_name)
os.makedirs(output_dir, exist_ok=True)
# 修复视频并生成临时文件(忽略解码错误)
fixed_video_path = os.path.join(video_dir, f"fixed_{video_name}.mp4")
ffmpeg_command = [
"ffmpeg", "-err_detect", "ignore_err", "-i", video_path, "-c", "copy", fixed_video_path
]
subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# 使用OpenCV读取修复后的视频
video = cv2.VideoCapture(fixed_video_path)
# 获取视频的帧率和大小
fps = video.get(cv2.CAP_PROP_FPS)
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 保存为MP4格式
# 初始化背景帧
ret, background = video.read()
if not ret:
print(f"无法读取视频: {video_path}")
return
background = cv2.cvtColor(background, cv2.COLOR_BGR2GRAY)
background = cv2.GaussianBlur(background, (21, 21), 0)
# 定义一些变量
recording = False
out = None
frame_count = 0
clip_count = 1
motion_frames = 0
motion_threshold = 10 # 连续运动的帧数阈值
while True:
ret, frame = video.read()
if not ret:
break
# 转换为灰度图像并进行模糊处理
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)
# 计算帧差异
diff = cv2.absdiff(background, gray)
_, thresh = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)
# 找到运动的轮廓
contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 如果检测到运动
detected = False
for contour in contours:
if cv2.contourArea(contour) > 8000: # 增大阈值,过滤掉更小的噪声
detected = True
break
if detected:
motion_frames += 1 # 累计运动帧数
if motion_frames > motion_threshold: # 只有运动超过一定帧数才认为有效
if not recording:
# 开始录制新的视频片段
video_filename = os.path.join(output_dir, f'clip_{video_name}_{clip_count}.mp4')
out = cv2.VideoWriter(video_filename, fourcc, fps, (width, height))
recording = True
clip_count += 1
print(f"开始保存片段: {video_filename}")
# 写入当前帧到视频文件
out.write(frame)
frame_count += 1
else:
motion_frames = 0 # 如果没有检测到运动,重置累计帧数
if recording:
# 如果已经开始录制且运动停止,停止录制
out.release()
recording = False
print(f"片段保存结束,帧数: {frame_count}")
frame_count = 0
# 更新背景帧
background = gray
# 按 'q' 退出
if cv2.waitKey(30) & 0xFF == ord('q'):
break
# 释放资源
video.release()
if out is not None:
out.release()
cv2.destroyAllWindows()
# 删除修复后的视频
if os.path.exists(fixed_video_path):
os.remove(fixed_video_path)
# 遍历文件夹中的所有MP4文件
folder_path = '/media/26x/T71/' # 替换为你的视频文件夹路径
for filename in os.listdir(folder_path):
if filename.endswith('.mp4'):
video_path = os.path.join(folder_path, filename)
process_video(video_path)
print("操作完成!")
截取好的视频就可以按照你想要的抽帧频率来抽了,但是注意要选择合理的大小,太大遇到的问题就是后续删选图片会很费时
抽帧代码参考:
import cv2
import os
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm
video_folder = r"D:\Desktop\detection\lll" # 视频文件夹路径
output_folder = r"D:\Desktop\detection\s_backpack/lllo" # 保存帧的文件夹路径
save_step = 10 # 间隔帧
def process_video(video_path):
video = cv2.VideoCapture(video_path)
video_name = os.path.splitext(os.path.basename(video_path))[0] # 提取视频文件名(不包含扩展名)
num = 0 # 计数器
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT)) # 获取视频总帧数
with tqdm(total=total_frames // save_step, desc=f"Processing {video_name}") as pbar:
while True:
ret, frame = video.read()
if not ret:
break
num += 1
if num % save_step == 0:
output_path = os.path.join(output_folder, f"{video_name}_{num}.jpg")
cv2.imwrite(output_path, frame)
pbar.update(1) # 更新进度条
video.release() # 释放视频捕获对象
# 遍历文件夹中的所有视频文件
video_files = [os.path.join(video_folder, filename) for filename in os.listdir(video_folder) if filename.endswith(".mp4")]
# 使用多线程处理视频文件
with ThreadPoolExecutor(max_workers=4) as executor: # 根据 CPU 核心数调整 max_workers
executor.map(process_video, video_files)
cv2.destroyAllWindows() # 关闭所有 OpenCV 窗口
注: 所有数据处理完,需要全部肉眼过滤一遍,把(模糊/重复多次/大部分无目标图片/部分负样本)按照需要删除掉,不然后续数据标注和模型训练加载都会带来不必要的工作量
三、数据标注
使用labelimg或者其他标注工具进行标注,这些教程太多了就不赘述了。下面介绍常见的会遇到的问题和快速标注的方式
labelimg常见的会遇到的问题:
1.软件闪退:
一般这种情况去C:\Users\AI下找下有没有.labelImgSettings.pkl文件,把这个文件删掉重新打开标注软件就好了(其他复杂情况可以看他弹出的后台界面,上边写的报错是什么,再针对性解决)
2.标注格式问题:
1.如果多人一起标注,注意classes.txt的标签要保持一致,尤其标注yolo格式的,举例:要标注的类型有ABC,如果其他小伙伴写成了BCA,因为yolo格式里的class类别是从上往下按照0123....往下排的,就会造成标注错乱了。如果标注格式是pacalVOC的话还有拯救办法,就是大家一起标完再写脚本把标签给改掉。
2.另外需要标注的图片,如果多人一起操作,需注意图片命名的唯一性,举例:我的文件夹的图片按照img_数字.jpg格式来的,如果其他也有人的图片(和我不重复的图片)也是这个命名规则,那最后合并的时候会造成标签不一致的问题。
快速标注/自动标注的方式:
1.使用开源OVD(万物检测)大模型进行自动标注。
缺点:有的大模型接口需要付费
标注的不准,肯定会漏标或者错标,需要二次校验标注
2.模型训练迭代标注:
如果需要标注的数据量很大的话,先标注一小部分数据,直接训练一个模型,然后拿这个模型去推理剩余未标注的图片,得到的标签再去人工核验,再继续迭代。训练的时候加上一个参数就可以生成标签文件,例如:
yolo task=detect mode=predict model=/media/26x/T71/手机/第一次标注训练/best.pt source=/media/26x/T71/手机/第一次数据汇总 imgsz=1280 save_txt=True save=True
四、图片标签数据训练前梳理
1.txt/xml(yolo/pacalVOC)格式转换
****参考脚本
# -*- coding: utf-8 -*-
import os
import xml.etree.ElementTree as ET
# 输入和输出路径
dirpath = r'D:\Desktop\datasets\labels_xml' # 原始 XML 文件目录
newdir = r'D:\Desktop\datasets\label' # 修改后生成的 TXT 文件目录
# 如果输出目录不存在,则创建
if not os.path.exists(newdir):
os.makedirs(newdir)
# 读取类别文件并生成字典
class_file = r'D:\Desktop\classes.txt'
with open(class_file, 'r', encoding='utf-8') as f:
data_info = f.readlines()
dict_info = {}
for idx, inf in enumerate(data_info):
inf = inf.strip() # 去除换行符和空格
dict_info[inf] = idx
# 处理 XML 文件
xml_files = [f for f in os.listdir(dirpath) if f.endswith('.xml')]
for fp in xml_files:
try:
# 解析 XML 文件
tree = ET.parse(os.path.join(dirpath, fp))
root = tree.getroot()
# 解析图像大小
sz = root.find('size')
if sz is None or len(sz) < 2:
print(f"文件 {fp} 的 size 节点有问题,跳过")
continue
width = float(sz.find('width').text)
height = float(sz.find('height').text)
# 输出文件路径
output_file = os.path.join(newdir, fp.replace('.xml', '.txt'))
with open(output_file, 'w', encoding='utf-8') as f_out:
for obj in root.findall('object'):
# 获取标签和边界框
label = obj.find('name').text
sub = obj.find('bndbox')
if label is None or sub is None:
print(f"文件 {fp} 的 object 节点有问题,跳过")
continue
# 获取坐标
try:
xmin = float(sub.find('xmin').text)
ymin = float(sub.find('ymin').text)
xmax = float(sub.find('xmax').text)
ymax = float(sub.find('ymax').text)
# 转换为 YOLO 格式
x_center = (xmin + xmax) / (2 * width)
y_center = (ymin + ymax) / (2 * height)
w = (xmax - xmin) / width
h = (ymax - ymin) / height
# 获取标签索引
label_index = dict_info.get(label, -1) # 默认值为 -1,表示未知类别
if label_index == -1:
print(f"文件 {fp} 中的未知标签 {label},跳过")
continue
# 写入文件
f_out.write(f"{label_index} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")
except (ValueError, ZeroDivisionError) as e:
print(f"文件 {fp} 中的坐标有问题:{e}")
except ET.ParseError as e:
print(f"解析 XML 文件 {fp} 时出错:{e}")
五、数据集划分
准备好的图片和标签数据需要按照train/val/test划分数据集,具体看你项目的需要。
参考脚本:
import os
import random
import shutil
def split_dataset(srcDir, trainDir, valDir, split_ratio=0.9):
"""
将数据集划分为训练集和验证集,并保存到相应的文件夹中。
Parameters:
- srcDir: 原始数据集文件夹路径,包含图像和标签文件。
- /media/ImageSets/imagesDir: 训练集文件夹路径,包含 'images' 和 'labels' 子文件夹。
- valDir: 验证集文件夹路径,包含 'images' 和 'labels' 子文件夹。
- split_ratio: 数据集划分比例,默认为 0.9,表示将 90% 的数据用于训练集,10% 用于验证集。
"""
os.makedirs(os.path.join(trainDir, 'images'), exist_ok=True)
os.makedirs(os.path.join(trainDir, 'labels'), exist_ok=True)
os.makedirs(os.path.join(valDir, 'images'), exist_ok=True)
os.makedirs(os.path.join(valDir, 'labels'), exist_ok=True)
# 获取数据集中所有文件的列表
file_list = os.listdir(srcDir)
random.shuffle(file_list)
print(file_list)
# 根据划分比例计算训练集和验证集的边界索引
split_index = int(len(file_list) * split_ratio)
train_files = file_list[:split_index]
val_files = file_list[split_index:]
# 将训练集数据移动到相应文件夹
# print(split_index)
for file in train_files:
# print(train_files)
# print(file)
if file.endswith('.jpg'):
# print('3ok')
img_src = os.path.join(srcDir, file)
label_src = os.path.join(srcDir, file[:-4] + '.txt')
# print('ok')
# print(img_src)
shutil.move(img_src, os.path.join(trainDir, 'images', file))
if os.path.exists(label_src):
shutil.move(label_src, os.path.join(trainDir, 'labels', file[:-4] + '.txt'))
if file.endswith('.png'):
# print('3ok')
img_src = os.path.join(srcDir, file)
label_src = os.path.join(srcDir, file[:-4] + '.txt')
# print('ok')
# print(img_src)
shutil.move(img_src, os.path.join(trainDir, 'images', file))
if os.path.exists(label_src):
shutil.move(label_src, os.path.join(trainDir, 'labels', file[:-4] + '.txt'))
# 将验证集数据移动到相应文件夹
for file in val_files:
if file.endswith('.jpg'):
img_src = os.path.join(srcDir, file)
label_src = os.path.join(srcDir, file[:-4] + '.txt')
shutil.move(img_src, os.path.join(valDir, 'images', file))
if os.path.exists(label_src):
shutil.move(label_src, os.path.join(valDir, 'labels', file[:-4] + '.txt'))
if __name__ == '__main__':
# 输入文件夹路径
srcDir = 'D:\Desktop\datasets/all'
trainDir = 'D:\Desktop\datasets\datasets/train'
valDir = 'D:\Desktop\datasets\datasets/val'
# 调用函数划分数据集
split_dataset(srcDir, trainDir, valDir)
好了,基本到这所有数据处理的苦力算是结束了,后边我会不定期继续分享图像类的部分算法的基础实操参考教程,希望家人们多多支持~