opencv-python图像处理模块(一)
本章节主要记录了图像颜色空间转换(着重讲述了hsv空间和对应的一个roi颜色提取实验);图形的基本绘制,包括绘制直线,矩形,圆和椭圆,还有中英文文字绘制;利用鼠标和键盘控制绘制图形,在图像上面显示某一点的坐标和对应的rgb像素值或者hsv值;以及在图片合适的位置添加logo水印。
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后的图片如下: