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绘制轮廓,并得边缘的坐标位置
- 计算边缘上所有的点到重心的距离,按照逆时针绕重心转动的方向绘制距离随角度变化的图。
- 进行距离归一化
实验结果
- 圆形及其对应的距离-角度变化图
![](https://i-blog.csdnimg.cn/blog_migrate/9b5a65a6fd141eb8d12e36c3e9b8bd52.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/90b65b13c68af7e542adb061994ffecf.jpeg)
- 椭圆及其对应的距离-角度变化图
![](https://i-blog.csdnimg.cn/blog_migrate/4cf55f462f996833d57e7aa80ab73c44.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/0a0e06f415f4301da0a8c4db857d5361.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/260da9a9caf535470a1678f5a3629437.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/8b71ec82f6845d14bb89b0694627dc82.jpeg)
- 五边形及其对应的距离-角度变化图
![](https://i-blog.csdnimg.cn/blog_migrate/dc5a23bfc53a2b137e15f15c62350ed4.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/4183a08ce37112b04bf73ee9373555d0.jpeg)
- 矩形及其对应的距离-角度变化图
![](https://i-blog.csdnimg.cn/blog_migrate/e9921f5ba986fbbc82baa06f82d8a413.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/43cdc4c5b0c63ea87772a262671143e4.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/f14f3a52ea11b31610612a6e1236b3b7.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/d3921c31a0f2cc34d10538c07e6fc7e1.jpeg)
- 正方形及其对应的距离-角度变化图
![](https://i-blog.csdnimg.cn/blog_migrate/408ff15f2ef6a9b5a4d3e8fc8b2fa652.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/5fa49cf36f2a61fae20798c692105b18.jpeg)
- 平行四边形及其对应的距离-角度变化图
![](https://i-blog.csdnimg.cn/blog_migrate/5cc258487adf3300f1db85e4de0eacb1.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/9e4c7a520315fca0c811893885dd5e6b.jpeg)
- 三角形及其对应的距离-角度变化图
![](https://i-blog.csdnimg.cn/blog_migrate/af332a14f41f318f60199876887803b1.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/cc76ce688b6888d206150d02fc1bc227.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/9c03b7a3a79320ad2a72108480f6f357.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/35c1baabad53e95919c8fda989c9baf9.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/91ce5c708694d9dd8374a3857042162f.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/4fc2097a8894a8d30779ad2ac3df6103.jpeg)
通过上面这些图可以得到图下结论:
- 多边形(不包括圆和椭圆)的距离-角度图像存在大量导数不存在的点,且不可导点的数量等于顶点的个数。
- 多边形的顶点对应的角度越大,其不可导点的尖锐程度越小,即左导数和右导数的差距越小。
- 对称图形的距离-角度图像呈现高度的对称性甚至周期性。
附代码
程序是早些时候写得,没有封装成独立的函数,还有些啰嗦。代码是进行批处理的,读者可以稍微改一下程序中使用的路径。
大体的思路是先找到轮廓,然后根据轮廓计算重心位置和轮廓各个点到重心的距离。但是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()