无人机项目整理
本文档是记录一下之前做过的使用传统轮廓检测方法的无人机项目,项目本身不难,记录的目的是觉得里面有很多零零碎碎使用的opencv函数以及代码写的比较工整,故此在这里做一个记录,算是见证一下自己的成长。
项目需求是从一张16bit图像中检测出无人机,采用的方式是阈值分割+轮廓检测,然后根据检测的轮廓生成无人机的检测框。
0. 环境和目录结构
开发环境
- windows10
- python 3.8.8
- OpenCV 3.4.9
项目目录结构
dataset
├── IR_LW_16
│ ├── IR_LW_16
│ ├── ├── img0.png
│ ├── ├── img1.png
│ ├── ├── ...
utils
├── logger.py
├── process.py
├── vis.py
result
video
final.py
final_bbox.py
img2video.py
dataset
目录下保存有图像数据集,图像文件名按照视频帧的先后顺序进行命名utils
目录下保存有项目构建的大部分函数logger.py
主要负责记录日志,可以将终端所有打印的文本进行保存process.py
负责基本的图像处理,包括阈值分割、高斯模糊、形态学操作、轮廓查找等等vis.py
包含有可视化的函数,用于将检测框、轮廓进行可视化显示
result
保存用户的中间图像结果,由用户自定义video
保存用户最终的视频结果final.py
用于显示图像中的无人机轮廓,也可以自己更改函数,保存无人机的轮廓图像final_bbox.py
用于显示无人机的检测框img2video.py
用于将检测图像转化为.avi
格式的视频流
项目运行
python final.py # 显示无人机周围轮廓
python final_bbox.py # 显示无人机的bbox
1. 代码实现
logger.py
import sys
# want to save everything printed to outfile
class Logger(object):
def __init__(self, name):
self.terminal = sys.stdout
self.log = open(name, "a")
def write(self, message):
self.terminal.write(message)
self.log.write(message)
def flush(self):
self.log.flush()
logger.py
参考子Always be dreaming的源码,可以将终端中打印的输出全部保存到日志文件中
实际测试的时候好像会有bug,没有把这个bug调通,等待有缘人解决。
process.py
import cv2
import numpy as np
import os
def load_img(img_path):
'''
功能: 使用cv2读取图像
param:
- img_path: 图像路径
return:
- img_int8: 读取得到的8位图
- img_int16: 读取得到的16位图
'''
img_int16 = cv2.imread(img_path, -1)
img_int8 = np.array(np.rint(
255 * ((img_int16 - img_int16.min()) / (img_int16.max() - img_int16.min()))), dtype=np.uint8)
return img_int8, img_int16
# 自定义函数:用于删除列表指定序号的轮廓
# 输入 1:contours:原始轮廓
# 输入 2:delete_list:待删除轮廓序号列表
# 返回值:contours:筛选后轮廓
def delet_contours(contours, delete_list):
'''
功能: 用于删除列表指定序号的轮廓
param:
- contours: 原始轮廓
- delete_list: 待删除轮廓序号列表
return:
- contours: 筛选后轮廓
'''
delta = 0
for i in range(len(delete_list)):
# print("i= ", i)
del contours[delete_list[i] - delta]
delta = delta + 1
return contours
def use_hierarchy(contours, hierarchy):
# 筛选轮廓
delete_list = [] # 新建待删除的轮廓序号列表
c, row, col = hierarchy.shape
for i in range(row):
if hierarchy[0, i, 2] > 0 or hierarchy[0, i, 3] > 0: # 有父轮廓或子轮廓
delete_list.append(i)
# 根据列表序号删除不符合要求的轮廓
contours = delet_contours(contours, delete_list)
return contours
def use_contour_length(contours, min_size=10, max_size=300):
'''
功能: 根据轮廓大小删除不符合要求的轮廓
param:
- contours: 输入的轮廓
- min_size: 小于min_size的轮廓会被删除
- max_size: 大于max_size的轮廓会被删除
return:
- contours: 删除后的轮廓
'''
delete_list = []
for i in range(len(contours)):
if (cv2.arcLength(contours[i], True) < min_size) or (cv2.arcLength(contours[i], True) > max_size):
delete_list.append(i)
# 根据列表序号删除不符合要求的轮廓
contours = delet_contours(contours, delete_list)
return contours
def pretreatment(img):
'''
功能: 查找图像轮廓
param:
- img: 输入图像,可以是3通道的图像
return:
- image: 原图像经过缩放后的图像
- contours: 提取的轮廓
- hierarchy: 轮廓层级信息
'''
rate = 1
height, width = img.shape
image = cv2.resize(img, (int(rate*width), int(rate*height)),
interpolation=cv2.INTER_CUBIC)
gray = cv2.GaussianBlur(image, (3, 3), 1)
ret, binary = cv2.threshold(gray, 80, 255, cv2.THRESH_BINARY_INV)
element = cv2.getStructuringElement(
cv2.MORPH_RECT, (3, 3)) # 3 * 3 正方形,8位uchar型,全1结构元素
binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, element)
contours, hierarchy = cv2.findContours(
binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:] # for opencv3.4
return image, contours, hierarchy
def pretreatment16(img_int16,thr=6000):
'''
功能: 对16位图像进行阈值分割
param:
- img_int16: 待分割的16位图像
- thr: 阈值分割阈值
return:
- img_out: 图像阈值分割结果,目标物体的像素值为255,其余像素值为0
'''
img_mask = img_int16 > thr
img_out = np.uint8(img_mask * 255)
return img_out
def Cnt2Bboxs(contours):
'''
功能: 生成图像轮廓的最小矩形框
param:
- contours: 轮廓
return:
- bboxs: 检测框
'''
bboxs = [cv2.boundingRect(cnt) for cnt in contours]
return bboxs
vis.py
# -*- coding:utf-8 -*-
import cv2
import numpy as np
import os
# 4. 绘制轮廓函数
# 自定义绘制轮廓的函数(为简化操作)
# 输入1:winName:窗口名
# 输入2:image:原图
# 输入3:contours:轮廓
# 输入4:draw_on_blank:绘制方式,True在白底上绘制,False:在原图image上绘制
def drawMyContours(winName, image, contours, draw_on_blank, write=False,seconds=1):
# cv2.drawContours(image, contours, index, color, line_width)
# 输入参数:
# image:与原始图像大小相同的画布图像(也可以为原始图像)
# contours:轮廓(python列表)
# index:轮廓的索引(当设置为-1时,绘制所有轮廓)
# color:线条颜色,
# line_width:线条粗细
# 返回绘制了轮廓的图像image
if contours:
if (draw_on_blank): # 在白底上绘制轮廓
temp = np.ones(image.shape, dtype=np.uint8) * 255
cv2.drawContours(temp, contours, -1, (0, 0, 0), 2)
else:
temp = image.copy()
cv2.drawContours(temp, contours, -1, (0, 0, 255), 2)
else:
temp = image.copy()
if write:
return temp
cv2.imshow(winName, temp)
cv2.waitKey(seconds)
# 根据输入的轮廓绘制bbox
def drawBbox(winName, image, bboxes, draw_on_blank, write=False,seconds=1):
if bboxes:
# 绘制bbox
if (draw_on_blank): # 在白底上绘制轮廓
temp = np.ones(image.shape, dtype=np.uint8) * 255
for bbox in bboxes:
[x, y, w, h] = bbox
cv2.rectangle(temp, (x, y), (x + w, y + h), (0, 0, 0), 2)
else:
temp = image.copy()
for bbox in bboxes:
[x, y, w, h] = bbox
cv2.rectangle(temp, (x, y), (x + w, y + h), (255, 255, 255), 2)
else:
temp = image.copy()
if write:
return temp
cv2.imshow(winName, temp)
cv2.waitKey(1)
final.py
# -*- coding:utf-8 -*-
# 参考资料:https://blog.csdn.net/iracer/article/details/90695914?ops_request_misc=&request_id=&biz_id=102&utm_term=del%20contours%5Bdelete_list%5Bi%5D%20-%20&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~sobaiduweb~default-0-90695914.article_score_rank_blog&spm=1018.2226.3001.4450
import cv2
import numpy as np
import os
import sys
import logging
from utils.vis import drawBbox,drawMyContours
from utils.process import delet_contours,use_hierarchy,use_contour_length
from utils.process import pretreatment,pretreatment16,load_img
from utils.logger import Logger
import matplotlib.pyplot as plt
from tqdm import tqdm
def main_int8():
# 处理8bit图像
root_path = r'dataset\IR_LW_16\IR_LW_16'
img_names = os.listdir(root_path)
data_size = len(img_names)
# print(img_names)
for i in range(data_size):
# 1. 载入图像
img_name = 'img{}.png'.format(i)
img_int16_path = os.path.join(root_path, img_name)
img_int8, img_int16 = load_img(img_int16_path)
# 2. 预处理+查找轮廓
image, contours, hierarchy = pretreatment(img_int8)
# 3. 使用层级结构筛选轮廓
contours = use_hierarchy(contours, hierarchy)
# 4. 使用轮廓长度筛选轮廓
contours = use_contour_length(contours)
# temp = drawMyContours('yep1', image, contours, True, write=True)
# cv2.imwrite('result\IR_LW_8\{}'.format(img_name), temp)
drawMyContours('use_hie', image, contours, False)
drawMyContours('yep', image, contours, True)
def LW_dataset(start_from=0):
logging.basicConfig(filename='log/LW.log', level=logging.INFO)
# 处理16bit图像
root_path = r'dataset\IR_LW_16\IR_LW_16'
num = 0
img_names = os.listdir(root_path)
data_size = len(img_names)
img_name = r'dataset\IR_LW_16\IR_LW_16\img0.png'
img_int8, img_int16_ori = load_img(img_name)
logging.info('data_size:{}'.format(data_size))
logging.info('LW img_size:{}'.format(img_int16_ori.shape))
print('data_size:{}'.format(data_size))
print('img_size:{}'.format(img_int16_ori.shape))
# print(img_names)
for i in tqdm(range(start_from,data_size)):
# 1. 载入图像
img_name = 'img{}.png'.format(i)
img_int16_path = os.path.join(root_path, img_name)
img_int8, img_int16_ori = load_img(img_int16_path)
img_int16 = pretreatment16(img_int16_ori,5800)
image, contours, hierarchy = pretreatment(img_int16)
contours = use_contour_length(contours)
if len(contours) == 0:
num += 1
# temp = drawMyContours('yep1', image, contours, False, write=True)
# cv2.imwrite(r'result\result_thr\{}.jpg'.format(i), temp)
# cv2.waitKey(1)
drawMyContours('ori', img_int16_ori, None, False)
drawMyContours('bin', img_int16, contours, False)
drawMyContours('yep', image, contours, True)
print('sucess nums:{}'.format(data_size-num))
def MW_dataset(start_from=0):
# 处理16bit图像
root_path = r'dataset/IR_MW_16/IR_MW_16'
num = 0
img_names = os.listdir(root_path)
data_size = len(img_names)
print(data_size)
for i in range(start_from,data_size):
# 1. 载入图像
img_name = 'img{}.png'.format(i)
img_int16_path = os.path.join(root_path, img_name)
img_int8, img_int16_ori = load_img(img_int16_path)
img_int16 = pretreatment16(img_int16_ori,3800)
image, contours, hierarchy = pretreatment(img_int16)
contours = use_contour_length(contours,min_size=8,max_size=1000)
drawMyContours('ori', img_int16_ori, None, False)
drawMyContours('bin', img_int16, contours, False)
drawMyContours('yep', image, contours, True)
def report_test():
img_int16_path = r'dataset\IR_LW_16\IR_LW_16\img10000.png'
img_int8, img_int16_ori = load_img(img_int16_path)
img_int16 = pretreatment16(img_int16_ori,5800)
image, contours, hierarchy = pretreatment(img_int16)
contours = use_contour_length(contours)
# drawMyContours('ori', img_int16_ori, None, False)
# drawMyContours('bin', img_int16, contours, False)
img = drawMyContours('yep', image, contours, True, write=True)
cv2.imwrite('result/contours_10000.jpg',img)
cv2.imshow('hello',img)
cv2.waitKey()
def lena_blur():
lena_path = r'dataset\lena.jpg'
img_lena = cv2.imread(lena_path)
blur_lena = cv2.GaussianBlur(img_lena, (7, 7), 1)
all_img = cv2.hconcat([img_lena,blur_lena])
cv2.imwrite('result/blur.jpg',all_img)
cv2.imshow('all_img',all_img)
cv2.waitKey()
if __name__ == '__main__':
# report_test()
# lena_blur()
# main_int8()
LW_dataset() # LW数据集
# MW_dataset()
final_bbox.py
# -*- coding:utf-8 -*-
import cv2
import numpy as np
import os
import sys
import logging
from utils.vis import drawBbox,drawMyContours
from utils.process import delet_contours,use_hierarchy,use_contour_length
from utils.process import pretreatment,pretreatment16,load_img
from utils.process import Cnt2Bboxs
def LW_dataset(start_from=0):
logging.basicConfig(filename='log/LW.log', level=logging.INFO)
# 处理16bit图像
root_path = r'dataset\IR_LW_16\IR_LW_16'
num = 0
img_names = os.listdir(root_path)
data_size = len(img_names)
img_name = r'dataset\IR_LW_16\IR_LW_16\img0.png'
img_int8, img_int16_ori = load_img(img_name)
logging.info('data_size:{}'.format(data_size))
logging.info('LW img_size:{}'.format(img_int16_ori.shape))
print('data_size:{}'.format(data_size))
print('img_size:{}'.format(img_int16_ori.shape))
# print(img_names)
for i in range(start_from,data_size):
# 1. 载入图像
img_name = 'img{}.png'.format(i)
img_int16_path = os.path.join(root_path, img_name)
img_int8, img_int16_ori = load_img(img_int16_path)
img_int16 = pretreatment16(img_int16_ori,5800)
image, contours, hierarchy = pretreatment(img_int16)
contours = use_contour_length(contours,min_size=8, max_size=300)
bboxs = Cnt2Bboxs(contours)
if len(contours) == 0:
num += 1
drawBbox('ori', img_int16_ori, None, False)
drawBbox('bin', img_int8, bboxs, False)
drawBbox('yep', image, bboxs, False)
# img = drawBbox('yep', img_int8, bboxs, False, write=True)
# cv2.imwrite('result/result_8/{}.jpg'.format(i),img)
# cv2.imshow('img',img)
# cv2.waitKey(1)
logging.info('sucess nums:{}'.format(data_size-num))
print('sucess nums:{}'.format(data_size-num))
def MW_dataset(start_from=0):
logging.basicConfig(filename='log/MW.log', level=logging.INFO)
# 处理16bit图像
root_path = r'dataset\IR_MW_16\IR_MW_16'
num = 0
img_names = os.listdir(root_path)
data_size = len(img_names)
img_name = r'dataset\IR_MW_16\IR_MW_16\img0.png'
img_int8, img_int16_ori = load_img(img_name)
logging.info('data_size:{}'.format(data_size))
logging.info('LW img_size:{}'.format(img_int16_ori.shape))
print('data_size:{}'.format(data_size))
print('img_size:{}'.format(img_int16_ori.shape))
# print(img_names)
for i in range(start_from,data_size):
# 1. 载入图像
img_name = 'img{}.png'.format(i)
img_int16_path = os.path.join(root_path, img_name)
img_int8, img_int16_ori = load_img(img_int16_path)
img_int16 = pretreatment16(img_int16_ori,3800)
image, contours, hierarchy = pretreatment(img_int16)
contours = use_contour_length(contours,min_size=8, max_size=300)
bboxs = Cnt2Bboxs(contours)
if len(contours) == 0:
num += 1
drawBbox('ori', img_int16_ori, None, False)
drawBbox('bin', img_int16, bboxs, False)
drawBbox('yep', image, bboxs, True)
logging.info('sucess nums:{}'.format(data_size-num))
print('sucess nums:{}'.format(data_size-num))
def report_test():
img_int16_path = r'dataset\IR_LW_16\IR_LW_16\img200.png'
img_int8, img_int16_ori = load_img(img_int16_path)
img_int16 = pretreatment16(img_int16_ori,5800)
image, contours, hierarchy = pretreatment(img_int16)
contours = use_contour_length(contours)
bboxs = Cnt2Bboxs(contours)
img = drawBbox('yep', img_int8, bboxs, False, write=True)
cv2.imwrite('result/bbox_100.jpg',img)
cv2.imshow('hello',img)
cv2.waitKey()
if __name__ == '__main__':
# report_test()
LW_dataset() # LW数据集
# MW_dataset()
img2video.py
import cv2
import os
import argparse
from tqdm import tqdm
def show_video(path, fps=3):
""" opencv显示视频,按q中断
"""
if not os.path.exists(path):
raise Exception('video dont exists!')
cap = cv2.VideoCapture(path)
while(cap.isOpened()):
ret, frame = cap.read()
cv2.imshow('frame', frame)
if cv2.waitKey(fps) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
def video2img(input_path, out_path, save=True, show=True, time=3):
# 参考博客:https://blog.csdn.net/qq_25436597/article/details/79621833
''' 将读取的视频转化为图像并保存在out_path文件夹下
params:
input_path: 原始视频文件文件夹
out_path: 保存的文件夹
save: 是否需要保存
show: 是否需要显示视频
time: 视频显示帧率
'''
if not os.path.exists(input_path):
raise Exception('video dont exists!')
if not os.path.exists(out_path):
os.makedirs(out_path)
print('{} is not exists,will makedir {}'.format(out_path, out_path))
i = 0
cap = cv2.VideoCapture(input_path)
ret, frame = cap.read()
while(ret):
ret, frame = cap.read()
if save:
# cv2.imwrite(frame,)
img_name = str(i)+'.jpg'
save_path = os.path.join(out_path, img_name)
cv2.imwrite(save_path, frame)
i += 1
# gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
if show:
cv2.imshow('frame', frame)
if cv2.waitKey(time) & 0xFF == ord('q'):
break
if save:
print('save video to img in :{}!'.format(out_path))
cap.release()
cv2.destroyAllWindows()
def img2video(input_path, out_path, fps=30, img_size=None, num_img=None):
# 参考博客:https://theailearner.com/2018/10/15/creating-video-from-images-using-opencv-python/
""" 将input_path文件夹下面的img变成视频.avi,保存在out_path文件下
param:
input_path: 图像文件夹路径
out_path: 输出视频文件
fps: 输出视频的帧率
img_size: 图像的大小,默认为None
num_img: 文件夹下面图像的数量,默认为None
"""
if not os.path.exists(input_path):
raise Exception('img dir dont exists!')
# if not os.path.exists(out_path):
# os.makedirs(out_path)
# print('{} is not exists,will makedir {}'.format(out_path,out_path))
img_name_list = os.listdir(input_path)
img_name_list.sort(key=lambda x: int(x.split('.')[0]))
if not num_img:
num_img = len(img_name_list)
print('图像总数:{}'.format(num_img))
if not img_size:
# 读取第一张图片
first_img_name = os.path.join(input_path, img_name_list[0])
first_img = cv2.imread(first_img_name)
img_h,img_w = first_img.shape[:2] # [H,W,3]
print('img_size:', img_size)
# 转为视频
# img_array = []
# for img_name in img_name_list:
# img_path = os.path.join(input_path, img_name)
# img = cv2.imread(img_path)
# img_array.append(img)
# print(img_name)
out = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(
*'DIVX'), 30, (img_w, img_h)) # 注意这里的size跟img_size是反过来的
bar = tqdm(img_name_list)
for img_name in bar:
img_path = os.path.join(input_path, img_name)
img = cv2.imread(img_path)
out.write(img)
# for i in range(len(img_array)):
# out.write(img_array[i])
out.release()
if __name__ == "__main__":
input_path = r'result\result_thr'
out_path = r'video/LW_Thr_001.avi'
img2video(input_path,out_path)
# video2img
# parser = argparse.ArgumentParser(description='video2img')
# parser.add_argument('--input_path', type=str, default='video/test001.mp4',
# help='path of video')
# parser.add_argument('--out_path', type=str, default='img',
# help='path of output img')
# opt = parser.parse_args()
# video2img(opt.input_path, opt.out_path)
2. 算法结果
读取图像
左边是读取的8位图,右边是16位图
轮廓提取
无人机检测
`