Skip to content

OpenCV

记得说resize有分辨率损失??TBA

Install

注:3.4.2后有部分算法申请了专利,无法使用

pip3 install opencv-python

import:

import cv2 as cv
import matplotlib.pyplot as plt
import time

Img Basic

Load & Save

img_mat = cv.imread('demo.png')    ## BGR
cv.imwrite('demo_w.png',img_mat)   ## BGR

help(img_mat)

Plot

plt.imshow(img_mat[:,:,[2,1,0]])   ## imread:BGR;plt:RGB

## np.hstack((mat1,mat2))  ## 并列展示两张图片

Window

cv.imshow('myWin', img_mat)
key = cv.waitKey(0)         ## 0: waiting;>0: display milliseconds
if key & 0xFF == ord('q'):  ## & 0xFF:取key(int,16位)的后8位;ord:取ascii码(8位)
    cv.destroyAllWindows()  ## 或:exit() 退出程序
  • imshow必须搭配waitKey;waitKey(0)时会持续等待输入,手动关闭窗口则返回-1;waitKey(10)会在10ms后关闭窗口、返回-1,就算中途关闭窗口也会等满10ms再返回值
  • 此外,还可以使用cv.namedWindow('myWin',cv.WINDOW_NORMAL)先创建空白window, cv.resizeWindow('myWin',500,600)修改window尺寸
  • 包装到函数中使用Window展示img:
def show_img_Window(img_mat,winName,waitTime=0,exitKey='q',ifDestroyAll=True):
    cv.imshow(winName, img_mat)
    while True:
        key = cv.waitKey(waitTime)
        if (key & 0xFF ==ord(exitKey)) or (key == -1):
            if ifDestroyAll:
                cv.destroyAllWindows()
            return key     ## exit via exitKey or via click/auto(-1)

## show_img_Window(img_mat,'winName',waitTime=1000,exitKey='q',ifDestroyAll=True)

只通过定时/click/exitKey关闭window,成功后返回True

Mat操作

Mat数据结构于python中转化为ndarray

img_mat_shallow = img_mat.view() ## 浅拷贝
img_mat_deep = img_mat.copy() ## 深拷贝

b,g,r = cv.split(img_mat) ## 分割通道
img_mat_ = cv.merge((b,g,r)) ## 合并通道

加减乘除

add/substract/multiply/divide:

img_add = cv.add(img_mat,img_mat)  ## 两个mat逐元素计算  
img_add = img_mat + 50      ## 每个元素加50

img_addw = cv.addWeighted(img_mat,0.4,img_mat_,0.6,0) ## y= x1*w1+x2*w2+b
  • mat范围为[0,255]
  • 理论上,若结果超过[0,255],一律计为0/255;实际上
    • Mat255 +1 ==> 0;这就可以解释为何 Mat255*2 ==> 254
    • Mat255 -256 ==> 65535;不过 Mat255 -255 -1 ==> 255;这是因为 Mat0 -1 ==> 255
    • 所以实际上是超限后+/-255
  • cv.add(img_mat,img_mat)不能写成cv.add(img_mat,50),否则只对第一维度起效;反之亦不能,两个mat必须使用cv.xxx,直接计算会有不能理解的事情发生

位运算

bitwise_not/bitwise_and/bitwise_or/bitwise_xor:

cv.bitwise_not(img_mat)  ## ~255
cv.bitwise_and(img_mat,img_mat) ## 255 & 255
cv.bitwise_or(img_mat,img_mat) ## 255 | 255 = 0
cv.bitwise_xor(img_mat,img_mat) ## 255 ^ 255 
  • mat范围为[0,255]
  • 同理,也可以写成类似:img_mat ^ 255

基本变换

h,w,ch = img_mat.shape

img_resize = cv.resize(img_mat,(w*2,h*2)) 

img_flip = cv.flip(img_mat,flipCode=-1)  ## 0: 上下;>0: 左右;<0: 上下左右;

img_rotate = cv.rotate(img_mat,0) ## 0:顺时针90, 1:180, 2:逆时针90

### M = cv.getRotationMatrix2d((90,90),15,1.5) 
src = np.float32([[11,11],[23,56],[99,88]])
dst = np.float32([[21,11],[33,56],[109,88]])
M = cv.getAffineTransform(src,dst) 
img_affine = cv.warpAffine(img_mat,M,(w,h))



src = np.float32([[11,11],[23,56],[99,88],[199,88]])
dst = np.float32([[21,11],[33,56],[109,88],[209,88]])
M = cv.getPerspectiveTransform(src,dst) 
img_perspective = cv.warpPerspective(img_mat,M,(w,h))

常用变换

各种卷积的处理方式,详情见Image Filtering

-- cv.func 说明
卷积 filter2D --
方盒滤波 boxFilter 取kernel的和(不归一化)或者均值(归一化)
均值滤波 blur 取kernel的均值
中值滤波 medianBlur 取kernel的中位数
双边滤波 bilateralFilter 图像边缘信息;空间距离高斯函数*灰度距离高斯函数
高斯滤波 GaussianBlur kernel转换成高斯分布;sigma越大、图像越平滑(模糊)
Sobel算子 Sobel 图像边缘信息;一阶求导
Scharr算子 Scharr 与Sobel类似、一阶求导;kernel系数不同,更灵敏
拉普拉斯算子 Laplacian 图像边缘信息;二阶求导,注意0可能是saddle point
腐蚀 erode --
  • 需要求导的算法一般默认返回mean(水平f'',垂直f'');实际使用时需要手动分开计算水平、垂直(e.g. dx=1,dy=0)后再add合并结果,否则效果不好
  • Miscellaneous Image Transformations
  • 漫水填充floodFill

绘图注释

在img上绘图、注释;参考: Drawing Functions

  • arrowedLine()
  • circle()
  • ellipse()
  • line()
  • rectangle()
  • polylines() 多边形 + fillPoly() 填充多边形
  • putText() 添加英语字,不支持中文
  • ...
demo_mat = cv.line(img_mat,(0,0),(100,900),(0,0,0),2,8,0)
show_img_Window(demo_mat,'myWin',waitTime=0,exitKey='q',ifDestroyAll=True)

注意:putText()不支持中文,需要使用Pillow + 字体包;Win可至C:\Windows\Fonts寻找ttc文件

from PIL import ImageFont,ImageDraw,Image
import numpy as np

font = ImageFont.truetype('demo.ttc',15)
img_pillow = Image.fromarray(img_mat)
draw = ImageDraw.Draw(img_pillow)
draw.text((0,0),'这是一',font=font,fill=(0,0,255))   ## fill=color

img_CN = np.array(img_pillow)   ## back to mat
show_img_Window(img_CN,'myWin',waitTime=0,exitKey='q',ifDestroyAll=True)

Video Basic

Load

VideoCapture捕获视频,本例中读取10张间隔为1s的摄像机图像,保存于frames中:

frames = []
cam = cv.VideoCapture(0)   ## 0:Camera
while cam.isOpened():                
    ret,frame_mat = cam.read()     ## start loading
    if not ret:
        print('cannot read frame..Break')
        break
    time.sleep(1)  ## second
    frames.append(frame_mat)
    print('show frame:{}'.format(len(frames)) )    ##  plot/window/save...
    if len(frames)>=5:             ## end video
        break

cam.release()
  • cv.VideoCapture可通过cam.release()释放资源;通过控制每一个frame的持续时间来控制读取速度、不然就是读取每一帧
  • cv.VideoCapture(0)使用摄像机,cv.VideoCapture(videoAddr)也使用本地/rtmp流网络视频

Save

VideoWriter将上例frames保存为视频格式,fourcc提供多媒体文件格式:

fourcc = cv.VideoWriter_fourcc(*'mp4v')
vidWriter = cv.VideoWriter('demo.mp4',fourcc,1,(640,480))  ## file, fourcc, framePerSecond, (dim)[, isColor]
for frame_mat in frames:
    vidWriter.write(frame_mat)

vidWriter.release()

Window

包装到函数中使用Window展示video:

def show_video_Window(winName,videoAddr,frameIntv=1000,exitKey='q'):  ## frameIntv: ms to show next frame
    cv.namedWindow(winName,cv.WINDOW_NORMAL)
    cam = cv.VideoCapture(videoAddr)     ## 0: camera
    while cam.isOpened():
        ret,frame_mat = cam.read()
        if not ret:
            print('cannot read frame..Break')
            break
        video_exit_key = show_img_Window(frame_mat,winName,waitTime=frameIntv,exitKey=exitKey,ifDestroyAll=False)
        if video_exit_key& 0xFF ==ord(exitKey):
            print('end..')
            break
    cam.release()
    cv.destroyAllWindows()

## show_video_Window('videoWin',0,frameIntv=1000,exitKey='q')

只能通过exitKey关闭window

Event

键盘/鼠标事件与响应的callback:

def mouse_callback(event,x,y,flags,userdata):  ## MouseEventNO.,x,y,KeyPressFlagsNO.,otherinputData
    print(event,x,y,flags,userdata)        ## e.g. 0,756,166,0,demo userdata  -- 0-EVENT_MOUSEMOVE & 

cv.namedWindow('myWin',cv.WINDOW_NORMAL)        ## Step1: new window
cv.setMouseCallback('myWin',mouse_callback,'demo userdata') ## Step2: set callback
show_img_Window(img_mat,'myWin',waitTime=0,exitKey='q')      ## Step3: open img in window

TrackBar

createTrackbar创建bar,getTrackbarPos、setTrackbarPos获取/设置当前bar的值:

def bar_callback(value):
    pass

cv.namedWindow('myWin',cv.WINDOW_NORMAL)              ## Step1: new window
cv.createTrackbar('barA','myWin',0,255,bar_callback)  ## Step2: new bar
while True:
    try:
        valA = cv.getTrackbarPos('barA','myWin')      ## opt: get current bar val
        show_img_Window(img_mat-valA,'myWin',waitTime=1,exitKey='q',ifDestroyAll=False)   ## Step3: open img in window
    except:
        break  
  • 必须waitKey(>0),是为更新频率;必须搭配imshow

色彩空间

  • HSV/HSL: Hue-色相(逆时针依次:0红~120绿~240蓝), Saturation-饱和度, Value-明度(0%黑~100%白)/Lightness亮度
  • HWB: Hue, Whiteness, Blackness
  • YUV: 颜色Subsamping编码方式,考虑人类的感知度,允许降低色度的带宽;示例: 4:4:0 = 横向4列中取4列、纵向4行中取1行 (0视为1)
  • OPENCV: Color Space Conversions
img_cvt = cv.cvtColor(img_mat,cv.COLOR_BGR2GRAY)

目标检测

  • pip install opencv-contrib-python安装额外模块;有时版本问题有坑,推荐opencv与contrib都 4.5.4.58
  • 详情见:cascade_classifier Training & cascade_classifier
  • pip show opencv-python找到site-packages,进入其中cv2/data/文件夹;可以找到 haarcascades_*.XML 文件
  • 只接受灰度图

## train
recognizer = cv.face.LBPHFaceRecognizer_create()
recognizer.train(faces_lst,id_lst) ### [face_areaA_img1_mat,face_areaB_img1_mat,...]  [id1,id1,...]
recognizer.write('xx.xml')

id_lst, confidence_lst = recognizer.predict(img_mat)

## detect
for x,y,w,h in cv.CascadeClassifier('xx.xml').detectMultiScale(img_mat):
    pass

参考

官网:https://opencv.org/
install:https://opencv.org/get-started/
Mouse Events in OpenCV: https://pythongeeks.org/mouse-events-in-opencv/
YUV知乎解说: https://zhuanlan.zhihu.com/p/85620611
色彩空间 知乎: https://zhuanlan.zhihu.com/p/67930839
Colors Tutorial:https://www.w3schools.com/colors/colors_hsl.asp