opencv物体轮廓提取和检测-python

无人机项目整理

本文档是记录一下之前做过的使用传统轮廓检测方法的无人机项目,项目本身不难,记录的目的是觉得里面有很多零零碎碎使用的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位图

轮廓提取

在这里插入图片描述

无人机检测

在这里插入图片描述

`

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值