opencv-python图像处理模块(一)

发布时间 2023-07-10 21:21:08作者: 寒水浮云

1 图像颜色空间转换

opencv提供了用于颜色空间转换的函数,用来适应在不同需求中的图像使用要求。 dst = cvtColor(img, mode) mode表示颜色空间转换方式(转换到RGB空间:COLOR_BGR2RGB;转换成灰度图片:COLOR_BGR2GRAY;转换到HSV空间:COLOR_BGR2HSV;转换到YUV空间:COLOR_BGR2YUV)

颜色空间转换代码如下:

import cv2
import numpy as np  #图片颜色空间转换

cv2.namedWindow('color',cv2.WINDOW_NORMAL)  #定义窗口
cv2.resizeWindow('color',(640,480))

img = cv2.imread('./cat.jpg')

def callback(value):   #回调函数
    pass

color_space = [cv2.COLOR_BGR2BGRA, cv2.COLOR_BGR2RGB,  cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSV,cv2.COLOR_BGR2YUV]

cv2.createTrackbar('trackbar','color',0,4,callback)  #创建进度条

while True:
    index = cv2.getTrackbarPos('trackbar','color')  #获取进度条数值
    img2 = cv2.cvtColor(img,color_space[index])  #图片颜色空间转换
    
    cv2.imshow('color',img2)
    key = cv2.waitKey(1)
    if key == ord('q'):
        break

cv2.destroyAllWindows()

 

 

 

除了最常用的RGB空间,HSV空间在有些时候也用的比较多,这里稍微介绍一下HSV空间,HSV即色相(Hue)、饱和度(Saturation)、亮度(Value)。HSV的颜色空间更符合人类对颜色的理解,人眼很难根据图像的rgb值推测出图像到底是什么颜色,但是可以根据hsv的值大致推测出图像的颜色,或者看到某一种颜色可以推测出对应的hsv值。

色调(H): 用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;在opencv中 取值范围是0-180。

饱和度(S)表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。在opencv中 取值范围是0-255。

亮度(V)表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。在opencv中 取值范围是0-255,事实上可以超过这个范围。

HSV柱状图如下:

常见颜色的大致hsv范围:

比如下图,狗的颜色大致是橙色,我希望通过颜色把狗单独提取出来。但是这个橙色不是确定的某一种颜色,而是一个范围,可以使用 OpenCV 的 inRange函数来检测和提取图像中存在的颜色。具体是使用该函数来创建颜色掩码, mask = inRange(img, LowerBlue, UpperBlue)  img表示待处理图像,LowerBlue表示低阈值范围,UpperBlue表示高阈值范围。inRange 函数将颜色的值设置为 1,如果颜色存在于给定的颜色范围内,则设置为白色,如果颜色不存在于指定的颜色范围内,则设置为 0。

import cv2
import numpy as np  #根据颜色提取roi区域

img = cv2.imread('./cat_dog.jpg')
print(img.shape)
mask = cv2.inRange(img,(70,90,160),(105,150,250)) #用inRange创建颜色掩膜 dog = cv2.bitwise_and(img,img,mask=mask) #用掩膜与原图像与运算获取roi区域 cv2.imshow('images',img) cv2.imshow('dog',dog) cv2.waitKey(0) cv2.destroyAllWindows()

 直接用rgb空间来获取颜色掩膜非常困难,经过多次尝试也没法获得比较满意的效果,如果转换到hsv空间后效果会好很多,如下:

import cv2
import numpy as np  #转换到HSV根据颜色提取roi区域


img = cv2.imread('./cat_dog.jpg')
print(img.shape)
HSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
H, S, V = cv2.split(HSV)

LowerBlue = np.array([0, 100, 150])
UpperBlue = np.array([20, 160, 240])

mask = cv2.inRange(HSV,LowerBlue,UpperBlue)
#mask = cv2.inRange(img,(70,90,160),(105,150,225))
print(HSV)

dog = cv2.bitwise_and(img,img,mask=mask)

cv2.imshow('images',img)
cv2.imshow('dog',dog)
cv2.waitKey(0)
cv2.destroyAllWindows()

总体来说,效果还是很不错的,比rgb提取的效果好很多。

2 基本图形绘制

1)绘制标记

opencv中提供了用于绘制标记的函数drawMarker,可以使用该函数在图像上标记一个点。 dst = drawMarker(img, position, color, markerType=None, thickness=None) position表示标记绘制的位置,需要传入整型;color表示标记的绘制颜色;markerType表示标记绘制类型,thickness表示标记线的粗细。

import cv2
import numpy as np  #绘制标记 drawMarker(图像,位置,颜色,标志类型,标记线粗细)

img = cv2.imread('./cat.jpg')

draw_marker = cv2.drawMarker(img,(250,250),(255,0,0),cv2.MARKER_CROSS,thickness=3)  #绘制十字架
draw_marker = cv2.drawMarker(img,(50,50),(0,255,0),cv2.MARKER_STAR,thickness=3)  #绘制星型
draw_marker = cv2.drawMarker(img,(400,400),(0,0,255),cv2.MARKER_DIAMOND,thickness=3) #绘制菱形
draw_marker = cv2.drawMarker(img,(50,400),(255,255,0),cv2.MARKER_SQUARE,thickness=3) #绘制长方形
draw_marker = cv2.drawMarker(img,(400,50),(255,0,255),cv2.MARKER_TILTED_CROSS,thickness=3) #绘制x型


print(img.shape)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 2)绘制直线和矩形

opencv提供的直线绘制函数line,dst = line(ing,pt1,pt2,color,thickness=None,lineType=None) pt1表示绘制直线的第一个点,pt2表示第二个点;color表示绘制的直线颜色;thickness表示绘制直线的粗细;lineType表示绘制的线型。绘制矩形的函数为rectangle,dst = rectangle(img, pt1,pt2, color, thickness=None, lineType=None) pt1表示绘制矩形的顶点,pt2表示与pt1相对的顶点。

绘制的图形可以用窗口显示,也可以显示在图像上。

import cv2  #绘制直线,矩形

#img = cv2.imread('./cat.jpg')
img = np.zeros((414,500,3),np.uint8)

draw_line1 = cv2.line(img,(50,50),(400,400),(0,0,255),thickness=3) #绘制直线
draw_line2 = cv2.line(img,(50,400),(400,50),(0,0,255),thickness=3)
draw_rectangle = cv2.rectangle(img,(50,50),(400,400),(255,0,0),thickness=3) #绘制矩形


cv2.imshow('img',draw_line1)   #此时的img和draw——line1,line2,rectangle是一样的了,相当于浅拷贝,共用一个内存
cv2.waitKey(0)
cv2.destroyAllWindows()

 

 3)绘制圆和椭圆

opencv提供了用于绘制圆的函数时circle,dst = circle(img, center, radius, color, thickness=None, lineType=None)  center表示圆心坐标,radius表示圆心半径。(注:thickness=-1的时候,绘制实心图形)

此外,opencv还提供了绘制椭圆的函数ellipse,dst = ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness=None, lineType=None)  center表示椭圆的圆心坐标,axes表示主轴尺寸的一半,angle表示椭圆旋转的角度,startAngle表示椭圆圆弧的起始角度,endAngle表示椭圆的终止角度(即可以只绘制椭圆的一部分)。

import cv2  #绘制圆,椭圆

img = cv2.imread('./cat.jpg')
img_copy =img.copy()

draw_circle = cv2.circle(img,(300,200),150,(0,0,255),thickness=3)#绘制圆
draw_marker = cv2.drawMarker(img,(300,200),(0,0,255),cv2.MARKER_CROSS,thickness=3) #标记圆心

draw_ellipse = cv2.ellipse(img,(100,200),(100,50),90,0,360,(0,255,0),thickness=3) #绘制椭圆
draw_marker = cv2.drawMarker(img,(100,200),(0,255,0),cv2.MARKER_CROSS,thickness=3) #标记椭圆圆心

draw_ellipse = cv2.ellipse(img_copy,(100,200),(100,50),90,0,180,(0,255,0),thickness=3) #绘制半个椭圆
draw_ellipse = cv2.ellipse(img_copy,(300,200),(100,100),180,0,180,(0,0,255),thickness=3) #绘制半个圆

cv2.imshow('img',img)
cv2.imshow('img_copy',img_copy)

cv2.waitKey(0)
cv2.destroyAllWindows()

4)绘制多边形

除了绘制上面的常见图形,也可以自定义坐标,然后根据坐标来绘制多边形,opencv绘制多边形的函数为 polylines,dst = polylines(img, pts, isClosed, color[, thickness[, lineType]])

pts表示存放点集坐标的二维数组,isClosed表示是否把绘制的多条线段首尾相连, thickness 线宽的值必须大于0。还可以绘制填充多边形,fillPoly(img, pts, color[, lineType])

import cv2    #绘制多边形
import numpy as np

img = np.zeros([480,640,3],np.uint8)

#多边形的点集,必须是int32位
pts = np.array([(50,50),(50,100),(100,200),(200,400),(400,100)],np.int32)


#cv2.polylines(img,[pts],True,[0,0,255],thickness=3) #绘制多边形
cv2.fillPoly(img,[pts],[255,0,0])    #绘制填充多边形

cv2.imshow('lines',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

5)绘制文字

在图像处理中 ,经常要对图像增加一些说明性文字,opencv提供了用于绘制文字的函数putText, dst = putText(img,text,org,fontFace,fontscale,color,thickness=None)  text是待绘制的文字,org表示文字在图像中绘制区域的左下角位置,fontFace表示字体,fontScale表示对字体的缩放比例,color表示颜色,thickness表示绘制文字的粗细。text也可以由用户手动输入。

import cv2   #绘制文字,opencv不能绘制中文

img = cv2.imread('./cat.jpg')

#text = "this is a cat"
text = input('enter your input:')  #从键盘自己输入文字

draw_text = cv2.putText(img,text,(200,50),cv2.FONT_ITALIC,1,(0,0,255),thickness=2)

cv2.imshow('img',draw_text)
cv2.waitKey(0)
cv2.destroyAllWindows() 

 此外,opencv没法直接绘制中文字符,可以用pillow包绘制中文,需要用的中文字体在windows下的fonts里面拷贝出来(我拷贝了微软雅黑字体),拷贝到当前路径会自动命名msyhbd.ttc。

  

 具体代码如下:

import cv2  #opencv没法绘制中文文本,可以用pillow包绘制中文,需要用的中文字体在windows下的fonts里面拷贝出来
import numpy as np
from PIL import ImageFont,ImageDraw,Image  

img = cv2.imread('./cat.jpg')
font = ImageFont.truetype('./msyhbd.ttc',20)  #导入字体文件,参数是字体大小

img_pil = Image.fromarray(img)  #创建一个pillow的图片
print(img_pil)

draw = ImageDraw.Draw(img_pil)   #利用draw绘制中文
text = '这是一只猫'

draw.text((250,10),text,font=font,fill=(0,0,255))

img = np.array(img_pil) #把图片重新变回ndarray格式才能显示  
cv2.imshow('img',img) cv2.waitKey(0) cv2.destroyAllWindows()

 

 3 利用鼠标和键盘控制绘制图形

首先介绍一下opencv的鼠标操作,鼠标事件回调函数为setMouseCallback,定义为:setMouseCallback(windowname, onMouse) windowname为窗口名,onMouse为鼠标事件回调函数。鼠标事件类型有MouseEventType定义,鼠标事件常用的主要有:EVENT_LBUTTONDBLCLK(左键双击),EVENT_RBUTTONDBLCLK(右键双击),EVENT_LBUTTONDOWN(按下左键),EVENT_RBUTTONDOWN(按下右键),EVENT_LBUTTONUP(左键释放),EVENT_RBUTTONUP(右键释放),EVENT_MOUSEMOVE(移动鼠标)。

1)通过鼠标操作绘制图形

本例通过不同的鼠标操作进行图形绘制,双击可以绘制圆,鼠标左键按下拖动绘制红色线,鼠标右键按下拖动绘制蓝色线。

import cv2           
import numpy as np

start_point=(0,0) #鼠标开始坐标和结束坐标(其实这里只需要定义一个鼠标坐标点就行)
end_point=(0,0)  #必须把这些参数定义为全局变量,如果在函数内部的话,每次调用函数这些参数都会被重置

lb_down,lb_up,rb_down,rb_up = False,False,False,False #鼠标按键按下/抬起的标志,bool型

def mouse_event(event,x,y,flags,param):    #鼠标回调函数 (event是鼠标事件,x,y是鼠标指针当前所指的坐标)
    global start_point,end_point,lb_down,lb_up,rb_down,rb_up #如果全局变量是int或者str,那么如果想要在函数中对函数变量进行修改,则需要
                                                           #先在函数内,声明其为global,再进行修改,如果是list或者dict则可以直接修改
    if event == cv2.EVENT_LBUTTONDBLCLK:
        cv2.circle(img,(x,y),100,(0,0,255),thickness=3)  #双击左键绘制圆
    
    elif event == cv2.EVENT_LBUTTONDOWN:  #左键按下,更新鼠标坐标,启动按下标志
        start_point = (x,y)
        end_point = start_point
        lb_down = True
    
    elif event == cv2.EVENT_RBUTTONDOWN:  #右键按下,更新鼠标坐标,启动按下标志
        start_point = (x,y)
        end_point = start_point
        rb_down = True
    
    elif event == cv2.EVENT_MOUSEMOVE:  #鼠标移动,绘制线
        if lb_down:   #如果左键按下,绘制红色线
            cv2.line(img,start_point,(x,y),(0,0,255),thickness=3)
        if rb_down:   #如果右键按下,绘制蓝色线
            cv2.line(img,start_point,(x,y),(255,0,0),thickness=3)
        start_point = (x,y)   #只要鼠标移动,就更新鼠标的坐标
        
    elif event == cv2.EVENT_LBUTTONUP: #左键释放
        lb_up = True
        lb_down = False
        cv2.line(img,start_point,(x,y),(0,0,255),thickness=3)  #鼠标点击后直接释放鼠标的时候也会绘制一个点
        
    elif event == cv2.EVENT_RBUTTONUP:  #右键释放
        rb_up = True
        rb_down = False
        cv2.line(img,start_point,(x,y),(255,0,0),thickness=3)  #鼠标点击后直接释放鼠标的时候也会绘制一个点
        
#img = np.zeros((512,512,3),np.uint8)  #创建一个黑色图像
img = cv2.imread('./cat.jpg')
cv2.namedWindow('image')   #新建窗口

cv2.setMouseCallback('image',mouse_event)  #设置鼠标回调

while True:
    cv2.imshow('image',img)
    if cv2.waitKey(1)==ord('q'): #waitKey参数不能写0,写0就需要键盘输入才会继续
        break

cv2.destroyAllWindows()
        

 

 2)通过鼠标键盘配合绘制图形

本来通过鼠标键盘绘制图形:按下l,拖动鼠标,绘制直线;按下r,拖动鼠标,绘制矩形;按下c,拖动鼠标,绘制圆。首先,绘制图形,需要记录起始位置start_point,按下鼠标左键的时候记录起始位置,鼠标左键释放的时候作为终点,绘制图形。还需要一个参数flag_key用来记录绘制那种图形的标志。

import cv2
import numpy as np

start_point = (0,0)  #鼠标起始位置
flag_key = 0  #选择绘制图形标志

def mouse_callback(event,x,y,flags,userdata):  #鼠标回调函数
    global start_point,flag_key  #声明全局变量
    
    if event == cv2.EVENT_LBUTTONDOWN:  #按下鼠标左键,记录起始位置
        start_point = (x,y)
        
    elif event == cv2.EVENT_LBUTTONUP: #松开鼠标左键,绘制图形
        if flag_key == 0:
            cv2.line(img,start_point,(x,y),[0,0,255],3)  #绘制直线
            
        elif flag_key ==1:
            cv2.rectangle(img,start_point,(x,y),[0,255,0],3) #绘制矩形
            
        elif flag_key ==2:
            a = x - start_point[0]
            b = y - start_point[1]
            radius = int((a**2 + b**2)**0.5)    #计算圆心
            cv2.circle(img,start_point,radius,[255,0,0],3,) #绘制圆


img = np.zeros((800,800,3),np.uint8) 
cv2.namedWindow('image')  

cv2.setMouseCallback('image',mouse_callback)  #设置鼠标回调

while True:
    cv2.imshow('image',img)
    key =cv2.waitKey(1)  #键盘按键判断,更新绘制图形标志
    if key == ord('l'):
        flag_key=0
    elif key == ord('r'):
        flag_key=1
    elif key == ord('c'):
        flag_key=2
    elif key == ord('q'):
        break

cv2.destroyAllWindows()
    

 

3)在图像上面显示某一点的坐标和对应的像素值

有时需要根据图像的像素值进行某些操作,图片上可以设置双击左键获取坐标和对应的rgb值,双击右键获取坐标和对应的hsv值。主要涉及的是鼠标操作setMouseCallback 和 绘制文字操作putText。

# 直接根据鼠标双击获取图像当前位置的像素值

import cv2
import numpy as np

img = cv2.imread('./cat.jpg')
img_hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV) #转换到hsv空间
#print(img.shape)

def mouse_event(event,x,y,flags,param):
    if event == cv2.EVENT_LBUTTONDBLCLK:   #双击左键显示图像的坐标和对应的rgb值
        print('img pixel value at(', x, ',', y, '):',img[y, x])  #坐标(x,y)对应的像素值应该是img[y,x]
        text = '(' + str(x) + ',' + str(y) + ')' + str(img[y,x]) 
        cv2.putText(img,text,(x,y),cv2.FONT_HERSHEY_SIMPLEX,0.5,(255,0,0),1)  #绘制文字
        
    if event == cv2.EVENT_RBUTTONDBLCLK:   #双击右键显示图像的坐标和对应的hsv值
        print('img_hsv pixel value at(', x, ',', y, '):',img_hsv[y, x])  #坐标(x,y)对应的像素值应该是img_hsv[y,x]
        text = '(' + str(x) + ',' + str(y) + ')' + str(img_hsv[y,x])
        cv2.putText(img,text,(x,y),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,255),1)  #绘制文字

cv2.namedWindow('image',cv2.WINDOW_NORMAL)  #定义窗口
#cv2.resizeWindow('image',(800,800))
 
cv2.setMouseCallback('image',mouse_event)  #鼠标回调

while True:
    cv2.imshow('image',img)
    if cv2.waitKey(1)==ord('q'):
        break

cv2.destroyAllWindows()

 

4 给图像加水印logo

用矩形或者圆等组合自己绘制一个合适的logo,然后把logo添加到图像上适当位置,比如如下logo,加入cat图片的右上角。

 把logo水印插入到指定的图片中,比较简单的想法是:从图片适当位置取出与logo大小一致的图片切片,然后与logo进行操作,这个操作可以把logo水印添加到图片切片中且不影响到切片背景,然后直接把融合后的切片放到之前的位置即可。这个操作还涉及到一个比较重要的东西,mask的作用。mask是掩膜,可以用来遮盖非感兴趣区,突出感兴趣区,使得图像处理只专注于ROI部分。

mask是一个二值图像,与对应尺寸的图片进行与操作可以获取感兴趣的区域,比如下列提取猫头的简单操作:

import cv2
import numpy as np

img = cv2.imread('./cat.jpg')
print(img.shape)

mask = np.zeros(img.shape[:2],np.uint8) #和原图像尺寸一样大的掩膜

mask[50:180,30:190] = 255 #猫头的大概位置

roi = cv2.bitwise_and(img,img,mask=mask)  #mask与对应图像进行与操作提取roi区域

cv2.imshow('img',img)
cv2.imshow('mask',mask)
cv2.imshow('roi',roi)

cv2.waitKey(0)
cv2.destroyAllWindows()

因此,根据刚才的思路,提取logo的掩膜,然后与图片切片进行掩膜操作提取logo外围黑色区域所需的背景,然后把所得图片和logo进行相加即可获得融合logo的图片。

import cv2   #给图像加上水印logo
import numpy as np

img = cv2.imread('./cat.jpg')
w,h,c = img.shape
img = cv2.resize(img,(int(h*1.5),int(w*1.5)),interpolation=cv2.INTER_AREA)  #图片放大1.5倍

logo = np.zeros((200,200,3),np.uint8)  #创建logo图片窗口

logo[20:120,20:120]=[0,0,255]  #logo上绘制矩形
logo[80:180,80:180]=[255,0,0]

cv2.circle(logo,(100,100),50,(0,255,0),-1) #绘制实心圆

mask = np.zeros((200,200),np.uint8)  #构造一个掩模图像,将该掩模图像作为按位与函数的掩模参数,实现保留图像的指定部分
mask[20:120,20:120]=255              #掩码是二维矩阵,当使用掩模参数时,操作只会在掩模值为非空的像素点上执行,并将其他像素点的值置为0
mask[80:180,80:180]=255

for i in range(200):                 #把logo绿色通道的圆对应的mask也拷贝过去
    for j in range(200):
        if logo[i][j][1] ==255:
            mask[i][j]=255
        else:
            pass

m = cv2.bitwise_not(mask)  #因为要插入logo时要保证logo附近的背景不变,因此这里用mask把logo附近的背景取出来,所以需要mask取反

temp = img[20:220,500:700]
img2 = cv2.bitwise_and(temp,temp,mask=m) #提取跟logo窗口一样大的图片,logo标志像素为0,背景与原背景相同

dst = cv2.add(img2,logo) 

img[20:220,500:700]=dst  #把最终的融合背景的logo放到图片对应的位置

cv2.imshow('logo',logo)  
cv2.imshow('mask',mask)  
cv2.imshow('img2',img2)
cv2.imshow('img',img)

cv2.waitKey(0)
cv2.destroyAllWindows()

logo,logo对应的掩膜,具有掩膜的背景图片切片和融合logo后的切片图片如下:

最终的添加logo后的图片如下: