当前位置 : 主页 > 网络编程 > 其它编程 >

[548]OpenCV之cv2函数

来源:互联网 收集:自由互联 发布时间:2023-07-02
1、主要函数1、cv2.imread()读入图片共两个参数第一个参数为要读入的图片文件名第二个参数为如何读取图片读入图片共两个参数第一个参数为要读入的图片文件名第二个参数为如何读取图
1、主要函数1、cv2.imread()读入图片共两个参数第一个参数为要读入的图片文件名第二个参数为如何读取图片读入图片共两个参数第一个参数为要读入的图片文件名第二个参数为如何读取图片包括

cv2.IMREAD_COLOR读入一副彩色图片cv2.IMREAD_GRAYSCALE以灰度模式读入图片cv2.IMREAD_UNCHANGED读入一幅图片并包括其alpha通道。

PS调用opencv就算图像的路径是错的OpenCV 也不会提醒你的但是当你使用命

令print(img)时得到的结果是None。

2、cv2.imshow()创建一个窗口显示图片共两个参数第一个参数表示窗口名字可以创建多个窗口中但是每个窗口不能重名第二个参数是读入的图片窗口大小自动调整为图片大小。

3、cv2.waitKey()键盘绑定函数共一个参数表示等待毫秒数将等待特定的几毫秒看键盘是否有输入返回值为ASCII值。如果其参数为0则表示无限期的等待键盘输入。

4、cv2.destroyAllWindows()删除建立的全部窗口。

5、cv2.destroyWindows()删除指定的窗口。

6、cv2.imwrite(fileimgnum)保存图片共3个参数第一个为保存文件名第二个为读入图片可选的第三个参数它针对特定的格式对于JPEG其表示的是图像的质量用0 - 100的整数表示默认95;对于png ,第三个参数表示的是压缩级别。默认为3.。

注意:

cv2.IMWRITE_JPEG_QUALITY类型为 long ,必须转换成 intcv2.IMWRITE_PNG_COMPRESSION, 从0到9 压缩级别越高图像越小。

cv2.imwrite(1.png,img, [int( cv2.IMWRITE_JPEG_QUALITY), 95])cv2.imwrite(1.png,img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])

cv2.imread()

使用opencv和caffe的伙伴们可能会有一个疑问那就是对于同时读取图片的cv2.imread()和caffe.io.loadimage两个函数有什么差别

1、cv2.imread()接口读图像读进来直接是BGR 格式数据格式在 0~255通道格式为(W,H,C)

2、caffe.io.load_image()读进来是RGB格式和 0~1(float

因此cv2.imread()读取的数据需要进过转换后才和caffe.io.load_image()相同例如

image caffe.io.load_image(examples/images/cat.jpg)

image1cv2.imread(examples/images/cat.jpg) image1cv2.cvtColor(image1,cv2.COLOR_BGR2RGB) image1image1/255.

经过转换后cv2.imread读取的image1和caffe.io.load_image()读取到的image格式相同。

所以在进行特征提取之前要在transformer中设置transformer.set_raw_scale(‘data’,255)(缩放至0~255 以及transformer.set_channel_swap(‘data’,(2,1,0)(将RGB变换到BGR。

调用caffe model进行特征提取分类时要注意区分image用何种方法读进来。


cv2.imread不能正常读取gif格式图片

Python中cv2模块的imread函数可以正常读取’jpg’,png’格式的图片但是不能处理’gif’图片。可以改用imageio模块来处理。

import cv2import imageiodef readImg(im_fn):im cv2.imread(im_fn)if im is None :print({} cv2.imread failed.format(im_fn))tmp imageio.mimread(im_fn)if tmp is not None:imt np.array(tmp)imt imt[0]im imt[:,:,0:3]return im

help(imageio.mimread)


关于cv2.imread()读取图像为BGR问题

opencv读取图像为b,g,r方法,比如

img cv2.imread("xx.jpg") cv2.imshow("xx",img)

展示的结果是正常的:

但是此时读取到的img已经为bgr方式了,如果我们再用其他使用rgb方式读取的函数进行读取时就会出错,比如我用plt对图像进行显示,效果如下:

因为plt函数是rgb方式读取的,所以会出错.这时我们可以手动改变img的通道顺序,如下:

b,g,r cv2.split(img) img_rgb cv2.merge([r,g,b]) plt.figure() plt.imshow(img_rgb) plt.show()

这时img_rgb就是rgb顺序的了.那么这时再用cv2.imshow()显示出来,rgb错误:

简单实例

1、以下面的图片为例 显示结果 显示结果 5、配合画图

import numpy as npimport cv2np.set_printoptions(thresholdnan)# 创建一个宽512高512的黑色画布RGB(0,0,0)即黑色imgnp.zeros((512,512,3),np.uint8)# 画直线,图片对象起始坐标(x轴,y轴)结束坐标颜色宽度cv2.line(img,(0,0),(311,511),(255,0,0),10)# 画矩形图片对象左上角坐标右下角坐标颜色宽度cv2.rectangle(img,(30,166),(130,266),(0,255,0),3)# 画圆形图片对象中心点坐标半径大小颜色宽度cv2.circle(img,(222,222),50,(255.111,111),-1)# 画椭圆形图片对象中心点坐标长短轴顺时针旋转度数开始角度(右长轴表0度上短轴表270度)颜色宽度cv2.ellipse(img,(333,333),(50,20),0,0,150,(255,222,222),-1)# 画多边形指定各个点坐标,array必须是int32类型ptsnp.array([[10,5],[20,30],[70,20],[50,10]], np.int32)# -1表示该纬度靠后面的纬度自动计算出来实际上是4pts pts.reshape((-1,1,2,))# print(pts)# 画多条线False表不闭合True表示闭合闭合即多边形cv2.polylines(img,[pts],True,(255,255,0),5)#写字,字体选择fontcv2.FONT_HERSHEY_SCRIPT_COMPLEX# 图片对象要写的内容左边距字的底部到画布上端的距离字体大小颜色粗细cv2.putText(img,"OpenCV",(10,400),font,3.5,(255,255,255),2)acv2.imwrite("picture.jpg",img)cv2.imshow("picture",img)cv2.waitKey(0)cv2.destroyAllWindows()

6.1、图像的表示

前面章节已经提到过了单通道的灰度图像在计算机中的表示就是一个8位无符号整形的矩阵。在OpenCV的C代码中表示图像有个专门的结构叫做cv::Mat不过在Python-OpenCV中因为已经有了numpy这种强大的基础工具所以这个矩阵就用numpy的array表示。如果是多通道情况最常见的就是红绿蓝RGB三通道则第一个维度是高度第二个维度是高度第三个维度是通道比如图6-1a是一幅3×3图像在计算机中表示的例子

代码实现的操作示意在下图中

执行这段代码得到如下窗口

#

cv2.waitKey()参数不为零的时候则可以和循环结合产生动态画面比如在6.2.4的延时小例子中我们把延时摄影保存下来的所有图像放到一个叫做frames的文件夹下。下面代码从frames的文件夹下读取所有图片并以24的帧率在窗口中显示成动画

import osfrom itertools import cycleimport cv2# 列出frames文件夹下的所有图片filenames os.listdir(frames)# 通过itertools.cycle生成一个无限循环的迭代器每次迭代都输出下一张图像对象img_iter cycle([cv2.imread(os.sep.join([frames, x])) for x in filenames])key 0while key 27:cv2.imshow(Animation, next(img_iter))key cv2.waitKey(42)

在这个例子中我们采用了Python的itertools模块中的cycle函数这个函数可以把一个可遍历结构编程一个无限循环的迭代器。另外从这个例子中我们还发现cv2.waitKey()返回的就是键盘上出发的按键。对于字母就是ascii码特殊按键比如上下左右等则对应特殊的值其实这就是键盘事件的最基本用法。

8.2 鼠标和键盘事件

因为GUI总是交互的所以鼠标和键盘事件基本使用必不可少上节已经提到了cv2.waitKey()就是获取键盘消息的最基本方法。比如下面这段循环代码就能够获取键盘上按下的按键并在终端输出

while key ! 27:cv2.imshow(Honeymoon Island, img)key cv2.waitKey()# 如果获取的键值小于256则作为ascii码输出对应字符否则直接输出值msg {} is pressed.format(chr(key) if key <256 else key)print(msg)

通过这个程序我们能获取一些常用特殊按键的值比如在笔者用的机器上四个方向的按键和删除键对应的值如下

  • 上↑65362

  • 下↓65364

  • 左←65361

  • 右→65363

  • 删除Delete65535

需要注意的是在不同的操作系统里这些值可能是不一样的。鼠标事件比起键盘事件稍微复杂一点点需要定义一个回调函数然后把回调函数和一个指定名称的窗口绑定这样只要鼠标位于画面区域内的事件就都能捕捉到。把下面这段代码插入到上段代码的while之前就能获取当前鼠标的位置和动作并输出

# 定义鼠标事件回调函数def on_mouse(event, x, y, flags, param):# 鼠标左键按下抬起双击if event cv2.EVENT_LBUTTONDOWN:print(Left button down at ({}, {}).format(x, y))elif event cv2.EVENT_LBUTTONUP:print(Left button up at ({}, {}).format(x, y))elif event cv2.EVENT_LBUTTONDBLCLK:print(Left button double clicked at ({}, {}).format(x, y))# 鼠标右键按下抬起双击elif event cv2.EVENT_RBUTTONDOWN:print(Right button down at ({}, {}).format(x, y))elif event cv2.EVENT_RBUTTONUP:print(Right button up at ({}, {}).format(x, y))elif event cv2.EVENT_RBUTTONDBLCLK:print(Right button double clicked at ({}, {}).format(x, y))# 鼠标中/滚轮键如果有的话按下抬起双击elif event cv2.EVENT_MBUTTONDOWN:print(Middle button down at ({}, {}).format(x, y))elif event cv2.EVENT_MBUTTONUP:print(Middle button up at ({}, {}).format(x, y))elif event cv2.EVENT_MBUTTONDBLCLK:print(Middle button double clicked at ({}, {}).format(x, y))# 鼠标移动elif event cv2.EVENT_MOUSEMOVE:print(Moving at ({}, {}).format(x, y))# 为指定的窗口绑定自定义的回调函数cv2.namedWindow(Honeymoon Island)cv2.setMouseCallback(Honeymoon Island, on_mouse)

8.3 代码物体检测标注的小工具

基于上面两小节的基本使用就能和OpenCV的基本绘图功能就能实现一个超级简单的物体框标注小工具了。基本思路是对要标注的图像建立一个窗口循环然后每次循环的时候对图像进行一次拷贝。鼠标在画面上画框的操作以及已经画好的框的相关信息在全局变量中保存并且在每个循环中根据这些信息在拷贝的图像上再画一遍然后显示这份拷贝的图像。

基于这种实现思路使用上我们采用一个尽量简化的设计

  • 输入是一个文件夹下面包含了所有要标注物体框的图片。如果图片中标注了物体则生成一个相同名称加额外后缀名的文件保存标注信息。

  • 标注的方式是按下鼠标左键选择物体框的左上角松开鼠标左键选择物体框的右下角鼠标右键删除上一个标注好的物体框。所有待标注物体的类别和标注框颜色由用户自定义如果没有定义则默认只标注一种物体定义该物体名称叫“Object”。

  • 方向键的←和→用来遍历图片↑和↓用来选择当前要标注的物体Delete键删除一张图片和对应的标注信息。

每张图片的标注信息以及自定义标注物体和颜色的信息用一个元组表示第一个元素是物体名字第二个元素是代表BGR颜色的tuple或者是代表标注框坐标的元组。对于这种并不复杂复杂的数据结构我们直接利用Python的repr()函数把数据结构保存成机器可读的字符串放到文件里读取的时候用eval()函数就能直接获得数据。这样的方便之处在于不需要单独写个格式解析器。如果需要可以在此基础上再编写一个转换工具就能够转换成常见的Pascal VOC的标注格式或是其他的自定义格式。

在这些思路和设计下我们定义标注信息文件的格式的例子如下

(Hill, ((221, 163), (741, 291)))(Horse, ((465, 430), (613, 570)))

元组中第一项是物体名称第二项是标注框左上角和右下角的坐标。这里之所以不把标注信息的数据直接用pickle保存是因为数据本身不会很复杂直接保存还有更好的可读性。自定义标注物体和对应标注框颜色的格式也类似不过更简单些因为括号可以不写具体如下

Horse, (255, 255, 0)Hill, (0, 255, 255)DiaoSi, (0, 0, 255)

第一项是物体名称第二项是物体框的颜色。使用的时候把自己定义好的内容放到一个文本里然后保存成和待标注文件夹同名后缀名为labels的文件。比如我们在一个叫samples的文件夹下放上一些草原的照片然后自定义一个samples.labels的文本文件。把上段代码的内容放进去就定义了小山头的框为黄色骏马的框为青色以及红色的屌丝。基于以上标注小工具的代码如下

import osimport cv2# tkinter是Python内置的简单GUI库实现一些比如打开文件夹确认删除等操作十分方便from tkFileDialog import askdirectoryfrom tkMessageBox import askyesno# 定义标注窗口的默认名称WINDOW_NAME Simple Bounding Box Labeling Tool# 定义画面刷新的大概帧率是否能达到取决于电脑性能FPS 24# 定义支持的图像格式SUPPOTED_FORMATS [jpg, jpeg, png]# 定义默认物体框的名字为Object颜色蓝色当没有用户自定义物体时用默认物体DEFAULT_COLOR {Object: (255, 0, 0)}# 定义灰色用于信息显示的背景和未定义物体框的显示COLOR_GRAY (192, 192, 192)# 在图像下方多出BAR_HEIGHT这么多像素的区域用于显示文件名和当前标注物体等信息BAR_HEIGHT 16# 上下左右ESC及删除键对应的cv.waitKey()的返回值# 注意这个值根据操作系统不同有不同可以通过6.4.2中的代码获取KEY_UP 65362KEY_DOWN 65364KEY_LEFT 65361KEY_RIGHT 65363KEY_ESC 27KEY_DELETE 65535# 空键用于默认循环KEY_EMPTY 0get_bbox_name {}.bbox.format# 定义物体框标注工具类class SimpleBBoxLabeling:def __init__(self, data_dir, fpsFPS, window_nameNone):self._data_dir data_dirself.fps fpsself.window_name window_name if window_name else WINDOW_NAME#pt0是正在画的左上角坐标pt1是鼠标所在坐标self._pt0 Noneself._pt1 None# 表明当前是否正在画框的状态标记self._drawing False# 当前标注物体的名称self._cur_label None# 当前图像对应的所有已标注框self._bboxes []# 如果有用户自定义的标注信息则读取否则用默认的物体和颜色label_path {}.labels.format(self._data_dir)self.label_colors DEFAULT_COLOR if not os.path.exists(label_path) else self.load_labels(label_path)# 获取已经标注的文件列表和还未标注的文件列表imagefiles [x for x in os.listdir(self._data_dir) if x[x.rfind(.) 1:].lower() in SUPPOTED_FORMATS]labeled [x for x in imagefiles if os.path.exists(get_bbox_name(x))]to_be_labeled [x for x in imagefiles if x not in labeled]# 每次打开一个文件夹都自动从还未标注的第一张开始self._filelist labeled to_be_labeledself._index len(labeled)if self._index > len(self._filelist) - 1:self._index len(self._filelist) - 1# 鼠标回调函数def _mouse_ops(self, event, x, y, flags, param):# 按下左键时坐标为左上角同时表明开始画框改变drawing标记为Trueif event cv2.EVENT_LBUTTONDOWN:self._drawing Trueself._pt0 (x, y)# 左键抬起表明当前框画完了坐标记为右下角并保存同时改变drawing标记为Falseelif event cv2.EVENT_LBUTTONUP:self._drawing Falseself._pt1 (x, y)self._bboxes.append((self._cur_label, (self._pt0, self._pt1)))# 实时更新右下角坐标方便画框elif event cv2.EVENT_MOUSEMOVE:self._pt1 (x, y)# 鼠标右键删除最近画好的框elif event cv2.EVENT_RBUTTONUP:if self._bboxes:self._bboxes.pop()# 清除所有标注框和当前状态def _clean_bbox(self):self._pt0 Noneself._pt1 Noneself._drawing Falseself._bboxes []# 画标注框和当前信息的函数def _draw_bbox(self, img):# 在图像下方多出BAR_HEIGHT这么多像素的区域用于显示文件名和当前标注物体等信息h, w img.shape[:2]canvas cv2.copyMakeBorder(img, 0, BAR_HEIGHT, 0, 0, cv2.BORDER_CONSTANT, valueCOLOR_GRAY)# 正在标注的物体信息如果鼠标左键已经按下则显示两个点坐标否则显示当前待标注物体的名称label_msg {}: {}, {}.format(self._cur_label, self._pt0, self._pt1) \if self._drawing \else Current label: {}.format(self._cur_label)# 显示当前文件名文件个数信息msg {}/{}: {} | {}.format(self._index 1, len(self._filelist), self._filelist[self._index], label_msg)cv2.putText(canvas, msg, (1, h12),cv2.FONT_HERSHEY_SIMPLEX,0.5, (0, 0, 0), 1)# 画出已经标好的框和对应名字for label, (bpt0, bpt1) in self._bboxes:label_color self.label_colors[label] if label in self.label_colors else COLOR_GRAYcv2.rectangle(canvas, bpt0, bpt1, label_color, thickness2)cv2.putText(canvas, label, (bpt0[0]3, bpt0[1]15),cv2.FONT_HERSHEY_SIMPLEX,0.5, label_color, 2)# 画正在标注的框和对应名字if self._drawing:label_color self.label_colors[self._cur_label] if self._cur_label in self.label_colors else COLOR_GRAYif self._pt1[0] > self._pt0[0] and self._pt1[1] > self._pt0[1]:cv2.rectangle(canvas, self._pt0, self._pt1, label_color, thickness2)cv2.putText(canvas, self._cur_label, (self._pt0[0] 3, self._pt0[1] 15),cv2.FONT_HERSHEY_SIMPLEX,0.5, label_color, 2)return canvas# 利用repr()导出标注框数据到文件staticmethoddef export_bbox(filepath, bboxes):if bboxes:with open(filepath, w) as f:for bbox in bboxes:line repr(bbox) \nf.write(line)elif os.path.exists(filepath):os.remove(filepath)# 利用eval()读取标注框字符串到数据staticmethoddef load_bbox(filepath):bboxes []with open(filepath, r) as f:line f.readline().rstrip()while line:bboxes.append(eval(line))line f.readline().rstrip()return bboxes# 利用eval()读取物体及对应颜色信息到数据staticmethoddef load_labels(filepath):label_colors {}with open(filepath, r) as f:line f.readline().rstrip()while line:label, color eval(line)label_colors[label] colorline f.readline().rstrip()return label_colors# 读取图像文件和对应标注框信息如果有的话staticmethoddef load_sample(filepath):img cv2.imread(filepath)bbox_filepath get_bbox_name(filepath)bboxes []if os.path.exists(bbox_filepath):bboxes SimpleBBoxLabeling.load_bbox(bbox_filepath)return img, bboxes# 导出当前标注框信息并清空def _export_n_clean_bbox(self):bbox_filepath os.sep.join([self._data_dir, get_bbox_name(self._filelist[self._index])])self.export_bbox(bbox_filepath, self._bboxes)self._clean_bbox()# 删除当前样本和对应的标注框信息def _delete_current_sample(self):filename self._filelist[self._index]filepath os.sep.join([self._data_dir, filename])if os.path.exists(filepath):os.remove(filepath)filepath get_bbox_name(filepath)if os.path.exists(filepath):os.remove(filepath)self._filelist.pop(self._index)print({} is deleted!.format(filename))# 开始OpenCV窗口循环的方法定义了程序的主逻辑def start(self):# 之前标注的文件名用于程序判断是否需要执行一次图像读取last_filename # 标注物体在列表中的下标label_index 0# 所有标注物体名称的列表labels self.label_colors.keys()# 待标注物体的种类数n_labels len(labels)# 定义窗口和鼠标回调cv2.namedWindow(self.window_name)cv2.setMouseCallback(self.window_name, self._mouse_ops)key KEY_EMPTY# 定义每次循环的持续时间delay int(1000 / FPS)# 只要没有按下Esc键就持续循环while key ! KEY_ESC:# 上下键用于选择当前标注物体if key KEY_UP:if label_index 0:passelse:label_index - 1elif key KEY_DOWN:if label_index n_labels - 1:passelse:label_index 1# 左右键切换当前标注的图片elif key KEY_LEFT:# 已经到了第一张图片的话就不需要清空上一张if self._index > 0:self._export_n_clean_bbox()self._index - 1if self._index <0:self._index 0elif key KEY_RIGHT:# 已经到了最后一张图片的话就不需要清空上一张if self._index len(self._filelist) - 1:self._index len(self._filelist) - 1# 删除当前图片和对应标注信息elif key KEY_DELETE:if askyesno(Delete Sample, Are you sure?):self._delete_current_sample()key KEY_EMPTYcontinue# 如果键盘操作执行了换图片则重新读取更新图片filename self._filelist[self._index]if filename ! last_filename:filepath os.sep.join([self._data_dir, filename])img, self._bboxes self.load_sample(filepath)# 更新当前标注物体名称self._cur_label labels[label_index]# 把标注和相关信息画在图片上并显示指定的时间canvas self._draw_bbox(img)cv2.imshow(self.window_name, canvas)key cv2.waitKey(delay)# 当前文件名就是下次循环的老文件名last_filename filenameprint(Finished!)cv2.destroyAllWindows()# 如果退出程序需要对当前进行保存self.export_bbox(os.sep.join([self._data_dir, get_bbox_name(filename)]), self._bboxes)print(Labels updated!)if __name__ __main__:dir_with_images askdirectory(titleWhere are the images?)labeling_task SimpleBBoxLabeling(dir_with_images)labeling_task.start()

需要注意的是几个比较通用且独立的方法前加上了一句staticmethod表明是个静态方法。执行这个程序并选择samples文件夹标注时的画面如下图

image


参考 https://www.cnblogs.com/xiaowuyi/p/4214271.html https://www.2cto.com/kf/201806/757475.html https://blog.csdn.net/weixin_39943271/article/details/79086131 https://blog.csdn.net/chuigedaqiqiu/article/details/80017510 https://blog.csdn.net/liu13364876993/article/details/79867061 https://www.cnblogs.com/shizhengwen/p/8719062.html

网友评论