工作中遇到了一个问题,好不容易解决了,写个博客了记录一下。

项目需要识别出汽车车前窗部分,通过ROI选取出来,再进行下一步的处理。我利用一些办法得到了如图1所示的轮廓,但是效果很不好。图中可见车窗四个边只有左侧、右侧和下侧轮廓,各个轮廓很不连续,断线问题很严重,并且这张图片已经是数据集里效果比较好的,还有更多的图片轮廓断线更为严重,直接提取ROI非常困难。

图1. 原始车窗识别效果

因为实验室光线的问题,轮廓的断线基本无法避免,利用传统CV算法补全很麻烦,而且不一定能适应所有的情况,我准备先用传统算法实现,然后下一步再利用深度学习的方法找出来车窗。想要真的适应更多的情况确实还是得用深度学习,后续我再写一篇分享一下,目前先分享我的传统CV解决方案。

我的思路是先把所有的点按边分类,分类完成后在对这些点进行拟合,然后对拟合的曲线进行修补和裁剪,防止轮廓出现凹陷的地方,最后再画一下轮廓就完成了。先把所有用到的包加载一下,sklearn用来拟合和分类,numba用来快速找两个多维数组的差异,其他的就是很常规的包。

import cv2
import numpy as np
import numba as nb
import copy as cp
from sklearn import linear_model
from sklearn.cluster import KMeans
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import mean_squared_error as MSE

单独写出来的函数如下所示,

    MSE_Singel

用来计算MSE,用于进一步分类所有的点。

    setdiff2d_nb

这个函数用来寻找两个二维数组中的差异元素,运算速度非常快。


def MSE_Singel(y_pred, y):return (y_pred-y)**2def temp_imshow(size, idx):class_left = np.full(size, 255)class_left[list(idx[0]), list(idx[1])] = 0class_left = class_left.astype(np.uint8)cv2.namedWindow('left', 0)cv2.imshow('left', class_left)cv2.waitKey(0)@nb.njit
def mul_xor_hash(arr, init=65537, k=37):result = initfor x in arr.view(np.uint64):result = (result * k) ^ xreturn result@nb.njit
def setdiff2d_nb(arr1, arr2):# : build `delta` set using hashesdelta = {mul_xor_hash(arr2[0])}for i in range(1, arr2.shape[0]):delta.add(mul_xor_hash(arr2[i]))# : compute the size of the resultn = 0for i in range(arr1.shape[0]):if mul_xor_hash(arr1[i]) not in delta:n += 1# : build the resultresult = np.empty((n, arr1.shape[-1]), dtype=arr1.dtype)j = 0for i in range(arr1.shape[0]):if mul_xor_hash(arr1[i]) not in delta:result[j] = arr1[i]j += 1return result

首先需要导入图片,二值化处理一下。需要补全的四个边最好处理的就是左右两个,因为基本就是直线。我用了K-means来聚类,为了剔除上下两边的影响,先把图片切成了cut_top,cut_middle,cut_bottom三块,中间块就只有左右两条边,因此用中间块来聚类。

image = cv2.imread('base_pic1.jpg')
image_gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
retVal, image_thresh = cv2.threshold(image_gray, 150, 255, cv2.THRESH_BINARY)
cv2.namedWindow('1', 0)
cv2.imshow('1', image_thresh)
cv2.waitKey(0)
#聚类
#1.照片三分类或者二分类,上半张图聚两类
image_cut = cp.copy(image_thresh)
H, W = image_cut.shape#数值填充
image_cut = np.pad(image_cut,((0, 3-H%3),(0,0)), constant_values=255)
cut_top, cut_middle, cut_bottom = np.split(image_cut, 3 , axis=0)#寻找坐标值为0的所有点位
idx = np.array(np.where(cut_middle == 0)).transpose()#聚类
km = KMeans(n_clusters=2,init='k-means++', n_init=10, max_iter=300, random_state=0)
km.fit(idx)
idx_class_1 = idx[np.where(km.labels_ == 0)].transpose().reshape(2, -1, 1)
idx_class_2 = idx[np.where(km.labels_ == 1)].transpose().reshape(2, -1, 1)
class_1 = np.full(cut_middle.shape, 255)
class_1[list(idx_class_1)] = 0
class_1 = class_1.astype(np.uint8)
class_2 = np.full(cut_middle.shape, 255)
class_2[list(idx_class_2)] = 0
class_2 = class_2.astype(np.uint8)

聚完类后面就需要拟合了,先把各个图像块的尺寸取出来,拟合虽然是在中间图片上拟合,但后续也需要在上下两个图像块中寻找属于左右两个边的点,因此用于拟合的点的坐标都是加上了cut_top这个图像的高。Derr_1 和Derr_2就是曲线的MSE误差,先取出来,后续用来归类cut_top和cut_bottom中属于左右两边的点。

#2.两类直线拟合,其余部分照片按距离划分像素点
H_top, W_top = cut_top.shape
H_middle, W_middle = cut_middle.shape
H_bottom, W_bottom = cut_bottom.shape
#拼合图像内的数值点
#middle图像的数值点line1
idx_middle_line1 = cp.copy(idx_class_1)
#idx_middle_line1 = np.squeeze(idx_middle_line1, axis=2)
#middle图像的数值点line2
idx_middle_line2 = cp.copy(idx_class_2)
#idx_middle_line2 = np.squeeze(idx_middle_line2, axis=2)
idx_middle_line1[0] = idx_middle_line1[0] + H_top
idx_middle_line2[0] = idx_middle_line2[0] + H_top
line1 = linear_model.LinearRegression()
line2 = linear_model.LinearRegression()
line1.fit(idx_middle_line1[0], idx_middle_line1[1])
line2.fit(idx_middle_line2[0], idx_middle_line2[1])
y1_pred = line1.predict(idx_middle_line1[0])
y2_pred = line2.predict(idx_middle_line2[0])
Derr_1 = MSE(idx_middle_line1[1], y1_pred)
Derr_2 = MSE(idx_middle_line2[1], y2_pred)

得到了直线的拟合方程就可以找剩下两张图中的点了,具体代码如下所示,切记cut_bottom中的点坐标需要加上cut_top和cut_middle的高。由于左右两边并不是严格意义上的直线,会带有一点的弧度,因此寻找上下两个图像块中属于左右两边的点时,可以适当的把Derr放大一点,我这边放大了100倍,看下来效果还行。

#3.按照Derr取cut_top和cut_bottom中的点
idx_top = np.array(np.where(cut_top == 0))
idx_top = np.expand_dims(idx_top, axis=-1)
y1_top_pred = line1.predict(idx_top[0])
y2_top_pred = line2.predict(idx_top[0])
line1_top_y = np.expand_dims(np.where(((y1_top_pred-idx_top[1])**2) <= 100*Derr_1)[0], axis=-1)
line2_top_y = np.expand_dims(np.where(((y2_top_pred-idx_top[1])**2) <= 100*Derr_2)[0], axis=-1)
idx_top_2dims = idx_top.squeeze(axis=-1)
#cut_top中分类至line1的坐标
idx_top_line1 = idx_top_2dims[:, list(line1_top_y)]
#cut_top中分类至line2的坐标
idx_top_line2 = idx_top_2dims[:, list(line2_top_y)]
#cut_bottom中的数值
idx_bottom = np.array(np.where(cut_bottom == 0))
idx_bottom = np.expand_dims(idx_bottom, axis=-1)
idx_bottom[0] = idx_bottom[0] + H_top + H_middle
y1_bottom_pred = line1.predict(idx_bottom[0])
y2_bottom_pred = line2.predict(idx_bottom[0])
line1_bottom_y = np.expand_dims(np.where(((y1_bottom_pred-idx_bottom[1])**2) <= 80*Derr_1)[0], axis=-1)
line2_bottom_y = np.expand_dims(np.where(((y2_bottom_pred-idx_bottom[1])**2) <= 80*Derr_2)[0], axis=-1)
idx_bottom_2dims = idx_bottom.squeeze(axis=-1)
#cut_bottom中分类至line1的坐标
idx_bottom_line1 = idx_bottom_2dims[:, list(line1_bottom_y)]
#cut_bottom中分类至line2的坐标
idx_bottom_line2 = idx_bottom_2dims[:, list(line2_bottom_y)]
#bottom图片显示,非必要注释
class_bottom = np.full(image_thresh.shape, 255)
class_bottom[list(idx_bottom[0]), list(idx_bottom[1])] = 0
class_bottom = class_bottom.astype(np.uint8)
cv2.namedWindow('bottom', 0)
cv2.imshow('bottom', class_bottom)
cv2.waitKey(0)
#拼合区域内的坐标值
idx_line1 = idx_middle_line1
idx_line2 = idx_middle_line2
if 0 not in idx_top_line1.shape:idx_line1 = np.concatenate((idx_top_line1, idx_line1), axis=1)if 0 not in idx_bottom_line1.shape:idx_line1 = np.concatenate((idx_line1, idx_bottom_line1), axis=1)
if 0 not in idx_top_line2.shape:idx_line2 = np.concatenate((idx_top_line2, idx_line2), axis=1)if 0 not in idx_bottom_line2.shape:idx_line2 = np.concatenate((idx_line2, idx_bottom_line2), axis=1)

到了这一步可以先简单看一下分类的结果,测试代码如下所示,得到的分类如图2和图3所示。

class_line1 = np.full(image_thresh.shape, 255)
class_line2 = np.full(image_thresh.shape, 255)
class_line1[list(idx_line1[0]), list(idx_line1[1])] = 0
class_line2[list(idx_line2[0]), list(idx_line2[1])] = 0
class_line1 = class_line1.astype(np.uint8)
class_line2 = class_line2.astype(np.uint8)
cv2.namedWindow('line1', 0)
cv2.namedWindow('line2', 0)
cv2.imshow('line1', class_line1)
cv2.imshow('line2', class_line2)
cv2.imshow('1', image_thresh)
cv2.waitKey(0)

图2. 左边分类效果                                             图3. 右边分类效果

提取出所有的归属于line1和line2的点后,就可以开始拟合下边曲线。我使用了四阶多项式进行拟合,注意这边需要使用列坐标来预测行坐标,和拟合直线正好相反,防止出现一个x值映射两个y值得情况。

#4.剩下像素点曲线拟合
idx_all = np.array(np.where(image_cut == 0)).transpose()
idx_left = setdiff2d_nb(idx_all, idx_line1.squeeze(axis=-1).transpose())
idx_left = setdiff2d_nb(idx_left, idx_line2.squeeze(axis=-1).transpose())
idx_left = idx_left.transpose().reshape([2, -1, 1])
#temp_imshow(image_cut.shape, idx_left)
H_half = image_cut.shape[0]//2
line3_x = np.expand_dims(np.where(idx_left[0] < H_half)[0], axis=-1)
curve1_x = np.expand_dims(np.where(idx_left[0] >= H_half)[0], axis=-1)
idx_left_2dims = idx_left.squeeze(axis=-1)
#cut_bottom中分类至line1的坐标
idx_line3 = idx_left_2dims[:, list(line3_x)]
idx_curve1 = idx_left_2dims[:, list(curve1_x)]
# temp_imshow(image_cut.shape, idx_curve1)
curve_model = Pipeline([('poly', PolynomialFeatures(degree=4)),('linear', linear_model.LinearRegression(fit_intercept=False))])
curve_model.fit(idx_curve1[1], idx_curve1[0])
y_original = np.unique(np.sort(idx_curve1[1], axis=0))
y_original = np.expand_dims(y_original, axis=-1)
#画图时需要取整,暂时不取
#x_pred = np.rint(curve_model.predict(y_original))
x_pred = curve_model.predict(y_original)

下一步需要用得到的拟合曲线代替图中的轮廓,用于后续的边界划分。

#5.拟合曲线代替像素轮廓
curve_pred = np.array([x_pred, y_original])
line1_x_original = np.unique(np.sort(idx_line1[0], axis=0))
line1_x_original = np.arange(line1_x_original[0], line1_x_original[-1]+1, 1).reshape([-1, 1])
line1_y_pred = line1.predict(line1_x_original)
line2_x_original = np.unique(np.sort(idx_line2[0], axis=0))
line2_x_original = np.arange(line2_x_original[0], line2_x_original[-1]+1, 1).reshape([-1, 1])
line2_y_pred = line2.predict(line2_x_original)
line1_pred = np.array([line1_x_original, line1_y_pred])
line2_pred = np.array([line2_x_original, line2_y_pred])

接下来就是对拟合曲线的数值点进行修补和裁剪,修补的目的是保证三个边线有相交,即补全初始图像中缺失的边界。裁剪就是保证三条线两两相交的地方不会出现凹陷的区域。具体代码如下所示。

#修补
#修补line1
while( True ):k = 0jump = 0for i in range(10):line1_ex_2 = line1_pred.squeeze(axis=-1).transpose()[-1].reshape([-1, 1])pred_temp = curve_model.predict(np.expand_dims(line1_ex_2[-1],axis=-1))if pred_temp[0,0] > line1_ex_2[0,0]:y_temp_pred = line1.predict((line1_pred[0, line1_pred.shape[1]-1]+1).reshape([-1, 1]))curev_x_pred = curve_model.predict(y_temp_pred)temp_curve_idx = np.array([curev_x_pred, y_temp_pred])temp = np.array([line1_pred[0, line1_pred.shape[1]-1]+1, line1.predict((line1_pred[0, line1_pred.shape[1]-1]+1).reshape([-1, 1])).squeeze(axis=-1)])temp = temp.reshape([2, -1, 1])line1_pred = np.append(line1_pred, temp, axis=1)curve_pred = np.concatenate((temp_curve_idx, curve_pred), axis = 1)else:jump = 1breakif i == 9:k += 1if jump == 1:breakelif k == 10:print('still no contact')break
#修补line2
while( True ):k = 0jump = 0for i in range(10):line2_ex_2 = line2_pred.squeeze(axis=-1).transpose()[-1].reshape([-1, 1])pred_temp = curve_model.predict(np.expand_dims(line2_ex_2[-1],axis=-1))if pred_temp[0,0] > line2_ex_2[0,0]:y_temp_pred = line2.predict((line2_pred[0, line2_pred.shape[1]-1]+1).reshape([-1, 1]))curev_x_pred = curve_model.predict(y_temp_pred)temp_curve_idx = np.array([curev_x_pred, y_temp_pred])temp = np.array([line2_pred[0, line2_pred.shape[1]-1]+1, line2.predict((line1_pred[0, line2_pred.shape[1]-1]+1).reshape([-1, 1])).squeeze(axis=-1)])temp = temp.reshape([2, -1, 1])line2_pred = np.append(line2_pred, temp, axis=1)curve_pred = np.concatenate((temp_curve_idx, curve_pred), axis = 1)else:jump = 1breakif i == 9:k += 1if jump == 1:breakelif k == 10:print('still no contact')break
#裁剪
#裁剪line1
while(True):line1_ex_2 = line1_pred.squeeze(axis=-1).transpose()[-1].reshape([-1,1])curve_ex_1 = curve_pred.squeeze(axis=-1).transpose()[0].reshape([-1,1])if line1_ex_2[0, 0] <= curve_ex_1[0, 0] and line1_ex_2[1, 0] <= curve_ex_1[1, 0]:breakif line1_ex_2[0, 0] > curve_ex_1[0, 0]:line1_pred = np.delete(line1_pred, -1, axis=1)if line1_ex_2[1, 0] > curve_ex_1[1, 0]:curve_pred = np.delete(curve_pred, 0, axis=1)
#裁剪line2
while(True):line2_ex_2 = line2_pred.squeeze(axis=-1).transpose()[-1].reshape([-1,1])curve_ex_2 = curve_pred.squeeze(axis=-1).transpose()[-1].reshape([-1,1])if line2_ex_2[0, 0] <= curve_ex_2[0, 0] and line2_ex_2[1, 0] >= curve_ex_2[1, 0]:breakif line2_ex_2[0, 0] > curve_ex_2[0, 0]:line2_pred = np.delete(line2_pred, -1, axis=1)if line2_ex_2[1, 0] > curve_ex_2[1, 0]:curve_pred = np.delete(curve_pred, -1, axis=1)

到此轮廓已经全部都有了,把轮廓里的点全部拼接起来,再利用drawcontours画一下,就得到了车窗完整的轮廓曲线了。

line1_pred_2dim = line1_pred.squeeze(axis=-1).transpose().reshape(-1, 1, 2)
line1_pred_int = np.rint(line1_pred_2dim)
line1_pred_int = line1_pred_int.astype(np.int64)
line2_pred_2dim = line2_pred.squeeze(axis=-1).transpose().reshape(-1, 1, 2)
line2_pred_int = np.rint(line2_pred_2dim)
line2_pred_int = line2_pred_int.astype(np.int64)
curve_pred_2dim = curve_pred.squeeze(axis=-1).transpose().reshape(-1, 1, 2)
curve_pred_int = np.rint(curve_pred_2dim)
curve_pred_int = curve_pred_int.astype(np.int64)contour_window = np.concatenate((line1_pred_int, curve_pred_int, line2_pred_int[::-1]), axis=0)[:,:,::-1]
contour_temp = (contour_window,)
img_result = cv2.drawContours(image, contour_temp, -1, (0,255,0), 3)
cv2.namedWindow('result', 0)
cv2.imshow('result', img_result)
cv2.waitKey(0)

最后得到的效果如图4所示:

看起来效果还可以,但是对于原始图片效果很差的,上述代码的效果也不太好。说白了还是传统算法对摄像环境的要求太高,切换了实验室后拍出来的照片,上述代码可能就不适用了,还是得用CNN来处理。

opencv 风挡轮廓补全相关推荐

  1. 在windows中python安装sit-packages路径位置 在Pycharm中导入opencv不能自动代码补全问题

    一.在windows中python安装sit-packages路径位置 C:\Users\shl\AppData\Local\Programs\Python\Python36\Lib\site-pac ...

  2. 【机器视觉学习笔记】python安装OpenCV并设置自动补全及代码提示

    目录 安装 测试 设置自动补全及代码提示 平台:Windows 10 20H2 Python 3.8.12 (default, Oct 12 2021, 03:01:40) [MSC v.1916 6 ...

  3. pycharm OpenCV代码补全失效解决方法

    转载于:https://blog.csdn.net/wjsiou123/article/details/88343328 问题描述: 最近在pycharm中打开以前的项目,发现cv2模块没有代码提示, ...

  4. 【Python+OpenCV+sklearn+easygui】人脸(口罩)识别+口罩下人脸补全的系统设计

    [写在前面:笔者是一个才接触python半年之久的编程菜鸡,刚好这学期的课程需要用到python做一些有关计算机视觉的设计,于是就根据自己所学,同时借鉴了一些CSDN上各位大佬的思路和代码,做了一个简 ...

  5. 终极大招~pycharm自动补全opencv代码提示功能

    你的pycharm还能自动补全opencv代码提示吗? 你可能通过修改cv2,进入__init__.py文件,一顿操作,还是不行. 你以为是工具问题,卸载重装? 还是opencv卸载重装好几次了 这次 ...

  6. 关于Opencv中Filter2D函数的补全方式

    目录 关于Opencv中Filter2D函数的补全方式 环境 验证 C++举例 Python举例 关于Opencv中Filter2D函数的补全方式 环境 OpenCV3.4.16(C++) openc ...

  7. python opencv vscode 增加自动补全 auto completement

    python opencv vscode 增加自动补全 auto completement 现状 解决方法 参考 现状 vscode中使用opencv时,无法自动补全. 解决方法 自己生成 pyi 文 ...

  8. OpenCV contrib 等的配置与vscode自动补全功能

    系统:Ubuntu18.04 版本:OpenCV+contrib 4.5.1 已经配置好的:https://pan.baidu.com/s/1Dl3ePy6dG5AQca37I7FFeg zlaf 安 ...

  9. OPENCV图像轮廓检测

    前面在图像转换的时候学到canny算子,可以检测出图像的轮廓信息,但是,该算子检测到的轮廓信息还需要我们手动的用眼睛去识别,而实际工程应用中,我们需要得到轮廓的具体数学信息,这就涉及到今天的主题,图像 ...

最新文章

  1. 单片机如何使用?51单片机C语言编程实例有哪些?
  2. SAP MM Consignment 寄售库存
  3. DLL动态链接库的工作原理
  4. ue4 android vulkan,在Android用vulkan完成蓝绿幕扣像
  5. 4020-基于链地址法的散列表的插入(C++,附思路以及头插法,尾插法两种代码)
  6. CISA:警惕俄罗斯 “Sandworm” 黑客组织使用的新型恶意软件框架
  7. vue2.x 给一个对象里添加一个没有的属性
  8. python源码_Python爬虫入门之获取网页源码
  9. 设计模式之三:装饰者模式(简单实现(星巴兹咖啡))
  10. 博弈论 从懵逼到入门 详解
  11. latex 引用网页 网址 网站 格式
  12. iOS:const的使用
  13. 2019最新北风网Ant+Java全套视频课程
  14. 神经网络arm neon加速实现
  15. 计算机高中期末总结作文,高中期末总结作文
  16. windows常见电脑蓝屏的解决办法
  17. windows批量修改文件权限
  18. 计算机上平方米的单位,word怎么写平方米 word中平方米的单位怎么打
  19. MySQL备份报错mysqldump: Got error: 1045: Access denied for user ‘root‘@‘localhost‘ (using password: YES)
  20. 机器学习-02 基于sklearn 广义线性模型-普通最小二乘法

热门文章

  1. java抓取网站数据
  2. 2018百度之星程序设计大赛资格赛(4道题的答案)
  3. #业余学习 java基础
  4. LC5454.统计全1子矩阵(矩阵统计)
  5. python利用numpy模块读取csv文件
  6. C#中break 和 continue 和 return在if语句和for循环中的区别
  7. idea部署RuoYi-Vue分离版详解,够细!你值得拥有
  8. 为什么要写单元测试?如何写单元测试?
  9. 设计模式学习笔记(十一)-组合模式
  10. 第13期《凤凰涅槃,浴火重生!》2月刊