opencv之基本形状识别
各种博客上的现有方法
- https://blog.csdn.net/xuxunjie147/article/details/76577298 这篇博客只是展示了代码,并没有讲解解决问题的思路。笔者通过阅读其代码,将其思路总结如下:
- https://www.cnblogs.com/long5683/p/9694983.html 这篇博客使用面积和多边形拟合后的顶点数进行各种形状的分类。但是这种方法只是适应于他给的那张图片。
- https://blog.51cto.com/gloomyfish/2104134?lb 这篇博客的方法主要是通过arcLength计算原始轮廓的周长,然后通过多边形拟合逼近原始周长。最后使用多边形拟合得到的顶点数进行分类。三个顶点就是三角形,4个顶点就是矩形,4-10个顶点是不规则多边形,10个以上是圆。但是对于不规则的四边形,这种方法显然会失效。因为该方法并没有考虑每个顶点处的角度。
- https://www.jianshu.com/p/2731f42882f4 这篇博客所使用的方法思路与上一篇博客基本类似。
新方法——从信号的角度分析
- 具体步骤如下:
- 使用findcontours找到所要识别的图形的轮廓。
- 在轮廓的基础上,使用图形的矩(moments)计算得到图形的重心。
- 使用drawcontours绘制轮廓,并得边缘的坐标位置
- 计算边缘上所有的点到重心的距离,按照逆时针绕重心转动的方向绘制距离随角度变化的图。
- 进行距离归一化
实验结果
- 圆形及其对应的距离-角度变化图
- 椭圆及其对应的距离-角度变化图
- 五边形及其对应的距离-角度变化图
- 矩形及其对应的距离-角度变化图
- 正方形及其对应的距离-角度变化图
- 平行四边形及其对应的距离-角度变化图
- 三角形及其对应的距离-角度变化图
通过上面这些图可以得到图下结论:
- 多边形(不包括圆和椭圆)的距离-角度图像存在大量导数不存在的点,且不可导点的数量等于顶点的个数。
- 多边形的顶点对应的角度越大,其不可导点的尖锐程度越小,即左导数和右导数的差距越小。
- 对称图形的距离-角度图像呈现高度的对称性甚至周期性。
附代码
程序是早些时候写得,没有封装成独立的函数,还有些啰嗦。代码是进行批处理的,读者可以稍微改一下程序中使用的路径。
大体的思路是先找到轮廓,然后根据轮廓计算重心位置和轮廓各个点到重心的距离。但是opencv的findcontours只会返回决定轮廓的几个重要的点,比如矩形轮廓只会返回4个顶点的坐标。为了得到所有轮廓的点,使用drawcontours先把轮廓画出来,然后根据像素值找到轮廓所有点的坐标位置。由于轮廓是画出来的,所以drawcontours的linewidth参数相当于控制绘制轮廓精细程度的一个参数。
代码中还使用fft和差分这两种时间序列分析手段分析我们的得到的数据。希望能对读者有所帮助。
代码很久没有运行,如果有bug,还望海涵。
# -*- coding:utf8 -*-
# @TIME : 2019/7/11 16:03
# @Author : SuHao
# @File : main.py
import numpy as np
import cv2
import os
import matplotlib.pyplot as plt
import scipy.fftpack as fft
path_pic = "./pic"
filename = os.listdir(path_pic)
path_result = "./result"
if not os.path.exists(path_result):
os.mkdir(path_result)
for i in range(len(filename)):
image = cv2.imread(path_pic + "/" + filename[i], 0)
ret, thresh = cv2.threshold(image, 100, 255, 0)
#计算轮廓
out, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
drawing = np.zeros(image.shape) + 255
imgnew = cv2.drawContours(drawing, contours, 1, (0, 255, 255), 0)
#确定轮廓的重心
mu = cv2.moments(contours[1])
cx = int(mu['m10']/mu['m00']) #表示列数,是横坐标
cy = int(mu['m01']/mu['m00'])
#计算轮廓到重心的距离
'''
这里遇到一个问题:轮廓不总是一个像素点,也就是说轮廓是有厚度的
'''
label = np.where(drawing == 0)
#注意此处矩阵元素的下标与在二维平面的坐标是反的,行表示纵坐标
location = np.array(label).T
location = location[:, ::-1]
location = location.astype("int64")
center = np.array([[cx, cy]])
center = np.tile(center, (location.shape[0], 1))
center = center.astype("int64")
dist = (location - center)**2
dist = np.sqrt(dist.sum(axis=1))
up = location[:, 1] <= cy #注意在重心以上,对应纵坐标是小
down = location[:, 1] > cy
location_up = location[up, :]
location_down = location[down, :]
dist_up = dist[up]
dist_down = dist[down]
angle_up = location_up[:, 0] - cx
angle_up = np.arccos(angle_up / dist_up)
angle_down = location_down[:, 0] - cx
angle_down = np.arccos(angle_down / dist_down) * (-1) + np.pi * 2
location = np.vstack((location_up, location_down))
dist = np.hstack((dist_up, dist_down))
angle = np.hstack((angle_up, angle_down))
order = np.argsort(angle, axis=0)
dist = dist[order]
location = location[order]
angle = angle[order]
plt.figure()
plt.plot(angle, dist / max(dist))
plt.title(filename[i])
plt.ylim(0, 1.2)
plt.savefig(path_result + "/" + filename[i])
plt.close()
# 离散DCT
time_domain = fft.dct(dist / max(dist))
plt.figure()
plt.plot(np.arange(0, 20, 1), np.abs(time_domain)[0:20])
plt.xticks(np.arange(20))
plt.title(filename[i])
plt.savefig(path_result + "/ifft_" + filename[i])
plt.close()
# 差分
diff_1 = np.gradient(dist / max(dist))
diff_2 = np.gradient(dist / max(dist), 2)
plt.figure()
plt.plot(np.arange(0, diff_1.shape[0], 1), diff_1)
plt.plot(np.arange(0, diff_2.shape[0], 1), diff_2)
plt.title(filename[i])
plt.legend(["order_1", "order_2"])
plt.plot(np.arange(0, diff_2.shape[0], 1), 0*np.arange(0, diff_2.shape[0], 1))
plt.savefig(path_result + "/diff_" + filename[i])
plt.close()
print(i)
# cv2.circle(drawing, (cx, cy), 2, (0, 255, 255), -1)
# cv2.imwrite(path_result + "/contour_" + filename[i], drawing)
# cv2.waitKey(1000)
# cv2.destroyAllWindows()