第 1 0 章OpenCV

news2024/9/21 5:37:21

本章概述如何通过 Python 接口使用流行的计算机视觉库 OpenCV。OpenCV 是一个C++ 库,用于(实时)处理计算视觉问题。实时处理计算机视觉的 C++ 库,最初由英特尔公司开发,现由 Willow Garage 维护。OpenCV 是在 BSD 许可下发布的开源库,这意味着它对于学术研究和商业应用是免费的。OpenCV 2.0 版本对于 Python 的支持已经得到了极大的改善。下面,我们会讲解一些基本的例子并深入了解视频与跟踪。

10.1 OpenCV的Python接口

OpenCV 是一个 C++ 库,它包含了计算机视觉领域的很多模块。除了 C++ 和 C,Python 作为一种简洁的脚本语言,在 C++ 代码基础上的 Python 接口得到了越来越广泛的支持。目前,OpenCV 的 Python 接口仍在发展,不过并不是所有的 OpenCV组件都提供了相应的 Python 接口,此处还有很多函数没有文档说明。因为该接口背后有一个很活跃的开发社区,所以这种现状很有可能得到改变。Python 接口文档说明见 http://opencv.willowgarage.com/documentation/python/index.html

OpenCV 2.3.1 版本实际上提供了两个 Python 接口。旧的 cv 模块使用 OpenCV 内部数据类型,并且从 NumPy 使用起来可能需要一些技巧。新的 cv2 模块用到了 NumPy 数组,并且使用起来更加直观 1 ,可以通过以下方式导入新的 cv2 模块:

import cv2

而对于旧的 cv 模块可以通过以下方式导入:

import cv2.cv

本章中我们将关注 cv2 模块;注意这些名字的衍变、函数名称的改变以及在后来版本中的定义。现在,OpenCV 和 Python 接口在飞速发展中。

10.2 OpenCV基础知识

OpenCV 自带读取、写入图像函数以及矩阵操作和数学库。关于 OpenCV 的细节,有一本很好的书 (只有 C++ 版)。我们现在来看一些基本的组件及其使用方法。

10.2.1 读取和写入图像

下面这个简短的例子会载入一张图像,打印出图像大小,对图像进行转换并保存为 .png 格式:

import cv2

# 读取图像

im = cv2.imread('empire.jpg')

h,w = im.shape[:2]

print h,w

# 保存图像

cv2.imwrite('result.png',im)

函数 imread() 返回图像为一个标准的 NumPy 数组,并且该函数能够处理很多不同格式的图像。如果你愿意,可以将该函数作为 PIL 模块读取图像的备选方案。函数imwrite() 会根据文件后缀自动转换图像。

10.2.2 颜色空间

在 OpenCV 中,图像不是按传统的 RGB 颜色通道,而是按 BGR 顺序(即 RGB 的倒序)存储的。读取图像时默认的是 BGR,但是还有一些可用的转换函数。颜色空间的转换可以用函数 cvColor() 来实现。例如,可以通过下面的方式将原图像转换成灰度图像:

im = cv2.imread('empire.jpg')

# 创建灰度图像

gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

在读取原图像之后,紧接其后的是 OpenCV 颜色转换代码,其中最有用的一些转换代码如下:

• cv2.COLOR_BGR2GRAY

• cv2.COLOR_BGR2RGB

• cv2.COLOR_GRAY2BGR

上面每个转换代码中,转换后的图像颜色通道数与对应的转换代码相匹配,比如对于灰度图像只有一个通道,对于 RGB 和 BGR 图像则有三个通道。最后的 cv2.COLOR_GRAY2BGR 将灰度图像转换成 BGR 彩色图像;如果你想在图像上绘制或覆盖有色彩的对象,CV2.COLOR_GAY2BGR 是非常有用的,我们会在后面的例子中用到它。

10.2.3 显示图像及结果

我们来看一些用 OpenCV 处理图像的例子,以及怎样利用 OpenCV 绘制功能和窗口管理功能来显示结果。

第一个例子是从文件中读取一幅图像,并创建一个整数图像表示:

import cv2

# 读取图像

im = cv2.imread('fisherman.jpg')

gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

# 计算积分图像

intim = cv2.integral(gray)

# 归一化并保存

intim = (255.0*intim) / intim.max()

cv2.imwrite('result.jpg',intim)

读取图像后,将其转化为灰度图像,函数 integral() 创建一幅图像,该图像的每个像素值是原图上方和左边强度值相加后的结果;这对于快速评估特征是一个非常有用的技巧。OpenCV 的 CascadeClassifier 用到了积分图像,立足于 Viola 和 Jones在文献中引入的框架。在保存图像前,通过除以图像中的像素最大值将其归一化到 0 至 255 之间。图 10-1 显示了示例图像结果。

第二个例子从一个种子像素进行泛洪填充:

import cv2

# 读取图像

filename = 'fisherman.jpg'

im = cv2.imread(filename)

h,w = im.shape[:2]

# 泛洪填充

diff = (6,6,6)

mask = zeros((h+2,w+2),uint8)

cv2.floodfill(im,mask,(10,10), (255,255,0),diff,diff)

# 在 OpenCV 窗口中显示结果

cv2.imshow('flood fill',im)

cv2.waitKey()

# 保存结果

cv2.imwrite('result.jpg',im)

图 10-1:用 OpenCV 的 integral() 函数计算积分图像

在该例中,对图像应用泛洪填充并在 OpenCV 窗口中显示结果。waitKey() 函数一直处于暂停状态,直到有按键按下,此时窗口才会自动关闭。这里的 floodfill() 函数获取(灰度或彩色)图像、一个掩膜(非零像素区域表明该区域不会被填充)、一个种子像素以及新的颜色值来代替下限和上限阈值差的泛洪像素。泛洪填充以种子像素为起始,只要能在阈值的差异范围内添加新的像素,泛洪填充就会持续扩展。不同的阈值差异由元组 (R,G,B) 给出,结果如图 10-2 所示。

作为最后一个例子,我们来看 SURF 特征的提取,SURF 特征是 SIFT 特征的一个更快特征提取版,文献 [1] 引入。这里,我们也会展示怎样使用一些基本的 OpenCV绘制命令:

import cv2

# 读取图像

im = cv2.imread('empire.jpg')

# 下采样

im_lowres = cv2.pyrDown(im)

# 变换成灰度图像

gray = cv2.cvtColor(im_lowres,cv2.COLOR_RGB2GRAY)

# 检测特征点

s = cv2.SURF()

mask = uint8(ones(gray.shape))

keypoints = s.detect(gray,mask)

# 显示结果及特征点

vis = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)

for k in keypoints[::10]:

cv2.circle(vis,(int(k.pt[0]),int(k.pt[1])),2,(0,255,0),-1)

cv2.circle(vis,(int(k.pt[0]),int(k.pt[1])),int(k.size),(0,255,0),2)

cv2.imshow('local descriptors',vis)

cv2.waitKey()

图 10-2:彩色图像泛洪填充。在左上角用单个种子进行泛洪填充,右图高亮区域标出了所有填充了的像素

读取图像后,如果没有给定新尺寸,则用 pyrDown() 进行下采样,创建一个尺寸为原图像大小一半的新图像,然后将该图像转换为灰度图像,并传递到一个 SURF 关键点检测对象;掩膜决定了在哪些区域应用关键点检测器。在画出检测结果时,我们将灰度图像转换成彩色图像,并用绿色通道画出检测到的关键点。我们在每到第十个关键点时循环一次,并在中心画一个圆环,每一个圆环显示出关键点的尺度(大小)。绘图函数 circle() 获取一幅图像、图像坐标(仅整数坐标)元组、半径、绘图的颜色元组以及线条粗细(-1 是实线圆环)。图 10-3 显示了提取出来的 SURF 特征。

图 10-3:用 OpenCV 提取 SURF 特征并画出提取出来的 SURF 特征

10.3 处理视频

单纯使用 Python 来处理视频有些困难,因为需要考虑速度、编解码器、摄像机、操作系统和文件格式。目前还没有针对 Python 的视频库,使用 OpenCV 的 Python 接口是唯一不错的选择。在本节中,我们来看一些处理视频的基本示例。

10.3.1 视频输入

OpenCV 能够很好地支持从摄像头读取视频。下面给出了一个捕获视频帧并在OpenCV 窗口中显示这些视频帧的完整例子:

import cv2

# 设置视频捕获

cap = cv2.VideoCapture(0)

while True:

ret,im = cap.read()

cv2.imshow('video test',im)

key = cv2.waitKey(10)

if key == 27:

break

if key == ord(' '):

cv2.imwrite('vid_result.jpg',im)

捕获对象 VideoCapture 从摄像头或文件捕获视频。我们通过一个整数进行初始化,该整数为视频设备的 id;如果仅有一个摄像头与计算机相连接,那么该摄像头的 id为 0。read() 方法解码并返回下一视频帧,第一个变量 ret 是一个判断视频帧是否成功读入的标志,第二个变量则是实际读入的图像数组。函数 waitKey() 等待用户按键:如果按下的是 Esc 键(ASCII 码是 27)键,则退出应用;如果按下的是空格键,就保存该视频帧。

拓展上面的例子,将摄像头捕获的数据作为输入,并在 OpenCV 窗口中实时显示经模糊的(彩色)图像,我们只需对上面的例子做简单的修改:

import cv2

# 设置视频捕获

cap = cv2.VideoCapture(0)

# 获取视频帧,应用高斯平滑,显示结果

while True:

ret,im = cap.read()

blur = cv2.GaussianBlur(im,(0,0),5)

cv2.imshow('camera blur',blur)

if cv2.waitKey(10) == 27:

break

每一视频帧都会被传递给 GaussianBlur() 函数,该函数会用高斯滤波器对传入的该帧图像进行滤波。这里,我们传递的是彩色图像,所以 Gaussian Blur() 函数会录入对彩色图像的每一个通道单独进行模糊。该函数需要为高斯函数设定滤波器尺寸(保存在元组中)及标准差;在本例中标准差设为 5。如果该滤波器尺寸设为 0,则它由标准差自动决定,显示出的结果与图 10-4 相似。

图 10-4:作者撰写本章时的一幅经过模糊的视频截图

以同样的方式从文件读取视频,不过我们调用 VideoCapture() 获取视频时是以文件名作为输入的:

capture = cv2.VideoCapture('filename')

10.3.2 将视频读取到NumPy数组中

使用 OpenCV 可以从一个文件读取视频帧,并将其转换成 NumPy 数组。下面是一个从摄像头捕获视频并将视频帧存储在一个 NumPy 数组中的例子:

import cv2

# 设置视频捕获

cap = cv2.VideoCapture(0)

frames = []

# 获取帧,存储到数组中

while True:

ret,im = cap.read()

cv2.imshow('video',im)

frames.append(im)

if cv2.waitKey(10) == 27:

break

frames = array(frames)

# 检查尺寸

print im.shape

print frames.shape

上述代码将每一视频帧数组添加到列表末,直到捕获结束。最终得到的数组会有帧数、帧高、帧宽及颜色通道数(3 个),打印出的结果如下:

(480, 640, 3)

(40, 480, 640, 3)

这里共记录了 40 帧。类似上面将视频数据存储在数组中对于视频处理是非常有帮助的,比如计算帧间差异以及跟踪。

10.4 跟踪

跟踪是在图像序列或视频里对其中的目标进行跟踪的过程。

10.4.1 光流

光流是目标、场景或摄像机在连续两帧图像间运动时造成的目标的运动。它是图像在平移过程中的二维矢量场。作为一种经典并深入研究了的方法,它在诸如视频压缩、运动估计、目标跟踪和图像分割等计算机视觉中得到了广泛的应用。

光流法主要依赖于三个假设。

(1) 亮度恒定 图像中目标的像素强度在连续帧之间不会发生变化。

(2) 时间规律 相邻帧之间的时间足够短,以至于在考虑运行变化时可以忽略它们之

间的差异。该假设用于导出下面的核心方程。

(3) 空间一致性 相邻像素具有相似的运动。

v=[u,v] 是运动矢量,It 是时间偏导。对于图像中那些单个的点,该方程是线性方程组。由于 v 包含两个未知变量,所以该方程是不可解的。通过强制加入空间一致性约束,则有可能获得该方程的解。在下面的 Lucas-Kanade 算法中,我们将看到怎样利用该假设。

OpenCV 包 含 了 一 些 光 流 实 现: 用 了 块 匹 配 的 CalcOpticalFlowBM();(这些只存在于旧的 cv 模块)的 CalcOpticalFlowHS();文献 中 的空间金字塔 Lucas-Kanade 算 法 calcOpticalFlowPyrLK();以及基于文献 的calcOpticalFlowFarneback()。最后一种被视为获取密集流场的最好方法之一。让我们看一个利用 calcOpticalFlowFarneback() 在视频中寻找运动矢量的例子(Lucas Kanade 光流法在下一节讲述)。

运行下面的脚本:

import cv2

def draw_flow(im,flow,step=16):

""" 在间隔分开的像素采样点处绘制光流 """

h,w = im.shape[:2]

y,x = mgrid[step/2:h:step,step/2:w:step].reshape(2,-1)

fx,fy = flow[y,x].T

# 创建线的终点

lines = vstack([x,y,x+fx,y+fy]).T.reshape(-1,2,2)

lines = int32(lines)

# 创建图像并绘制

vis = cv2.cvtColor(im,cv2.COLOR_GRAY2BGR)

for (x1,y1),(x2,y2) in lines:

cv2.line(vis,(x1,y1),(x2,y2),(0,255,0),1)

cv2.circle(vis,(x1,y1),1,(0,255,0), -1)

return vis

# 设置视频捕获

cap = cv2.VideoCapture(0)

ret,im = cap.read()

prev_gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

while True:

# 获取灰度图像

ret,im = cap.read()

gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

# 计算流

flow = cv2.calcOpticalFlowFarneback(prev_gray,gray,None,0.5,3,15,3,5,1.2,0)

prev_gray = gray

# 画出流矢量

cv2.imshow('Optical flow',draw_flow(gray,flow))

if cv2.waitKey(10) == 27:

break

在这个例子中,利用摄像头捕获图像,并对每个连续图像对进行光流估计。由calcOpticalFlowFarneback() 返回的运动光流矢量保存在双通道图像变量 flow 中。除了需要获取需要获取前一帧和当前帧,该函数还需要一系列参数。如果有兴趣可以查找相关的文献。辅助函数 draw_flow() 会在图像均匀间隔的点处绘制光流矢量,它利用 OpenCV 的绘图函数 line() 和 circle(),并用变量 step 控制流样本的间距。最终的结果如图 10-5 所示:圆环网格表示流样本的位置,带有线条的流矢量显示了每个样本点是怎样运动的。

图 10-5:在书本平移和头部转动的视频上展示光流矢量(每隔 15 个像素采样一次)

10.4.2 Lucas-Kanade算法

跟踪最基本的形式是跟随感兴趣点,比如角点。对此,一次流行的算法是 Lucas Kanade 跟踪算法,它利用了稀疏光流算法。

Lucas-Kanade 跟踪算法可以应用于任何一种特征,不过通常使用一些角点,比如2.1 节的 Harris 角点。goodFeaturesToTrack() 函数采用 Shi 和 Tomasi 在文献 [33] 中设计的算法检测角点;角点是结构张量(Harris 矩阵)中有两个较大特征值的那些点,且更小的特征值要大于某个阈值。

如果基于每一个像素考虑,该光流方程组是欠定方程,即每个方程中含很多未知变量。利用相邻像素有相同运动这一假设,对于 n 个相邻像素,可以将这些方程写成如下系统方程:

标准的 Lucas-Kanade 跟踪适用于小位移;为了能够处理较大位移,需要采用分层的方法。在该情形下,光流可以通过对图像由粗到精采样计算得到。这就是 OpenCV函数 calcOpticalFlowPyrLK() 要做的事。

这些 Lucas-Kanade 函数包含在 OpenCV 中,让我们看看怎样利用这些函数建立一个Python 跟踪类。创建名为 lktrack.py 的文件,向其添加下面的类和构造函数:

import cv2

# 一些常数及默认参数

lk_params = dict(winSize=(15,15),maxLevel=2,

criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,0.03))

subpix_params = dict(zeroZone=(-1,-1),winSize=(10,10),

criteria = (cv2.TERM_CRITERIA_COUNT | cv2.TERM_CRITERIA_EPS,20,0.03))

feature_params = dict(maxCorners=500,qualityLevel=0.01,minDistance=10)

class LKTracker(object):

""" 用金字塔光流 Lucas-Kanade 跟踪类 """

def __init__(self,imnames):

""" 使用图像名称列表初始化 """

self.imnames = imnames

self.features = []

self.tracks = []

self.current_frame = 0

用一个文件名列表对跟踪对象进行初始化,变量 features 和 tracks 分别保存角点和对这些角点进行跟踪的位置,同时,我们也利用一个变量对当前帧进行跟踪。我们定义了三个字典变量用于特征提取、跟踪、和亚像素特征点的提炼。

在开始检测角点时,我们需要载入实际图像,并转换成灰度图像,提取“利用跟踪的好的特征”点。OpenCV 函数 goodFeaturesToTrack() 方法可以完成这一主要工作。将下面的 detect_points() 添加到 LKTracker 类中:

def detect_points(self):

""" 利用子像素精确度在当前帧中检测“利于跟踪的好的特征”( 角点 ) """

# 载入图像并创建灰度图像

self.image = cv2.imread(self.imnames[self.current_frame])

self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)

# 搜索好的特征点

features = cv2.goodFeaturesToTrack(self.gray, **feature_params)

# 提炼角点位置

cv2.cornerSubPix(self.gray,features, **subpix_params)

self.features = features

self.tracks = [[p] for p in features.reshape((-1,2))]

self.prev_gray = self.gray

上述代码用 cornerSubPix() 提炼角点位置,并保存在成员变量 features 和 tracks中。需要注意的是,运行该函数会清除跟踪历史。

现在我们已经可以检测这些角点,接下来还需要对其进行跟踪。首先我们需要获得下一帧图像,然后应用 OpenCV 函数 calcOpticalFlowPyrLK() 找出这些点运动到哪里了,最后清除这些包含跟踪点的列表。下面的 track_points() 方法可以完成该过程:

def track_points(self):

""" 跟踪检测到的特征 """

if self.features != []:

self.step() # 移到下一帧

# 载入图像并创建灰度图像

self.image = cv2.imread(self.imnames[self.current_frame])

self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)

#reshape() 操作,以适应输入格式

tmp = float32(self.features).reshape(-1, 1, 2)

# 计算光流

features,status,track_error = cv2.calcOpticalFlowPyrLK(self.prev_gray,

self.gray,tmp,None,**lk_params)

# 去除丢失的点

self.features = [p for (st,p) in zip(status,features) if st]

# 从丢失的点清楚跟踪轨迹

features = array(features).reshape((-1,2))

for i,f in enumerate(features):

self.tracks[i].append(f)

ndx = [i for (i,st) in enumerate(status) if not st]

ndx.reverse()# 从后面移除

for i in ndx:

self.tracks.pop(i)

self.prev_gray = self.gray

下面定义一个辅助函数 step(),用于移动到下一视频帧:

def step(self,framenbr=None):

""" 移到下一帧。如果没有给定参数,直接移到下一帧 """

if framenbr is None:

self.current_frame = (self.current_frame + 1) % len(self.imnames)

else:

self.current_frame = framenbr % len(self.imnames)

该方法会跳转到一个给定的视频帧,如果没有给定参数则直接跳转到下一帧。

最后,我们还希望能够用 OpenCV 窗口和绘图函数画出最终的跟踪结果。添加draw() 方法到 LKTracker 类:

def draw(self):

""" 用 OpenCV 自带的画图函数画出当前图像及跟踪点,按任意键关闭窗口 """

# 用绿色圆圈画出跟踪点

for point in self.features:

cv2.circle(self.image,(int(point[0][0]),int(point[0][1])),3,(0,255,0),-1)

cv2.imshow('LKtrack',self.image)

cv2.waitKey()

现在,我们用 OpenCV 函数实现了一个完整独立的跟踪系统。

1. 使用跟踪器

我们将该跟踪类用于真实的场景中。下面的脚本初始化一个跟踪对象,对视频序列

进行角点检测、跟踪,并画出跟踪结果:

import lktrack

imnames = ['bt.003.pgm', 'bt.002.pgm', 'bt.001.pgm', 'bt.000.pgm']

# 创建跟踪对象

lkt = lktrack.LKTracker(imnames)

# 在第一帧进行检测,跟踪剩下的帧

lkt.detect_points()

lkt.draw()

for i in range(len(imnames)-1):

lkt.track_points()

lkt.draw()

每次画出一帧,并显示当前跟踪到的点,按任意键会转移到序列的下一帧。图 10-6显示了牛津 corridor 序列(牛津对多视图数据集中的一个序列,参见 http://www.robots.ox.ac.uk/~vgg/data/data-mview.html)的前 4 幅图像的跟踪结果。

图 10-6:通过 LKTrack 类利用 Lucas-Kanade 算法进行跟踪

2. 使用发生器

将下面的方法添加到 LKTracker 类:

def track(self):

""" 发生器,用于遍历整个序列 """

for i in range(len(self.imnames)):

if self.features == []:

self.detect_points()

else:

self.track_points()

# 创建一份 RGB 副本

f = array(self.features).reshape(-1,2)

im = cv2.cvtColor(self.image,cv2.COLOR_BGR2RGB)

yield im,f

上面的方法创建一个发生器,可以使遍历整个序列并将获得的跟踪点和这些图像以RGB 数组保存,以方便画出跟踪结果。将它用于经典的牛津“dinosaur”序列(也来源于上面提到的多视图数据集),并画出这些点及这些点的跟踪结果,代码如下:

import lktrack

imnames = ['viff.000.ppm', 'viff.001.ppm',

'viff.002.ppm', 'viff.003.ppm', 'viff.004.ppm']

# 用 LKTracker 发生器进行跟踪

lkt = lktrack.LKTracker(imnames)

for im,ft in lkt.track():

print 'tracking %d features' % len(ft)

# 画出轨迹

figure()

imshow(im)

for p in ft:

plot(p[0],p[1],'bo')

for t in lkt.tracks:

plot([p[0] for p in t],[p[1] for p in t])

axis('off')

show()

该发生器使前面定义的跟踪类的使用变得非常容易,并且完全向用户隐藏了OpenCV 里的函数。该示例生成的结果如图 10-6 右下图和图 10-7 所示。

图 10-7:用 Lucas-Kanade 跟踪算法在转盘序列上跟踪并画出跟踪点的轨迹

10.5 更多示例

OpenCV 自带很多关于如何使用 Python 接口的有用示例。这些示例在子目录samples/python2/ 中,用这些例子来熟悉 OpenCV 是一种非常好的方式。这里选择了一些来说明 OpenCV 的一些其他功能。

10.5.1 图像修复

对图像丢失或损坏的部分进行重建的过程叫做修复,既包括以复原为目的的对图像丢失数据或损坏部分进行恢复的算法,也包括在照片编辑应用程序中去除红眼或物体的算法。典型的例子是,图像的一个区域标记为“破损”,并需要利用余下的数据对该区域进行填补。

试着运行下面的命令:

$ python inpaint.py empire.jpg

运行上面的命令会打开一个交互窗口,在该窗口中你可以画一些需要修复的区域。最终修复的结果会在一个单独的窗口中显示出来,图 10-8 展示了一个示例。

图 10-8:用 OpenCV 进行图像修复的示例。左图显示了由用户标记的“破损”区域。右图显示了经过图像修复后的结果

10.5.2 利用分水岭变换进行分割

OpenCV 中的分水岭变换使用 Meyer [22] 的算法,可以使用下面的命令:

$ python watershed.py empire.jpg

该命令会打开一个交互窗口,你可以在该窗口中画一些种子区域作为输入。图 10-9右图显示了用分水岭变换进行分割后的结果,在变换后的灰度图像中,不同颜色代表分割覆盖的区域。

10.5.3 利用霍夫变换检测直线

霍夫变换(http://en.wikipedia.org/wiki/Hough_transform)是一种用于在图像中寻找各种形状的方法,原理是在参数空间中使用投票机制。霍夫变换常用于在图像中寻找直线结构。在该情况下,可以在二维直线参数空间对相同的直线参数进行投票,将边缘和线段组合在一起。

OpenCV 可以利用该方法进行直线检测 1 ,运行下面的命令:

$ python houghlines.py empire.jpg

它会给出图 10-10 中的两个窗口。第一个窗口显示了原图像进行灰度变换后的灰度图像,第二个显示了检测到的直线边缘,这些检测出来的直线在参数空间获得的投票最多。注意,这些直线通常是无限长的;如果你想在图像中找到线段的端点,可以使用边缘映射找到这些端点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2130630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于深度学习的医学图像分类与诊断系统(开题报告免费领)

深度学习开始大放异彩的工作,莫过于在ImageNet数据集上,对输入图片的分类了。只要输入图片,就能判断图片中主体所属的类别。然而,和分类问题输入图像输出主体的类别不一样,分割问题需要对每个像素点的类别进行识别。下…

Unity实现自己的协程系统(协程有顺序)

你的类可以在不继承Mono的脚本使用协程,但本质仍然需要借助其他Mono对象的Update来调度 实现了一个有执行顺序的协程的调度器,用于在 Unity 中管理多个协程的执行。通过 ICoroutineNodeOrder 和 ICoroutineWaitCondition 两个接口,可以定义每个协程的执行…

labview禁用8080端口

需求背景 最近电脑上安装了labview全家桶,发现idea的8080端口项目启动报错,一直提示8080端口被占用。最简单的办法就是找到8080端口的服务,然后关闭这个服务。但是我不想这么做,我想把labview的web服务器的端口给修改了。 操作教程 1、cmd查看8080端口 2、windows进程 同…

ICM20948 DMP代码详解(17)

接前一篇文章:ICM20948 DMP代码详解(16) 前一篇文章讲到了inv_icm20948_set_chip_power_state函数中尚需解析的3个函数中的第1个函数:inv_icm20948_write_single_mems_reg_core。并没有完全讲完,本回继续解析。为了便于…

搭建本地DVWA靶场教程 及 靶场使用示例

1. DVWA简介 DVWA(Damn Vulnerable Web Application)一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用平台,旨在为网络安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。 DVW…

利用MR设备实现弹吉他教学:实战案例详解

随着混合现实(Mixed Reality, MR)技术的发展,越来越多的应用开始探索如何将这种沉浸式的体验融入到教育与娱乐中。特别是在音乐教育领域,MR技术为乐器学习提供了全新的可能性。本文将通过分析一个基于Unity开发的吉他教学应用案例,探讨如何利用MR设备,如Oculus Quest或Ap…

Boot中使用Redis缓存

除了RedisTemplate,Spring Cache 还有如下方式 即使不写Repository也可以自动注入 只要extends CrudRepository 最好不要写Repository有可能冲突 自动注入用Autowired或Resource都可

Chainlit集成Langchain并使用通义千问实现和数据库交互的网页对话应用增强扩展(text2sql)

前言 我在上一篇文章中《Chainlit集成Langchain并使用通义千问实现和数据库交互的网页对话应用(text2sql)》 利用langchain 中create_sql_agent 创建一个数据库代理智能体,但是实测中发现,使用 create_sql_agent 在对话中&#x…

yolo学习 (一) 安装yolov8及训练

随便搞个python环境,直接装或者anaconda都行,python版本最低3.8以上 一、安装yolov8 (cpu版本) pip install ultralytics yolov8安装版本比较省事,不过这里默认装的是CPU版本 import torch print(torch.__version_…

基于stm32单片机使用 RT-Thread 系统的 ADC 外设

一、ADC 介绍 来源:RT-Thread 文档中心   ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射…

手机玩机常识-------谷歌系列机型解锁bl详细步骤 其他机型可以借鉴参考

谷歌公司自从在2005年收购了Android公司之后一直在开发一款手机操作系统,谷歌的这一举动正是为了推出自己的手机而作准备.目前。谷歌Pixel 系列为很多玩家所持有。其独特的安装原生系统为很多粉丝所青睐。今天我们来看看谷歌Pixel 系列机型解锁bl的相关常识 谷歌Pi…

CTF(misc)

1、隐写3 题目链接 观察这个图片感觉图片高度有问题,010editor打开,查看CRC python脚本求宽高 import os import binascii import struct crcbp open("dabai.png","rb").read() for i in range(1024):for j in range(1024):data …

操作系统 ---- 调度器、闲逛进程

一、调度器/调度程序(scheduler) 2、③由调度程序引起,调度程序决定: 让谁运行?――调度算法运行多长时间?―一时间片大小 调度时机――什么事件会触发“调度程序”? 创建新进程进程退出运行进程阻塞I/O中断发生(可能唤醒某…

大受欢迎的游戏却又意外被作者下架的《Flappy Bird》将重返iPhone

据"Flappy Bird 基金会"官网称,标志性的侧卷轴滚动游戏《Flappy Bird》将很快回归 iPhone。《Flappy Bird》于 2013 年发布,很快就获得了数千万次下载。然而,这款游戏在2014 年突然从 App Store 下架,原因是其越南开发者…

SD卡挂载FatFs文件系统

一、简介 实验目的:SD卡挂载FATFS文件系统,并生成.txt文件 MCU:ST32F103ZET6 SD卡:16G;SPI读写模式; 引脚定义:VCC:5V GND:GND MISO:PA6 …

常用环境部署(十八)——CentOS7搭建DNS服务器

一、安装Bind服务器软件并启动 1、安装Bind服务 yum -y install bind bind* 2、 启动服务 systemctl start named 3、开机自启动 systemctl enable named 二、查看named进程是否正常启动 1、检查进程 ps -eaf|grep named 2、检查监听端口 ss -nult|grep :53 三、关闭…

EmguCV学习笔记 C# 11.6 图像分割

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

宠物毛发对人体有什么危害?宠物空气净化器小米、希喂、352对比实测

作为一个呼吸科医生,我自己也养猫。软软糯糯的小猫咪谁不爱啊,在养猫的过程中除了欢乐外,也面临着一系列的麻烦,比如要忍耐猫猫拉粑粑臭、掉毛、容易带来细菌等等的问题。然而我发现,现在许多年轻人光顾着养猫快乐了&a…

Vue生命周期钩子在UniApp中的应用

1、Vue 3 生命周期钩子介绍 onMounted()//注册一个回调函数,在组件挂载完成后执行。onUpdated()//注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。onUnmounted()//注册一个回调函数,在组件实例被卸载之后调用。onBefor…

西部数据发布的一款西数硬盘检测修复工具-支持WD-L/WD-ROYL板,能进行硬盘软复位,可识别硬盘查看或清除-供大家学习参考

使用方法: 1、运行WDR5.3正式版.exe 2、导入WDR5.3.key 3、PORTTALK.SYS放入系统目录 第一步:注册完打开软件 第二步:设置维修盘端口:点击设置--》端口--》会出现主要端口 ,次要端口 ,定制端口 USB。一般如果不是USB移动硬盘都选择“定制端口”