1.傅里叶变换
为什么要傅里叶变换?
傅里叶变换可以将人眼看到的不同频率的波形分离出来,可以区分出哪些区域的信号(图像像素)变化特别强,哪些区域的信号变化不那么强,从而可以任意的标记噪声区域,感兴趣区域,前景和背景等。
傅里叶变换是很多常见的图形处理操作的基础,比如边缘检测,形状线段的检测。
高通滤波器:
通俗来说,就是取小块图片,计算中央像素与周围邻近像素的亮度差值之后,如果亮度变化很大,那么中央像素的亮度就会增加,反之不会,变化很大的地方,就让他亮度更大。
低通滤波器:与高通滤波器相反,如果在某处像素与周围邻近像素的亮度差值很小,小于某一特定值,就平滑该像素的亮度,使其更不突出。主要用于去噪和模糊化。(高斯模糊是最常见的模糊滤波器,削弱高信号强度的低通滤波器)
###FFT变换
import cv2
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
kernel_3_3 = np.array([[-1,-1,-1],
[-1,8,-1],
[-1,-1,-1]])
kernel_5_5 = np.array([[-1,-1,-1,-1,-1],
[-1,1,2,1,-1],
[-1,2,4,2,-1],
[-1,1,2,1,-1],
[-1,-1,-1,-1,-1]])
#这些滤波器中各数值之和是0
下面用这两个高通滤波器做实验
随便选一副图像进行测试:
img = cv2.imread('E:\\wms1.jpg',0)
#cv2.imshow('result',img)
k3 = ndimage.convolve(img,kernel_3_3)
k5 = ndimage.convolve(img,kernel_5_5)
blurred = cv2.GaussianBlur(img,(11,11),0)
g_hpf = img - blurred
cv2.imshow('3*3',k3)
cv2.imshow('5*5',k5)
cv2.imshow('g_hpf',g_hpf)
cv2.waitKey()
cv2.destroyAllWindows()
分别用kernel_3_3和kernel_5_5和原图减去高斯低通得到的高通来处理原图。
cv2.imshow('original',img)
cv2.imshow('3*3',k3)
cv2.imshow('5*5',k5)
cv2.imshow('ditonglvbo',blurred)
cv2.imshow('g_hpf',g_hpf)
将五张图片对比一下:
原图是:
低通处理之后:
变模糊了,所以也叫高斯模糊
用原图减去低通,得到的是高通的图:
用上面特定的55高通滤波器得到的:
和33滤波器得到的:
注意:这里的滤波处理只能对灰度图像,所以对彩色图片的读取和后面的高斯滤波都有个参数0:
img = cv2.imread(‘E:\wms1.jpg’,0)
blurred = cv2.GaussianBlur(img,(11,11),0)
2.Canny边缘检测
import cv2
import numpy as np
img = cv2.imread('E:\\wms1.jpg',0)
cv2.imwrite('canny.jpg',cv2.Canny(img,200,300))
img_canny = cv2.imread('canny.jpg')
cv2.imshow('Canny',img_canny)
cv2.waitKey()
cv2.destroyAllWindows()
3.轮廓检测
import cv2
import numpy as np
img = np.zeros((200,200),dtype = np.uint8)
img[50:150,50:150] = 255 #这个时候img是一个(200,200)的矩阵
#构造一个明显的有轮廓的图出来
ret,thresh = cv2.threshold(img,127,255,0)
#cv2.threshold()阈值函数,若像素高于127,就赋值为255,否则赋值为0
#返回的ret是阈值127,thresh是结果图,重新赋值之后的矩阵
image,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#cv2.findContours()函数来查找检测物体的轮廓
#image此时是(200,200)修改后的图片
#contours是轮廓图索引,是一个列表
#hierarchy是一个四通道的值,每个通道都只有一个数-1
color = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
#将img转成彩色图,由三通道(200,200,3)
img_ = cv2.drawContours(color,contours,-1,(0,255,0),2)
#img表示输入的需要画的图片, contours表示轮廓值,-1表示轮廓的索引,(0, 0, 255)表示颜色, 2表示线条粗细
#cv采用BGR,所以(0,255,0)就是绿色的边框
cv2.imshow('contours',img_)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.threshhold()函数:
当像素值高于阈值时,我们给这个像素赋予一个新值,否则给他赋予另一个值。
cv2.findContours()函数:
cv2.findContours()函数来查找检测物体的轮廓。
该函数有三个参数:
第一个参数是寻找轮廓的图像;
第二个参数表示轮廓的检索模式,有四种(本文介绍的都是新的cv2接口):
cv2.RETR_EXTERNAL表示只检测外轮廓
cv2.RETR_LIST检测的轮廓不建立等级关系
cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
cv2.RETR_TREE建立一个等级树结构的轮廓。
第三个参数method为轮廓的近似办法
cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
该函数返回三个值:修改后的图片,图片的轮廓,层次
hierarchy层次:
第一个数:表示同一级轮廓的下个轮廓的编号,如果这一级轮廓没有下一个轮廓,一般是这一级轮廓的最后一个的时候,则为-1
第二个数:表示同一级轮廓的上个轮廓的编号,如果这一级轮廓没有上一个轮廓,一般是这一级轮廓的第一个的时候,则为-1
第三个数:表示该轮廓包含的下一级轮廓的第一个的编号,假如没有,则为-1
第四个数: 表示该轮廓的上一级轮廓的编号,假如没有上一级,则为-1
这里是[-1,-1,-1,-1]
contours:
轮廓索引
for c in contours:
print©
得到轮廓的四个点:
[[[ 50 50]]
[[ 50 149]]
[[149 149]]
[[149 50]]]
4.边界框,最小矩形区域,最小闭圆轮廓
原图:
import cv2
import numpy as np
#所有的轮廓都是画在这个img上面的,所以最后imshow()img可以看到所有画的轮廓
img = cv2.pyrDown(cv2.imread('hammer.jpg',cv2.IMREAD_UNCHANGED))
#这个时候img是一个三通道图(634,618,3)
gray_img = cv2.cvtColor(img.copy(),cv2.COLOR_BGR2GRAY)
#将彩色图片转为灰度图片,这个时候只有一个通道(634,618)
ret,thresh = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY)
#图像二值化处理,只有255和0两个值了
image,contours,hier = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#找轮廓,返回的image是修改之后的图片,contours轮廓索引,heir是层次
#cv2.RETR_EXTERNAL表示只检测外轮廓
#cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标
#例如一个矩形轮廓只需4个点来保存轮廓信息
for c in contours:
###计算一个简单的边界框,绿色
#c是轮廓的每个点的坐标
# print(c) #c有812个坐标
x,y,w,h = cv2.boundingRect(c)#通过这些点画一个矩形,计算w,h
# print(cv2.boundingRect(c))#(54, 115, 415, 437)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)#画轮廓
#轮廓是长方形的由(x,y)(x+w,y+h)确定
#颜色是绿色
#线宽是2
###计算包围目标的最小矩形区域,红色
rect = cv2.minAreaRect(c)
# print(rect)
#((256.23016357421875, 309.781494140625), (435.0880432128906, 389.2210388183594), -81.33019256591797)
box = cv2.boxPoints(rect)
#opencv没有直接从轮廓信息中计算最小矩形轮廓顶点坐标的函数,所以这里单独计算一下点坐标
# print(box)
#变成矩形的四个点坐标了
box = np.int0(box)
# print(box)#坐标取整
cv2.drawContours(img,[box],0,(0,0,255),3)
#这个drawContours函数第二个参数只接受轮廓的数组形式,所以将box变成数组
#检测边界轮廓最小的闭圆,蓝色
(x,y),radius = cv2.minEnclosingCircle(c)
#返回一个二元数组,第一个是圆心坐标,第二个是半径值
# print(cv2.minEnclosingCircle(c))
#((299.5028381347656, 325.4397888183594), 246.93551635742188)
center = (int(x),int(y))
radius = int(radius)
cv2.circle(img,center,radius,(255,0,0),4)
img_ = cv2.drawContours(img,contours,-1,(0,255,255),3) #黄色
cv2.imshow('contours',img)
cv2.waitKey()
cv2.destroyAllWindows()
4.凸轮廓与Douglas-Peucker算法
import cv2
import numpy as np
#所有的轮廓都是画在这个img上面的,所以最后imshow()img可以看到所有画的轮廓
img = cv2.pyrDown(cv2.imread('hammer.jpg',cv2.IMREAD_UNCHANGED))
#pyrDown这个函数可以将图像降采样和平滑滤波
#缩小了图像的尺寸,将图像尺寸行和列方向缩减一半,不然图片太大了,显示不全
gray_img = cv2.cvtColor(img.copy(),cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY)
image,contours,hier = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#找轮廓,返回的image是修改之后的图片,contours轮廓索引,heir是层次
#cv2.RETR_EXTERNAL表示只检测外轮廓
#cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标
#例如一个矩形轮廓只需4个点来保存轮廓信息
###画轮廓,黄色
contours_img = cv2.drawContours(img,contours,-1,(0,255,255),2) #黄色 画轮廓
###画轮廓多变形框,青色
cnt = contours[0]
#np.shape(contours)是(1, 812, 1, 2)
epsilon = 0.01*cv2.arcLength(cnt,True)#参数越小,多边形越精确
approx = cv2.approxPolyDP(cnt,epsilon,True)#approx是很多点坐标组成的列表
#计算近似的多边形框,有三个参数
#cnt是轮廓
#epsilon是源轮廓与近似多边形的最大差值
#第三个参数表示该多边形是否闭合
cv2.drawContours(img, approx, -1, (255, 255, 0), 5)#把这个多边形框的每个点画出来
cv2.polylines(img, [approx], True, (255, 255, 0), 2)#用直线把这些点连起来
###标注多边形框的面积和周长
area = cv2.contourArea(approx)
length = cv2.arcLength(approx, True)
font = cv2.FONT_HERSHEY_SIMPLEX # 设置字体样式
text = 'Area: '+str(int(area))+' Length: '+str(int(length))
cv2.putText(img, text, (10, 30), font, 0.5, (0, 255, 0), 1, cv2.LINE_AA, 0)
###画凸包,红色
hull = cv2.convexHull(cnt)#hull同approx一样是点坐标组成的列表
cv2.drawContours(img, hull, -1, (0,0,255), 5)#把这个凸包框的每个点画出来
cv2.polylines(img, [hull], True, (0,0,255), 2)#用直线把这些点连起来
如果要单独把这些轮廓,近似多边形,凸包单独显示:
###轮廓画到另一张图上
img_ = np.zeros((700,700),dtype = np.uint8)
img_ = cv2.cvtColor(img_,cv2.COLOR_GRAY2BGR)
#画轮廓,黄色
contours_img = cv2.drawContours(img_,contours,-1,(0,255,255),2) #黄色 画轮廓
#青色,多边形
cv2.drawContours(img_, approx, -1, (255, 255, 0), 5)#把这个多边形框的每个点画出来
cv2.polylines(img_, [approx], True, (255, 255, 0), 3)#用直线把这些点连起来
#红色,凸包
cv2.drawContours(img_, hull, -1, (0,0,255), 5)#把这个凸包框的每个点画出来
cv2.polylines(img_, [hull], True, (0,0,255), 2)#用直线把这些点连起来
cv2.imshow('contours',img_)
cv2.waitKey()
cv2.destroyAllWindows()
5.直线检测
import cv2
import numpy as np
img = cv2.imread('line_det.jpg',cv2.IMREAD_UNCHANGED)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,120)
##第二,三个参数是指低阈值low和高阈值high
minLineLength = 20
maxLineGap = 5
lines = cv2.HoughLinesP(edges,1.0,np.pi/180,20,minLineLength,maxLineGap)
for line in lines:
for x1,y1,x2,y2 in line:
#print(x1,y1,x2,y2)
#lines里面储存着检测到的直线段的首末坐标点
#一个列表里的四个值代表着一条线段
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)
cv2.imshow('edges',edges)
cv2.imshow('lines',img)
cv2.waitKey()
cv2.destroyAllWindows()
canny边缘检测函数为什么要有两个阈值呢,低阈值和高阈值
高的那个阈值是将要提取轮廓的物体与背景区分开来,就像阈值分割的参数一样,是决定目标与背景对比度的。
低的阈值是用来平滑边缘的轮廓,有时高的阈值设置太大了,可能边缘轮廓不连续或者不够平滑,通过低阈值来平滑轮廓线,或者使不连续的部分连接起来。
T1,T2。大于T1的称为强边界。T1和T2之间的为弱边界。
如果只有强边界,那么边界可能断断续续。而且会少分割。所以弱边界的作用就是解决上面这个问题。如果强边界点的8连通区域内有弱边界点,那么认为该弱边界点为强边界。
HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None)
参数:
image:必须是二值图像,推荐使用canny边缘检测的结果图像;
rho:线段以像素为单位的距离精度,double类型的,推荐用1.0;
theta:线段以弧度为单位的角度精度,推荐用numpy.pi/180;
threshold:累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试;
lines:这个参数的意义未知,发现不同的lines对结果没影响,但是不要忽略了它的存在;
minLineLength:线段以像素为单位的最小长度,根据应用场景设置;
maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段。
6.圆检测
import cv2
import numpy as np
planets = cv2.imread('circle_det_.png')
gray_img = cv2.cvtColor(planets,cv2.COLOR_BGR2GRAY)
img = cv2.medianBlur(gray_img,5)#降噪处理
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,120,param1 = 100,param2 = 30,minRadius = 0,maxRadius = 0)
#参数1:传递的图片
#参数2:method使用什么方法检测
#参数3:dp,默认为1
#参数4:minDist,不同圆心的最小距离,120,单位为像素
#参数5:param1,设计canny算法,80为canny算法的上限,这里的canny算法下限自动设置为为上限一半
#参数6:param2,
#参数7,8:最小半径和最大半径,避免识别物体之外的圈
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
print(i)
#画圆圈
cv2.circle(planets,(i[0],i[1]),i[2],(0,255,0),2)
#画圆心
cv2.circle(planets,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('HoughCircles',planets)
cv2.waitKey()
cv2.destroyAllWindows()
我们刚开始没有限制检测出来的圆形大小,然后误检了很多圆:
然后我们将检测的圆限制一下范围半径在20-90之间:
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,120,param1 = 100,param2 = 30,minRadius = 20,maxRadius = 90)
#参数1:传递的图片
#参数2:method使用什么方法检测
#参数3:dp,默认为1
#参数4:minDist,不同圆心的最小距离,120,单位为像素
#参数5:param1,设计canny算法,80为canny算法的上限,这里的canny算法下限自动设置为为上限一半
#参数6:param2,
#参数7,8:最小半径和最大半径,避免识别物体之外的圈
检测出的圆:
[634 630 69]
[410 630 69]
[176 394 70]
[860 394 69]
[276 154 69]
[634 394 70]
[760 160 70]
[178 630 69]
[400 392 69]
[860 628 69]
[524 158 89]