035-Linux高效运维工具

[toc]

Linux高效运维工具

1: 系统性能,资源

1.1 top

1.2 htop

  • yum -y install htop

1.3 btop/gotop

1.4 系统资源监控-NMON

NMON 是一种在 AIX 与各种 Linux 操作系统上广泛使用的监控与分析工具.

go zap日志库

go zap日志库笔记

1: 概述

  • go zap 高性能日志库;

2: 创建实例

  • 通过调用 zap.NewProduction()/zap.NewDevelopment()或者 zap.Example()创建一个 Logger。这三个方法的区别在于它将记录的信息不同,参数只能是 string 类型
  • 三种创建方式对比:
  • Example 和 Production 使用的是 json 格式输出,Development 使用行的形式输出;
  • Development:
    • 从警告级别向上打印到堆栈中来跟踪
    • 始终打印包/文件/行(方法)
    • 在行尾添加任何额外字段作为 json 字符串
    • 以大写形式打印级别名称
    • 以毫秒为单位打印 ISO8601 格式的时间戳
  • Development
    • 从警告级别向上打印到堆栈中来跟踪
    • 始终打印包/文件/行(方法)
    • 在行尾添加任何额外字段作为 json 字符串
    • 以大写形式打印级别名称
    • 以毫秒为单位打印 ISO8601 格式的时间戳
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//代码
var log *zap.Logger
log = zap.NewExample()
log, _ := zap.NewDevelopment()
log, _ := zap.NewProduction()
log.Debug("This is a DEBUG message")
log.Info("This is an INFO message")

//Example 输出
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}

//Development 输出
2018-10-30T17:14:22.459+0800 DEBUG development/main.go:7 This is a DEBUG message
2018-10-30T17:14:22.459+0800 INFO development/main.go:8 This is an INFO message

//Production 输出
{"level":"info","ts":1540891173.3190675,"caller":"production/main.go:8","msg":"This is an INFO message"}
{"level":"info","ts":1540891173.3191047,"caller":"production/main.go:9","msg":"This is an INFO message with fields","region":["us-west"],"id":2}
}

3: 格式化输出

  • zap 有两种类型,分别是zap.Loggerzap.SugaredLogger,它们惟一的区别是,我们通过调用主 logger 的. Sugar()方法来获取一个 SugaredLogger,然后使用 SugaredLogger 以 printf 格式记录语句,例如:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var sugarLogger *zap.SugaredLogger

func InitLogger() {
logger, _ := zap.NewProduction()
sugarLogger = logger.Sugar()
}

func main() {
InitLogger()
defer sugarLogger.Sync()
sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
}

4: 写入文件

  • 默认情况下日志都会打印到应用程序的 console 界面,但是为了方便查询,可以将日志写入文件,但是我们不能再使用前面创建实例的 3 个方法,而是使用zap.New()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"os"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	writeSyncer, _ := os.Create("./info.log")         //日志文件存放目录
	encoderConfig := zap.NewProductionEncoderConfig() //指定时间格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)               //获取编码器,NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
	log = zap.New(core, zap.AddCaller())                              //AddCaller()为显示文件名和行号
	log.Info("hello world")
	log.Error("hello world")
}


// log输出结果:
2020-12-16T17:53:30.466+0800 INFO geth/main.go:18 hello world
2020-12-16T17:53:30.486+0800 ERROR geth/main.go:19 hello world

5: 同时输出控制台和文件

  • 如果需要同时输出控制台和文件,只需要改造一下 zapcore.NewCore 即可,示例:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	// 获取编码器,NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 文件writeSyncer
	fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./info.log", //日志文件存放目录
		MaxSize:    1,            //文件大小限制,单位MB
		MaxBackups: 5,            //最大保留日志文件数量
		MaxAge:     30,           //日志文件保留天数
		Compress:   false,        //是否压缩处理
	})
	fileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer, zapcore.AddSync(os.Stdout)), zapcore.DebugLevel) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	log = zap.New(fileCore, zap.AddCaller()) //AddCaller()为显示文件名和行号

	log.Info("hello world")
	log.Error("hello world")
}

6: 文件切割

  • 日志文件会随时间越来越大,为了避免日志文件把硬盘空间占满,需要按条件对日志文件进行切割,zap 包本身不提供文件切割的功能,但是可以用 zap 官方推荐的lumberjack包处理
1
2
3
4
5
6
7
8
//文件writeSyncer
fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./info.log", //日志文件存放目录,如果文件夹不存在会自动创建
MaxSize: 1, //文件大小限制,单位MB
MaxBackups: 5, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})

7: 按级别写入文件

  • 为了管理人员的查询方便,一般我们需要将低于 error 级别的放到 info.log,error 及以上严重级别日志存放到 error.log 文件中,我们只需要改造一下 zapcore.NewCore 方法的第 3 个参数,然后将文件 WriteSyncer 拆成 info 和 error 两个即可,示例:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

import (
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	var coreArr []zapcore.Core

	// 获取编码器
	encoderConfig := zap.NewProductionEncoderConfig()            //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder        //指定时间格式
	encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
	// encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 日志级别
	highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //error级别
		return lev >= zap.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
		return lev < zap.ErrorLevel && lev >= zap.DebugLevel
	})

	// info文件writeSyncer
	infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/info.log", //日志文件存放目录,如果文件夹不存在会自动创建
		MaxSize:    1,                //文件大小限制,单位MB
		MaxBackups: 5,                //最大保留日志文件数量
		MaxAge:     30,               //日志文件保留天数
		Compress:   false,            //是否压缩处理
	})
	infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	// error文件writeSyncer
	errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/error.log", //日志文件存放目录
		MaxSize:    1,                 //文件大小限制,单位MB
		MaxBackups: 5,                 //最大保留日志文件数量
		MaxAge:     30,                //日志文件保留天数
		Compress:   false,             //是否压缩处理
	})
	errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	coreArr = append(coreArr, infoFileCore)
	coreArr = append(coreArr, errorFileCore)
	log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller()为显示文件名和行号,可省略

	log.Info("hello info")
	log.Debug("hello debug")
	log.Error("hello error")
}
  • 这样修改之后,info 和 debug 级别的日志就存放到 info.log,error 级别的日志单独放到 error.log 文件中了

8: 控制台按级别显示颜色

  • 指定编码器的 EncodeLevel 即可
1
2
3
4
5
//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
encoder := zapcore.NewConsoleEncoder(encoderConfig)

9: 显示文件路径和行号

  • 前面说到要显示文件路径和行号,只需要 zap.New 方法添加参数 zap.AddCaller()即可,如果要显示完整的路径,需要在编码器配置中指定
1
2
3
4
5
6
//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
encoder := zapcore.NewConsoleEncoder(encoderConfig)

10: 完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import (
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	var coreArr []zapcore.Core

	// 获取编码器
	encoderConfig := zap.NewProductionEncoderConfig()            //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder        //指定时间格式
	encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
	// encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 日志级别
	highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //error级别
		return lev >= zap.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
		return lev < zap.ErrorLevel && lev >= zap.DebugLevel
	})

	// info文件writeSyncer
	infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/info.log", //日志文件存放目录,如果文件夹不存在会自动创建
		MaxSize:    2,                //文件大小限制,单位MB
		MaxBackups: 100,              //最大保留日志文件数量
		MaxAge:     30,               //日志文件保留天数
		Compress:   false,            //是否压缩处理
	})
	infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
	// error文件writeSyncer
	errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/error.log", //日志文件存放目录
		MaxSize:    1,                 //文件大小限制,单位MB
		MaxBackups: 5,                 //最大保留日志文件数量
		MaxAge:     30,                //日志文件保留天数
		Compress:   false,             //是否压缩处理
	})
	errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	coreArr = append(coreArr, infoFileCore)
	coreArr = append(coreArr, errorFileCore)
	log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller()为显示文件名和行号,可省略

	log.Info("hello info")
	log.Debug("hello debug")
	log.Error("hello error")
}

git初始化新仓库

1: 使用 docker 安装配置 gitlab

  • 设置默认分支。默认是main,更改为master

2: 初始化新仓库

  • 在 gitlab 创建新仓库
  • 把本地目录上传到这个仓库
1
2
3
4
5
6
7
cd xxx
git init
git add -A
git commit -m "init"
git remote add origin http://47.115.218.14:9980/ms/oems-test-v3.git
git branch -M master
git push -uf origin master
  • You are not allowed to force push code to a protected branch on this project.主要原因是因为向一个受保护的分支强制提交了代码,可以在仓库里面进行设置来解决这个问题.

使用 docker 安装配置 gitlab

使用 docker 安装配置 gitlab

1: 下载 gitlab images

  • Gitlab是一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面进行访问公开的或者私人项目。它拥有与 Github 类似的功能,能够浏览源码,管理缺陷和注释,可以管理团队对仓库的访问,它非常易于浏览提交的版本并提供一个文件历史库。团队成员可以利用内置的简单的聊天程序进行交流。它还提供一个代码片段收集功能可以实现代码复用。

005-10分钟搞定pandas+实例

1:什么是 pandas

先看-> 10 分钟搞定 pandas+实例

转自: https://pyzh.readthedocs.io/en/latest/python-pandas.html 本文是 pandas 官网 10 Minutes to pandas 的翻译 。

go实现并发文件传输,断点续传

go实现并发文件传输,断点续传

1: 概述

很多文件基本都是多协程下载文件要么就只有单协程的断点续传,这里给大家写一个支持有进度条的多协程下载文件,也支持断点续传

2: 使用示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package main

import (
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"sync"

	"github.com/qianlnk/pgbar"
)

/**
* 需求:
1. 多协程并发下载文件
2. 断点续连
**/
func main() {
	//获取要下载文件
	DownloadFileName := "./Charles.4.5.6.zip"
	//copy的文件
	copyFileName := "./test.zip"
	storgeFileName := "./downloadRate.txt"
	//打开文件
	sfile, err := os.Open(DownloadFileName)
	if err != nil {
		panic(err)
	}
	defer sfile.Close()
	//获取文件大小
	info, _ := sfile.Stat()
	downloadSize := info.Size()
	var scount int64 = 1
	if downloadSize%5 == 0 {
		scount *= 5
	} else {
		scount *= 10
	}
	//分给每个协程的大小
	si := downloadSize / scount
	fmt.Printf("文件总大小:%v, 分片数:%v,每个分片大小:%v\n", downloadSize, scount, si)
	//open copy file
	copyFile, err := os.OpenFile(copyFileName, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		panic(err)
	}
	storgeFile, err := os.OpenFile(storgeFileName, os.O_CREATE|os.O_RDWR, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer copyFile.Close()

	var currentIndex int64 = 0
	wg := sync.WaitGroup{}
	fmt.Println("文件下载进度条...")
	pgb := pgbar.New("")
	for ; currentIndex < scount; currentIndex++ {
		wg.Add(1)
		go func(current int64) {
			p := pgb.NewBar(fmt.Sprintf("%02d", (current+1))+"st", int(si))
			p.SetSpeedSection(900, 100)
			b := make([]byte, 10240)
			bs := make([]byte, 16)
			currentIndex, _ := storgeFile.ReadAt(bs, current*16)
			//取出所有整数
			reg := regexp.MustCompile(`\d+`)
			countStr := reg.FindString(string(bs[:currentIndex]))
			total, _ := strconv.ParseInt(countStr, 10, 0)
			progressBar := 1
			for {
				if total >= si {
					wg.Done()
					break
				}
				//从指定位置开始读
				n, err := sfile.ReadAt(b, current*si+total)
				if err == io.EOF {
					wg.Done()
					break
				}
				//从指定位置开始写
				copyFile.WriteAt(b, current*si+total)
				storgeFile.WriteAt([]byte(strconv.FormatInt(total, 10)+" "), current*16)
				total += int64(n)
				if total >= si/10*int64(progressBar) {
					progressBar += 1
					p.Add(int(si / 10))
				}
			}

		}(currentIndex)
	}
	wg.Wait()
	storgeFile.Close()
	os.Remove(storgeFileName)
	fmt.Printf("\n下载完成...\n")
}

goplugin功能

Go Plugin插件(so动态库)

1: 官方实现

  • golang 1.8 及以上版本提供了一个创建共享库(shared object)的新工具,称为 Plugins。
  • 目前 Plugins 仅在 Linux、FreeBSD 和 macOS 上受支持,且只支持 golang 调用。

2: 使用示例,定义一个 plugin.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"log"
)

func init() {
	log.Println("plugin init")
}

type SayHello struct {
}

func (s *SayHello) CallMe(name string) string {
	log.Println("hello ", name)
	return "I am plugin"
}

// SayHelloPlugin 导出变量
var SayHelloPlugin SayHello

/*
使用 -buildmode=plugin 模式编译出 plugin.so 共享库
go build -o plugin.so -buildmode=plugin plugin.go

*/
  • 使用 -buildmode=plugin 模式编译出 plugin.so 共享库;
  • go build -o plugin.so -buildmode=plugin plugin.go

3: main.go 中调用插件(共享库)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
	"log"
	"plugin"
)

type CustomPlugin interface {
	CallMe(name string) string
}

func main() {
	// 打开插件(并发安全)
	p, err := plugin.Open("plugin.so")
	if err != nil {
		panic(err)
	}
	// 在插件中搜索可导出的变量或函数
	sayHelloPlugin, err := p.Lookup("SayHelloPlugin")
	if err != nil {
		panic(err)
	}
	// 断言插件类型
	if sayHello, ok := sayHelloPlugin.(CustomPlugin); ok {
		log.Println(sayHello.CallMe("togettoyou"))
	}
}

// go run main.go
// # 输出
// 2021/07/28 17:07:21 plugin init
// 2021/07/28 17:07:21 hello  togettoyou
// 2021/07/28 17:07:21 I am plugin

4:定义一个插件共享库总结:

1
2
3
4
5
6
7
package 包名需要定义为 main

必须有可导出的变量或函数

不需要 main 函数

插件加载时会先执行 init 函数

5:Traefik Yaegi 实现

  • yaegi 是一个 go 解释器;
  • https://github.com/traefik/yaegi
  • Yaegi 运行在 Go 运行时之上,可以直接作为嵌入式解释器,或使用交互式 shell ,解释运行 Go 代码

5.1 代码结构

  • 这里有个注意点,Yaegi 的插件需要放在 src 目录下;
1
2
3
4
5
6
7
8
9
  go.mod
  go.sum
  main.go

└─plugin
    └─src
        └─hello
                go.mod
                hello.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package hello

import (
 "fmt"
)

func init() {
 fmt.Println("hello plugin init")
}

func CallMe(msg string) string {
 fmt.Println(msg)
 return "I am plugin"
}

5.3 main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
 "fmt"
 "github.com/traefik/yaegi/interp"
 "github.com/traefik/yaegi/stdlib"
)

func main() {
 // 初始化解释器
 i := interp.New(interp.Options{GoPath: "./plugin/"})

 // 插件需要使用标准库
 if err := i.Use(stdlib.Symbols); err != nil {
  panic(err)
 }

 // 导入 hello 包
 if _, err := i.Eval(`import "hello"`); err != nil {
  panic(err)
 }

 // 调用 hello.CallMe
 v, err := i.Eval("hello.CallMe")
 if err != nil {
  panic(err)
 }
 callMe := v.Interface().(func(string) string)
 fmt.Println(callMe("togettoyou"))
}
go run main.go

# 输出
hello plugin init
togettoyou
I am plugin

0091-python好用的脚本

1:图片处理

1.1 截图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#! /usr/bin/env python3
# encoding=utf-8


from mss import mss

import PIL.ImageGrab


def screenshot_1():
    """python 截屏方法1
    mss
    """
    with mss() as screenshot:
        screenshot.shot(output="scr.png")


def screenshot_2():
    """python 截屏方法2
    pillow
    """
    scr = PIL.ImageGrab.grab()
    scr.save("scr.png")


def main():
    print("this is main function")
    # screenshot_1()
    screenshot_2()


if __name__ == "__main__":
    main()
    print('__name__ value:', __name__)

1.2 将图像转换为素描图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 图像转换
import cv2
# 读取图片
img = cv2.imread("img.jpg")
# 灰度
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
invert = cv2.bitwise_not(grey)
# 高斯滤波
blur_img = cv2.GaussianBlur(invert, (7, 7), 0)
inverse_blur = cv2.bitwise_not(blur_img)
sketch_img = cv2.divide(grey, inverse_blur, scale=256.0)
# 保存
cv2.imwrite('sketch.jpg', sketch_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

2:

3:

4:

5:

6:

7:

8:

9: