pip install OpenCV-python
测试能否正常使用:
import cv2
print(cv2.__version__)
cv.imread(文件地址)
示例代码:
import cv2 as cv
import numpy as np
path = "./imgs/cat1.png"
img=cv.imread(path,cv.IMREAD_COLOR)
cv.imshow("cat_pic",img)
h,w,c=img.shape
cv.waitKey(0)
cv.destroyAllWindows()
创建一个三维的数组
大小为:height*weight*3(颜色通道),数据类型转化为uint8
img=np.zeros((height,width,3),np.uint8)#参数:宽,高,通道
import cv2
import numpy as np
height,width = 480,640
img=np.zeros((height,width,3),np.uint8)#参数:宽,高,通道
print(img.shape)
img[:,:,:]=np.random.randint(125,255,img.shape)#random.randint参数:随机数范围,图片尺寸
print(img.shape)#打印图片的尺寸
#打印图片维度
print(img.ndim)
cv2.imshow("img",img)#显示图片
cv2.waitKey(0)#等待键盘输入
cv2.destroyAllWindows()#销毁所有窗口
cv.line(参数:图片,起始点,终止点,颜色,线宽)
import cv2 as cv
cat=cv.imread('./imgs/cat1.png')
cv.line(cat,(0,0),(cat.shape[1],cat.shape[0]),(0,255,0),2)#参数:图片,起始点,终止点,颜色,线宽
cv.cirle(参数:图片,圆心,半径,颜色,线宽 线宽为-1则填充为实心圆)
cv.circle(cat,(cat.shape[1]//2,cat.shape[0]//2),100,(0,255,0),-1)#参数:图片,圆心,半径,颜色,线宽 线宽为-1则填充为实心圆
cv.rectangle(参数:图片,左上角点,右下角点,颜色,线宽,-1,填充为实心)
cv.rectangle(cat,(cat.shape[1],cat.shape[0]),(cat.shape[1]//4*3,cat.shape[0]//4*3),(0,255,0),-1)
cv.putText(参数:图片,字,坐标,字体,字体大小,颜色,线宽)
cv.putText(cat,'Hello',(cat.shape[1]//2,cat.shape[0]//2),cv.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)
线的类型:#cv.LINE_AA为抗锯齿: cv.LINE_AA, cv.LINE_4, cv.LINE_8, cv.LINE_AA
cv.imwrite("存储位置",需要存储的图像)
import cv2 as cv
cap=cv.VideoCapture('./img/7233x3100 (4).mp4')
while True:
ret,frame=cap.read()#参数为True时,读取下一帧,为False时,读取最后一帧
if ret==False:
print('读取失败')
break
cv.imshow('frame',frame)
if cv.waitKey(40)&0xFF==ord('q'):#意思:回第八位的值,0xFF为十六进制的FF,即二进制的11111111
break
cap.release()
cv.destroyAllWindows()
读取图片
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img=cv.imread('./imgs/cat1.png')
cv.resize(img,(480,480))
cv.imshow('cat',img)
cv.waitKey(0)
cv.destroyAllWindows()
cv2.flip(img,flipcode)
参数:
示例代码:
cat_fx=cv.flip(img,0)
cat_fy=cv.flip(img,1)
cat_fxy=cv.flip(img,-1)
cv.imshow('cat',img)
cv.imshow('cat_fx',cat_fx)#沿x轴翻转上下
cv.imshow('cat_fy',cat_fy)#沿y轴翻转左右
cv.imshow('cat_fxy',cat_fxy)#沿着原点中心翻转
cv.waitKey(0)
cv.destroyAllWindows()
使用关键仿射变换函数
cv.warpAffline(参数:图片名,仿射变换矩阵,图片大小,插值方法)
得到仿射变换矩阵M --> 带入仿射变换函数 --> 输出
设置参数旋转中心,旋转角度,缩放比例
使用cv.getRotationMatrix2D(设置旋转中心,旋转角度,缩放比例)
示例代码:
h,w,_=img.shape
#设置旋转中心
center=(w//2,h//2)
angle=0
scale=0.01#缩放比例
M=cv.getRotationMatrix2D(center,angle,scale)#仿射变换矩阵
#使用仿射变换函数进行变换旋转
img_rotate=cv.warpAffine(img,M,(w,h),flags=cv.INTER_LINEAR)#参数:图片名,仿射变换矩阵,输出图片大小,插值方法
cv.imshow('img_rotate',img_rotate)
cv.imshow('cat',img)
cv.waitKey(0)
cv.destroyAllWindows()
平移:读取图像-》设置参数(平移量tx,ty)-》获取平移矩阵np.float32-》仿射变换函数进行平移-》显示图像
tx = 200
ty = 100
M = np.float32([[1,0,tx],[0,1,ty]])
print(M)
img_remove = cv.warpAffine(img,M,(w,h))#挪移后多余的像素点舍弃
cv.imshow('img_remove',img_remove)
cv.waitKey(0)
cv.destroyAllWindows()
M仿射矩阵:设置缩放比例
M = np.float32([[缩放比例, 0, 0], [0, 缩放比例, 0]])
#读取图像
M = np.float32([[0.1, 0, 0], [0, 0.1, 0]])
img_little = cv.warpAffine(img, M, (img.shape[1], img.shape[0]))
cv.imshow('img',img_little)
cv.waitKey(0)
cv.destroyAllWindows()
4.3.4图像剪切
示例代码
import numpy as np
import cv2 as cv
#读取图像
shx=0.2
shy=0.2
img=cv.imread('./imgs/cat1.png')
img=cv.resize(img,(300,300))
M_y = np.float32([[1,shy, 0], [0, 1, 0]])
M_x = np.float32([[1, 0, 0], [shx, 1, 0]])
M_xy= np.float32([[1,shy, 0], [shx, 1, 0]])
M= np.float32([[1,0, 0], [0, 1, 0]])
img_little_y = cv.warpAffine(img, M_y, (360, 360))
img_little_x = cv.warpAffine(img, M_x, (360, 360))
img_little_xy = cv.warpAffine(img, M_xy, (360, 360))
img_li=cv.warpAffine(img, M, (360, 360))
cv.imshow('img_y',img_little_y)
cv.imshow('img_x',img_little_x)
cv.imshow('img_xy',img_little_xy)
cv.imshow('img_li',img_li)
cv.waitKey(0)
cv.destroyAllWindows()
4.4图片压缩
4.4.1最近邻插值法
new_img=cv.warpAffine(img,M,(300,300),flags=cv.INTER_NEAREST)
cv.imshow("new_img",new_img)
cv.waitKey(0)
cv.destroyAllWindows()
4.4.2双线性插值法
new_img=cv.warpAffine(img,M,(300,300),flags=cv.INTER_LINEAR)
cv.imshow("new_img",new_img)
cv.waitKey(0)
cv.destroyAllWindows()
4.4.3像素区域插值法
st=cv.imread('./img/blackstocking.png')
st=cv.resize(st,(160,240))
dst_x=cv.warpAffine(st,M,(160,240),flags=cv.INTER_NEAREST)
dst_l=cv.warpAffine(st,M,(160,240),flags=cv.INTER_LINEAR)
dst_c=cv.warpAffine(st,M,(160,240),flags=cv.INTER_AREA)#像素区域插值
cv.imshow('st',st)
cv.imshow('dst_x',dst_x)
cv.imshow('dst_l',dst_l)
cv.imshow('dst_c',dst_c)
cv.waitKey(0)
cv.destroyAllWindows()
cv.warpAffine(img,M,(cols,rows),flags=cv.INTER_CUBIC)
cv.warpAffine(img,M,(cols,rows),flags=cv.INTER_LANCZOS4)
旋转缩放图像做准备
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
face=cv.imread("./images/face.png")
#获取旋转矩阵
scale=0.1
angle=45
center=(face.shape[1]//2,face.shape[0]//2)
M=cv.getRotationMatrix2D(center,angle,scale)
#使用仿射变换矩阵
img=cv.warpAffine(face,M,(face.shape[1],face.shape[0]))
cv.imshow("img",img)
cv.imshow("face",face)
cv.waitKey(0)
cv.destroyAllWindows()
cv.warpAffine(参数:图像,输出图片大小,插值方式,边界模式
borderMode=cv.BORDER_REPLICATE
img_boradercv=cv.warpAffine(face,M,(face.shape[1],face.shape[0]),cv.INTER_LANCZOS4,borderMode=cv.BORDER_REPLICATE)
cv.imshow("img",img)
cv.imshow("face",face)
cv.imshow("img_boradercv",img_boradercv)
cv.waitKey(0)
cv.destroyAllWindows()
borderMode=cv.BORDER_REFLECT
img_reflect = cv.warpAffine(face,M,(face.shape[1],face.shape[0]), cv.INTER_LANCZOS4,borderMode=cv.BORDER_REFLECT)
cv.imshow('reflect', img_reflect)
cv.imshow("img",img)
cv.imshow("face",face)
cv.imshow("img_boradercv",img_boradercv)
cv.waitKey(0)
cv.destroyAllWindows()
borderMode=cv.BORDER_REFLECT101
img_reflect101=cv.warpAffine(face,M,dsize=(img.shape[1],img.shape[0]),flags=cv.INTER_LINEAR,borderMode=cv.BORDER_REFLECT101)
cv.imshow('reflect', img_reflect)
cv.imshow("img",img)
cv.imshow("face",face)
cv.imshow("img_boradercv",img_boradercv)
cv.imshow("img_reflect101",img_reflect101)
cv.waitKey(0)
cv.destroyAllWindows()
borderMode=cv.BORDER_CONSTANT,borderValue=(0,0,255)
img_ch=cv.warpAffine(face,M,(face.shape[1],face.shape[0]),cv.INTER_LANCZOS4,borderMode=cv.BORDER_CONSTANT,borderValue=(0,0,255))
cv.imshow('img_ch',img_ch)
cv.waitKey(0)
cv.destroyAllWindows()
borderMode=cv.BORDER_WRAP
img_wrap=cv.warpAffine(face,M,(face.shape[1],face.shape[0]),cv.INTER_LANCZOS4,borderMode=cv.BORDER_WRAP)
cv.imshow('wrap',img_wrap)
cv.waitKey(0)
cv.destroyAllWindows()
步骤:对原图需要矫正位置的坐标进行框中选定,输出图像的大小坐标分别创建两个numpy数组
使用cv.getPerspectiveTransform(pic1,pic_2)创建仿射变换矩阵M
使用cv.warpPerspective(img_tou,M,(w,h),flags=cv.INTER_LINEAR,borderMode=cv.BORDER_CONSTANT)
cv.warpPerspective(参数:图像片,变换矩阵,输出尺寸,插值方法,边界填充方式)
import numpy as np
pic1=np.float32([[182,113],[521,141],[115,294],[501,340]])
print(pic1[0].shape)
M = cv.getPerspectiveTransform(pic1,pic_2)
new_img=new_pic = cv.warpPerspective(img_tou,M,(w,h),flags=cv.INTER_LINEAR,borderMode=cv.BORDER_CONSTANT)#参数:图像片,变换矩阵,输出尺寸,插值方法,边界填充方式
cv.imshow('new_img',new_img)
cv.imshow('img_tou',img_tou)
cv.waitKey(0)
cv.destroyAllWindows()
pig=cv.imread('./images/pig.png')
cao=cv.imread('./images/cao.png')
img_numpy=cao+pig
print(img_numpy)
img_add=cv.add(pig,cao)
print(cv.add(pig,cao))#add函数
img_weight=cv.addWeighted(pig,0.5,cao,0.5,0)#加权求和运算·
函数cv.cvtcolor(参数:转换方式)
cat_grey=cv.cvtColor(cat,cv.COLOR_RGB2GRAY)
import cv2 as cv
cat=cv.imread('./images/cat1.png')
cv.imshow('cat',cat)
cat_grey=cv.cvtColor(cat,cv.COLOR_RGB2GRAY)
cv.imshow('cat_grey',cat_grey)
cv.waitKey(0)
cv.destroyAllWindows()
cat_hsv=cv.cvtColor(cat,cv.COLOR_BGR2HSV)
cat_hsv=cv.cvtColor(cat,cv.COLOR_BGR2HSV)
cv.imshow("cat_hsv",cat_hsv)
cv.waitKey(0)
cv.destroyAllWindows()
使用for循环迭代,取颜色通道中的最大值,并且赋值到颜色通道上去
import cv2 as cv
import numpy as np
pig_y=cv.imread('./images/pig.png')
pig=cv.imread('./images/pig.png')
for i in range(pig.shape[0]):
for j in range(pig.shape[1]):
pig[i][j][0]=max(pig[i][j])
pig[i][j][1]=max(pig[i][j])
pig[i][j][2]=max(pig[i][j])
cv.imshow('pig',pig)
cv.imshow('pig_y',pig_y)
cv.waitKey(0)
cv.destroyAllWindows()
import cv2 as cv
import numpy as np
pig = cv.imread("./images/pig.png")
h,w,_ = pig.shape
av_grey=np.zeros((h,w),dtype=np.uint8)#uint8:0~255
for i in range(h):
for j in range(w):
av_grey[i,j] = int(sum((pig[i,j])))//3
cv.imshow("av",av_grey)
cv.imshow("pig",pig)
cv.waitKey(0)
cv.destroyAllWindows()
wr,wg,wb=0.299,0.576,0.114
import cv2 as cv
import numpy as np
pig = cv.imread("./images/pig.png")
h,w,_ = pig.shape
weight_grey=np.zeros((h,w),dtype=np.uint8)#uint8:0~255
for i in range(h):
for j in range(w):
weight_grey[i,j] = np.array([pig[i,j,0]*wr,pig[i,j,1]*wg,pig[i,j,2]*wb]).sum()/3
cv.imshow("av",weight_grey)
cv.imshow("pig",pig)
cv.waitKey(0)
cv.destroyAllWindows()
cv.threshold(图片,阈值,maxval,方法)
小于等于阈值的像素就被设置为0(通常代表背景),大于阈值的像素就被设置为maxval(通常代表前景)
像素值大于阈值时,该像素值将会变成0(黑),当灰度图的像素值小于等于阈值时,该像素值将会变成maxval
像素值大于阈值的部分将会被修改为阈值,小于等于阈值的部分不变。
像素值小于等于阈值的部分被置为0(也就是黑色),大于阈值的部分不变。
像素值大于阈值的部分置为0(也就是黑色),像素值小于等于阈值的部分不变。
采取最大类间距的策略,该方法是使用是寻找最佳阈值
cv.adaptiveThreshold(参数1:灰度图,参数2:最大值,参数3:自适应阈值方法,参数4:二值化方法,参数5:窗口大小,参数6:偏移量)
明暗分布不均匀可以使用自适应二值化
示例代码:
img_mean=cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_MEAN_C,cv.THRESH_BINARY,5,10)
cv.imshow('mean',img_mean)
cv.waitKey(0)
cv.destroyAllWindows()
示例代码:
img_guss=cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,11,2)
cv.imshow('img_guss',img_guss)
cv.waitKey(0)
cv.destroyAllWindows()
图像掩膜是图像处理中的一个重要概念,它用于对图像进行遮罩,从而实现对图像的裁剪、过滤、增强等操作。
设置:color_low=np.array([23,46,46]) color_high=np.array([34,255,255])
根据hsv表创建掩膜,最大值和最小值,掩膜输出的是目标区域显示为白色,其他为黑色
参数:src:源图像,lowerb:颜色下限,upperb:颜色上限,dst:输出图像
做与运算输出,输出结果将目标白色替换为原色,其他不变
颜色bgr转hsv
import cv2 as cv
import numpy as np
demo=cv.imread("./images/demo.png")
demo=cv.resize(demo,(480,480))
#颜色bgr转hsv
demo_hsv=cv.cvtColor(demo,cv.COLOR_BGR2HSV)
创建掩膜:cv.inRange(src,lowerb,upperb,dst)
#创建掩膜:cv.inRange(src,lowerb,upperb),参数:src:源图像,lowerb:颜色下限,upperb:颜色上限
color_low=np.array([23,46,46])
color_high=np.array([34,255,255])
mask=cv.inRange(demo_hsv,color_low,color_high)#只显示黑白色
做与运算输出掩膜cv.bitwise_and(demo,demo,mask=mask)
#创建掩膜与原图大小一样,是一个二值化图像
mask_and=cv.bitwise_and(demo,demo,mask=mask)#做两次运算前两个颜色,再与掩膜运算
将原图目标色进行替换
#修改颜色
demo[mask==255]=[0,255,0]
cv.imshow('demo',demo)
cv.waitKey(0)
cv.destroyAllWindows()
也就是使用numpy数组进行切割,不作赘述
步骤:
从原图切下需要添加水印的区域,并进行灰度处理
import cv2 as cv
import numpy as np
logo=cv.imread('images/logohq.png')
bg=cv.imread('images/bg.png')
h,w=logo.shape[:2]
logo_grey=cv.cvtColor(logo,cv.COLOR_BGR2GRAY)
bg_zi=bg[:h,:w]
分别将logo图进行二值化,分别使用阈值法和反阈值法进行变换
阈值法白底黑字
_,logo_grey_black=cv.threshold(logo_grey,170,255,cv.THRESH_BINARY)
反阈值法白字黑底
_,logo_grey_binary=cv.threshold(logo_grey,170,255,cv.THRESH_BINARY_INV)
分别将两个所得图原logo图和截取区域进行与运算
将白字替换为原来的颜色,制作掩膜
logo_and=cv.bitwise_and(logo,logo,mask=logo_grey_binary)#掩膜
将另一张图的黑字与截取区域作与运算,得到一个黑字logo和原背景图结合的图片
logo_and_black=cv.bitwise_and(bg_zi,bg_zi,mask=logo_grey_black)
对所得掩膜进行add运算
bg_zi[:]=cv.add(logo_and,logo_and_black)
cv.blur(img,(3,3))
import cv2 as cv
#均值滤波cv.blur
img=cv.imread("./images/lvbo2.png")
img_blur=cv.blur(img,(3,3))#均值滤波
cv.imshow("img",img)
cv.imshow("img_blur",img_blur)
cv.waitKey(0)
cv.destroyAllWindows()
cv.boxFilter(img, -1, (3,3),normalize=True、False)
#方框滤波cv.boxFilter
img_box=cv.boxFilter(img, -1, (3,3),normalize=True)#参数:输入图像,输出图像的深度,核大小,是否归一化
img_box_F=cv.boxFilter(img, -1, (3,3),normalize=False)
cv.imshow('img_box',img_box)
cv.imshow('img_box_F',img_box_F)
cv.waitKey(0)
cv.destroyAllWindows()
cv.GaussianBlur(img,(3,3),1)
#高斯滤波:好用cv.GaussianBlur
img_gauss=cv.GaussianBlur(img,(3,3),1)#参数:1、原图;2、核大小;3、标准差
cv.imshow('img_gauss',img_gauss)
cv.imshow("img",img)
cv.imshow("img_blur",img_blur)
cv.waitKey(0)
cv.destroyAllWindows()
cv.medianBlur(img_jy, 5)
img_jy=cv.imread("images/lvbo3.png")#更适合椒盐噪声
#中值滤波:cv.medianBlur
img_median = cv.medianBlur(img_jy, 5)#参数1.原图,2.核大小
cv.imshow("median", img_median)
cv.imshow("jy", img_jy)
cv.waitKey(0)
cv.destroyAllWindows()
cv.bilateralFilter(img,9,75,75)
img_doubleb=cv.bilateralFilter(img,9,75,75)#参数:图片,区域大小,高斯核,高斯核
cv.imshow('img_doubleb',img_doubleb)
cv.waitKey(0)
cv.destroyAllWindows()
滤波是应用卷积来实现的,卷积的关键就是卷积核,我们来考察下面这个卷积核:
k1=[−101−202−101] k1=\left[\begin{array}{c c c}{{-1}}&{{0}}&{{1}}\\ {{-2}}&{{0}}&{{2}}\\ {{-1}}&{{0}}&{{1}}\end{array}\right] k1= −1−2−1000121
cv.Sobel(img,-1,1,1,3),会进行边缘反射_101,再进行计算
参数:图像,深度,横向,纵向,核大小
横向可以设置1或者0,为1则绘制横向边缘
纵向同理
import cv2 as cv
img_sobel=cv.Sobel(img,-1,1,1,3)
img_sobel_x=cv.Sobel(img,-1,1,0,3)#求纵向边缘
img_sobel_y=cv.Sobel(img,-1,0,1,3)#求横向边缘
cv.imshow("sobel",img_sobel)
cv.imshow("sobel_x",img_sobel_x)
cv.imshow("sobel_y",img_sobel_y)
cv.waitKey(0)
cv.destroyAllWindows()
cv.Laplacian(img,-1)
参数:输入图像,输出图像的深度,核大小,偏移量,核类型(以上示例只填写了前两个)
import numpy as np
# 模拟一张图像,灰度图
img=np.array([[100,102,109,110,98,20,19,18,21,22],
[109,101,98,108,102,20,21,19,20,21],
[109,102,105,108,98,20,22,19,19,18],
[109,98,102,108,102,20,23,19,20,22],
[109,102,105,108,98,20,22,19,20,18],
[100,102,108,110,98,20,19,18,21,22],
[109,101,98,108,102,20,22,19,20,21],
[109,102,108,108,98,20,22,19,19,18],
],dtype=np.float32)
img_la=cv.Laplacian(img,-1)#参数:输入图像,输出图像的深度,核大小,偏移量,核类型
print(img)
cv.imshow('laplacian',img_la)
print(img_la)
输出:
[[ 22. 3. -28. -17. -58. 77. 4. 6. -4. -4.]
[-25. 7. 31. -14. -84. 83. -4. 2. 0. -4.]
[-14. 5. -10. -13. -60. 80. -5. 3. 1. 9.]
[-22. 23. 8. -12. -84. 85. -9. 5. 0. -12.]
[-23. 6. 0. -11. -64. 80. -7. 3. -2. 12.]
[ 22. 3. -17. -18. -62. 77. 6. 6. -4. -7.]
[-25. 7. 33. -14. -84. 84. -8. 3. 0. -4.]
[-14. 11. -26. -10. -60. 80. -5. 3. 1. 8.]]
不是一个算子,是完整的一整套方案。
就是去除噪点!
边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理。这里使用的是一个5*5的高斯核对图像进行消除噪声。上一个实验中已经介绍了高斯滤波的具体过程,这里就不再过多叙述,只展示一下用到的5*5的高斯核:
这里使用了sobel算子来计算图像的梯度值,在上一章节中,我们了解到sobel算子其实就是一个核值固定的卷积核,如下所示:
sobel(水平方向)=[−101−202−101] sobel(水平方向)=\left[\begin{array}{c c c}{{-1}}&{{0}}&{{1}}\\ {{-2}}&{{0}}&{{2}}\\ {{-1}}&{{0}}&{{1}}\end{array}\right] sobel(水平方向)= −1−2−1000121
sobel(垂直方向)=[−1−2−1000121] sobel(垂直方向)=\left[\begin{array}{c c c}{{-1}}&{{-2}}&{{-1}}\\ {{0}}&{{0}}&{{0}}\\ {{1}}&{{2}}&{{1}}\end{array}\right] sobel(垂直方向)= −101−202−101
首先使用sobel算子计算中心像素点的两个方向上的梯度GxG_{x}Gx和GyG_{y}Gy,然后就能够得到其具体的梯度值:
G=Gx2+Gy2 G={\sqrt{G_{x}{}^{2}+G_{y}{}^{2}}} G=Gx2+Gy2
也可以使用G=∣Gx+Gy∣G=|G_{x}+G_{y}|G=∣Gx+Gy∣来代替。在OpenCV中,默认使用G=∣Gx+Gy∣G=|G_{x}+G_{y}|G=∣Gx+Gy∣来计算梯度值。
然后我们根据如下公式可以得到一个角度值
GyGx=tan (θ){\frac{G_{\mathrm{y}}}{G_{x}}}=\tan\,(\theta)GxGy=tan(θ)
θ=arctan (GyGx) \theta=\arctan\,({\frac{G_{\mathrm{y}}}{G_{x}}}) θ=arctan(GxGy)
这个角度值其实是当前边缘的梯度的方向。通过这个公式我们就可以计算出图片中所有的像素点的梯度值与梯度方向,然后根据梯度方向获取边缘的方向。
a). 并且如果梯度方向不是0°、45°、90°、135°这种特定角度,那么就要用到插值算法来计算当前像素点在其方向上进行插值的结果了,然后进行比较并判断是否保留该像素点。这里使用的是单线性插值,通过A1和A2两个像素点获得dTmp1与dTmp2处的插值,然后与中心点C进行比较(非极大值抑制)。具体的插值算法请参考图像旋转实验。
b). 得到θ\thetaθ的值之后,就可以对边缘方向进行分类,为了简化计算过程,一般将其归为四个方向:水平方向、垂直方向、45°方向、135°方向。并且:
当θ\thetaθ值为-22.5°~22.5°,或-157.5°~157.5°,则认为边缘为水平边缘;
当法线方向为22.5°~67.5°,或-112.5°~-157.5°,则认为边缘为45°边缘;
当法线方向为67.5°~112.5°,或-67.5°~-112.5°,则认为边缘为垂直边缘;
当法线方向为112.5°~157.5°,或-22.5°~-67.5°,则认为边缘为135°边缘;
得到每个边缘的方向之后,其实把它们连起来边缘检测就算完了,但是为什么还有这一步与下一步呢?是因为经过第二步得到的边缘不经过处理是没办法使用的,因为高斯滤波的原因,边缘会变得模糊,导致经过第二步后得到的边缘像素点非常多,因此我们需要对其进行一些过滤操作,而非极大值抑制就是一个很好的方法,它会对得到的边缘像素进行一个排除,使边缘尽可能细一点。
在该步骤中,我们需要检查每个像素点的梯度方向上的相邻像素,并保留梯度值最大的像素,将其他像素抑制为零。假设当前像素点为(x,y),其梯度方向是0°,梯度值为G(x,y),那么我们就需要比较G(x,y)与两个相邻像素的梯度值:G(x-1,y)和G(x+1,y)。如果G(x,y)是三个值里面最大的,就保留该像素值,否则将其抑制为零。
cv2.Canny(image, threshold1, threshold2),
参数:图像,阈值1,阈值2
即使读到的是彩色图也可以进行处理。
#边缘检测
img=cv.imread('images/lvbo2.png')
ret,img_binary=cv.threshold(img,127,255,cv.THRESH_BINARY)
img_ga=cv.GaussianBlur(img,(3,3),1)#作用:高斯模糊,作用:降噪
ret,img_ga_binary=cv.threshold(img_ga,127,255,cv.THRESH_BINARY)
dst_img=cv.Canny(img_binary,30,70)#参数:二值化后的图像,阈值1,阈值2
dst_img_gau=cv.Canny(img_ga_binary,30,70)
cv.imshow('dst_img_gau',dst_img_gau)
cv.imshow('dst_img',dst_img)
cv.waitKey(0)
cv.destroyAllWindows()
- 轮廓是一系列连续的点和组成的曲线,代表了物体的基本外形。是一个闭合和封闭的图形
1、先对图像进行预处理
转灰度,二值化
#绘制轮廓步骤,RETR_EXTERNAL 只绘制最外轮廓 存储中:CHAIN_APPROX_SIMPLE
num_y=cv.imread("../images/num.png")
#读图转灰度
number=cv.imread("../images/num.png",cv.IMREAD_GRAYSCALE)
#二值化
_,num_t=cv.threshold(number,127,255,cv.THRESH_OTSU+cv.THRESH_BINARY_INV)
2、关键操作:调用cv.findContours(目标图,找轮廓点的种类,保存方法)会返回两个值:轮廓坐标集合和层级结构
表示只查找最外层的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
2.3.4.会查找所有轮廓,但会有层级关系。
表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照成对的方式显示。
在 RETR_CCOMP
模式下,轮廓被分为两个层级:
表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照树的方式显示,其中最外层的轮廓作为树根,其子轮廓是一个个的树枝。
#查找轮廓
contours,h=cv.findContours(num_t,mode=cv.RETR_EXTERNAL,method=cv.CHAIN_APPROX_SIMPLE)
绘制轮廓
使用cv.drawContours()将要绘制在什么图上,深度,所得的轮廓坐标列表,颜色,绘制线宽度依次传入
num=cv.drawContours(num_y,contours,-1,(0,0,255),2)
显示图像
cv.imshow("num_t",num_t)
cv.waitKey(0)
cv.destroyAllWindows()
在进行凸包特征检测之前,首先要了解什么是凸包。通俗的讲,凸包其实就是将一张图片中物体的最外层的点连接起来构成的凸多边形,它能包含物体中所有的内容。
一般来说,凸包都是伴随着某类点集存在的,也被称为某个点集的凸包。
对于一个点集来说,如果该点集存在凸包,那么这个点集里面的所有点要么在凸包上,要么在凸包内。
凸包检测常用在物体识别、手势识别、边界检测等领域。穷举法时间复杂度高不特殊情况不采用
预处理灰度和二值化
#读图
img=cv.imread('../images/tu.png')
#转灰度
img_g=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
#二值化
ret,img_b=cv.threshold(img_g,127,255,cv.THRESH_BINARY)
查找轮廓
#查找轮廓
ct,h=cv.findContours(img_b,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
将凸包找出
#寻找凸包
hull=cv.convexHull(ct[0])
print(hull)
绘制凸包
#绘制凸包
cv.polylines(img,[hull],True,(0,255,0),1,cv.LINE_AA)
#显示图像
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
旋转卡壳法
读图、图像预处理、灰度、二值化
白色图像为目标
#绘制矩形
import cv2 as cv
img=cv.imread('../images/num.png')
num=img.copy()
num_gray=cv.cvtColor(num,cv.COLOR_BGR2GRAY)
_,num_binary=cv.threshold(num_gray,150,255,cv.THRESH_BINARY_INV)#反阈值法
找到轮廓,并且绘制
c,h=cv.findContours(num_binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
img_Con=cv.drawContours(img,c,-1,(0,0,255),2)
使用cv.boundingRect(cnt),找出矩形坐标,返回x,y和矩形的长和宽是
利用cv.rectangle绘制出来
for cnt in c:
x,y,w,h=cv.boundingRect(cnt)
print(cnt,end=',')
cv.rectangle(img_Con,(x,y),(x+w,y+h),(0,255,0),1,cv.LINE_AA)
cv.imshow('drawing img_Con',img_Con)
cv.waitKey(0)
cv.destroyAllWindows()
无关方向,最小能包裹目标图像的矩形
读图、图像预处理、灰度、二值化
白色图像为目标
#绘制矩形
import cv2 as cv
img=cv.imread('../images/num.png')
num=img.copy()
num_gray=cv.cvtColor(num,cv.COLOR_BGR2GRAY)
_,num_binary=cv.threshold(num_gray,150,255,cv.THRESH_BINARY_INV)#反阈值法
找到轮廓,并且绘制
c,h=cv.findContours(num_binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
img_Con=cv.drawContours(img,c,-1,(0,0,255),2)
使用cv.minAreaRect的方式已找到,cv.minAreaRect(),返回最小外接矩形的中心点、宽度和角度
利用cv.boxPoints(rect)将中心、尺寸和角度转换为四个角点坐标
并且转化为int类型
使用cv.polylines()绘制参数:目标图,坐标列表,是否闭合,颜色,宽度,线形
for cnt in c:
# 计算最小外接矩形
rect = cv.minAreaRect(cnt)#返回最小外接矩形的中心点、宽高和角度
# 将最小外接矩形的中心、尺寸和角度转换为四个角点坐标
box = cv.boxPoints(rect)
box = box.astype('int') # 转换为整数类型
# 绘制旋转后的最小外接矩形
cv.polylines(img, [box], isClosed=True, color=(0, 0, 255), thickness=1, lineType=cv.LINE_AA)
#绘制矩阵
x,y,w,h=cv.boundingRect(cnt)
cv.rectangle(img_Con,(x,y),(x+w,y+h),(255,0,0),2,cv.LINE_AA)
#绘制凸包
hull = cv.convexHull(cnt)
# cv.drawContours(img_Con, [hull], 0, (0, 255, 0), 2, cv.LINE_AA)
cv.polylines(img_Con, [hull], isClosed=True, color=(0, 255, 0), thickness=2, lineType=cv.LINE_AA)
读图
利用函数cv.calcHist([img],[2],None,[256],[0,256])
返回一个数组列表,代表每一个像素值的像素数量
参数:图片,通道,掩膜,直方图大小,直方图范围
再使用cv.minMaxLoc提取最少数量像素值及其索引像素值和最多数量像素值及其索引像素值
import matplotlib.pyplot as plt
img=cv.imread('../images/lvbo.png')
hist=cv.calcHist([img],[2],None,[256],[0,256])#计算直方图,参数:图片,通道,掩膜,直方图大小,直方图范围
minVal,maxVal,minLoc,maxLoc=cv.minMaxLoc(hist) #返回值最少的像素个数,最多的像素个数,最小值位置索引,最大值位置索引
可以使用matplotlib进行直线绘制
# 绘制直方图
plt.figure(figsize=(6, 4))
plt.plot(hist, color='r')
使用cv.line进行绘制直方图,将数据归一化,先限制直方图高度
arr=np.zeros((256,256,3),np.uint8)
#限制直方图的高
hpt=int(0.9*256)
#使用cv.line 画出直方图
for i in range(256):
cv.line(arr,(i,256),(i,256-int(hist[i].item()*hpt/maxVal)),(0,0,255),2,1)
cv.imshow('hist',arr)
cv.waitKey(0)
cv.destroyAllWindows()
将绘制直方图的代码封装成函数
def draw(img,m,color):
hist=cv.calcHist([img],[m],None,[256],[0,256])
minVal,maxVal,minLoc,maxLoc=cv.minMaxLoc(hist) #返回值最少的像素个数,最多的像素个数,最小值位置索引,最大值位置索引
arr=np.zeros((256,256,3),np.uint8)
#限制直方图的高
hpt=int(0.9*256)
#使用cv.line 画出直方图
for i in range(256):
cv.line(arr,(i,256),(i,256-int(hist[i].item()*hpt/maxVal)),color,2,1)
return arr
img=cv.imread('../images/lvbo.png')
cv.imshow("hist",draw(img,2,(0,0,255)))
cv.waitKey(0)
cv.destroyAllWindows()
同样的进行灰度化
#读图
img=cv.imread('../images/zhifang.png')
#转灰度图
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
直方图均衡化使用cv.equalizeHist(gray)
eqh=cv.equalizeHist(gray)#直方图均衡化
dst_eqh=draw(eqh,0,(0,255,0))
cv.imshow('eqh',eqh)
cv.imshow('dst_eqh',dst_eqh)
对比度受限自适应直方图变化:
clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=None)
(8, 8)
,表示将图像划分成8x8的小块进行独立的直方图均衡化处理。分块大小的选择会影响到CLAHE的效果以及处理速度。创建CLAHE对象后,可以使用 .apply()
方法对图像进行CLAHE处理:
img=clahe.apply(image)
clahe=cv.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))#对比度受限自适应直方图变化
img_clahe=clahe.apply(gray)#意义:对比度受限
cv.imshow('img_clahe',img_clahe)
dst_clahe=draw(img_clahe,0,(0,255,0))
cv.imshow('dst_clahe',dst_clahe)
图像输出:
dst_gray=draw(gray,0,(0,0,255))
cv.imshow('img',img)
cv.imshow('gray',gray)
cv.imshow('dst_gray',dst_gray)
cv.waitKey(0)
cv.destroyAllWindows()
进行匹配,读取原图和目标图并且进行转灰度图
import cv2 as cv
import numpy as np
#读图
img=cv.imread('../images/game.png')#目标图
temp=cv.imread('../images/temp.png')#模板图
#转灰度
img_gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)#目标图转灰度
temp_gray=cv.cvtColor(temp,cv.COLOR_BGR2GRAY)
使用cv.matchTemplate(原图,目标图,匹配的方法)
匹配方法,可以是以下之一:
返回值res
函数在完成图像模板匹配后返回一个结果矩阵,这个矩阵的大小与原图像相同。矩阵的每个元素表示原图像中相应位置与模板图像匹配的相似度。
设置阈值,使用np.where()的函数获取符合条件的坐标
threshold=0.8
loc=np.where(res>=threshold)
h,w=temp.shape[:2]#获取目标图的长和宽
使用zip(loc)返回一个迭代器*(解包操作)**
进行图像绘制
for pt in zip(*loc):#py===>(y,x)
leftupper=pt[::-1]#(x,y)
rightbottom=(pt[1]+w,pt[0]+h)#(x+w,y+h)
cv.rectangle(img,leftupper,rightbottom,(0,0,255),2)
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
霍夫变换是图像处理的一种技术,主要用于检测图像中的直线、圆等几何形状。基本思想就是将图像空间中的点映射到参数空间中,通过在参数空间中寻找累计最大值实现对特定形状的检测。
cv.HoughLines(目标图像,r的精度,角度θ的精度,阈值)主要参数
目标图像:二值化,目标白,背景黑
r的精度:r的精度,以像素为单位,表示霍夫空间中每一步的距离增量, 值越大,考虑越多的线。
角度θ的精度:通常以弧度为单位,表示霍夫空间中每一步的角度增量。值越小,考虑越多的线。
阈值:满足目标斜率的函数图像上的点的最少数量
import cv2 as cv
import numpy as np
img=cv.imread('../images/huofu.png')#读取图片
img_g=cv.cvtColor(img,cv.COLOR_BGR2GRAY)#灰度化
dst=cv.Canny(img_g,30,70)#获得边缘检测后的图像
lines=cv.HoughLines(dst,0.9,np.pi/180,90)#进行霍夫直线变换
#返回ro值和角度theta值
print(lines)
for line in lines:
ro = line[0][0]#提取ro
theta= line[0][1]#提取角度
sint_t=np.sin(theta)#计算cos和sin值
cost_t=np.cos(theta)
x1,x2=0,img.shape[1]#图的边界点
y1,y2=int((ro-x1*cost_t)/sint_t),int((ro-x2*cost_t)/sint_t)#将边界点带入计算
cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)#绘制直线
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows
对于霍夫变换,对图像的预处理也尤其重要,若遇到明暗分布不均匀的图像,需要进行必要的处理
lines=cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=0, maxLineGap=0)**
image:输入图像,通常为二值图像,其中白点表示边缘点,黑点为背景。
rho:极径分辨率,以像素为单位,表示极坐标系中的距离分辨率。
theta:极角分辨率,以弧度为单位,表示极坐标系中角度的分辨率。
threshold:阈值,用于过滤掉弱检测结果,只有累计投票数超过这个阈值的直线才会被返回。
lines(可选):一个可初始化的输出数组,用于存储检测到的直线参数。
minLineLength(可选):最短长度阈值,比这个长度短的线会被排除。
maxLineGap(可选):同一直线两点之间的最大距离。当霍夫变换检测到一系列接近直角的线段时,这些线段可能是同一直线的不同部分。
注意:
import cv2 as cv
import numpy as np
img=cv.imread('../images/daolu.jpg')
#降噪
img=cv.GaussianBlur(img,(3,3),0)
img_g=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
dst=cv.Canny(img_g,50,80)
#统计概率霍夫直线变换
lines=cv.HoughLinesP(dst,1,np.pi/180,100,minLineLength=130,maxLineGap=50)#参数:图片,rho精度,theta精度,阈值,最小线段长度,最大线段间隔
#遍历取得数据
img_c=img.copy()
for line in lines:
x1,y1,x2,y2=line[0]
cv.line(img_c,(x1,y1),(x2,y2),(0,255,0),1,cv.LINE_AA)
cv.imshow('img_c',img_c)
cv.imshow('img',img)
cv.imshow('dst',dst)
cv.waitKey()
cv.destroyAllWindows()
circles=cv2.HoughCircles(image, method, dp, minDist, param1, param2)
image:输入图像,通常是灰度图像。
method:使用的霍夫变换方法:霍夫梯度法,可以是 cv2.HOUGH_GRADIENT,这是唯一在OpenCV中用于圆检测的方法。
dp:累加器分辨率与输入图像分辨率之间的降采样比率,用于加速运算但不影响准确性。设置为1表示霍夫梯度法中累加器图像的分辨率与原图一致
minDist:检测到的圆心之间的最小允许距离,以像素为单位。在霍夫变换检测圆的过程中,可能会检测到许多潜在的圆心。minDist 参数就是为了过滤掉过于接近的圆检测结果,避免检测结果过于密集。当你设置一个较小的 minDist 值时,算法会尝试找出尽可能多的圆,即使是彼此靠得很近的圆也可能都被检测出来。相反,当你设置一个较大的 minDist 值时,算法会倾向于只检测那些彼此间存在一定距离的独立的圆。
param1 和 param2:这两个参数是在使用 cv2.HOUGH_GRADIENT 方法时的特定参数,分别为:
param1(可选):阈值1,决定边缘强度的阈值。
param2:阈值2,控制圆心识别的精确度。较大的该值会使得检测更严格的圆。param2 通常被称为圆心累积概率的阈值。在使用霍夫梯度方法时,param2 设置的是累加器阈值,它决定了哪些候选圆点集合被认为是有效的圆。较高的 param2 值意味着对圆的检测更严格,只有在累加器中积累了足够高的响应值才认为是真实的圆;较低的 param2 值则会降低检测的门槛,可能会检测到更多潜在的圆,但也可能包含更多的误检结果。
返回值:cv2.HoughCircles 返回一个二维numpy数组,包含了所有满足条件的圆的参数。
import cv2 as cv
import numpy as np
img=cv.imread('../images/huofu.png')
imd_g=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
dst=cv.Canny(imd_g,30,70)
circles=cv.HoughCircles(dst,cv.HOUGH_GRADIENT,1,20,param2=30)
circles=np.int_(circles)
for i in circles:
x,y,r=i[0]
cv.circle(img,(x,y),r,(0,255,0),2)
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
读图
#读图
img=cv.imread('../images/cat1.png')
使用cv2.addweighted(sr1,alpha,sr2,beta,gamma)函数(输入图像,权重,输出图像,权重,偏移量(亮度))
将原图的所有像素值带入计算:
"""
np.zeros_like(img):返回一个与img相同形状和类型的零数组。
np.ones_like(img):返回一个与img相同形状和类型的1数组。
np.full_like(img,value):返回一个与img相同形状和类型的value数组。
"""
dst=cv.addWeighted(img,1,np.zeros_like(img),1,200)
cv2.addWeighted(src1, alpha, src2, beta, gamma)**
src1
:第一张输入图像,它将被赋予权重 alpha
。
alpha
:第一个输入图像的权重。
src2
:第二张输入图像,它将被赋予权重 beta
。
beta
:第二个输入图像的权重。
gamma
:一个标量,将被添加到权重求和的结果上,可用于调整总体亮度。
计算公式为: dst = src1 * alpha + src2 * beta + gamma
滑条控制图像
"""
原图:img
输出:dst=img+p
防止溢出:np.clip(dst,0,255)
用一个滑条来控制p的值,p=0时,输出与原图相同,p=255时,输出全黑
"""
cv.createTrackbar(trackbarName,windowName,value,count,onChange)
clip=np.clip(img,50,180)
再利用np.clip修改数值小于所给的阈值,变成所给阈值,大于所给阈值,也修改为对应阈值
给滑条创建窗口
#给滑条创建窗口
win_name='trackbar'
cv.namedWindow(win_name)
def onChange(p):
再将滑条映射到[-255,255]
def onChange(p):
#把滑条值映射到[-255,255]
p=p/255*(255-(-255))-255
#读图
dst=np.uint8(np.clip(img+p,0,255))
cv.imshow('img',img)
cv.imshow('dst',dst)
设置初始参数
max_val=255#最大值
trackbar_name='p_value'#滑条名字
initial_value=150#初始值
初始化滑条
onChange(initial_value)
创建滑条
cv.createTrackbar(trackbar_name,win_name,0,max_val,onChange)
cv.waitKey(0)
cv.destroyAllWindows()
形态学变换是基于图像形状的一系列操作,主要针对二值图像,使用结构元素(核)与图像进行相互作用。
cv2.erode(src, kernel, iterations)
cv2.dilate(src, kernel, iterations)
cv2.morphologyEx(src, cv2.MORPH_OPEN, kernel)
cv2.morphologyEx(src, cv2.MORPH_CLOSE, kernel)
cv2.morphologyEx(src, cv2.MORPH_TOPHAT, kernel)
cv2.morphologyEx(src, cv2.MORPH_BLACKHAT, kernel)
cv2.morphologyEx(src, cv2.MORPH_GRADIENT, kernel)
kernel = cv2.getStructuringElement(shape, size)