• 毕业设计——基于OpenCV和数字图像处理的图像识别项目——停车场车位识别


    完整项目可私信博主获取

    在这里插入图片描述

    任务描述

    停车场车位识别任务,主要完成下面几件事情:

    1. 在整个停车场当中,一共有多少辆车,一共有多少个空余的停车位,这样就能知道有多少个停车位是空余的
    2. 把空余的停车位标识出来,这样用户停车的时候,就可以一步到位,直接去空余的停车位处,为停车节省了很多时间

    整体流程梳理

    1. 先通过一步过滤操作, 去掉多余的部分,只把停车场部分保留下来
    2. 把每一个停车位都给标记出来
    3. 训练模型判断当前的停车位有没有车(二分类任务)
    4. 把没有车的车位标识出来即可

    数据介绍

    这里面用个test_images目录,这里面的两张图片就是从视频中截出来的两帧图像。 后面就是基于这两张图片进行预测操作。


    这里面还有train目录,存放的是模型训练的的图片,这个就和之前做的那个花分类的任务一致了,这里为了增加难度,打算后面用pytorch去训练模型,而不是用keras了。正好拿这个项目练练手。

    图像预处理

    图像预处理主要是一些二值化,灰度化的一些操作,将图片中的重要信息进行突出显示。

    1. 首先是阈值的操作,把图片中的背景信息给过滤掉,具体的话是根据像素的阈值,制作了一个mask矩阵,然后把这个矩阵放到图片上进行遮盖,保留了有效的信息
    2. 接下来,把上面的图片转成了灰度图像
    3. 采用边缘检测算法,把图片中的边缘给找出来
    4. 特定点标定连线,把停车场的这部分图像保留下来,把停车场之外的图片给去掉,具体实施就是手动选取停车场周围的点,一般是角点,然后把线连起来
    5. 基于霍夫变换中的直线检测,去找到停车场中的直线, 后面要根据直线锁定车位
    6. 区域划分,把这个大的停车场划分成一列一列的,然后再从每一列里面去找停车位,然后标定号
    7. 选择出每一列去操作。通过手动坐标标定,把一个个的停车位找到拿出来

    模型训练

    这里是训练神经网络分类模型的过程,这里的网络是用的VGG16, 采用迁移学习的方式,一个原因是训练数据太少,从头开始训练效果不好,另一个是节省训练时间。训练完了之后,就对上面的一个个的停车位进行二分类的预测, 看看有没有车即可

    Description:

    图像预处理部分,主要是处理原始的停车场图片,通过一些图像预处理的技术,把里面的停车位一个个的提取出来, 主要包括下面的流程:

    1. 读入图像, 过滤掉背景
    2. Canny边缘检测找边缘
    3. 选出停车场区域, 使用霍夫变换找直线
    4. 根据直线信息, 以列为单位把停车位先进行划分开,每一列用一个矩形框先划分出来
    5. 可视化下,然后人工把每一列的矩形框进行微调,保证正好把所有停车位包起来
    6. 遍历每一列停车位,使用矩形框把每一个停车位用直线划分开,这个就是常规的坐标操作
    7. 可视化下上面划分结果,根据实际情况手工对直线进行调整,这里依然是保证尽量直线对其停车位分割线
    8. 调整完之后, 把停车位的坐标(左上和右下)以及编号进行保存, 这里要注意保存之前去掉无效的停车位。

    保存的数据,作为后面卷积神经网络预测的数据集

    import os
    # golb模块是用来查找符合特定规则命名的文件名的“路径+文件名”,其功能就是检索路径
    import glob  
    import numpy as np
    import cv2
    import matplotlib.pyplot as plt
    from PIL import Image
    import pickle
    
    import operator
    import collections
    
    def cv_imshow(title, img):
        cv2.imshow(title, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    
    def plt_imshow(img, cmap=None):
        cmap = 'gray' if len(img.shape) == 2 else cmap
        plt.imshow(img, cmap=cmap)
        plt.xticks([])
        plt.yticks([])
        plt.show()
    

    读入图像数据

    # 读入数据
    test_images = [cv2.imread(path) for path in glob.glob('../test_images/*.jpg')]  # BGR
    
    • glob.glob: 返回所有匹配的文件路径列表,如果路径这里使用绝对路径,那么返回的列表也是绝对路径
    • glob.iglob: 获取一个可遍历对象,使用它可以逐个获取匹配的文件路径名。
      • 与glob.glob()的区别是:glob.glob同时获取所有的匹配路径,而glob.iglob一次只获取一个匹配路径,同时glob.iglob返回的是一个生成器类型
    cv_imshow('test', test_images[1])
    
    # 后面就直接用某一张
    test_image = test_images[1]
    

    过滤掉背景

    def select_rgb_white_yellow(image):
        # 过滤背景
        lower = np.uint8([120, 120, 120])
        upper = np.uint8([255, 255, 255])
        # 三个通道内,低于lower和高于upper的部分分别变成0, 在lower-upper之间的值变成255, 相当于mask,过滤背景
        # 保留了像素值在120-255之间的像素值
        white_mask = cv2.inRange(image, lower, upper)
        masked_img = cv2.bitwise_and(image, image, mask=white_mask)
        return masked_img
    

    这里看到inRange,想到了之前用到的二值化的方法threshold, 我在想这俩有啥区别? 为啥这里不用这个了? 下面是我经过探索得到的几点使用经验:

    1. cv2.threshold(src, thresh, maxval, type[, dst]):针对的是单通道图像(灰度图), 二值化的标准,type=THRESH_BINARY: if x > thresh, x = maxval, else x = 0, 而type=THRESH_BINARY_INV: 和上面的标准反着,目前常用到了这俩个
    2. cv2.inRange(src, lowerb, upperb):可以是单通道图像,可以是三通道图像,也可以进行二值化,标准是if x >= lower and x <= upper, x = 255, else x = 0

    这里做了一个实验, 事先把图片转化成灰度图warped = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY),然后下面两句代码的执行结果是一样的:

    • cv2.threshold(warped, 119, 255, cv2.THRESH_BINARY)[1]
    • cv2.inRange(warped, 120, 255)
    masked_img = select_rgb_white_yellow(test_image)
    
    cv_imshow('masked_img', masked_img)
    
    # 转成灰度图
    gray_img = cv2.cvtColor(masked_img, cv2.COLOR_BGR2GRAY)
    
    cv_imshow('masked_img', gray_img)
    

    Canny边缘检测

    low_threshold, high_threshold = 50, 200
    edges_img = cv2.Canny(gray_img, low_threshold, high_threshold)
    
    cv_imshow('edge_img', edges_img)
    

    停车场区域提取

    接下来, 只选出停车场的这块区域来, 把其余部分去掉

    def select_region(image):
        """这里手动选择区域"""
        rows, cols = image.shape[:2]
        
        # 下面定义6个标定点, 这个点的顺序必须让它化成一个区域,如果调整,可能会交叉起来,所以不要动
        pt_1  = [cols*0.06, rows*0.90]   # 左下
        pt_2 = [cols*0.06, rows*0.70]    # 左上
        pt_3 = [cols*0.32, rows*0.51]    # 中左
        pt_4 = [cols*0.6, rows*0.1]      # 中右
        pt_5 = [cols*0.90, rows*0.1]     # 右上
        pt_6 = [cols*0.90, rows*0.90]    # 右下
        
        vertices = np.array([[pt_1, pt_2, pt_3, pt_4, pt_5, pt_6]], dtype=np.int32)
        point_img = image.copy()
        point_img = cv2.cvtColor(point_img, cv2.COLOR_GRAY2BGR)
        for point in vertices[0]:
            cv2.circle(point_img, (point[0], point[1]), 10, (0, 0, 255), 4)
        # cv_imshow('points_img', point_img)
        
        # 定义mask矩阵, 只保留点内部的区域
        mask = np.zeros_like(image)
        if len(mask.shape) == 2:
            cv2.fillPoly(mask, vertices, 255)   # 点框住的地方填充为白色
            #cv_imshow('mask', mask)
        roi_image = cv2.bitwise_and(image, mask)
        return roi_image
    
    roi_image = select_region(edges_img)
    
    cv_imshow('roi_image', roi_image)
    

    霍夫变换找到直线

    def hough_lines(image):
        # 输入的图像需要是边缘检测后的结果
        # minLineLengh(线的最短长度,比这个短的都被忽略)和MaxLineCap(两条直线之间的最大间隔,小于此值,认为是一条直线)
        # rho距离精度,theta角度精度,threshod超过设定阈值才被检测出线段
        return cv2.HoughLinesP(image, rho=0.1, theta=np.pi/10, threshold=15, minLineLength=9, maxLineGap=4)
    
    list_of_lines = hough_lines(roi_image)
    
    # 过滤检测到的直线
    def draw_lines(image, lines, make_copy=True):
        # 过滤霍夫变换检测到直线
        if make_copy:
            image = np.copy(image) 
        cleaned = []
        for line in lines:
            for x1,y1,x2,y2 in line:
                # 这里是过滤直线,必须保证不能是斜的线,且水平方向不能太长或者太短
                if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
                    cleaned.append((x1,y1,x2,y2))
                    cv2.line(image, (x1, y1), (x2, y2), [255, 0, 0], 2)
        print("lines detected: ", len(cleaned))
        return image
    
    temp = draw_lines(roi_image, list_of_lines)
    
    cv_imshow('img', temp)
    

    检测到每列停车位

    def identity_blocks(image, lines, make_copy=True):
        if make_copy:
            new_image = image.copy()
        
        # 过滤部分直线
        stayed_lines = []
        for line in lines:
            for x1, y1, x2, y2 in line:
                # 这里是过滤直线,必须保证不能是斜的线,且水平方向不能太长或者太短
                if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
                    stayed_lines.append((x1,y1,x2,y2))
        
        # 对直线按照x1排序, 这样能让这些线从上到下排列好, 这个排序是从第一列的第一条横线,往下走,然后是第二列第一条横线往下,...
        list1 = sorted(stayed_lines, key=operator.itemgetter(0, 1))
    
        # 找到多个列,相当于每列是一排车
        clusters = collections.defaultdict(list)
        dIndex = 0
        clus_dist = 10   # 每一列之间的那个距离
        for i in range(len(list1) - 1):
            # 看看相邻两条线之间的距离,如果是一列的,那么x1这个距离应该很近,毕竟是同一列上的
            # 如果这个值大于10了,说明是下一列的了,此时需要移动dIndex, 这个表示的是第几列 
            distance = abs(list1[i+1][0] - list1[i][0])
            if distance <= clus_dist:
                clusters[dIndex].append(list1[i])
                clusters[dIndex].append(list1[i+1])
            else:
                dIndex += 1
        
        # 得到每列停车位的矩形框
        rects = {}
        i = 0
        for key in clusters:
            all_list = clusters[key]
            cleaned = list(set(all_list))
            # 有5个停车位至少
            if len(cleaned) > 5:
                cleaned = sorted(cleaned, key=lambda tup: tup[1])
                avg_y1 = cleaned[0][1]
                avg_y2 = cleaned[-1][1]
                if abs(avg_y2-avg_y1) < 15:
                    continue
                avg_x1 = 0
                avg_x2 = 0
                for tup in cleaned:
                    avg_x1 += tup[0]
                    avg_x2 += tup[2]
                avg_x1 = avg_x1 / len(cleaned)
                avg_x2 = avg_x2 / len(cleaned)
                
                rects[i] = [avg_x1, avg_y1, avg_x2, avg_y2]
                i += 1
        print('Num Parking Lanes: ', len(rects))
        
        # 把列矩形画出来
        buff = 7
        for key in rects:
            tup_topLeft = (int(rects[key][0] - buff), int(rects[key][1]))
            tup_botRight = (int(rects[key][2] + buff), int(rects[key][3]))
            cv2.rectangle(new_image, tup_topLeft, tup_botRight, (0, 255, 0), 3)
        return new_image, rects
    
    new_image, rects = identity_blocks(test_image, list_of_lines)
    
    cv_imshow('new_image', new_image)
    
    def rect_finetune(image, rects, copy_img=True):
        if copy_img:
            image_copy = image.copy()
        # 下面需要对上面的框进行坐标微调, 让框更加准确
        # 这个框很重要,影响后面停车位的统计,尽量不能有遗漏
        for k in rects:
            if k == 0:
                rects[k][1] -= 10
            elif k == 1:
                rects[k][1] -= 10
                rects[k][3] -= 10
            elif k == 2 or k == 3 or k == 5:
                rects[k][1] -= 4
                rects[k][3] += 13
            elif k == 6 or k == 8:
                rects[k][1] -= 18
                rects[k][3] += 12
            elif k == 9:
                rects[k][1] += 10
                rects[k][3] += 10
            elif k == 10:
                rects[k][1] += 45
            elif k == 11:
                rects[k][3] += 45
        
        buff = 8
        for key in rects:
            tup_topLeft = (int(rects[key][0]-buff), int(rects[key][1]))
            tup_botRight = (int(rects[key][2]+buff), int(rects[key][3]))
            cv2.rectangle(image_copy, tup_topLeft, tup_botRight, (0, 255, 0), 3)
        
        return image_copy, rects
    
    new_image, rects = rect_finetune(test_image, rects)
    cv_imshow('new_image', new_image)
    

    框定出每个停车位

    def draw_parking(image, rects, make_copy=True, save=True):
        if make_copy:
            new_image = image.copy()
        
        gap = 15.5
        spot_dict = {}  # 一个车位对应一个位置
        tot_spots = 0
        
        #微调
        adj_x1 = {0: -8, 1:-15, 2:-15, 3:-15, 4:-15, 5:-15, 6:-15, 7:-15, 8:-10, 9:-10, 10:-10, 11:0}
        adj_x2 = {0: 0, 1: 15, 2:15, 3:15, 4:15, 5:15, 6:15, 7:15, 8:10, 9:10, 10:10, 11:0}
        fine_tune_y = {0: 4, 1: -2, 2: 3, 3: 1, 4: -3, 5: 1, 6: 5, 7: -3, 8: 0, 9: 5, 10: 4, 11: 0}
        
        for key in rects:
            tup = rects[key]
            x1 = int(tup[0] + adj_x1[key])
            x2 = int(tup[2] + adj_x2[key])
            y1 = int(tup[1])
            y2 = int(tup[3])
            cv2.rectangle(new_image, (x1, y1),(x2,y2),(0,255,0),2)
            
            num_splits = int(abs(y2-y1)//gap)
            for i in range(0, num_splits+1):
                y = int(y1+i*gap) + fine_tune_y[key]
                cv2.line(new_image, (x1, y), (x2, y), (255, 0, 0), 2)
            if key > 0 and key < len(rects) - 1:
                # 竖直线
                x = int((x1+x2) / 2)
                cv2.line(new_image, (x, y), (x, y2), (0, 0, 255), 2)
            
            # 计算数量   除了第一列和最后一列,中间的都是两列的
            if key == 0 or key == len(rects) - 1:
                tot_spots += num_splits + 1
            else:
                tot_spots += 2 * (num_splits + 1)
            
            # 字典对应好
            if key == 0 or key == len(rects) - 1:
                for i in range(0, num_splits+1):
                    cur_len = len(spot_dict)
                    y = int(y1 + i * gap) + fine_tune_y[key]
                    spot_dict[(x1, y, x2, y+gap)] = cur_len + 1
            else:
                for i in range(0, num_splits+1):
                    cur_len = len(spot_dict)
                    y = int(y1 + i * gap) + fine_tune_y[key]
                    x = int((x1+x2) / 2)
                    spot_dict[(x1, y, x, y+gap)] = cur_len + 1
                    spot_dict[(x, y, x2, y+gap)] = cur_len + 2
        
        if save:
            filename = 'with_parking.jpg'
            cv2.imwrite(filename, new_image)
        
        return new_image, spot_dict
    
    new_image, spot_dict = draw_parking(test_image, rects)
    cv_imshow('parking', new_image)
    
    # 去掉多余的停车位
    invalid_spots = [10, 11, 33, 34, 37, 38, 61, 62, 93, 94, 95, 97, 98, 135, 137, 138, 187, 249, 
               250, 253, 254, 323, 324, 327, 328, 467, 468, 531, 532]
    valid_spots_dict = {}
    cur_idx = 1
    for k, v in spot_dict.items():
        if v in invalid_spots:
            continue
        valid_spots_dict[k] = cur_idx
        cur_idx += 1
    
    len(valid_spots_dict)  # 有效的停车位一共540个
    
    # 把每一个有效停车位标记出来
    tmp_img = test_image.copy()
    for k, v in valid_spots_dict.items():
        cv2.rectangle(tmp_img, (int(k[0]), int(k[1])),(int(k[2]),int(k[3])), (0,255,0) , 2)
    cv_imshow('valid_pot', tmp_img)
    
    cv2.imwrite('valid_pot_img.jpg', tmp_img)
    
    # 保存这个停车位字典
    with open('spot_dict.pickle', 'wb') as handle:
        pickle.dump(valid_spots_dict, handle, protocol=pickle.HIGHEST_PROTOCOL)
    

    为CNN生成预测图片

    def save_images_for_cnn(image, spot_dict, folder_name = '../cnn_pred_data'):
        for spot in spot_dict.keys():
            (x1, y1, x2, y2) = spot
            (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
            
            # 裁剪
            spot_img = image[y1:y2, x1:x2]
            spot_img = cv2.resize(spot_img, (0, 0), fx=2.0, fy=2.0)
            spot_id = spot_dict[spot]
            
            filename = 'spot_{}.jpg'.format(str(spot_id))
            
            # print(spot_img.shape, filename, (x1,x2,y1,y2))
            cv2.imwrite(os.path.join(folder_name, filename), spot_img)
    
    save_images_for_cnn(test_image, valid_spots_dict)
    
  • 相关阅读:
    Python爬虫动态ip代理防止被封的方法
    接口测试神器Apifox究竟有多香?
    SSM学习——Spring事务(9)
    设计师都有些常用的组件库?
    【软考软件评测师】第三章节 白盒测试测试方法
    转变命运!揭秘反转链表的神奇算法!
    Vscode配置C#编程环境(win10)
    什么是低代码(Low-Code)?低代码适用于哪些场景?
    uboot移植之环境变量bootcmd
    围绕防汛抗旱抓落实 国稻种芯-荔波县:抓指导统筹粮食增产
  • 原文地址:https://blog.csdn.net/abackcab/article/details/139708013