树莓派计算机视觉编程:11~13

发布时间 2023-04-19 11:06:56作者: ApacheCN

原文:Raspberry Pi Computer Vision Programming

协议:CC BY-NC-SA 4.0

译者:飞龙

本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。

当别人说你没有底线的时候,你最好真的没有;当别人说你做过某些事的时候,你也最好真的做过。

十一、计算机视觉的实际应用

在上一章中,我们研究了计算机视觉中的各种高级概念,例如形态运算和轮廓。

本章是我们在前面各章中学习和展示的所有计算机视觉概念的最终总结。 在本章中,我们将使用我们较早学习的计算机视觉操作来实现一些实际项目。 我们还将学习一些新概念,例如背景减法和光流计算,然后在小型应用中进行演示。 本章包含许多动手的编程示例,以及有关代码和新功能的详细说明。

在本章中,我们将学习和演示以下主题的代码:

  • 实现最大 RGB 过滤器
  • 实现背景减法
  • 计算光流
  • 检测并跟踪运动
  • 检测图像中的条形码
  • 实现色度键效果

完成本章后,您将能够实现所学到的概念,以使用 Raspberry Pi(RPi)和一些相机传感器来创建现实应用,例如安全系统和运动检测系统。

技术要求

可以在 GitHub 上找到本章的代码文件。

观看以下视频,以查看这个页面上的“正在执行的代码”。

实现最大 RGB 过滤器

我们知道,过滤器允许并根据某些标准阻止信号或数据。 让我们根据像素颜色的强度值手动编写用于实现特殊过滤器的代码。 这称为最大 RGB 过滤器。 在 Max RGB 过滤器中,我们比较每个像素的彩色图像的所有颜色通道的强度。

然后,我们将通道的强度保持为最大强度,并将所有其他通道的强度降低为零。 对于图像中的每个像素都会发生这种情况。 假设对于一个像素,强度为(30, 200, 120)。 然后,在应用最大 RGB 过滤器后,它将为(0, 200, 0)。 让我们看一个将使用 NumPy 和 OpenCV 函数实现此功能的程序:

import cv2
import numpy as np
def maxRGB(img):
    b = img[:, :, 0]
    g = img[:, :, 1]
    r = img[:, :, 2]
    M = np.maximum(np.maximum(b, g), r)
    b[b < M] = 0
    g[g < M] = 0
    r[r < M] = 0
    return(cv2.merge((b, g, r)))
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    cv2.imshow('Max RGB Filter', maxRGB(frame))
    if cv2.waitKey(1) == 27:
        break
cv2.destroyAllWindows()
cap.release()

运行前面的程序并查看输出。 看到过滤后的实时供稿很有趣。 输出如下:

Figure 11.1 – Output of a Max RGB filter

图 11.1 –最大 RGB 过滤器的输出

在下一节中,我们将学习并演示背景减法的概念。

实现背景减法

静态摄像机用于的许多应用中,例如安全性和监视。 我们可以通过应用称为背景减法的过程来分离背景和运动对象。 通常返回二进制图像,背景图像(场景的静态部分)以黑色像素为单位,而运动部分(变化或动态)以白色像素为单位。 OpenCV 可以通过两种算法来实现。 第一个是createBackgroundSubtractorKNN()。 这将创建 K 最近邻KNN)背景减法器对象。 然后,我们可以使用对象调用apply()函数以获得前景遮罩。 我们可以直接实时显示前景遮罩。

以下是如何使用它的演示:

import cv2
import numpy as np
cap = cv2.VideoCapture(0)
fgbg = cv2.createBackgroundSubtractorKNN()
while(True):
    ret, frame = cap.read()
    fgmask = fgbg.apply(frame)
    cv2.imshow('frame', fgmask)
    if cv2.waitKey(30) == 27:
        break
cap.release()
cv2.destroyAllWindows()

输出为二进制视频流,如以下屏幕截图所示。 我挥舞着我的手,该手以白色像素突出显示:

Figure 10.2 – Background subtraction with KNN

图 10.2 –使用 KNN 进行背景减法

请注意,如果您不动手一段时间,OpenCV 会将其视为背景的一部分,并将其慢慢溶解在输出中。

另一个类似的函数是cv2.createBackgroundSubtractorMOG2()。 这还会使用apply()函数生成前景遮罩。 以下是使用它的示例程序:

import cv2
import numpy as np
cap = cv2.VideoCapture(0)
fgbg = cv2.createBackgroundSubtractorMOG2()
while(True):
    ret, frame = cap.read()
    fgmask = fgbg.apply(frame)
    cv2.imshow('frame', fgmask)
    if cv2.waitKey(30) == 27:
        break
cap.release()
cv2.destroyAllWindows()

运行前面的程序并查看输出。 在这两个程序中,我们都将创建fgbg对象,并使用apply()函数来计算前景遮罩,即fgmask。 然后,我们只是使用imshow()函数实时显示前景遮罩。 可以在前面的屏幕快照中看到此代码的预期输出。 运行程序并自己查看输出。

计算光流

光流(也称为,称为光流)是视频(实时或录制)中对象运动时出现的模式。 注意前一句中的外观一词。 这意味着,如果观察者(在我们的示例中为摄像机)处于运动中,则场景中的对象即使在静止时也被视为正在移动。 这称为相对运动。 简而言之,光流突出显示了视频中的相对运动。 OpenCV 具有许多可以计算光流的功能的实现。 cv2.calcOpticalFlowFarneback()函数使用密集方法计算光流。 这意味着它将计算所有点的流量。 此函数实现 Gunner Farneback 算法。

注意:

您可以通过以下 URL 阅读有关 Gunner Farneback 参数的更多信息:

http://www.diva-portal.org/smash/get/diva2:273847/FULLTEXT01.pdfTwo-Frame

让我们看看如何使用以下代码使用 OpenCV 和 Python 3 计算光流:

import cv2
import numpy as np
cap = cv2.VideoCapture(0)
ret, frame1 = cap.read()
prvs = cv2.cvtColor(frame1,
                    cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[..., 1] = 255
while(cap):
    ret, frame2 = cap.read()
    next = cv2.cvtColor(frame2,
                        cv2.COLOR_BGR2GRAY)
    flow = cv2.calcOpticalFlowFarneback(prvs,
                                       next,
                                       None, 0.5,
                                       3, 15,
                                       3, 5,
                                       1.2, 0)
    mag, ang = cv2.cartToPolar(flow[..., 0],
                               flow[..., 1])
    hsv[..., 0] = ang * 180/np.pi/2
    hsv[..., 2] = cv2.normalize(mag, None, 0,
                                255, cv2.NORM_MINMAX)
    rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
    cv2.imshow('Optical Flow', rgb)
    if cv2.waitKey(1) == 27:
        break
    prvs = next
cap.release()
cv2.destroyAllWindows()

在前面的程序中, cv2.calcOpticalFlowFarneback()返回 XY(笛卡尔)系统中的流坐标。 然后,使用cv2.cartToPolar()函数将其转换为极性。 然后,色相显示运动的角度,而值显示最终 HSV 帧中运动的强度,该强度将转换为 BGR 并显示为输出。 输出看起来就像前面的屏幕快照所示。 唯一的区别是光流将由各种颜色表示。

光流的概念在以下领域具有应用:

  • 目标检测与跟踪
  • 运动检测和跟踪
  • 机器人导航

检测并跟踪运动

让我们构建一个用于使用 RPi,OpenCV 和 Python 实时检测和跟踪运动的系统。 我们将使用非常简单的技术来检测运动。 基本上,我们将计算视频源(视频文件或 USB 网络摄像头的实时源)的连续帧之间的差异。 然后,我们将在希望检测连续帧之间差异的像素区域周围绘制轮廓:

  1. 我们将从导入 OpenCV 和 NumPy 开始。 另外,初始化对应于 USB 网络摄像头的对象:

    import cv2
    
    import numpy as np
    
    cap = cv2.VideoCapture(0)
    
  2. 我们将对视频中的帧应用扩散操作。 为此,我们需要一个核。 我们将在视频循环之前定义它。 让我们定义如下:

    k = np.ones((3, 3), np.uint8)
    
  3. 以下代码捕获并在单独变量中存储连续帧:

    t0 = cap.read()[1]
    
    t1 = cap.read()[1]
    
  4. 现在,让我们为while循环编写该块。 在此块中,我们计算之前捕获的帧之间的绝对差。 我们将为此使用cv2.absdiff()函数。 然后,我们将计算出的绝对差转换为灰度以进行进一步处理:

    while(True):
    
    d=cv2.absdiff(t1, t0)
    
    grey = cv2.cvtColor(d, cv2.COLOR_BGR2GRAY)
    

    以下是前面代码的输出。 它显示了连续捕获的帧之间的绝对差的灰度:

    Figure 11.3 – Absolute difference between successive frames

    图 11.3 –连续帧之间的绝对差

  5. 我们在上一步中计算的输出有一些噪声。 因此,我们必须首先使用高斯模糊技术对其进行模糊处理以消除噪声:

    blur = cv2.GaussianBlur(grey, (3, 3), 0)
    
  6. 我们使用二进制阈值化技术将上一步的模糊输出转换为二进制图像,并通过以下代码进行进一步处理:

    ret, th = cv2.threshold(blur, 15, 255, cv2.THRESH_BINARY)
    
  7. 现在,让我们将膨胀形态学操作应用于该二进制图像。 这使检测阈值图像中的边界变得容易:

    dilated = cv2.dilate(th, k, iterations=2)
    

    以下是膨胀操作的输出:

    Figure 11.4 – Dilated output

    图 11.4 –扩展输出

  8. 让我们继续在膨胀图像中查找轮廓:

    contours, hierarchy = cv2.findContour(dilated,
    
    cv2.RETR_TREE,
    
    cv2.CHAIN_APPROX_SIMPLE)
    
    t2=t0
    
    cv2.drawContours(t2, contours, -1, (0, 255, 0), 2)
    
    cv2.imshow('Output', t2)
    
  9. 现在,让我们将最新的帧复制到保存较旧帧的变量,然后使用网络摄像头捕获下一帧:

    t0=t1
    
    t1=cap.read()[1]
    

    当按下键盘上的Esc键时,我们结束while循环:

    if cv2.waitKey(5) == 27 :
    
    break
    

    循环结束后,我们将执行通常的清理任务,例如释放相机捕获对象并破坏显示窗口:

    cap.release()
    
    cv2.destroyAllWindows()
    

    以下是执行程序的输出:

Figure 11.5 – Detected and highlighted movement

图 11.5 –检测到并突出显示的移动

请记住,此代码在计算上是昂贵的。 不要期望 RPi 的较老型号和非超频型号具有很高的帧率。 作为练习,绘制不同颜色的轮廓。 我们还可以借助cv2.moments()函数来计算质心,并用小圆圈表示它们。 这将使输出更有趣。

检测图像中的条形码

条形码是一种信息,可以直观地表示信息,对于特定用途的机器而言,易于理解。 条码格式很多。 常用格式具有不同厚度的平行垂直线,并且它们之间的间距不同。

在本节中,我们将演示如何从静止图像中检测简单的平行线格式的条形码。 我们将使用以下汽水罐图像:

图 11.6 –原始源图像

  1. 让我们使用以下代码读取汽水罐的原始图像:

    import numpy as np
    
    import cv2
    
    image=cv2.imread('/home/pi/book/dataset/barcode.jpeg', 1)
    
    input = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
  2. 条形码的水平图像具有低和高的垂直梯度。 因此,候选图像必须具有适合该标准的区域。 我们将使用cv2.Sobel()函数来计算水平和垂直导数,然后计算差值以找出符合条件的区域。 让我们看看如何做到这一点:

    hor_der = cv2.Sobel(input, ddepth=-1, dx=1, dy=0, ksize = 5)
    
    ver_der = cv2.Sobel(input, ddepth=-1, dx=0, dy=1, ksize=5)
    
    diff = cv2.subtract(hor_der, ver_der)
    
  3. OpenCV 提供cv2.convertScaleAbs()函数。 它将任何数字数组转换为 8 位无符号整数的数组。 让我们使用它,如下所示:

    diff = cv2.convertScaleAbs(diff)
    

    输出如下:

    Figure 11.7 – Difference between horizontal and vertical Sobel derivatives

    图 11.7 –水平和垂直 Sobel 导数之间的差异

  4. 前面的输出显示了具有非常水平和非常低的垂直梯度的区域。 让我们应用高斯模糊来消除前面输出的噪声。 使用以下代码执行此操作:

    blur = cv2.GaussianBlur(diff, (3, 3), 0)
    

    以下是上述代码的输出:

    Figure 11.8 – After applying a Gaussian blur

    图 11.8 –应用高斯模糊之后

  5. 现在,让我们通过对其应用阈值将其转换为二进制图像。 以下是执行此操作的代码:

    ret, th = cv2.threshold(blur, 225, 255, cv2.THRESH_BINARY)
    

    以下是输出二进制图像:

    Figure 11.9 – Binary output

    图 11.9 –二进制输出

  6. 如上图所示,它是二进制图像,突出显示了条形码和其他高垂直梯度区域。 我们可以扩大图像以进行进一步处理。 它填补了垂直线之间的空隙:

    dilated = cv2.dilate(th, None, iterations = 10)
    

    前面代码的输出包含许多与原始图像中的条形码和其他区域相对应的矩形框。 我们对包含条形码的区域感兴趣,而不对其他区域感兴趣:

    Figure 11.10 – Dilated binary output

    图 11.10 –扩展的二进制输出

  7. 形态腐蚀操作将消除与条形码不对应的其他大部分区域:

    eroded = cv2.erode(dilated, None, iterations = 15)
    

    以下是上述代码的输出:

    Fig.11.11 – Eroded image

    图 11.11 –腐蚀图像

  8. 让我们在此计算出的二进制图像中找到所有轮廓的列表。 使用以下代码来执行此操作:

    (contours, hierarchy) = cv2.findContours(eroded, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
  9. 二值图像中最大的轮廓是与条形码区域相对应的轮廓。 以下代码在图像中找到最大轮廓:

    areas = [cv2.contourArea(temp) for temp in contours]
    
    max_index = np.argmax(areas)
    
    largest_contour = contours[max_index]
    
  10. 让我们使用 OpenCV cv2.boundingRect()函数检索最大轮廓的边界矩形的坐标,然后在图像中绘制该矩形:

```py
x, y, width, height = cv2.boundingRect(largest_contour)
```

```py
cv2.rectangle(image, (x, y), (x+width, y+height),(0,255,0), 2)
```

```py
cv2.imshow('Detected Barcode',image)
```

```py
cv2.waitKey(0)
```

```py
cv2.destroyAllWindows()
```

前面的代码在与图像中最大轮廓(条形码的区域)相对应的区域上绘制边界矩形,如以下输出所示:

Figure 11.12 – Detected barcode

图 11.12 –检测到的条形码

如先前的屏幕截图所示,条形码的大致区域以蓝色矩形勾勒出轮廓。 相同的代码可能不适用于许多图像,但适用于大多数图像。 我们可能需要调整代码以检测其他图像中带有条形码的区域。 您可能需要为特定输入更改以下代码行:

blur = cv2.GaussianBlur(diff, (3, 3), 0)
dilated = cv2.dilate(th, None, iterations = 10)
eroded = cv2.erode(dilated, None, iterations = 15)

基于此程序,我们可以创建许多实际应用。 第一个应用是条形码区域检测器,用于检测来自 USB 网络摄像头的实时视频。 我们可以创建的另一个应用是检测条形码的通用程序。 为了调整传递给函数的参数,我们可以使用跟踪栏。

在下一节中,我们将学习如何使用 RPi 和 USB 网络摄像头通过 OpenCV 和 Python 3 应用胶片风格的色度键控。

实现色度键效果

色度键控也称为色度键合成。 由于我们在创建绿色或蓝色效果时会使用绿色或蓝色背景,因此俗称它为绿色或蓝色屏幕效果。 它是后期制作技术,也可以用于静止图像和实时视频。 在色度键效果中,我们将一个对象或一个人放在前景中并捕获图像或镜头。 背景通常是绿色或蓝色的织物或墙壁。 然后,我们将捕获的图像或素材中的绿色或蓝色替换为另一个视频或图像。 这使观看者感到前景中的人物或物体与他们拍摄的摄影棚不在同一个位置。 此效果是新闻广播中电影制作和实时天气预报中最常用的效果之一:

  1. 首先,导入所有需要的库并启动视频捕获对象:

    import numpy as np
    
    import cv2
    
    cap = cv2.VideoCapture(0)
    
  2. 为了获得更好的帧频或每秒帧FPS)速率,我们将 USB 网络摄像头的分辨率设置为640x480像素。 这样会产生更好的帧速率,并且绿屏效果看起来很自然:

    cap.set(3, 640)
    
    cap.set(4, 480)
    ```** 
    
  3. 在下一步中,我们读取将用作背景的图像。 用作背景的图像必须具有与网络摄像头设置的分辨率相同的分辨率。 在这种情况下,我们将其设置为640x480。 我们知道,我们将在 NumPy 数组上执行的所有算术和逻辑运算(在这种情况下,背景图像和来自连接到 RPi 的 USB 网络摄像头的实时馈送的帧)都需要操作数数组具有相同的尺寸 ; 否则,Python 3 解释器将引发错误。 以下是此代码:

    bg = cv2.imread('/home/pi/book/dataset/bg.jpg', 1)
    
  4. 让我们为循环编写熟悉的逻辑,并从 USB 网络摄像头读取实时供稿的帧,如下所示:

    while True:
    
    ret, frame = cap.read()
    

    我们可以使用绿色的布(例如窗帘)或纸作为本演示的背景。 我们还将 RPi 摄像机模块的框用作前景中的对象。 以下是原始场景的照片:

    Figure 11.13 – Input video

    图 11.13 –输入视频

  5. 正如我们在第 6 章,“颜色空间,变换和阈值”中所讨论的那样,当我们需要使用颜色范围时,HSV 颜色空间是表示颜色的最佳方法。 让我们将图像转换为 HSV 色彩空间,然后使用以下代码为背景中的绿色屏幕计算遮罩:

    hsv=cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    image_mask=cv2.inRange(hsv, np.array([40, 50, 50]),
    
    np.array([80, 255, 255]))
    

    这将计算图像的遮罩。 绿色(或其任何阴影)的像素被替换为颜色,其余像素被替换为黑色:

    Figure 11.14 – Computing the mask

    图 11.14 –计算遮罩

  6. 在计算出与背景图像相对应的遮罩之后,我们可以将该遮罩应用于背景中的图像,以便将具有黑色像素的对象隐藏在前景中,如下所示:

    bg_mask=cv2.bitwise_and(bg, bg, mask=image_mask)
    

    前面的代码将白色像素替换为我们选择作为背景的图像像素。 另外,与前景相对应的像素为黑色,如以下输出所示:

    图 11.15 –遮罩中的白色像素替换为背景

  7. 现在,我们必须从 USB 网络摄像头的实时供稿中提取前景。 我们可以使用以下代码来做到这一点:

    fg_mask=cv2.bitwise_and(frame, frame, mask=cv2.bitwise_not(image_mask))
    

    前面的代码提取所有非绿色(或其阴影)的像素。 还将黑色分配给与背景(绿色屏幕)相对应的像素:

    图 11.16 –遮罩中的黑色像素替换为前景

  8. 现在,是时候添加我们计算的最后两个输出了。 这将用自定义图像替换绿色背景,并产生色度键效果:

    cv2.imshow('Output', cv2.add(bg_mask, fg_mask))
    
    if cv2.waitKey(1) == 27:
    
    break
    
    cv2.destroyAllWindows()
    
    cap.release()
    

    以下是最终输出:

Figure 11.17 – Final output

图 11.17 –最终输出

恭喜你! 我们已经通过我们的 RPi 相机模块盒和一块绿色抹布实现了胶片风格的色度键效果。 我们可以看到效果并不完美。 这是由于照明效果不理想。 网络摄像头没有将几个像素注册为绿色像素,而是将其他颜色的像素注册。 对此的补救措施是使绿色背景和前景对象具有良好且均匀的照明。

实现色度键效果时,遵循的简单规则是,我们要进行色度键操作的对象的颜色不得与背景屏幕的颜色相同。 因此,如果我们使用的是绿色背景,则该对象或其任何部分都不能为绿色。 蓝色背景屏幕也是如此。

总结

在本章中,已经学习了如何使用我们在本书前几章中学到的计算机视觉中的概念和技术来演示实际应用。 利用我们在本章中学到的概念,我们可以编写一个用于创建简单安全应用的程序。

从现在开始,利用从本书实验中获得的知识,我们可以更详细地探索 OpenCV 的图像处理和计算机视觉领域。 我们围绕 OpenCV 库的旅程到此结束。

在下一章中,我们将学习如何为 Python 使用另一个功能强大但非常易于使用的计算机视觉库 Mahotas。 我们还将学习并演示如何使用 Jupyter 笔记本进行 Python 3 的科学编程。

十二、结合 Mahotas 和 Jupyter

在上一章中,我们了解了如何使用具有 OpenCV 和 Python 3 编程功能的 Raspberry Pi 在计算机视觉领域中实际应用的使用并进行了演示。

在本章中,我们将学习另一个计算机视觉库 Mahotas 的基础。 我们还将看一个 Jupyter 项目,并了解如何将 Jupyter 笔记本用于 Python 3 编程。 我们将在本章中学习的主题如下:

  • 用 Mahotas 处理图像
  • 结合 Mahotas 和 OpenCV
  • 其他流行的图像处理库
  • 探索适用于 Python 3 编程的 Jupyter 笔记本

遵循本章内容后,您将习惯于使用 Mahotas 进行图像处理。 您还可以使用 Jupyter 笔记本放心地运行 Python 3 程序。

技术要求

可以在 GitHub 上找到本章的代码文件。

观看以下视频,以查看这个页面上的“正在执行的代码”。

使用 Mahotas 处理图像

Mahotas 是一个 Python 库,用于图像处理和与计算机视觉相关的任务。 它是由路易斯·佩德罗(Luis Pedro)开发的。

它实现了许多与计算机视觉相关的算法。 它已经用 C++ 实现,并且可以在 NumPy 数组上运行。 它还具有适用于 Python 3 的简洁接口。

Mahotas 当前具有 100 多种图像处理和计算机视觉功能,并且随着每个版本的增加,这一数字还在不断增长。 该项目正在积极开发中,每隔几个月就会发布一个新版本。 除了增加的功能之外,每个新版本都带来了性能上的改进。

注意:

您可以通过访问这个页面了解有关 Mahotas 的更多信息。

我们可以使用以下命令在 Raspberry Pi 上安装mahotas

pip3 install mahotas

Mahotas 的组件将安装在/home/pi/.local/bin中。 我们可以将永久添加到PATH变量中,如下所示:

  1. 通过运行以下命令,以编辑方式打开~/.profile文件:

    nano ~/.profile
    
  2. 将以下行添加到末尾:

    PATH='$PATH:/home/pi/.local/bin'
    
  3. 重新启动 Raspberry Pi:

    sudo reboot
    

我们可以通过在命令提示符处运行以下命令来验证mahotas是否已成功安装:

python3 -c 'import mahotas'

如果此命令没有返回错误,则说明安装成功。 现在,让我们看一下使用 Mahotas 创建一些程序。

读取图像和内置图像

Mahotas 具有许多内置图像。 让我们看看如何使用它们。 在下面的代码中查看:

import matplotlib.pyplot as plt
import mahotas
photo = mahotas.demos.load('luispedro')
plt.imshow(photo)
plt.axis('off')
plt.show()

在前面的代码中, mahotas.demos.load()用于将内置图像加载到 NumPy 数组。 luispedro是该库作者的图像。 与 OpenCV 不同,Mahotas 以 RGB 格式读取和存储彩色图像。 我们还可以在灰度模式下加载和显示图像,如下所示:

photo = mahotas.demos.load('luispedro', as_grey=True)
plt.imshow(photo, cmap='gray')

我们可以加载其他库图像,如下所示:

photo = mahotas.demos.load('nuclear')
photo = mahotas.demos.load('lena')
photo = mahotas.demos.load('DepartmentStore')

我们还可以读取存储在磁盘上的图像,如下所示:

photo= mahotas.imread('/home/pi/book/dataset/4.1.01.tiff')

此函数的工作方式与cv2.imread() OpenCV 函数相同。

对图像应用阈值

我们已经知道阈值化的基础知识。 我们可以通过使用mahotas中提供的函数对灰度图像进行阈值处理。 让我们演示大津的二值化:

import matplotlib.pyplot as plt
import numpy as np
import mahotas
photo = mahotas.demos.load('luispedro', as_grey=True)
photo = photo.astype(np.uint8)
T_otsu = mahotas.otsu(photo)
plt.imshow(photo > T_otsu, cmap='gray')
plt.axis('off')
plt.show()

mahotas.otsu()函数接受灰度图像作为参数,并返回阈值。photo > T_otsu代码返回阈值图像。 以下是输出:

图 12.1 –大津的二值化

我们也可以使用 Riddler-Calvard 方法执行阈值化,如下所示:

T_rc = mahotas.rc(photo)
plt.imshow(photo > T_rc, cmap='gray')

mahotas.rc()函数接受灰度图像作为参数,并返回阈值。 photo > T_rc代码返回阈值图像。 运行此命令并检查输出。 它将使用 Riddler-Calvard 方法向我们显示阈值图像。

距离变换

距离变换是一种形态学操作。 最好使用二进制(0 和 1)图像进行可视化。 它将二进制图像转换为灰度图像,以使点的灰度强度可视化其距图像边界的距离。 mahotas.distance()函数接收图像并计算距离变换。 让我们看一个例子:

import matplotlib.pyplot as plt
import numpy as np
import mahotas
f = np.ones((256, 256), bool)
f[64:191, 64:191] = False
plt.subplot(121)
plt.imshow(f, cmap='gray')
plt.title('Original Image')
dmap = mahotas.distance(f)
plt.subplot(122)
plt.imshow(dmap, cmap='gray')
plt.title('Distance Transform')
plt.show()

这将创建一个正方形的自定义图像,在白色背景上填充有黑色。 然后,它计算距离变换并将其可视化。 这将产生以下输出:

Figure 12.2 – Distance transform demonstration

图 12.2 –距离变换演示

色彩空间

我们可以将 RGB 图像转换为棕褐色,如下所示:

import matplotlib.pyplot as plt
import mahotas
photo = mahotas.demos.load('luispedro')
photo = mahotas.colors.rgb2sepia(photo)
plt.imshow(photo)
plt.axis('off')
plt.show()

前面的代码从库中读取灰度图像,并使用rgb2sepia()函数的调用将其转换为具有棕褐色色彩空间的图像。 它接受图像作为参数并返回转换后的图像。 以下是,是先前程序的输出:

Figure 12.3 – A sepia image

图 12.3 –棕褐色图像

在下一节中,我们将学习如何结合 Mahotas 和 OpenCV 的代码。

结合 Mahotas 和 OpenCV

像 OpenCV 一样,Mahotas 使用 NumPy 数组存储并处理图像。 我们还可以将 OpenCV 和 Mahotas 结合起来。 让我们来看一个这样的示例,如下所示:

import cv2
import numpy as np
import mahotas as mh
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    T_otsu = mh.otsu(frame)
    output = frame > T_otsu
    output = output.astype(np.uint8) * 255
    cv2.imshow('Output', output)
    if cv2.waitKey(1) == 27:
        break
cv2.destroyAllWindows()
cap.release()

在前面的程序中,我们将实时帧转换为灰度版本。 然后,我们应用了大津二进制化的 Mahotas 实现,将来自实时视频源的帧转换为布尔二进制图像。 我们需要将其转换为np.uint8类型,然后将其乘以255(所有形式均采用二进制 8 位的形式),以便将其与cv2.imshow()。 输出如下:

Figure 12.4 – A screenshot of the output window

图 12.4 –输出窗口的屏幕截图

我们通常使用 OpenCV 功能从 USB 网络摄像头读取实时供稿。 然后,我们可以使用 Mahotas 或任何其他图像处理库中的函数来处理帧。 这样,我们可以合并来自两个不同图像处理库的代码。

在下一节中,我们将学习其他一些 Python 图像处理库的名称和 URL。

其他流行的图像处理库

Python 3 有许多第三方库。 这些库中许多都使用 NumPy 来处理图像。 让我们来看看可用库的列表:

这些都是基于 NumPy 的图像处理库。 Python 图像库及其维护良好的分支 Pillow 是非基于 NumPy 的图像处理库。 它们还具有在 NumPy 和 PIL 图像格式之间转换图像的接口。

我们可以结合使用各种库的代码来创建具有所需功能的各种计算机视觉应用。

在下一部分中,我们将探索 Jupyter 笔记本。

探索适用于 Python 3 编程的 Jupyter 笔记本

Jupyter 笔记本是基于的基于 Web 的交互界面,其工作方式类似于 Python 3 的交互模式。Jupyter 笔记本具有 40 种编程语言,包括 Python 3,R,Scala 和 Julia。 它为编程提供了一个交互式环境,该环境也可以具有可视化,富文本,代码和其他组件。

Jupyter 是 IPython 项目的一个分支。 IPython 的所有与语言无关的部分已移至 Jupyter,而 Jupyter 的 Python 相关功能由 IPython 内核提供。 让我们看看如何在 Raspberry Pi 上安装 Jupyter:

  1. 在命令提示符中逐一运行以下命令:

    sudo pip3 uninstall ipykernel 
    

    先前的命令将卸载ipykernel工具的早期版本。

  2. 以下命令将安装所有必需的库:

    sudo pip3 install ipykernel==4.8.0 
    
    sudo pip3 install jupyter 
    
    sudo pip3 install prompt-toolkit==2.0.5
    

    这些命令将在 Raspberry Pi 上安装 Jupyter 和必需的组件。

要启动 Jupyter 笔记本,请登录 Raspberry Pi 的图形环境(直接或使用远程桌面),然后在lxterminal中运行以下命令:

jupyter notebook

这将启动 Jupyter 笔记本服务器进程并打开具有 Jupyter 笔记本界面的 Web 浏览器窗口,如下所示:

Figure 12.5 – The startup directory

图 12.5 –启动目录

上一个屏幕截图显示了运行命令以启动的目录的目录结构。 我在当前章节/home/pi/book/chapter12的代码目录中运行了它。 我们运行命令的 LXTerminal 窗口显示服务器日志,如下所示:

Figure 12.6 – The Jupyter Notebook server log

图 12.6 – Jupyter 笔记本服务器日志

现在,让我们将返回到运行 Jupyter 的浏览器窗口。 在浏览器窗口的右上角,我们有注销和退出的选项。 在其下方,我们可以看到上传按钮,新建下拉菜单以及刷新符号。

在右侧,我们可以看到三个标签。 正如我们已经看到的,第一个显示了在命令提示符中启动 Jupyter 的目录结构。 第二个选项卡显示当前正在运行的进程。

让我们探索右侧的新建下拉选项。 以下屏幕截图显示了此菜单下可用的选项:

Figure 12.7 – The New menu dropdown

图 12.7 –新建菜单下拉菜单

笔记本部分下,我们可以看到 Python 3 的选项。 如果您有使用 Jupyter 的任何其他编程语言,那么这些语言也会在此处显示。 我们将很快进行探讨。 其他选项是文本文件文件夹终端其它下的前两个选项分别创建一个空白文件和一个空白目录。 单击终端,将在浏览器窗口的新选项卡中启动 LXTerminal,如以下屏幕截图所示:

Figure 12.8 – Command Prompt running in a web browser tab

图 12.8 –在 Web 浏览器选项卡中运行的命令提示符

如果我们单击原始选项卡(在浏览器选项卡中列为主页),然后在运行选项下进行检查,则可以看到对应于当前的终端窗口选项卡的一个条目,如以下屏幕截图所示:

Figure 12.9 – A list of running subprocesses in Jupyter

图 12.9 – Jupyter 中正在运行的子流程的列表

如我们所见,有选项可以查看在此服务器下启动的当前笔记本和终端。 我们可以从这里关闭它们。 转到文件,然后在新建下拉菜单下,选择 Python 3。 这将在同一浏览器窗口的新选项卡下打开 Python 3 笔记本:

Figure 12.10 – A new Jupyter Notebook tab

图 12.10 –新的 Jupyter 笔记本选项卡

我们可以看到笔记本的名称是Untitled。 我们可以单击名称,这将打开一个模式对话框,重命名笔记本,如下所示:

Figure 12.11 Renaming a notebook

图 12.11 重命名笔记本

重命名笔记本。 之后,在主页主页中的文件下,我们可以看到test01.ipynb文件。 在这里,IPYNB 表示一个 IPython 笔记本。 您也可以在运行选项卡中看到一个条目。 在/home/pi/book/chapter12/目录中,我们可以找到test01.ipynb文件。 现在,让我们看看如何使用此文件进行 Python 3 编程。 再次在浏览器中切换到test01笔记本选项卡。 让我们详细研究界面:

Figure 12.12 – A Jupyter notebook

图 12.12 – Jupyter 笔记本

In []:文本之后,我们可以看到一个较长的文本区域。 我们可以在此处编写代码段。 确保从菜单的下拉菜单中选择了代码。 然后,将以下代码添加到文本区域:

print('Hello World')

我们可以通过单击菜单栏中的运行按钮来运行它。 输出将被打印在此处,并且将出现一个新的文本区域。 光标将在此处自动设置:

Figure 12.13 – Running the Hello World! program

图 12.13 –运行 HelloWorld 程序

关于此笔记本的最好的事情是我们可以编辑并重新执行较早的单元。 让我们尝试了解菜单中的图标:

Figure 12.14: The icon buttons in the menu

图 12.14:菜单中的图标按钮

让我们从左到右。 第一个符号(软盘)将被保存。 +符号在当前突出显示的单元格之后添加一个文本区域单元格。 然后,我们有剪切,复制和粘贴选项。 上下箭头用于上下移动当前文本区域单元格。 然后,我们有运行并中断内核,重新启动内核,然后重新启动并运行整个笔记本按钮。 下拉框确定单元格的类型。 它具有以下四个选项:

Figure 12.15 – Types of cells

图 12.15 –单元格类型

如果希望单元运行代码,则选择代码Markdown 是用于 RTF 的标记语言。 选择一个空白文本区域单元,并将其更改为 Markdown 类型。 然后,在单元格中输入#Test并执行它。 这将创建一个一级标题,如下所示:

Figure 12.16 – A level 1 heading

图 12.16 – 1 级标题

我们可以将##用于第二级标题,将###用于三级标题,依此类推。

Jupyter 笔记本的主要功能之一是,我们甚至可以在笔记本中运行 OS 命令。 我们需要在命令前面加上!符号,然后将其作为代码在单元格中运行。 让我们看一下这个例子。 在笔记本中运行!ls -la命令,它将产生以下结果:

Figure 12.17 – Running OS commands in the Jupyter notebook

图 12.17 –在 Jupyter 笔记本中运行 OS 命令

我们也可以在笔记本中使用 Matplotlib 显示的可视化效果和图像。 为此,我们需要使用神奇的%matplotlib函数。 我们可以使用将 Matplotlib 的后端设置为 Jupyter 笔记本的内联后端,如下所示:

%matplotlib inline

让我们看一下这个的简短演示:

Figure 12.18 – Showing images in the Jupyter notebook

图 12.18 –在 Jupyter 笔记本中显示图像

这就是我们可以在笔记本本身中显示可视化效果和图像的方式。

这是一个非常有用的概念。 我们可以在一个笔记本中使用富文本,操作系统命令,Python 代码和输出(包括可视化)。 我们甚至可以电子方式共享这些 IPYNB 笔记本文件。 就像 Python 3 一样,我们可以将 Jupyter 笔记本与许多语言一起使用,例如 Julia,R 和 Scala。 唯一的限制是我们不能在一个笔记本中混合使用多种编程语言的代码。

我要解释的最后一件事是如何清除输出。 单击单元格菜单。 它看起来如下:

Figure 12.19 – Clearing all the output

图 12.19 –清除所有输出

当前输出所有输出下都具有清除选项。 这些分别清除当前单元格和整个笔记本的输出。

我建议您自己浏览菜单栏中的所有选项。

总结

在本章中,我们探讨了 Mahotas 的基础知识,它是一个基于 NumPy 的图像处理库。 我们研究了一些与图像处理相关的功能,并学习了如何结合 Mahotas 和 OpenCV 代码进行图像处理。 我们还了解了其他基于 NumPy 和非基于 NumPy 的图像处理库的名称。 您可以进一步探索这些库。

我们了解到的最后一个主题是 Jupyter 笔记本型原型和通过电子方式共享代码非常有用。 现在,许多计算机视觉和数据科学专业人员都将 Jupyter 笔记本用于其 Python 编程项目。

在本书的“附录”部分中,我解释了本章无法列出的所有主题。 这些主题对于将 Raspberry Pi 用于各种目的的任何人都将非常有用。

十三、附录

本书主要章节中未涵盖的所有主题都将在此进行介绍。 本附录主要是有用的主题的集合,包括提示和技巧。 因此,让我们看一些与 Raspberry Pi,Python 3 和 OpenCV 有关的技巧。

技术要求

可以在 GitHub 上找到本章的代码文件。

观看以下视频,以查看这个页面 上的“正在执行的代码”。

性能评估和 OpenCV 的管理

OpenCV 有很多优化和未优化的代码。 优化的代码使用了现代微处理器的功能,例如指令流水线和 AVX。

我们可以检查是否正在使用cv2.useOptimized()函数在当前使用的计算机上启用 OpenCV 优化。 我们还可以使用cv2.setUseOptimized()函数来切换优化。 cv2.getTickCount()函数返回自打开计算机后开始的时钟滴答数(也称为时钟周期)。 在执行我们感兴趣的代码段之前和之后调用此函数。

然后,我们计算时钟周期之间的差,并返回执行代码段所需的时钟周期数。 cv2.getTickFrequency()函数返回时钟周期的频率。 然后,我们可以将时钟周期之间的差除以时钟周期的频率,以获得执行代码段所需的时间:

import cv2
cv2.setUseOptimized(True)
print(cv2.useOptimized())
img = cv2.imread('/home/pi/book/dataset/4.1.01.tiff', 0)
e1 = cv2.getTickCount()
img1 = cv2.medianBlur(img, 23)
e2 = cv2.getTickCount()
t = (e2 - e1)/cv2.getTickFrequency()
print(t)

前面代码的输出如下:

True
0.004361807

我们还可以使用 Python 3 time库中的函数来确定运行任何代码段所需的时间。 试试看作为练习。 接下来,我们将看到如何重用 Raspbian OS microSD 卡。

复用 Raspbian OS microSD 卡

我们已经学会了使用 Win32 Disk Imager 将 Raspbian 操作系统写入 microSD 卡。 现在,我们将了解如何将 microSD 卡重用于其他用途。 将 microSD 卡插入 microSD 卡读取器,然后将其连接到 Windows PC。 它将显示两个分区。 其中只有一个是可读的,并且将其标记为启动。 它还应具有config.txt文件,其大小约为 250 MB。 另一个分区不可读。 我们无法将此 microSD 卡用于其他目的。 因此,我们需要使用一些工具来格式化该卡,然后才能将其再次用于任何其他目的。

使用 SD 卡格式化器格式化 SD 卡

有免费工具,用于格式化 SD 卡。 我们可以从这个页面下载。 安装此工具并打开它,它将显示以下窗口。 根据计算机上驱动器的数量,驱动器号可能会不同。 以下是该应用的屏幕截图:

Figure 13.1 – The SD card formatter

图 13.1 – SD 卡格式化程序

选择任何驱动器(它将始终格式化整个卡),然后单击格式化按钮上的。 这将显示以下确认框:

Figure 13.2 – Confirmation dialogue

图 13.2 –确认对话框

单击按钮,它将格式化卡。 格式化后,与该卡相对应的只有一个驱动器号。 该卡现在已完全格式化为,我们可以将其当作新的一样使用。

Windows 中的磁盘管理工具

我们甚至可以在 Windows 中使用磁盘管理工具来格式化 microSD 卡。 在搜索栏中,键入磁盘,您将找到创建并格式化磁盘分区选项。 您也可以从 Windows 控制面板找到此工具。 再次将您要重复使用的 Raspbian OS microSD 卡插入 SD 卡读取器,并将其连接到 Windows 计算机。 打开磁盘管理工具,您将看到以下屏幕快照:

Figure 13.3 – The Disk Management utility window

图 13.3 –“磁盘管理”工具窗口

此列表列出了连接到系统的所有磁盘(可移动磁盘和不可移动磁盘)。 其中,可移动的(具有 256 MB 的启动分区)是 microSD 卡。 如您在前面的屏幕快照中所见,我在不扩展文件系统的情况下插入了 Raspbian OS microSD 卡(我的意思是,我为其编写了 Raspbian OS,但没有使用它来启动 Raspberry Pi 板)。 这就是为什么显示两个已分配分区和一个未分配分区的原因。 如果您使用该卡来启动 Raspberry Pi 板,则它将扩展文件系统,并且第二大分区占用未分配的部分。 因此,使用过的 Raspbian OS microSD 卡仅显示两个分区。 无论如何,我们可以右键单击分配的分区,然后选择删除卷选项。 对两个分配的分区执行此操作:

Figure 13.4 – Deleting partitions of the SD card

图 13.4 –删除 SD 卡的分区

删除所有分配的部分后,磁盘将如下所示:

Figure 13.5 – Creating a new partition on the SD card

图 13.5 –在 SD 卡上创建新分区

只需右键单击与 microSD 卡相对应的磁盘,然后单击新建简单卷。 这将启动向导到新卷。 使用所有默认选项完成向导向导,然后单击,您将获得新的磁盘以供重复使用。 您可以重写 Raspbian OS 或使用它存储您喜欢的 MP3 歌曲。 磁盘管理工具使我们可以更好地控制磁盘格式化和分区的各个方面。

浏览raspi-config命令行工具

我们可以通过以下三种方法之一使用来配置 Raspberry Pi:

  • Raspbian OS 菜单中的 Raspberry Pi 配置工具
  • 通过更改/boot/config.txt的内容
  • 使用raspi-config命令行工具

我们将在本节中详细介绍raspi-config工具的。 打开 Raspberry Pi 命令提示符并运行以下命令:

sudo raspi-config

这将在命令提示符中打开 Raspberry Pi 配置工具,如以下屏幕截图所示:

Figure 13.6 – The main menu of the raspi-config utility

图 13.6 – raspi-config工具的主菜单

第一个选项用于更改pi用户的密码。 主菜单中的第二个选项网络选项可以更改 Raspberry Pi 板连接到网络的方式:

Figure 13.7 – The network options

图 13.7 –网络选项

主菜单中的第三个选项(引导选项)详细说明了引导选项,如下所示:

Figure 13.8 – Boot Options

图 13.8 –引导选项

主菜单中的第四个选项(本地化选项)使您可以如下设置区域设置,时区,键盘布局和 Wi-Fi 国家/地区:

Figure 13.9 – Localization Options

图 13.9 –本地化选项

主菜单中的第五个选项是接口选项,显示如下:

Figure 13.10 – Interfacing Options

图 13.10 –接口选项

在前面的选项中,我们已经为演示启用了 P1 摄像机P2 SSHP3 VNC

主菜单中的第六个选项用于对 Raspberry Pi 1 和 Raspberry Pi 2 超频。其他型号必须手动超频。

主菜单中的第七个选项是高级选项,如下所示:

Figure 13.11 – Advanced Options

图 13.11 –高级选项

在前面的屏幕截图中,A1 扩展文件系统扩展文件系统以确保 microSD 卡在中的所有空间均可用。 A3 内存分割用于为图形分配内存。

第八个选项更新raspi-config工具。 如果要访问 Raspberry Pi 板的命令提示符,则这是配置 Raspberry Pi 的最佳方法。

Windows,Debian 和 Ubuntu 上的安装和环境设置

我们可以在和 Windows 和 Linux OS 上展示在其他台式计算机上学到的所有领域。 由于与台式机主板通常通常没有 DSI 端口,因此只有与 Raspberry Pi 摄像头模块相关的部件不能与其他计算机一起使用。 我们还可以在运行 Debian 或 Ubuntu 的其他单板计算机上运行代码示例。

在 Ubuntu,Debian 及其衍生产品上,安装包的过程是相同的。 所有现代 Linux 发行版均随附 Python3。我们只需要使用aptpip3工具进行安装。

对于 Windows PC,我们需要从头开始安装所有内容。 让我们开始通过以下步骤来了解如何安装 Python 3:

  1. 访问 www.python.org 并下载最新 Python 3 版本的安装文件:

    Figure 13.12 – The Python Foundation home page

    图 13.12 – Python Foundation 主页

    运行下载的安装文件。 它将打开一个安装向导,如下所示:

    Figure 13.13 – The Python 3 installation options

    图 13.13 – Python 3 安装选项

    确保选中“将 Python 3.8 添加到PATH”复选框。

  2. 然后,点击“自定义安装”。 将出现以下窗口:

    Figure 13.14 - Optional features for installation

    图 13.14 –安装的可选功能

    选中所有复选框,然后单击下一个按钮。 在下一个窗口中,将所有选项保持为,并完成安装。

  3. 安装完成后,我们可以通过在 Windows 搜索栏中搜索IDLE来进行验证。 另外,在cmd(Windows 的命令提示符)中,我们可以验证pythonpip3命令是否正常运行。

Python 3 解释器以 Windows 二进制可执行文件python.exe文件的形式出现,如果我们在安装过程中检查了适当的选项,则可以直接在命令提示符下调用它,如前所述。 我们可以通过命令提示符上的pip3工具安装本书前面各章中使用的所有包。

Python 实现和 Python 发行版

Python 实现是充当 Python 编程语言解释器的程序。 这个页面提供的解释器和 Linux 附带的是,称为 CPython。 其他流行的实现包括(但不限于)以下:

  • MicroPython
  • IronPython
  • Stackless Python
  • Jython
  • PyPy
  • CircuitPython

我们可以在这个页面找到替代的实现及其项目 URL 的列表。

Python 发行版是 Python 解释器的实现,还有一组捆绑在一起的其他包。 一些 Python 实现本身就是发行版。 实际上,术语实现与分发之间没有明确的区别。 我们可以在这个页面上找到有关发行版的更多信息。