分类 软件开发 下的文章

巧用 Bash 脚本程序能帮助你完成很多极具挑战的任务。

 title=

系统管理员经常写脚本程序,不论长短,这些脚本可以完成某种任务。

你是否曾经查看过某个软件发行方提供的安装用的 脚本 script 程序?为了能够适应不同用户的系统配置,顺利完成安装,这些脚本程序经常包含很多函数和逻辑分支。多年来,我积累了一些改进脚本程序的一些技巧,这里分享几个,希望能对朋友们也有用。这里列出一组短脚本示例,展示给大家做脚本样本。

初步尝试

我尝试写一个脚本程序时,原始程序往往就是一组命令行,通常就是调用标准命令完成诸如更新网页内容之类的工作,这样可以节省时间。其中一个类似的工作是解压文件到 Apache 网站服务器的主目录里,我的最初脚本程序大概是下面这样:

cp january_schedule.tar.gz /usr/apache/home/calendar/
cd /usr/apache/home/calendar/
tar zvxf january_schedule.tar.gz

这帮我节省了时间,也减少了键入多条命令操作。时日久了,我掌握了另外的技巧,可以用 Bash 脚本程序完成更难的一些工作,比如说创建软件安装包、安装软件、备份文件系统等工作。

1、条件分支结构

和众多其他编程语言一样,脚本程序的条件分支结构同样是强大的常用技能。条件分支结构赋予了计算机程序逻辑能力,我的很多实例都是基于条件逻辑分支。

基本的条件分支结构就是 if 条件分支结构。通过判定是否满足特定条件,可以控制程序选择执行相应的脚本命令段。比如说,想要判断系统是否安装了 Java ,可以通过判断系统有没有一个 Java 库目录;如果找到这个目录,就把这个目录路径添加到可运行程序路径,也就可以调用 Java 库应用了。

if [ -d "$JAVA_HOME/bin" ] ; then
    PATH="$JAVA_HOME/bin:$PATH"

2、限定运行权限

你或许想只允许特定的用户才能执行某个脚本程序。除了 Linux 的权限许可管理,比如对用户和用户组设定权限、通过 SELinux 设定此类的保护权限等,你还可以在脚本里设置逻辑判断来设置执行权限。类似的情况可能是,你需要确保只有网站程序的所有者才能执行相应的网站初始化操作脚本。甚至你可以限定只有 root 用户才能执行某个脚本。这个可以通过在脚本程序里设置逻辑判断实现,Linux 提供的几个环境变量可以帮忙。其中一个是保存用户名称的变量 $USER, 另一个是保存用户识别码的变量 $UID 。在脚本程序里,执行用户的 UID 值就保存在 $UID 变量里。

用户名判别

第一个例子里,我在一个带有几个应用服务器实例的多用户环境里指定只有用户 jboss1 可以执行脚本程序。条件 if 语句主要是判断,“要求执行这个脚本程序的用户不是 jboss1 吗?”当此条件为真时,就会调用第一个 echo 语句,接着是 exit 1,即退出这个脚本程序。

if [ "$USER" != 'jboss1' ]; then
     echo "Sorry, this script must be run as JBOSS1!"
     exit 1
fi
echo "continue script"

根用户判别

接下来的例子是要求只有根用户才能执行脚本程序。根用户的用户识别码(UID)是 0,设置的条件判断采用大于操作符(-gt),所有 UID 值大于 0 的用户都被禁止执行该脚本程序。

if [ "$UID" -gt 0 ]; then
     echo "Sorry, this script must be run as ROOT!"
     exit 1
fi
echo "continue script"

3、带参数执行程序

可执行程序可以附带参数作为执行选项,命令行脚本程序也是一样,下面给出几个例子。在这之前,我想告诉你,能写出好的程序并不只是写出我们想要它执行什么的程序,程序还需要不执行我们不要它执行的操作。如果运行程序时没有提供参数造成程序缺少足够信息,我愿意脚本程序不要做任何破坏性的操作。因而,程序的第一步就是确认命令行是否提供了参数,判定的条件就是参数数量 $# 是否为 0 ,如果是(意味着没有提供参数),就直接终止脚本程序并退出操作。

if [ $# -eq 0 ]; then
    echo "No arguments provided"
    exit 1
fi
echo "arguments found: $#"

多个运行参数

可以传递给脚本程序的参数不止一个。脚本使用内部变量指代这些参数,内部变量名用非负整数递增标识,也就是 $1$2$3 等等递增。我只是扩展前面的程序,并在下面一行输出显示用户提供的前三个参数。显然,要针对所有的每个参数有对应的响应需要更多的逻辑判断,这里的例子只是简单展示参数的使用。

echo $1 $2 $3

我们在讨论这些参数变量名,你或许有个疑问,“参数变量名怎么跳过了 $0,(而直接从$1 开始)?”

是的,是这样,这是有原因的。变量名 $0 确实存在,也非常有用,它储存的是被执行的脚本程序的名称。

echo $0

程序执行过程中有一个变量名指代程序名称,很重要的一个原因是,可以在生成的日志文件名称里包含程序名称,最简单的方式应该是调用一个 echo 语句。

echo test >> $0.log

当然,你或许要增加一些代码,确保这个日志文件存放在你希望的路径,日志名称包含你认为有用的信息。

4、交互输入

脚本程序的另一个好用的特性是可以在执行过程中接受输入,最简单的情况是让用户可以输入一些信息。

echo "enter a word please:"
read word
echo $word

这样也可以让用户在程序执行中作出选择。

read -p "Install Software ?? [Y/n]: " answ
if [ "$answ" == 'n' ]; then
    exit 1
fi
    echo "Installation starting..."

5、出错退出执行

几年前,我写了个脚本,想在自己的电脑上安装最新版本的 Java 开发工具包(JDK)。这个脚本把 JDK 文件解压到指定目录,创建更新一些符号链接,再做一下设置告诉系统使用这个最新的版本。如果解压过程出现错误,在执行后面的操作就会使整个系统上的 Java 破坏不能使用。因而,这种情况下需要终止程序。如果解压过程没有成功,就不应该再继续进行之后的更新操作。下面语句段可以完成这个功能。

tar kxzmf jdk-8u221-linux-x64.tar.gz -C /jdk --checkpoint=.500; ec=$?
if [ $ec -ne 0 ]; then
     echo "Installation failed - exiting."
     exit 1
fi

下面的单行语句可以给你快速展示一下变量 $? 的用法。

ls T; ec=$?; echo $ec

先用 touch T 命令创建一个文件名为 T 的文件,然后执行这个单行命令,变量 ec 的值会是 0。然后,用 rm T 命令删除文件,再执行该单行命令,变量 ec 的值会是 2,因为文件 T 不存在,命令 ls 找不到指定文件报错。

在逻辑条件里利用这个出错标识,参照前文我使用的条件判断,可以使脚本文件按需完成设定操作。

结语

要完成复杂的功能,或许我们觉得应该使用诸如 Python、C 或 Java 这类的高级编程语言,然而并不尽然,脚本编程语言也很强大,可以完成类似任务。要充分发挥脚本的作用,有很多需要学习的,希望这里的几个例子能让你意识到脚本编程的强大。


via: https://opensource.com/article/20/1/improve-bash-scripts

作者:Alan Formy-Duval 选题:lujun9972 译者:fisherue 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

一致性可以避免当你有多个开发人员开发同一个项目时出现问题。

 title=

当你有多个不同开发环境的开发人员在一个项目上工作时,编码和测试的不一致性是一种风险。Visual Studio Code(VS Code)是一个集成开发环境(IDE),可以帮助减少这些问题。它可以和容器结合起来,为每个应用程序提供独立的开发环境,同时提供一个一致的开发环境。

VS Code 的 “Remote - Containers” 扩展 使你能够创建一个容器定义,使用该定义来构建一个容器,并在容器内进行开发。这个容器定义可以和应用程序代码一起被签入到源代码库中,这使得所有的开发人员可以使用相同的定义在容器中进行构建和开发。

默认情况下,“Remote - Containers” 扩展使用 Docker 来构建和运行容器,但使用 Podman 的容器运行环境环境也很容易,它可以让你使用 免 root 容器

本文将带领你完成设置,通过 Podman 在免 root 容器内使用 VS Code 和 “Remote - Containers” 扩展进行开发。

初始配置

在继续之前,请确保你的红帽企业 Linux(RHEL)或 Fedora 工作站已经更新了最新的补丁,并且安装了 VS Code 和 “Remote - Containers” 扩展。(参见 VS Code 网站了解更多安装信息)

接下来,用一个简单的 dnf install 命令来安装 Podman 和它的支持包:

$ sudo dnf install -y podman

安装完 Podman 后,配置 VS Code 以使用 Podman 的可执行文件(而不是 Docker)与容器进行交互。在 VS Code 中,导航到 “文件 > 首选项 > 设置”,点击 “扩展” 旁边的 “>” 图标。在出现的下拉菜单中,选择 “Remote - Containers”,并向下滚动找到 “Remote - Containers: Docker Path” 选项。在文本框中,用 “podman” 替换 “docker”。

 title=

现在配置已经完成,在 VS Code 中为该项目创建一个新的文件夹或打开现有的文件夹。

定义容器

本教程以创建 Python 3 开发的容器为例。

“Remote - Containers” 扩展可以在项目文件夹中添加必要的基本配置文件。要添加这些文件,通过在键盘上输入 Ctrl+Shift+P 打开命令面板,搜索 “Remote-Containers: Add Development Container Configuration Files”,并选择它。

 title=

在接下来的弹出窗口中,定义你想设置的开发环境的类型。对于这个例子的配置,搜索 “Python 3” 定义并选择它。

 title=

接下来,选择将在容器中使用的 Python 的版本。选择 “3 (default)” 选项以使用最新的版本。

 title= option")

Python 配置也可以安装 Node.js,但在这个例子中,取消勾选 “Install Node.js”,然后点击 “OK”。

 title=

它将创建一个 .devcontainer 文件夹,包含文件devcontainer.jsonDockerfile。VS Code 会自动打开devcontainer.json 文件,这样你就可以对它进行自定义。

启用免 root 容器

除了明显的安全优势外,以免 root 方式运行容器的另一个原因是,在项目文件夹中创建的所有文件将由容器外的正确用户 ID(UID)拥有。要将开发容器作为免 root 容器运行,请修改 devcontainer.json 文件,在它的末尾添加以下几行:

"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,Z",
"workspaceFolder": "/workspace",

"runArgs": ["--userns=keep-id"],
"containerUser": "vscode"

这些选项告诉 VS Code 用适当的 SELinux 上下文挂载工作区,创建一个用户命名空间,将你的 UID 和 GID 原样映射到容器内,并在容器内使用 vscode 作为你的用户名。devcontainer.json 文件应该是这样的(别忘了行末的逗号,如图所示):

 title=

现在你已经设置好了容器的配置,你可以构建容器并打开里面的工作空间。重新打开命令调板(用 Ctrl+Shift+P),并搜索 “Remote-Containers: Rebuild and Reopen in Container”。点击它,VS Code 将开始构建容器。现在是休息一下的好时机(拿上你最喜欢的饮料),因为构建容器可能需要几分钟时间:

 title=

一旦容器构建完成,项目将在容器内打开。在容器内创建或编辑的文件将反映在容器外的文件系统中,并对这些文件应用适当的用户权限。现在,你可以在容器内进行开发了。VS Code 甚至可以把你的 SSH 密钥和 Git 配置带入容器中,这样提交代码就会像在容器外编辑时那样工作。

接下来的步骤

现在你已经完成了基本的设置和配置,你可以进一步加强配置的实用性。比如说:

  • 修改 Dockerfile 以安装额外的软件(例如,所需的 Python 模块)。
  • 使用一个定制的容器镜像。例如,如果你正在进行 Ansible 开发,你可以使用 Quay.ioAnsible Toolset。(确保通过 Dockerfile 将 vscode 用户添加到容器镜像中)
  • .devcontainer 目录下的文件提交到源代码库,以便其他开发者可以利用容器的定义进行开发工作。

在容器内开发有助于防止不同项目之间的冲突,因为隔离了不同项目的依赖关系及代码。你可以使用 Podman 在免 root 环境下运行容器,从而提高安全性。通过结合 VS Code、“Remote - Containers” 扩展和 Podman,你可以轻松地为多个开发人员建立一个一致的环境,减少设置时间,并以安全的方式减少开发环境的差异带来的错误。


via: https://opensource.com/article/21/7/vs-code-remote-containers-podman

作者:Brant Evans 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

让用户用命令行选项调整你的 Java 应用程序运行方式。

 title=

通常向终端中输入命令时,无论是启动 GUI 应用程序还是仅启动终端应用程序,都可以使用 命令行选项 options or switches or flags 以下简称选项)来修改应用程序的运行方式。这是 POSIX 规范 设定的标准,因此能够检测和解析选项对 Java 程序员而言是很有用的技能。

Java 中有若干种解析选项的方法,其中我最喜欢用的是 Apache Commons CLI 库,简称 commons-cli

安装 commons-cli

如果你使用类似 Maven 之类的项目管理系统以及 集成开发环境 Integrated Development Environment (简称 IDE),可以在项目属性(比如 pom.xml 配置文件或者 Eclipse 和 NetBeans 的配置选项卡)中安装 Apache Commons CLI 库。

而如果你采用手动方式管理库,则可以从 Apache 网站下载 该库的最新版本。下载到本地的是几个捆绑在一起的 JAR 文件,你只需要其中的一个文件 commons-cli-X.Y.jar(其中 X 和 Y 代指最新版本号)。把这个 JAR 文件或手动或使用 IDE 添加到项目,就可以在代码中使用了。

将库导入至 Java 代码

在使用 commons-cli 库之前,必须首先导入它。对于本次选项解析的简单示例而言,可以先在 Main.java 文件中简单写入以下标准代码:

package com.opensource.myoptparser;

import org.apache.commons.cli.*;

public class Main {
    public static void main(String[] args) {
    // code 
    }
}

至此在 Java 中解析选项的准备工作已经做好了。

在 Java 中定义布尔选项

要实现解析选项,首先要定义应用程序可接收的有效选项。使用 Option(注意是单数)类来创建选项对象,使用 Options(注意是复数)类来追踪项目中创建的所有选项。

首先为选项创建一个组,按照惯例命名为 options

    //code
    Options options = new Options();

接下来,通过列出短选项(即选项名简写)、长选项(即全写)、默认布尔值(LCTT 译注:设置是否需要选项参数,指定为 false 时此选项不带参,即为布尔选项)和帮助信息来定义选项,然后设置该选项是否为必需项(LCTT 译注:下方创建 alpha 对象的代码中未手动设置此项),最后将该选项添加到包含所有选项的 options 组对象中。在下面几行代码中,我只创建了一个选项,命名为 alpha

    //define options
    Option alpha = new Option("a", "alpha", false, "Activate feature alpha");
    options.addOption(alpha);

在 Java 中定义带参选项

有时用户需要通过选项提供 truefalse 以外的信息,比如给出配置文件、输入文件或诸如日期、颜色这样的设置项值。这种情况可以使用 builder 方法,根据选项名简写为其创建属性(例如,-c 是短选项,--config 是长选项)。完成定义后,再将定义好的选项添加到 options 组中:

    Option config = Option.builder("c").longOpt("config")
        .argName("config")
        .hasArg()
        .required(true)
        .desc("set config file").build();
    options.addOption(config);

builder 函数可以用来设置短选项、长选项、是否为必需项(本段代码中必需项设置为 true,也就意味着用户启动程序时必须提供此选项,否则应用程序无法运行)、帮助信息等。

使用 Java 解析选项

定义并添加所有可能用到的选项后,需要对用户提供的参数进行迭代处理,检测是否有参数同预设的有效短选项列表中的内容相匹配。为此要创建命令行 CommandLine 本身的一个实例,其中包含用户提供的所有参数(包含有效选项和无效选项)。为了处理这些参数,还要创建一个 CommandLineParser 对象,我在代码中将其命名为 parser。最后,还可以创建一个 HelpFormatter 对象(我将其命名为 helper),当参数中缺少某些必需项或者用户使用 --help-h 选项时,此对象可以自动向用户提供一些有用的信息。

    // define parser
    CommandLine cmd;
    CommandLineParser parser = new BasicParser();
    HelpFormatter helper = new HelpFormatter();

最后,添加一些条件判断来分析用户提供的选项,我们假设这些选项已经作为命令行输入被获取并存储在 cmd 变量中。这个示例应用程序有两种不同类型的选项,但对这两种类型都可以使用 .hasOption 方法加上短选项名称来检测选项是否存在。检测到一个存在的选项后,就可以对数据做进一步操作了。

try {
    cmd = parser.parse(options, args);
    if(cmd.hasOption("a")) {
    System.out.println("Alpha activated");
    }

    if (cmd.hasOption("c")) {
    String opt_config = cmd.getOptionValue("config");
    System.out.println("Config set to " + opt_config);
    }
} catch (ParseException e) {
    System.out.println(e.getMessage());
    helper.printHelp("Usage:", options);
    System.exit(0);
}

解析过程有可能会产生错误,因为有时可能缺少某些必需项如本例中的 -c--config 选项。这时程序会打印一条帮助信息,并立即结束运行。考虑到此错误(Java 术语中称为异常),在 main 方法的开头要添加语句声明可能的异常:

public static void main(String[] args) throws ParseException {

示例程序至此就大功告成了。

测试代码

你可以通过调整传递给代码的默认参数来在 IDE 中测试应用程序,或者创建一个 JAR 文件并在终端运行测试。这个过程可能会因 IDE 的不同而不同。具体请参阅相应的 IDE 文档,以及我写过的关于如何创建 JAR 文件的文章,或者参考 Daniel Oh 的关于如何使用 Maven 执行同样操作的文章。

首先,省略必需项 -c--config 选项,检测解析器的异常处理:

$ java -jar dist/myapp.jar                 
Missing required option: c
usage: Usage:
 -a,--alpha             Activate feature alpha
 -c,--config <config>   Set config file

然后提供输入选项再进行测试:

java -jar dist/myantapp.jar --config foo -a
Alpha activated
Config set to foo

选项解析

为用户提供选项功能对任何应用程序来说都是很重要的。有了 Java 和 Apache Commons,要实现这个功能并不难。

以下是完整的演示代码,供读者参考:

package com.opensource.myapp;

import org.apache.commons.cli.*;

public class Main {
    
    /**
     * @param args the command line arguments
     * @throws org.apache.commons.cli.ParseException
     */ 
    public static void main(String[] args) throws ParseException {
        // define options
        Options options = new Options();
        
        Option alpha = new Option("a", "alpha", false, "Activate feature alpha");
        options.addOption(alpha);
        
        Option config = Option.builder("c").longOpt("config")
                .argName("config")
                .hasArg()
                .required(true)
                .desc("Set config file").build();
        options.addOption(config);
     
        // define parser
        CommandLine cmd;
        CommandLineParser parser = new BasicParser();
        HelpFormatter helper = new HelpFormatter();

        try {
            cmd = parser.parse(options, args);
            if(cmd.hasOption("a")) {
                System.out.println("Alpha activated");
            }
          
            if (cmd.hasOption("c")) {
                String opt_config = cmd.getOptionValue("config");
                System.out.println("Config set to " + opt_config);
            }
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            helper.printHelp("Usage:", options);
            System.exit(0);
        }
    }
}

使用 Java 和选项

选项使用户可以调整命令的工作方式。使用 Java 时解析选项的方法有很多,其中之一的 commons-cli 是一个强大而灵活的开源解决方案。记得在你的下一个 Java 项目中尝试一下哦。


via: https://opensource.com/article/21/8/java-commons-cli

作者:Seth Kenlon 选题:lujun9972 译者:unigeorge 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

封面图片选自文森特·梵高的《星空》,公共领域,通过维基共享资源发布

技术世界每天都在变化,对计算机视觉、人工智能和机器学习的需求也在增加。让计算机和手机能够看到周围环境的技术被称为 计算机视觉。这个重新创造人眼的工作始于 50 年代。从那时起,计算机视觉技术有了长足的发展。计算机视觉已经通过不同的应用进入了我们的手机。这篇文章将介绍 Fedora Linux 上的 OpenCV

什么是 OpenCV?

OpenCV( 开源计算机视觉库 Open Source Computer Vision Library )是一个开源的计算机视觉和机器学习软件库。OpenCV 的建立是为了给计算机视觉应用提供一个通用的基础设施,并加速机器感知在商业产品中的应用。它有超过 2500 种优化后的算法,其中包括一套全面的经典和最先进的计算机视觉和机器学习算法。这些算法可用于检测和识别人脸、识别物体、对视频中的人类行为进行分类,并建立标记,将其与增强现实叠加等等。

opencv.org – about

在 Fedora Linux 上安装 OpenCV

要开始使用 OpenCV,请从 Fedora Linux 仓库中安装它:

$ sudo dnf install opencv opencv-contrib opencv-doc python3-opencv python3-matplotlib python3-numpy

注意: 在 Fedora Silverblue 或 CoreOS 上,Python 3.9 是核心提交的一部分。用以下方法安装 OpenCV 和所需工具:

rpm-ostree install opencv opencv-doc python3-opencv python3-matplotlib python3-numpy

接下来,在终端输入以下命令,以验证 OpenCV 是否已经安装:

$ python
Python 3.9.6 (default, Jul 16 2021, 00:00:00)
[GCC 11.1.1 20210531 (Red Hat 11.1.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2 as cv
>>> print( cv.__version__ )
4.5.2
>>> exit()

当你输入 print 命令时,应该显示当前的 OpenCV 版本,如上图所示。这表明 OpenCV 和 Python-OpenCV 库已经成功安装。

此外,如果你想用 Jupyter Notebook 做笔记和写代码,并了解更多关于数据科学工具的信息,请查看早期的 Fedora Magazine 文章:Fedora 中的 Jupyter 和数据科学

开始使用 OpenCV

安装完成后,使用 Python 和 OpenCV 库加载一个样本图像(按 S 键以 png 格式保存图像的副本并完成程序):

$ cp /usr/share/opencv4/samples/data/starry_night.jpg .
$ python starry_night.py

starry_night.py 的内容:

import cv2 as cv
import sys
img = cv.imread(cv.samples.findFile("starry_night.jpg"))
if img is None:
    sys.exit("Could not read the image.")
cv.imshow("Display window", img)
k = cv.waitKey(0)
if k == ord("s"):
    cv.imwrite("starry_night.png", img)

通过在 cv.imread 函数中添加参数 0,对图像进行灰度处理,如下所示。

img = cv.imread(cv.samples.findFile("starry_night.jpg"),0)

这些是一些可以用于 cv.imread 函数的第二个参数的替代值:

  • cv2.IMREAD_GRAYSCALE0:以灰度模式加载图像。
  • cv2.IMREAD_COLOR** 或1`:以彩色模式载入图像。图像中的任何透明度将被移除。这是默认的。
  • cv2.IMREAD_UNCHANGED** 或-1`:载入未经修改的图像。包括 alpha 通道。

使用 OpenCV 显示图像属性

图像属性包括行、列和通道的数量、图像数据的类型、像素的数量等等。假设你想访问图像的形状和它的数据类型。你可以这样做:

import cv2 as cv

img = cv.imread(cv.samples.findFile("starry_night.jpg"))
print("Image size is", img.shape)
print("Data type of image is", img.dtype)

Image size is (600, 752, 3)
Data type of image is uint8

print(f"Image 2D numpy array \n {img}")

Image 2D numpy array
 [[[0 0 0]
  [0 0 0]
  [0 0 0]
  ...
  [0 0 0]
  [0 0 0]
  [0 0 0]]

 [[0 0 0]
  [0 0 0]
  [0 0 0]
  ...
  • img.shape:返回一个行数、列数和通道数的元组(如果是彩色图像)。
  • img.dtype:返回图像的数据类型。

接下来用 Matplotlib 显示图像:

import cv2 as cv
import matplotlib.pyplot as plt
img = cv.imread(cv.samples.findFile("starry_night.jpg"),0)
plt.imshow(img)
plt.show()

发生了什么?

该图像是作为灰度图像读入的,但是当使用 Matplotlib 的 imshow 函数时,它不一定会以灰度显示。这是因为 imshow 函数默认使用不同的颜色映射。要指定使用灰度颜色映射,请将 imshow 函数的第二个参数设置为 cmap='gray',如下所示:

plt.imshow(img,cmap='gray')

这个问题在以彩色模式打开图片时也会发生,因为 Matplotlib 期望图片为 RGB(红、绿、蓝)格式,而 OpenCV 则以 BGR(蓝、绿、红)格式存储图片。为了正确显示,你需要将 BGR 图像的通道反转。

import cv2 as cv
import matplotlib.pyplot as plt
img = cv.imread(cv.samples.findFile("starry_night.jpg"),cv.IMREAD_COLOR)
fig, (ax1, ax2) = plt.subplots(1,2)
ax1.imshow(img)
ax1.set_title('BGR Colormap')
ax2.imshow(img[:,:,::-1])
ax2.set_title('Reversed BGR Colormap(RGB)')
plt.show()

分割和合并颜色通道

import cv2 as cv
import matplotlib.pyplot as plt

img = cv.imread(cv.samples.findFile("starry_night.jpg"),cv.IMREAD_COLOR)
b,g,r = cv.split(img)

fig,ax = plt.subplots(2,2)

ax[0,0].imshow(r,cmap='gray')
ax[0,0].set_title("Red Channel");
ax[0,1].imshow(g,cmap='gray')
ax[0,1].set_title("Green Channel");
ax[1,0].imshow(b,cmap='gray')
ax[1,0].set_title("Blue Channel");

# Merge the individual channels into a BGR image
imgMerged = cv.merge((b,g,r))
# Show the merged output
ax[1,1].imshow(imgMerged[:,:,::-1])
ax[1,1].set_title("Merged Output");
plt.show()

  • cv2.split:将一个多通道数组分割成几个单通道数组。
  • cv2.merge:将几个数组合并成一个多通道数组。所有的输入矩阵必须具有相同的大小。

注意: 白色较多的图像具有较高的颜色密度。相反,黑色较多的图像,其颜色密度较低。在上面的例子中,红色的密度是最低的。

转换到不同的色彩空间

cv2.cvtColor 函数将一个输入图像从一个颜色空间转换到另一个颜色空间。在 RGB 和 BGR 色彩空间之间转换时,应明确指定通道的顺序(RGB2BGRBGR2RGB)。注意,OpenCV 中的默认颜色格式通常被称为 RGB,但它实际上是 BGR(字节是相反的)。 因此,标准(24 位)彩色图像的第一个字节将是一个 8 位蓝色分量,第二个字节是绿色,第三个字节是红色。然后第四、第五和第六个字节将是第二个像素(蓝色、然后是绿色,然后是红色),以此类推。

import cv2 as cv
import matplotlib.pyplot as plt
img = cv.imread(cv.samples.findFile("starry_night.jpg"),cv.IMREAD_COLOR)
img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()

更多信息

关于 OpenCV 的更多细节可以在在线文档中找到。

感谢阅读。


via: https://fedoramagazine.org/use-opencv-on-fedora-linux-part-1/

作者:Onuralp SEZER 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

优化查询语句不过是一项简单的工程,而非什么高深的黑魔法。

 title=

许多人将数据库查询语句的调优视作哈利波特小说中某种神秘的“黑魔法”;使用错误的咒语,数据就会从宝贵的资源变成一堆糊状物。

实际上,对关系数据库系统的查询调优是一项简单的工程,其遵循的规则或启发式方法很容易理解。查询优化器会翻译你发送给 MySQL 实例的查询指令,然后将这些启发式方法和优化器已知的数据信息结合使用,确定获取所请求数据的最佳方式。再读一下后面这半句:“优化器已知的数据信息。”查询优化器需要对数据所在位置的猜测越少(即已知信息越多),它就可以越好地制定交付数据的计划。

为了让优化器更好地了解数据,你可以考虑使用索引和直方图。正确使用索引和直方图可以大大提高数据库查询的速度。这就像如果你按照食谱做菜,就可以得到你喜欢吃的东西;但是假如你随意在该食谱中添加材料,最终得到的东西可能就不那么尽如人意了。

基于成本的优化器

大多数现代关系型数据库使用 基于成本的优化器 cost-based optimizer 来确定如何从数据库中检索数据。该成本方案是基于尽可能减少非常耗费资源的磁盘读取过程。数据库服务器内的查询优化器代码会在得到数据时对这些数据的获取进行统计,并构建一个获取数据的历史模型。

但历史数据是可能会过时的。这就好像你去商店买你最喜欢的零食,然后突然发现零食涨价或者商店关门了。服务器的优化进程可能会根据旧信息做出错误的假设,进而制定出低效的查询计划。

查询的复杂性可能会影响优化。优化器希望提供可用的最低成本查询方式。连接五个不同的表就意味着有 5 的阶乘(即 120)种可能的连接组合。代码中内置了启发式方法,以尝试对所有可能的选项进行快捷评估。MySQL 每次看到查询时都希望生成一个新的查询计划,而其他数据库(例如 Oracle)则可以锁定查询计划。这就是向优化器提供有关数据的详细信息至关重要的原因。要想获得稳定的性能,在制定查询计划时为查询优化器提供最新信息确实很有效。

此外,优化器中内置的规则可能与数据的实际情况并不相符。没有更多有效信息的情况下,查询优化器会假设列中的所有数据均匀分布在所有行中。没有其他选择依据时,它会默认选择两个可能索引中较小的一个。虽然基于成本的优化器模型可以制定出很多好的决策,但最终查询计划并不是最佳方案的情况也是有可能的。

查询计划是什么?

查询计划 query plan 是指优化器基于查询语句产生的,提供给服务器执行的计划内容。查看查询计划的方法是在查询语句前加上 EXPLAIN 关键字。例如,以下查询要从城市表(city)和相应的国家表(country)中获得城市名称(和所属国家名称),城市表和国家表通过国家唯一代码连接。本例中仅查询了英国的字母顺序前五名的城市:

SELECT city.name AS 'City',
               country.name AS 'Country'
FROM city
JOIN country ON (city.countrycode = country.code)
WHERE country.code = 'GBR'
LIMIT 5;

在查询语句前加上 EXPLAIN 可以看到优化器生成的查询计划。跳过除输出末尾之外的所有内容,可以看到优化后的查询:

SELECT `world`.`city`.`Name` AS `City`,
                'United Kingdom' AS `Country`
FROM `world`.`city`
JOIN `world`.`country`
WHERE (`world`.`city`.`CountryCode` = 'GBR')
LIMIT 5;

看下比较大的几个变化, country.name as 'Country' 改成了 'United Kingdom' AS 'Country'WHERE 子句从在国家表中查找变成了在城市表中查找。优化器认为这两个改动会提供比原始查询更快的结果。

索引

在 MySQL 世界中,你会听到索引或键的概念。不过,索引是由键组成的,键是一种识别记录的方式,并且大概率是唯一的。如果将列设计为键,优化器可以搜索这些键的列表以找到所需的记录,而无需读取整个表。如果没有索引,服务器必须从第一列的第一行开始读取每一行数据。如果该列是作为唯一索引创建的,则服务器可以直接读取该行数据并忽略其余数据。索引的值(也称为基数)唯一性越强越好。请记住,我们在寻找更快获取数据的方法。

MySQL 默认的 InnoDB 存储引擎希望你的表有一个主键,并按照该键将你的数据存储在 B+ 树中。“不可见列”是 MySQL 最近添加的功能,除非在查询中明确指明该不可见列,否则不会返回该列数据。例如,SELECT * FROM foo; 就不会返回任何不可见列。这个功能提供了一种向旧表添加主键的方法,且无需为了包含该新列而重写所有查询语句。

更复杂的是,有多种类型的索引,例如函数索引、空间索引和复合索引。甚至在某些情况下,你还可以创建这样一个索引:该索引可以为查询提供所有请求的信息,从而无需再去访问数据表。

本文不会详细讲解各种索引类型,你只需将索引看作指向要查询的数据记录的快捷方式。你可以在一个或多个列或这些列的一部分上创建索引。我的医师系统就可以通过我姓氏的前三个字母和出生日期来查找我的记录。使用多列时要注意首选唯一性最强的字段,然后是第二强的字段,依此类推。“年-月-日”的索引可用于“年-月-日”、“年-月”和“年”搜索,但不适用于“日”、“月-日”或“年-日”搜索。考虑这些因素有助于你围绕如何使用数据这一出发点来设计索引。

直方图

直方图就是数据的分布形式。如果你将人名按其姓氏的字母顺序排序,就可以对姓氏以字母 A 到 F 开头的人放到一个“逻辑桶”中,然后将 G 到 J 开头的放到另一个中,依此类推。优化器会假定数据在列内均匀分布,但实际使用时多数情况并不是均匀的。

MySQL 提供两种类型的直方图:所有数据在桶中平均分配的等高型,以及单个值在单个桶中的等宽型。最多可以设置 1,024 个存储桶。数据存储桶数量的选择取决于许多因素,包括去重后的数值量、数据倾斜度以及需要的结果准确度。如果桶的数量超过某个阈值,桶机制带来的收益就会开始递减。

以下命令将在表 t 的列 c1 上创建 10 个桶的直方图:

ANALYZE TABLE t UPDATE HISTOGRAM ON c1 WITH 10 BUCKETS;

想象一下你在售卖小号、中号和大号袜子,每种尺寸的袜子都放在单独的储物箱中。如果你想找某个尺寸的袜子,就可以直接去对应尺寸的箱子里找。MySQL 自从三年前发布 MySQL 8.0 以来就有了直方图功能,但该功能却并没有像索引那样广为人知。与索引不同,使用直方图插入、更新或删除记录都不会产生额外开销。而如果更新索引,就必须更新 ANALYZE TABLE 命令。当数据变动不大并且频繁更改数据会降低效率时,直方图是一种很好的方法。

选择索引还是直方图?

对需要直接访问的且具备唯一性的数据项目使用索引。虽然修改、删除和插入操作会产生额外开销,但如果数据架构正确,索引就可以方便你快速访问。对不经常更新的数据则建议使用直方图,例如过去十几年的季度结果。

结语

本文源于最近在 Open Source 101 会议 上的一次报告。报告的演示文稿源自 PHP UK Conferenc 的研讨会。查询调优是一个复杂的话题,每次我就索引和直方图作报告时,我都会找到新的可改进点。但是每次报告反馈也表明很多软件界中的人并不精通索引,并且时常使用错误。我想直方图大概由于出现时间较短,还没有出现像索引这种使用错误的情况。


via: https://opensource.com/article/21/5/mysql-query-tuning

作者:Dave Stokes 选题:lujun9972 译者:unigeorge 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

每种编程语言处理文件数据的方式不尽相同,Python 是这么干的。

 title=

有些数据是临时的,它们在应用程序运行时存储在内存中,然后丢弃。但是有些数据是持久的。它们存储在硬盘驱动器上供以后使用,而且它们通常是用户最关心的东西。对于程序员来说,编写代码读写文件是很常见的,但每种语言处理该任务的方式都不同。本文演示了如何使用 Python 处理文件数据。

安装 Python

在 Linux 上,你可能已经安装了 Python。如果没有,你可以通过发行版软件仓库安装它。例如,在 CentOS 或 RHEL 上:

$ sudo dnf install python3

在 macOS 上,你可以使用 MacPortsHomebrew 安装。在 Windows 上,你可以使用 Chocolatey 安装。

一旦安装了 Python,打开你最喜欢的文本编辑器,准备好写代码吧。

使用 Python 向文件中写入数据

如果你需要向一个文件中写入数据,记住有三个步骤:

  1. 打开
  2. 写入
  3. 关闭

这与你在计算机上编码、编辑照片或执行其他操作时使用的步骤完全相同。首先,打开要编辑的文档,然后进行编辑,最后关闭文档。

在 Python 中,过程是这样的:

f = open('example.txt', 'w')
f.write('hello world')
f.close()

这个例子中,第一行以模式打开了一个文件,然后用变量 f 表示,我使用了 f 是因为它在 Python 代码中很常见,使用其他任意有效变量名也能正常工作。

在打开文件时,有不同的模式:

  • w 代表写入
  • r+ 代表可读可写
  • a 表示追加

第二行表示向文件中写入数据,本例写入的是纯文本,但你可以写入任意类型的数据。

最后一行关闭了文件。

使用 with 语法写入数据

对于快速的文件交互,常用有一种简短的方法可以写入数据。它不会使文件保持打开状态,所以你不必记得调用 close() 函数。相反,它使用 with 语法:

with open('example.txt', 'a') as f:
    f.write('hello open source')

使用 Python 读取数据

如果你或你的用户需要通过应用程序需要向文件中写入一些数据,然后你需要使用它们,那么你就需要读取文件了。与写入类似,逻辑一样:

  1. 打开
  2. 读取
  3. 关闭

同样的,这个逻辑反映了你一开始使用计算机就已知的内容。阅读文档,你可以打开、阅读,然后关闭。在计算机术语中,“打开”文件意味着将其加载到内存中。

实际上,一个文本文件内容肯定不止一行。例如,你需要读取一个配置文件、游戏存档或乐队下一首歌曲的歌词,正如你打开一本实体书时,你不可能立刻读完整本书,代码也只能解析已经加载到内存中的文件。因此,你可能需要遍历文件的内容。

f = open('example.tmp', 'r')

for line in f:
    print(line)

f.close()

示例的第一行指明使用 模式打开一个文件,然后文件交由变量 f 表示,但就像你写数据一样,变量名是任意的。f 并没有什么特殊的,它只是单词 “file” 的最简表示,所以 Python 程序员会经常使用它。

在第二行,我们使用了 line,另一个任意变量名,用来表示 f 的每一行。这告诉 Python 逐行迭代文件的内容,并将每一行的内容打印到输出中(在本例中为终端或 IDLE)。

使用 with 语法读取数据

就像写入一样,使用 with 语法是一种更简短的方法读取数据。即不需要调用 close() 方法,方便地快速交互。

with open('example.txt', 'r') as f:
    for line in f:
        print(line)

文件和 Python

使用 Python 有很多方法向文件写入数据,包括用 JSON、YAML、TOML 等不同的格式写入。还有一个非常好的内置方法用于创建和维护 SQLite 数据库,以及许多库来处理不同的文件格式,包括 图像、音频和视频等。


via: https://opensource.com/article/21/7/read-write-files-python

作者:Seth Kenlon 选题:lujun9972 译者:MjSeven 校对:turbokernel

本文由 LCTT 原创编译,Linux中国 荣誉推出