🚀毕业设计-day3
⭐️ yolov9 + 思路2(滑动窗口检测)
⭐️ 对目标检测结果的准确率有效提升
⭐️ 完整项目代码请私信,或者催作者将资源发布
文章目录
1 滑动窗口检测的思想
原始的滑动窗口检测 (步长思想)
- 1.针对同一张图片,设定一个 w * h 的小窗口
- 2.小窗口按照一定的步长在图片上移动
- 3.针对小窗口进行目标检测,直到遍历整张图片
- 4.将多个小窗口的检测结果进行拼接,得到最终的结果
利用滑动窗口的思想,简化之后应用到 yolov9 中 (分割思想)
- 1.针对同一张图片,将其划分为 n * n 的区域(等价于小窗口,相当于步长取小窗口的宽)
- 2.检测每个区域的目标
- 3.检测整张图片(原因:分割线处的目标可能被划成两半,导致效果不佳,对整张图片进行目标检测来弥补分割线导致的目标割裂情况)
- 4.将上述 n*n +1 个检测结果进行拼接,得到最终的结果
优缺点分析 (时间换性能)
-
优点,针对小目标物体,检测效果增加(等价于图片中的小目标被放大了)
-
缺点,原本只需要进行一次检测;如今一张图片要重复检测很多次,牺牲时间换性能,甚至“步长思想”比“分割思想”更加耗时。
2 分步实现
- 2.1 - 图像 n * n 分割的代码实现与效果
- 效果(以 2*2 为例)
- 代码
# 原始图像分割并保存本地, image文件夹中
def sliding(self, filename='01015.jpg'):
image_path = 'image/' + filename # 路径
# 读取图片
original_image = cv2.imread(image_path)
width, height = int(original_image.shape[1]/self.level), int(original_image.shape[0]/self.level)
print('width: ',width,' height: ', height)
# # 调整图像尺寸
# original_image = cv2.resize(original_image, (640*self.level, 640*self.level))
# 图像分割
for i in range(self.level):
for j in range(self.level):
target_region = original_image[i*height:i*height+height, j*width:j*width+width]
# 将结果写入文件
image_path = 'image/' + str(i*self.level+j) + '.jpg'
cv2.imwrite(image_path, target_region)
# # 显示分割结果
# cv2.imshow('target_region', target_region)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
- 2.2 - 对每一部分进行目标检测
- 效果(以 2*2 为例)
- 代码
def run10(self):
# 总检测
self.yo.choise(self.filename)
self.yo.run()
# 分检测
for i in range(self.level * self.level):
self.yo.choise(str(i) + '.jpg')
self.yo.run()
- 2.3 - 检测的结果进行拼接组合
难度⭐️⭐️⭐️
有这样一个过程:
- 将子图像的检测结果映射到原始图像上
- 将子图像边界处的目标检测结果剔除(防止割裂的目标影响最终效果)边界判定阈值为0.001
- 对比原图像目标检测结果,将子图像中相似的重复检测目标剔除
- 将原始图像目标检测结果和经过剔除的子图像目标检测结果进行汇总
- 将汇总的结果保存在本地txt文件中
- 将检测的标签在原始图像上进行标注
- 首先进行一个简单的理论分析(标签映射):
- 然后进行一个简单的理论分析,边界判定阈值为0.001(边界判断):
当然,符合上图条件的目标将会被剔除,这里的目标均是子图像的检测结果。
- 在剔除子图像与原图像中相似的目标时,需要使用目标定位误差(通常是均方误差或平滑的L1损失)因为尺度越大,L1误差越大,必须做大小目标均衡
- 这里自定义了 L1 损失,来计算位置误差,主要思想是中心点照常计算,图像尺寸“均衡化”
- 具体实现如下所示,其中1,2,3,4代表的是yolo标签的下标对应数组取出来的数字,而不是简单数字1234:
- 后来测试发现,效果不好,于是弃用,探索其他均衡化方法中。
- 发现只使用中心点来判断目标是否重合是非常合理的思路,于是使用如下公式
-
根据上述一些操作,我们得到了最终的 txt 标签文件,保存到本地的过程此处省略。
-
接下来是在图像上添加带有标签的边界框,这里在yolo类中添加 了一个绘图函数:
# 绘图函数
def draw(self, img_name='01015.jpg', txt_array=[]):
image_path = 'image/' + img_name # 路径
im0 = cv2.imread(image_path)
# 获取图像的像素大小
height, width = im0.shape[:2]
annotator = Annotator(im0, line_width=self.line_thickness, example=str(self.names))
# txt转换为box
def convert(size, box):
xmin = (box[1] - box[3] / 2.) * size[1]
xmax = (box[1] + box[3] / 2.) * size[1]
ymin = (box[2] - box[4] / 2.) * size[0]
ymax = (box[2] + box[4] / 2.) * size[0]
box = [int(xmin), int(ymin), int(xmax), int(ymax)]
return box
for i in range(len(txt_array)):
label = self.names[int(txt_array[i][0])] + ' ' + str(round(txt_array[i][5], 2))
box = convert(im0.shape, txt_array[i])
annotator.box_label(box, label, color=colors(txt_array[i][0], True))
im0 = annotator.result()
save_path = 'detect/images/'+'sli-'+img_name
cv2.imwrite(save_path, im0)
- 然后是子结果合并的代码实现:
# 将结果拼接,并保存到本地
def splicing(self):
# 总结果的处理
all_txt = []
match = re.search(r'(\d+)(?=.jpg)', self.filename)
number = match.group(1)
try:
with open('detect/labels/' + str(number) + ".txt", 'r') as f:
lines = f.readlines()
# 筛选出小目标
for line in lines:
# 一个目标的坐标(中心点位置x1 y1,宽高 x2 y2)
line = line.strip().split()
line = [float(i) for i in line]
line[0] = int(line[0])
all_txt.append(line)
except FileNotFoundError:
print('检测结果为空')
# n*n 个结果汇总 和 【转化】
add_txt = []
for z in range(self.level * self.level):
# 某张子图片
file_path = 'detect/labels/' + str(z) + '.txt'
try:
with open(file_path, 'r') as file:
# 某张图片中的目标
lines = file.readlines()
# 筛选出小目标
for line in lines:
# 一个目标的坐标(中心点位置x1 y1,宽高 x2 y2)
line = line.strip().split()
line = [float(i) for i in line]
line[0] = int(line[0])
# 将边界目标剔除
a1 = line[1] - line[3]/2
a2 = 1 - line[1] - line[3]/2
a3 = line[2] - line[4]/2
a4 = 1 - line[2] - line[4]/2
if abs(a1)<0.001:
print('-'*120)
print('将边界目标剔除: ', a1, a2, a3, a4)
continue
# 进行坐标的转化
j = z%self.level
i = int(z/self.level)
line[1] = (line[1]+j)/self.level
line[2] = (line[2]+i)/self.level
line[3] = line[3]/self.level
line[4] = line[4]/self.level
add_txt.append(line)
except FileNotFoundError:
pass
# print('检测结果为空666')
# print('-'*120)
# print(all_txt)
# print('-'*120)
# print(add_txt)
print('-'*120)
print('原图:', len(all_txt), '子图:', len(add_txt))
# 计算原图像和子图像两者的相对误差 L1,并剔除(误差计算需要平衡不同尺度目标)
for a in add_txt:
min_distance = abs(a[1] - all_txt[0][1]) + abs(a[2] - all_txt[0][2])
# min_distance = abs(a[1] - all_txt[0][1]) + abs(a[2] - all_txt[0][2]) + abs(a[3] - all_txt[0][3]) + abs(a[4] - all_txt[0][4])
# 找到 min distance
for b in all_txt:
distance = abs(a[1] - b[1]) + abs(a[2] - b[2])
# distance = abs(a[1] - b[1]) + abs(a[2] - b[2]) + abs(a[3] - b[3]) + abs(a[4] - b[4])
if distance < min_distance:
min_distance = distance
if min_distance > 0.02: # 说明是新目标
# 写 txt
with open(file_path, 'a') as file:
strings = ''
for value in a:
strings = strings + str(value) + " "
file.write(strings + '\n')
# 更新 all_txt
all_txt.append(a)
print(' - - - 找到新目标 中心点位置 loss 如下 - - - ')
print('min_distance: ', min_distance)
print('-' * 120)
print('增强后-原图:', len(all_txt), '子图:', len(add_txt))
self.my_txt = all_txt
# 写 jpg
self.draw()
3 最终效果
- 这真的是 very very nice!
4 进一步优化
在特征层分割提取特征,在检测层进行子图像的特征融合。
从而只需要进行一次检测头输出,从而可以使用验证集进行验证)
实现:略,难度⭐️⭐️⭐️⭐️,暂时没有空来实现了