首先说一下我的整体思路:
① 首先定义了一个识别器类型,封装了计算边长,识别形状和展示结果三个函数。
② 主函数先读入图片,然后将图片转化为灰度图片,然后高斯滤波平滑处理,然后将灰度图片转化为黑白两色图片。
③ 调用函数识别图片中所有的轮廓,然后列表形式返回图片,轮廓等信息,然后只取轮廓的所有点信息(每个点的信息为平面坐标)作为一个列表程序(第91,92,93行代码)
④ 用之前创建的识别器实例对每个轮廓中的点进行多边形拟合,得到顶点的坐标的列表中去(class中28~32行代码)
⑤ 输出识别结果
以下是代码部分:
import cv2
import math
THRESHOLE_VALUE=60
COEFFICIENT=0.02
class ShapeDetector:
#初始化类
def __init__(self):
#字典类型对应每一种图形的计数器
self.counter = {
"unrecognized image": 0, "triangle": 0, "rhombus": 0, "rectangle": 0, "pentagon": 0,
"hexagon": 0, "circle": 0}
#初始化图形类型为不可识别
self.shape = "unrecognized image"
#图形顶点集置空
self.approx = []
#初始化该图形的周长为0
self.peri = 0
# 计算欧式距离(主要作用通过计算边长区分菱形和长方形)
def distance(self, x1, y1, x2, y2):
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
def detect(self, c):
#cv2.arcLength函数返回周长
self.peri = cv2.arcLength(c, True)
#cv2.approxPolyDP用多边形取拟合,返回的是顶点的列表
self.approx = cv2.approxPolyDP(c, COEFFICIENT * self.peri, True)
#3个顶点,三角形
if len(self.approx) == 3:
self.shape = "triangle"
#同理,四个顶点,四边形
elif len(self.approx) == 4:
#计算相邻两边的长度,做差判在误差范围内是否相等
dist1 = self.distance(self.approx[0][0][0], self.approx[0][0][1], self.approx[1][0][0],
self.approx[1][0][1])
dist2 = self.distance(self.approx[0][0][0], self.approx[0][0][1], self.approx[3][0][0],
self.approx[3][0][1])
result = math.fabs(dist1 - dist2)
# print(result)
#误差小于10,可近似认为相等,为菱形
if result <= 10:
self.shape = "rhombus"
else:
self.shape = "rectangle"
#五边形
elif len(self.approx) == 5:
self.shape = "pentagon"
#六边形
elif len(self.approx) == 6:
self.shape = "hexagon"
#圆
else:
self.shape = "circle"
#相应形状计数器加一
self.counter[self.shape] += 1
#返回形状
return self.shape
def Display(self):
#展现结果
for kind in self.counter.keys():
print("The number of {} is {}".format(kind, self.counter[kind]))
def main():
#读入图片
testID = "test.png"
image = cv2.imread(testID)
#将图片转换为灰度图片
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 高斯滤波,图像平滑处理
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
#根据阈值,将灰度图片转化为黑白两色图片
thresh = cv2.threshold(blurred, THRESHOLE_VALUE, 255, cv2.THRESH_BINARY_INV)[1]
#返回图片和图中轮廓信息,列表形式返回到cnts中
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#只需要取轮廓上点的信息
cnts = cnts[1]
#创建一个识别器实例
sd = ShapeDetector()
#分别对每个轮廓进行处理
for c in cnts:
#得到形状
shape = sd.detect(c)
#print(shape)
#输出结果
sd.Display()
if __name__=="__main__":
main()
以下为本程序用到的函数表:
(第9行)init(self) (类初始化函数)
(第23行)distance(self, x1, y1, x2, y2): (计算(x1,y1),(x2,y2)两点之间的距离,点为像素坐标)
(第24行)math.sqrt() (数学开平方运算)
(第26行)detect(self, c): (判断轮廓的形状)
(第28行)cv2.arcLength()(计算周长函数。一参数c是轮廓的点集;二参数true代表闭合,false代表开放。)
链接:https://blog.csdn.net/u011854789/article/details/79836242
(第31行) cv2.approxPolyDP()(多边形拟合函数。
一参数c是轮廓的点集;
二参数代表图形边长允许的偏差范围,因为图片中的图形边长上是凹凸不平的,但是电脑是根据标准的直线来进行边的拟合,所以难免每个点处都有和标准直线偏差的距离,此参数便是设置最大偏差距离不能超过多少,不然就会用一个新的边拟合,此处设置的是边长的百分之二;
三参数为true指示拟合的多边闭合,false为开放。 返回值为拟合出来的所有图形顶点坐标,用列表表示)
链接:https://blog.csdn.net/brooknew/article/details/103512228
(有好几行) len()(计算可迭代对象的长度)
(第71行)Display(self)(输出结果)
(第74行) print()(通过标准输出流将缓冲区信息输出到控制台(str.format的用法自己查))
(第79行) cv2.imread()(读入图片,
一参数图片路径;
二参数加载形式,可缺省)
链接:https://blog.csdn.net/lccrun/article/details/95594268
(第83行) cv2.cvtColor(转换颜色空间函数,
一参数为原图片;
二参数为转换的方法,常见的转化方法见链接)
链接:https://blog.csdn.net/m0_37192554/article/details/81946430
(第85行) cv2.GaussianBlur()(高斯滤波函数,图像平滑处理。
一参数是源图片;
二参数是高斯矩阵的大小;
三参数表示标准差。此函数用用就行不用搞懂具体干啥,如果感兴趣可以看下面的链接)
链接:https://blog.csdn.net/weixin_44657197/article/details/102679434
(第87行) cv2.threshold()(图像阈值处理函数,
一参数为源图片;
二参数为阈值;
三参数为设置颜色的最大RGB值;
四参数为划分的方法。
此函数的目的是为了将图片转化为黑白二色图,根据常识,颜色深的是黑色,颜色浅的是白色对吧,那么如何定义颜色的深浅呢?先补充一下:
***RGB:***计算机中常用的表示颜色的方法。计算机上每一个像素点都是三个颜色不同比例得到的,由一个元组来表示(R,G,B),分别代表红色,绿色和蓝色。
主流的划分是每个位置划分为256个段位,用0~255的整数表示,比如(255,255,255)就是最白的白色,(0,0,0)就是最黑的黑色,(255,0,0)就是最亮的红色。
***灰度:***前面的cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)就是将RGB彩色图片转换为灰度图片。具体的转换公式不用管,但是灰度为50的图片RGB值为(50,50,50)这个知道就行了。
第二参数阈值:对图片的每个像素点,根据RGB值计算其灰度,如果灰度大于这个阈值,那么就变成黑色(或者白色),小于等于就变成白色(或者黑色)。
第三参数最大RGB值:就是把灰度变成255,说白了就是白色 第四参数划分方法:见下图
dst(x,y)是(x,y)处像素之后的灰度,src(x,y)是源图片在(x,y)坐标像素的灰度)
链接:https://blog.csdn.net/a19990412/article/details/81172426
(第90行) cv2.findContours()(寻找图片中的所有轮廓,
一参数为带轮廓的图片;
二参数为找到的轮廓的输出形式;
三参数为指定轮廓的近似方法,返回值的话只需要取列表中的第二个,即可获得所有轮廓的所有点坐标。详细见链接)
链接:https://blog.csdn.net/u014120499/article/details/99675967
备注:
1、在使用cv2.threshold()函数时,第四个参数划分方法,当背景为白色时要用cv2.THRESH_BINARY_INV但是背景为黑色是要去掉_INV,用cv2.THRESH_BINARY。其他背景颜色自己想一想吧。。。。。。
2、要是统计的个数出现了问题的话,这是精度的问题,比如我当前设置下,圆的边数是8,改变精度只需要调整全局变量COEFFICIENT的大小就行
祝学习愉快~
评价(涂红):* * * * *