OPENCV
工业应用还是Halcon https://www.mvtec.com/cn/products/halcon/documentation/
安装 numpy、opencv-python、matplotlib 并检查
import cv2 as cv
print( cv.__version__ )
静态图[ imread,imshow,imwrite ]
! 注意 :应当直接保存到文件运行,不要在宿主进程(如 cmd.exe)中运行
import numpy as np
import cv2 as cv
img = cv.imread('',0)
cv.imshow('frameTitle',img)
cv.waitKey(0)
cv.destroyAllWindows()
- imread第二个参数是标志位:
- cv.IMREAD_COLOR 1 加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。
- cv.IMREAD_GRAYSCALE 0 以灰度模式加载图像
- cv.IMREAD_UNCHANGED -1 加载图像,包括alpha通道
视频[暂时跳过]
绘制
bg=np.zeros((512,512,8),np.uint8)
cv.line(bg,(0,0),(511,511),(255,0,0),5)
- 线段: cv.line(图,起点,终点,BGR颜色,宽度-1表示填充否则表示宽度)
- 矩形: cv.rectangle(图,左上角,右下角,BGR颜色,宽度)
- 圆形: cv.circle(图,中心点,半径,BGR颜色,宽度-1表示填充否则表示宽度)
- 椭圆: cv.ellipse(图,中心点,(长轴长,短轴长),逆时针转的角度,startAngle,endAngle,BGR颜色,宽度-1表示填充否则表示宽度)
- 多边形: cv.polylines(图,点集,是否自动闭合,BGR颜色)
- 点集是将 顶点坐标集合(如 [[1,2],[3,4],[5,6]])转换为ROWS12 的张量表示,一般使用reshape(-1,1,2)转换,例如
points = np.array([[1,2],[3,4],[5,6],[7,8]],np.int32) points = points.reshape((-1,1,2)) cv.polylines(pic,[points],True,(255,255,0))
- 文字: cv.putText(图片,文字,位置,字体,大小,颜色,厚度,线条类型)
font = cv.FONT_HERSHEY_SIMPLEX cv.putText(img,'OpenCV',(10,500), font, 4,(255,255,255),2,cv.LINE_AA)
练习
创建拖拽出圆或矩形的绘制面板
import numpy as np
import cv2 as cv
import math
drawStatus=False
isRectangle=False
ix,iy=-1,-1
def click_callback(event,x,y,flags,param):
global drawStatus,isRectangle,ix,iy
if event == cv.EVENT_LBUTTONDOWN:
drawStatus = True
ix,iy=x,y
elif event == cv.EVENT_MOUSEMOVE:
if drawStatus==True:
if isRectangle==True:
cv.rectangle(bg,(ix,iy),(x,y),(0,255,0),1)
else:
cv.circle(bg,(ix,iy),math.trunc(math.fabs(x-ix)),(255,0,0),1)
elif event == cv.EVENT_LBUTTONUP:
drawStatus=False
if isRectangle==True:
cv.rectangle(bg,(ix,iy),(x,y),(0,255,0),1)
else:
cv.circle(bg,(ix,iy),math.trunc(math.fabs(x-ix)),(255,0,0),1)
bg = np.zeros((512,512,3),np.uint8)
cv.namedWindow('image')
cv.setMouseCallback('image',click_callback)
while(1):
cv.imshow('image',bg)
k = cv.waitKey(20)
if k==77 | k==109:
isRectangle = not isRectangle
if cv.waitKey(20)==27:
break
cv.destroyAllWindows()
控制栏
- 创建控制栏 cv.createTrackbar(名称,窗体名,最小值,最大值,回调函数)
- 获取控制栏值 cv.getTrackbarPos(名称)
def donothing(arg):
pass
img = np.zeros((300,512,3),np.uint8)
cv.namedWindow('image')
cv.createTrackbar('B','image',0,255,donothing)
cv.createTrackbar('G','image',0,255,donothing)
cv.createTrackbar('R','image',0,255,donothing)
while(1):
cv.imshow('image',img)
k = cv.waitKey(20)
if k==27:
break
r = cv.getTrackbarPos('R','image')
g = cv.getTrackbarPos('G','image')
b = cv.getTrackbarPos('B','image')
img[:]=[b,g,r]
cv.destroyAllWindows()
章节练习
创建颜色和粗细可调的绘制面板
r,g,b,width=0,0,0,1
def donothing(val):
pass
img = np.zeros((300,512,3),np.uint8)
cv.namedWindow('image')
cv.createTrackbar('R','image',0,255,donothing)
cv.createTrackbar('G','image',0,255,donothing)
cv.createTrackbar('B','image',0,255,donothing)
cv.createTrackbar('W','image',1,10,donothing)
ix,iy=0,0
drawStatus = False
def click_callback(event,x,y,flags,arg):
global drawStatus,ix,iy,width
if event == cv.EVENT_LBUTTONDOWN:
drawStatus = True
ix,iy=x,y
elif event == cv.EVENT_MOUSEMOVE:
if drawStatus is True:
print(img)
cv.circle(img,(ix,iy),math.trunc(math.fabs(x-ix)),(b,g,r),width)
elif event == cv.EVENT_LBUTTONUP:
drawStatus = False
cv.setMouseCallback('image',click_callback)
while(1):
cv.imshow('image',img)
k = cv.waitKey(20)
if k==27:
break
r = cv.getTrackbarPos('R','image')
g = cv.getTrackbarPos('G','image')
b = cv.getTrackbarPos('B','image')
width = cv.getTrackbarPos('W','image')
cv.destroyAllWindows()
图像的基本操作
-
像素编辑
- 优化前
img = cv.imread('pic.png') pos = img[100,100] '''位置100,100的像素''' blue = img[100,100,0] ''' 访问 100,100 处的 blue 像素 ,因为是BGR颜色,第0位是蓝色 ''' img[100,100] = [200,0,0] '''修改像素'''
- 优化后
red = img.item(5,5,2) img.itemset((5,5,2),100) ''' 修改 5,5 处 红色 值为 100 '''
-
图像属性
- img.shape : 行,列和通道数的元组(如果图像是彩色的) ,如果是灰度(0)则不返回元组,常用来区别是否是灰度图
- img.size : 像素总数
- img.dtype : 图像数据类型
-
ROI(感兴趣区域)的编辑
- 置换
ball = img[280:340, 330:390] img[273:333, 100:160] = ball
-
拆分通道(耗时操作)
- 普通做法
b,g,r = cv.split(img) >>> img = cv.merge((b,g,r)) or b = img [:, :, 0]
- 索引 例如将所有红色像素都设置为0
img [:, :, 2] = 0
-
边框
cv.copyMakeBorder(图像,上,下,左,右,边框类型)
-
图像加法
- cv.add(img1,img2) 不推荐使用numpy的 img1+img2
-
图像融合
- dst = cv.addWeighted(img1,0.7,img2,0.3,0)
-
融合练习
img1 = cv.imread('1.png') img2 = cv.imread('2.png') cv.namedWindow('image') cv.namedWindow('combine') bg = np.zeros((300,521,3),np.uint8) cv.createTrackbar('Opacity_IMG1','image',0,100,lambda x:0) cv.createTrackbar('Opacity_IMG2','image',0,100,lambda x:0) while(1): cv.imshow('image',bg) k = cv.waitKey(20) if k==27: break op1 = cv.getTrackbarPos('Opacity_IMG1','image') op2 = cv.getTrackbarPos('Opacity_IMG2','image') img3 = cv.addWeighted(img1,op1/100,img2,op2/100,0) cv.imshow('combine',img3) cv.destroyAllWindows()
对象追踪
-
获取输入
#cap = cv.VideoCapture(0) #实时视频流 cap = cv.VideoCapture('blueBall.mp4') #视频流
-
对hsv追踪时,需要先对BGR颜色转为hsv颜色,例如对于 BGR: 246,138,39 (hsv:106,215,246),使用下列参数追踪效果较好:
121,128,72 - 253,248,255
-
读取实时图像: _, frame = cap.read() ,其中 _ 表示是否可以继续读取,frame 表示当前一帧图像
-
改变颜色空间 cv.cvtColor(图像/颜色, flag)
-
flag 常见的包括: COLOR_BGR2GRAY / COLOR_BGR2HSV
可以使用 flags = [i for i in dir(cv) if i.startswith('COLOR_')] 获取所有flag
-
当需要转换颜色时
#例如颜色的 BGR = 10,11,12 custom_color=np.uint8([[[10,11,12]]]) hsv_volor=cv.cvtColor(custom_color,cv.COLOR_BGR2HSV)
-
-
获取掩膜,可实现二值化操作
- mask = cv.inRange(一帧hsv图像,hsv颜色左边界,hsv颜色右边界)
-
合并图像和掩膜
- res = cv.bitwise_and(inputFrame1,inputFrame2,outputFrame,mask=mask) ,可以使用多个掩膜合并同一个图像
当前帧(frame)、掩膜(mask)、合并结果(res) 都可以实时预览,例如使用 cv.imshow('windowName',frame) 即可
-
扩展 : 灰度化比例公式 :R * 0.3 + G * 0.59 + B * 0.11
-
练习1:追踪蓝色小球
cv.namedWindow('control') bg = np.zeros((300,600,6),np.uint8) cv.createTrackbar('LB','control',0,255,lambda x:0) cv.createTrackbar('LG','control',0,255,lambda x:0) cv.createTrackbar('LR','control',0,255,lambda x:0) cv.createTrackbar('HB','control',0,255,lambda x:0) cv.createTrackbar('HG','control',0,255,lambda x:0) cv.createTrackbar('HR','control',0,255,lambda x:0) while(1): cap = cv.VideoCapture('mixColor.mp4') #视频流 while(cap.isOpened()): _, frame = cap.read() if not _: break # 转换颜色空间 BGR 到 HSV hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV) # 定义HSV中蓝色的范围 lb=cv.getTrackbarPos('LB','control') lg=cv.getTrackbarPos('LG','control') lr=cv.getTrackbarPos('LR','control') hb=cv.getTrackbarPos('HB','control') hg=cv.getTrackbarPos('HG','control') hr=cv.getTrackbarPos('HR','control') #print('low_bgr : %d,%d,%d'%(lb,lg,lr)) #print('high_bgr : %d,%d,%d'%(hb,hg,hr)) lower_blue = np.array([lb,lg,lr]) upper_blue = np.array([hb,hg,hr]) mask = cv.inRange(hsv, lower_blue, upper_blue) # 将掩膜和图像逐像素相加 res = cv.bitwise_and(frame,frame, mask = mask) cv.imshow('frame',frame) cv.imshow('mask',mask) cv.imshow('res',res) k = cv.waitKey(5) & 0xFF if k == 27: break cap.release() print('release') cv.destroyAllWindows()
-
练习2:追踪红、蓝、绿多颜色
cv.namedWindow('control') bg = np.zeros((300,512,3),np.uint8) cv.createTrackbar('Look_B','control',0,1,lambda x:0) cv.createTrackbar('Look_G','control',0,1,lambda x:0) cv.createTrackbar('Look_R','control',0,1,lambda x:0) cv.createTrackbar('B','control',0,255,lambda x:0) cv.createTrackbar('G','control',0,255,lambda x:0) cv.createTrackbar('R','control',0,255,lambda x:0) cv.createTrackbar('END','control',0,255,lambda x:0) #225 - 285 #112.5 - 142.5 b_min=np.array([105,190,130]) b_max=np.array([116,255,255]) #0 - 75 g_min=np.array([40,40,0]) g_max=np.array([83,255,255]) #135 - 225 #70-115 r_min=np.array([160,60,0]) r_max=np.array([225,255,180]) while(1): cap = cv.VideoCapture('allColor.mp4') while(cap.isOpened()): _,frame = cap.read() if not _: break k = cv.waitKey(5) & 0xff if k is 27: break o_b=cv.getTrackbarPos('Look_B','control') o_g=cv.getTrackbarPos('Look_G','control') o_r=cv.getTrackbarPos('Look_R','control') b=cv.getTrackbarPos('B','control') g=cv.getTrackbarPos('G','control') r=cv.getTrackbarPos('R','control') #g_min=np.array([b,g,r]) maskB,maskG,maskR=0,0,0 resB,resG,resR,res=0,0,0,frame[:] hsv = cv.cvtColor(frame,cv.COLOR_BGR2HSV) if o_b is 1: maskB = cv.inRange(hsv,b_min,b_max) resB = cv.bitwise_and(frame,frame,mask=maskB) if o_g is 1: maskG = cv.inRange(hsv,g_min,g_max) resG = cv.bitwise_and(frame,res,mask=maskG) if o_r is 1: maskR = cv.inRange(hsv,r_min,r_max) resR = cv.bitwise_and(frame,frame,mask=maskR) dst = cv.addWeighted(resB,1,resG,1,0) dst = cv.addWeighted(dst,1,resR,1,0) if len(dst)==4: cv.imshow('res',frame) else: cv.imshow('res',dst) #cv.imshow('maskR',maskR) cv.imshow('frame',frame) cap.release() print('release') q = cv.waitKey(100) & 0xff if q is 27: break cv.destroyAllWindows()
难点: 如何通过BGR确定HSV的范围。如果没有具体数值追踪,建议保存HSV色盘,通过实际检测确定范围
几何变换 04/03/2020
-
缩放
- cv.resize(输入图像,(缩放后宽,缩放后高),fx=宽度缩放倍数,fy=高度缩放倍数,interpolation = 缩放模式) ,窗口一同变化
缩放模式包括: cv.INTER_LINEAR(推荐) / cv.INTER_CUBIC(慢) / cv.INTER_AREA(默认)
例如:cv.resize(img,fx=2,fy=0.5,interpolation=cv.INTER_LINEAR) 表示宽度放大一倍,高度缩小为原来的 1/2 -
平移
-
cv.warpAffine(原图,移动参数,(行数rows,列数cols)) 仿射变换 warpAfine :( 是 warp-使 不是 wrap-包 )
img = cv.imread('1.png',0) rows,cols=img.shape M = np.float32([[1,0,100],[0,1,50]])#转换矩阵 res = cv.warpAffine(img,M,(rows,cols)) cv.imshow('image',res)
其中
M[0] 表示X方向,M[0][2]表示向右平移量,M[0][1]表示逆时针斜向拉伸角度 45°,M[0][0]表示X方向缩放倍数 ;
M[1] 表示Y方向,M[1][2]表示向下平移量,M[0][1]表示Y方向缩放倍数,M[0][0]表示顺时针斜向拉伸角度 45°;
定义时应该考虑的是对应位置不冲突,因此:
[[ X缩放,逆时针拉伸45°,X平移量 ] , [ 顺时针拉伸45°,Y缩放,Y平移量 ]]
-
练习:在渲染窗口中使用鼠标控制缩放、移动图像
img = cv.imread('1.png',1) rows,cols,x=img.shape cv.imshow('image',img) canMove=False ix,iy,res,res_move=0,0,img,img rrows,rcols,x=res.shape lenx,leny,M=0,0,np.float32([[1,0,0],[0,1,0]])#保存之前的平移量,防止回弹;或者直接用img接收warpAffine结果,但会改变原始图像 def mouse_callback(event,x,y,flags,params): global canMove,ix,iy,lenx,leny,rows,cols,res,scale,img,rrows,rcols,res_move,M if event == cv.EVENT_LBUTTONDOWN: canMove=True ix,iy=x,y elif event == cv.EVENT_MOUSEMOVE: if canMove is True: M = np.float32([[1,0,math.trunc(x-ix)+lenx],[0,1,math.trunc(y-iy)+leny]]) res_move = cv.warpAffine(res,M,(rrows,rcols))#img cv.imshow('image',res_move) elif event == cv.EVENT_LBUTTONUP: canMove = False lenx+=(x-ix) leny+=(y-iy) elif event == cv.EVENT_MOUSEWHEEL: step = 10 if flags>0: rrows+=step rcols+=step elif flags<0: rrows-=step rcols-=step res = cv.resize(img,(rrows,rcols),interpolation=cv.INTER_LINEAR)#放大后移动需返回当前窗体,否则一经置0无法恢复 cv.imshow('image',res) cv.setMouseCallback('image',mouse_callback)
-
-
旋转
-
cv.getRorationMatrix2D(中心点坐标,逆时针旋转角度,缩放倍数) 获取用于旋转的转换矩阵
rows.cols=img.shape centerX,centerY=(rows-1)/2.0,(cols-1)/2.0 M = cv.getRorationMatrix2D((centerX,centerY),90,1)#转换矩阵 res = cv.warpAffine(img,M,(rows,cols))#最后一组参数是显示的大小,旋转后可能长宽不一,要全部显示可以适当扩大一些
-
-
补充
- matplotlib.pyplot 的 subplot 作用是在一个窗体绘制多幅图像
- 定义 : matplotlib.subplot(rowsNum,colsNum,rowPlotOrder)
- 示例: