爬取自己CSDN上所有的博客并转存为md文件

1.先考虑怎么获得自己所有博客的地址

a.打开自己的个人博客,尝试从HTML里提取有用信息,没能成功。

b.发现下滑后动态加载自己的博客,于是抓包,找到连接

zhuabaolianjie

c.发现连接中除了page=%d这一部分外,其余都相同,所以考虑使用for循环请求多个连接,并在返回数据中提取网址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
import re

N = 6
idname = 'wldcmzy'

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36 Edg/101.0.1210.32'
}
url_base = 'https://blog.csdn.net/community/home-api/v1/get-business-list?page={page}&size=20&businessType=lately&noMore=false&username={name}'.format(page = '{page}', name = idname)

lst = []
for i in range(1, N):
res = requests.get(url_base.format(page = i), headers = headers)
lst.extend(re.findall("https://blog.csdn.net/%s/article/details/[0-9]+" % idname, res.text))


2.写抓取博文的代码

由于本人太菜了,没有成功吧博文提取出来,所以去网上搜索到了一篇大佬的文章,这篇文章是爬取CSDN热门文章的,可以复制源码略加改动,打到爬自己博文的目的

中间还是遇到了一些错误的,毕竟自己的需求和原文有所不同,比如大佬的代码里没有处理特殊字符\t,以及使用BeautifSoup检索标签时需要在class_参数中加上"markdown_views prism-atom-one-dark"。这些问题可以很快被发现并且修正。

附上改后的代码:

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
@Author:survive
@Blog(个人博客地址): https://blog.csdn.net/haojie_duan

@File:csdn.py.py
@Time:2022/2/10 8:49

@Motto:我不知道将去何方,但我已在路上。——宫崎骏《千与千寻》

代码思路:
1.确定目标需求:将csdn文章内容保存成 html、PDF、md格式
- 1.1首先保存为html格式:获取列表页中所有的文章ur1地址,请求文章ur1地址获取我们需要的文章内容
- 1.2 通过 wkhtmitopdf.exe把html文件转换成PDF文件
- 1.3 通过 wkhtmitopdf.exe把html文件转换成md文件

2.请求ur1获取网页源代码
3.解析数据,提取自己想要内容
4.保存数据
5.转换数据类型把HTML转换成PDF、md文伴
"""


html_str = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
{article}
</body>
</html>
"""

import requests
import parsel
#import pdfkit #用来将html转为pdf
import re
import os
import urllib.parse
from bs4 import BeautifulSoup
import html2text #用来将html转换为md
import random

# user_agent库:每次执行一次访问随机选取一个 user_agent,防止过于频繁访问被禁止
USER_AGENT_LIST = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36"
]

class CSDNSpider():
def __init__(self, lst):
self.url = 'https://blog.csdn.net/csdndevelopers/category_10594816.html'
self.headers = {
'user-agent':random.choice(USER_AGENT_LIST)
}

self.lst = lst

def send_request(self, url):
response = requests.get(url=url, headers=self.headers)
response.encoding = "utf-8"
if response.status_code == 200:
return response

def parse_content(self, reponse):
html = reponse.text
selector = parsel.Selector(html)
href = selector.css('.column_article_list a::attr(href)').getall()
name = 00
for link in href:
print(link)
name = name + 1
# 对文章的url地址发送请求
response = self.send_request(link)
if response:
self.parse_detail(response, name)

def parse_detail(self, response, name):
html = response.text
# print(html)
selector = parsel.Selector(html)
title = selector.css('#articleContentId::text').get()
# content = selector.css('#content_views').get()

# 由于这里使用parsel拿到的网页文件,打开会自动跳转到csdn首页,并且不能转为pdf,就想在这里用soup
soup = BeautifulSoup(html, 'lxml')
with open('debugdir/' + str(name) + '.css', 'w') as f:
f.write(str(soup))
#print(soup)
content = soup.find('div',id="content_views",class_=["markdown_views prism-atom-one-dark", "markdown_views prism-atom-one-light" , "htmledit_views"]) #class_="htmledit_views"
print(content)
# print(content)
# print(title, content)
html = html_str.format(article=content)
self.write_content(html, title)

def write_content(self, content, name):

print(name)

html_path = "HTML/" + str(self.change_title(name)) + ".html"
pdf_path ="PDF/" + str(self.change_title(name))+ ".pdf"
md_path = "MD/" + str(self.change_title(name)) + ".md"

# 将内容保存为html文件
with open(html_path, 'w',encoding="utf-8") as f:
f.write(content)
print("正在保存", name, ".html")

# 将html文件转换成PDF文件
#config = pdfkit.configuration(wkhtmltopdf=r'G:\Dev\wkhtmltopdf\bin\wkhtmltopdf.exe')
#pdfkit.from_file(html_path, pdf_path, configuration=config)
#print("正在保存", name, ".pdf")
# os.remove(html_path)

# 将html文件转换成md格式
html_text = open(html_path, 'r', encoding='utf-8').read()
markdown = html2text.html2text(html_text)
with open(md_path, 'w', encoding='utf-8') as file:
file.write(markdown)
print("正在保存", name, ".md")

def change_title(self, title):
mode = re.compile(r'[\\\/\:\?\*\"\<\>\|\!\t]')
new_title = re.sub(mode,'_', title)

return new_title

def start(self):
#response = self.send_request(self.url)
#if response:
# self.parse_content(response)
for i, link in enumerate(self.lst):
print(i + 1, link)
response = self.send_request(link)
if response:
self.parse_detail(response, i)


if __name__ == '__main__':
csdn = CSDNSpider(lst)
csdn.start()


3.效果

虽然得到了所有博客的md文件,整体效果稍微差一点,比如代码框没有代码类型,代码框中回车较多会被截成好几份等,但是也可以接受

hexo框架下博客应用夏娜主题并略微魔改

应用夏娜主题

夏娜主题github连接

我直接进行一个主题README.md文件的搬

安装

1
git clone https://github.com/ShanaMaid/hexo-theme-shana themes/shana

配置

修改hexo根目录下的 _config.yml

1
2
3
language: zh-CN

theme: shana

同时将themes/shana/_source/tagscategories文件夹拷贝到hexo根目录下的source文件夹下

更新

1
2
3
cd themes/shana

git pull origin master

瞎搞和魔改过程

由于我不是很懂前端,所以不能随意更改博客,只能走一步看一步

更改网站的favicon

把你想改的图片放到hexo根目录下的source目录中,然后修改shana主图根目录下_config.yml配置文件favicon属性的值为图片名即可,如:

1
favicon: /favicon.png

修改博客标题、副标题、描述、作者等内容

在hexo根目录修改配置文件_config.yml对应内容即可。

1
2
3
4
5
6
7
8
# Site
title: 蓝湖畔的雨
subtitle: 'All tragedy erased, I see only wonders...'
description: ''
keywords:
author: StarsWhisper
language: zh-CN
timezone: ''

增加菜单

一开始应该只有主页、归档、分类、标签四个菜单,可以通过修改shana主题根目录中的_config.yml配置文件来实现添加菜单

1
2
3
4
5
6
7
menu:
Home: /
Archives: /archives
Categories: /categories
Tags: /tags
About: /about
rss: /atom.xml

仿照 themes/shana/_source/ 中的文件夹,建立about文件夹以及里面的内容,并把它扔到hexo根目录下的source文件夹下即可

若不在翻译文件中加入自定义的名称,自定义菜单选项会显示英文,所以我们在 \themes\shana\languages 目录下修改zh-CN.yml 添加如下内容

1
2
#你的自定义名称: 中文翻译
About: 关于啊啊啊

增加点击菜单后显示的内容,只需要在对应的index.md文件中增加内容即可

增加或修改右键菜单内容

在shana主题配置文件中找到gaymenu_item:,比如我的已经改成了

1
2
3
4
5
6
7
8
9
10
11
12
13
gaymenu_item:
- name: 公告
url : /announcement
- name: 标签
url : /tags
- name: 分类
url : /categories
- name: 归档
url : /archives
- name: 关于
url : /knightabout
- name: 传送门
url : /bridges

仿照修改就可以

修改右键菜单图标

\themes\shana\source\plugin\galmenu 目录下修改img.png文件,自己P一下就行。

原图:

img1

修改后:

img

社交连接和友情连接

修改shana配置文件, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 社交链接
social:
- url: https://github.com/Wldcmzy
name: Github
- url: https://blog.csdn.net/wldcmzy
name: CSDN
- url: https://space.bilibili.com/83743701
name: bilibili(无技术和学习内容)

# 友情链接
# name : url
link_title: 友情链接
links:
夏娜主题作者的博客: https://shanamaid.github.io/

修改头像

在shana配置文件中更改avatar属性的值为头像图片的url即可。

若想使用本地图片,则在 shana/source/ 目录下创建images文件夹,并把头像图片扔到里面,avatar的值这样写:

1
avatar: /images/avatar.png(这个是你的头像图片名)

修改背景图

\shana\source\plugin\bganimation 目录下,直接把你想用的背景图片放进去。

然后修改文件bg.css

下方代码中对应的图片名部分改为自己的背景图片名,也可以使用图片对应的url

1
2
3
4
5
6
.cb-slideshow li:nth-child(1) span { background-image: url('qianbei.jpg');}
.cb-slideshow li:nth-child(2) span { background-image: url('sister.jpg');}
.cb-slideshow li:nth-child(3) span { background-image: url('brother.jpg');}
.cb-slideshow li:nth-child(4) span { background-image: url('shadow.jpg');}
.cb-slideshow li:nth-child(5) span { background-image: url('knight.jpg');}
.cb-slideshow li:nth-child(6) span { background-image: url('kuiruo.jpg');}

修改鼠标指针

\shana\source\css\images/ 目录下修改icon.png文件,可以自己找图或者p一个或者画一个。喜欢小骑士的可以用我这个:

icon

修改代码高亮、代码背景等

对应文件 \shana\source\css\ _partial\highlight.styl

修改代码背景: 修改下方代码中 background 的值

1
2
3
4
5
6
7
8
$code-block
border-left: 5px solid #0000FF
background: #2244AA
margin: 0 article-padding * -1
padding: 15px article-padding
overflow: auto
color: highlight-foreground
line-height: font-size * line-height

修改文章版透明度

修改文件 **\themes\shana\source\css\ _variables.styl ** 中color-background-rgba属性的第四个值

更换换页按钮的文本

修改文件 themes\shana\layout_partial\archive.ejs 中最下方对应内容

1
2
3
4
5
6
7
8
<% if (page.total > 1){ %>
<nav id="page-nav">
<%- paginator({
prev_text: "<<< 上一页",
next_text: "下一页 >>>"
}) %>
</nav>
<% } %>

调宽博文显示栏

打开文件 \themes\shana\source\css_partial\article.styl 在.artical下增加宽度内容

1
2
3
.article
margin: block-margin 0
width: 115%

hexo框架下博客的创建、编辑和发布

创建一篇博客

1
hexo new "博客名称"

之后会提示你在特定的目录下创建了对应博客的md文件,以markdown格式编辑文件即可。

markdown格式基本语法不在本文记录范围之内。

插入本地图片

在hexo根目录的_config.yml文件中更改

1
post_asset_folder: true

之后你使用 hexo new 命令创建新博客时,也会在同目录下创建同名文件夹

然后安装插件hexo-image-link

1
npm install hexo-image-link --save

在创建好的同名文件夹中放置图片,并在md文件中以相对路径引用即可,生成博客后可以看到图片

使用数学公式

在md文件中使用数学公式在网页中是体现不出来的,需要更换插件以及更改配置文件

1
2
npm un hexo-renderer-marked --save
npm i hexo-renderer-markdown-it-plus --save

在hexo根目录下的_config.yml文件中添加内容

1
2
3
4
5
6
7
8
9
10
markdown_it_plus:
highlight: true
html: true
xhtmlOut: true
breaks: true
langPrefix:
linkify: true
typographer:
quotes: “”‘’
pre_class: highlight

标签与分类

Front-matter 是文件最上方 --- 之间的区域,可以通过修改Front-matter来添加标签与分类,比如本文为:

1
2
3
4
5
6
7
8
9
10
11
---
title: 使用hexo框架发布博客
date: 2022-05-06 10:48:25
categories:
- [教练我想学挂边多牛, 博客搭建]
tags:
- 博客搭建
- git
- web
- hexo
---

若是单级分类直接写分类名就行,多级分类需要像上面一样使用中括号

windows环境零基础使用hexo框架+Github搭建个人博客

介绍

Hexo是一个基于nodejs静态 (划重点) 博客网站生成器,可以让用户通过简单的操作快速生成一系列静态文件。

此外利用它搭建博客,还需要版本控制系统Git

前置

Nodejs

Nodejs官网下载一个长期维护的稳定版本(LTS版本)后,无脑下一步安装即可

安装完成后,打开命令行 (按组合键win+r,在弹出窗口中输入cmd后按回车),分别输入以下语句,若成功显示版本号,则说明安装成功。其中npm是Nodejs包管理和分发工具。

1
2
node -v
npm -v

Git

Git官网下载Git, 可以使用与上面相似的方法查看Git是否安装成功

1
git --version

Hexo的安装

我们先找好或新建一个文件夹(最好是空文件夹),当作储存博客信息的地方,并在这个文件夹中通过右键菜单打开Git Bash(与命令行类似)

Git Bush中输入下方语句安装hexo

1
npm install hexo-cli -g

成功安装之后,它会反馈给你一个版本以及安装时间(至少我安装的时候是这样),你也可以通过命令来查看版本明细

1
hexo -v

生成静态文件并将网页服务在本地运行

按顺序执行下方代码:

初始化操作,这一步操作后,生成网页的基础条件就达成了

1
hexo init

生成网页,由于现在还没有对相应文件做任何改动,所以生成默认的landscape主题网页

1
hexo g

在本地运行网页服务

1
hexo s

默认在你机器的4000端口开启网页服务(因为刚才的指令没有加别的参数),可以在浏览器中输入localhost:4000查看

但是一旦你在Git Bush中关闭了服务,网页服务就会终止,若想让网页长久存在,需要把它发布到互联网(挂到github的服务器)上。

把网页挂到互联网上

建立github仓库

若没有github账号需要先注册。

新建一个名称为: 你的用户名.github.io的仓库,其他配置可以都不管。

建立仓库的方法是:

1.点击右上角你的头像,在下拉框中点击Your repositories

2.你能看到一个绿色的New,点击它创建一个新的仓库

3.仓库名必须要是"你的用户名.github.io" 其他的不用管,直接点Create repositories创建仓库

生成SSH key

先配置一些变量,这算是你的身份标识,在对应的地方填写你的用户名和注册邮箱

1
2
git config --global user.name "用户名"
git config --global user.email "注册邮箱"

输入这两条语句可以查看这两个变量是否修改成功

1
2
git config user.name
git config user.email

然后生成ssh key 一直按回车就行

1
ssh-keygen -t rsa -C "注册邮箱"

然后在你电脑的 C:\Users\你的windows用户名.ssh ** 目录下会生成key的信息, 其中Users目录的名字在你的电脑上可能汉化成了"用户",若如此,你应该寻找C:\用户\你的windows用户名.ssh **目录

把SSH key添加到Github

我们需要在Github中新建SSH key

1.点击右上角你的头像,在下拉菜单中点击Settings

2.在左边找到SSH and GPG keys选项卡,点击

3.点击哪个绿油油的New SSH key

4.你需要确认一件事,在.ssh目录中,后缀名为id_rsa.pub文件是公钥,没有后缀名的id_rsa文件是私钥,公钥是可以任何人知道的,而私钥你必须保密(感兴趣的去查询非对称密码体制,RAS算法),所以你在下一步一定要保证你上传的是公钥

5.在Title中随便为SSH key取一个名字,用记事本打开.ssh目录中的公钥id_rsa.pub原封不动地复制粘贴到Key框中,开头结尾不要多加空格,你添加的东西和公钥内容完全相同。然后点击Add SHH key

将网页部署到Github

打开你存放博客信息的文件夹,在刚才用hexo init命令初始化出来的文件中找到配置文件 _config.yml ,用记事本打开它,拉到最下面,修改以下信息,没有的就自己加上。(实际上repo对应的就是你仓库的连接)(我猜会不会跟"种族主义"有关,github现在建议你将master分支改成main,如果你改了的话,branch应该对应main而不是master,如果你没改就当我放P)

1
2
3
4
deploy:
type: git
repo: https://github.com/你的Github用户名/你的Github用户名.github.io.git
branch: master

Git Bush输入以下命令,安装deploy-git

1
npm install hexo-deployer-git --save

然后输入命令

1
2
3
hexo clean (清空hexo缓存(有时你更新了网页内容却发现没有更新,建议清空一下缓存))
hexo generate (根据你当前的配置生成网页文件)
hexo deploy (把网页部署到github服务器上)

这三句可以简写为

1
2
3
hexo clean
hexo g
hexo d

你还记得hexo s是干嘛的吗?

在浏览器中输入 你的用户名.github.io即可查看博客

注意

1.国内网络访问Github可能非常不稳定,有时候传不上去,可以尝试多试几次或者搭个梯子。很长时间传不上去也无所谓,在本地测试好明天再传。

2.如果你改了东西在浏览器上体现不出来,不妨试一下ctrl+f5刷新浏览器缓存。

3.文件传到Github后,在服务器上部署需要花一段时间,可以在你仓库中点击Actions菜单查看,部署完成后,再访问才会显示修改后的结果。

完结撒花

关于如何发布博客、让博客应用其他的主题,以及想看我这个啥也不会的垃圾怎么摸索魔改博客的,欢迎来读我的其他文章

【已重置】【学习记录】python matplotlib 自学入门(随缘更新).md

说明 - 2022 - 09 - 07

已经完成人工重置,发布时间设定在1444年11月11日,表示这篇博文写于博客建立之前,并于为重置的远古文章区分。

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

建议

画图建议去matplotlib官网搬砖

貌似基础

add_subplot 前两个参数表示把面板划分成怎样的形状(几行几列)第三个参数表示图在面板中的位置

1
2
3
4
import matplotlib.pyplot as plt
fig =plt.figure()
ax1 = fig.add_subplot(2,2,1)
ax2 = fig.add_subplot(2,2,2)

在这里插入图片描述

1
2
3
fig,axes = plt.subplots(nrows= 3,ncols= 3)
axes[1,1].set(title = 'middle')
axes[0,2].set(title = 'NE')

在这里插入图片描述

线+貌似基础

画一条线

1
2
3
4
5
fig,ax = plt.subplots()
x = np.linspace(0,np.pi)
y_sinx = np.sin(x)
ax.plot(x,y_cosx,linewidth = 1, markersize = 6,color = 'red',linestyle='-.')
#图上的和这个不一样的写法,一个效果

在这里插入图片描述

加点盐

1
2
3
4
5
fig,ax = plt.subplots()
x = np.linspace(0,np.pi)
y_sinx = np.sin(x)
ax.plot(x,y_cosx,linewidth = 1, markersize = 6,color = 'red',linestyle='-.')
ax.set(title = 'ABC',xlabel = 'X QwQ',ylabel = '-Y---',xlim = [0,5],ylim = [-1.2,1.2])

在这里插入图片描述

填充两线之间的空间

再加点盐 : fill_between 填充两线之间的空间

1
2
3
4
5
6
7
8
fig,ax = plt.subplots()
x = np.linspace(0,np.pi)
y_sinx = np.sin(x)
y_cosx = np.cos(x)
y_tanx = np.tan(x)
ax.plot(x,y_cosx,linewidth = 1, markersize = 6,color = 'red',linestyle='-.')
ax.set(title = 'ABC',xlabel = 'X QwQ',ylabel = '-Y---',xlim = [0,5],ylim = [-1.2,1.2])
ax.fill_between(x,y_cosx,y_sinx,color = 'yellow')

在这里插入图片描述

加入网格线

换一种写法 并加入网格线

1
2
3
4
5
6
7
8
9
10
11
fig,ax = plt.subplots()
x = np.linspace(0,np.pi)
data = {'x' : x,
'sin' : np.sin(x),
'cos' : np.cos(x),
'tan' : np.tan(x)}

ax.plot('x','cos',linewidth = 1, markersize = 6,color = 'red',linestyle='-.',data = data)
ax.set(title = 'ABC',xlabel = 'X QwQ',ylabel = '-Y---',xlim = [0,5],ylim = [-1.2,1.2])
ax.fill_between('x','cos','tan',color = 'yellow',data = data)
ax.grid()

在这里插入图片描述

散点图

参数c同color表示颜色 s表示大小

1
2
3
x = np.arange(6)
y = x*3%8
plt.scatter(x,y,marker = '3',color = ['blue','green','blue','green','blue','green',], s = 600)

在这里插入图片描述

1
2
3
x = np.arange(10)
y = x*3%13
plt.scatter(x,y,marker = '*',c = np.random.rand(10),s = (np.random.rand(10)*30)**2)

在这里插入图片描述

条形图

调整画布大小

参数figsize调整画布大小

1
2
3
4
5
6
7
x = np.arange(10)
y = np.random.randint(0,10,10)
fig,axes = plt.subplots(ncols = 2,figsize = (20,6))
axes[0].bar(x,y,color = 'red')
axes[0].set(title = 'E GAY WOLY GIAO~GIAO',xlabel = 'q',ylim = [-2,10])
axes[1].barh(x,y,color = 'lightgreen')
axes[1].set(ylim = [-3,15])

在这里插入图片描述

画直线

加一道线

1
2
3
4
5
6
7
8
9
x = np.arange(10)
y = np.random.randint(0,10,10)
fig,axes = plt.subplots(ncols = 2,figsize = (20,6))
axes[0].bar(x,y,color = 'red')
axes[0].set(title = 'E GAY WOLY GIAO~GIAO',xlabel = 'q',ylim = [-2,10])
axes[1].barh(x,y,color = 'lightgreen')
axes[1].set(ylim = [-3,15])
axes[0].axhline(0,color = 'blue',linewidth = 6)
axes[1].axvline(2,color = 'lightgrey',linewidth = 3)

在这里插入图片描述
有负数的情况
在这里插入图片描述

饼图

1
2
3
4
names = ['AA','BB','CC','DD','EE']
weights = [200,300,500,159,66]
plt.pie(weights,labels = names)
pass

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
names = ['AA','BB','CC','DD','EE']
weights = [200,300,500,159,66]
colors = ['lightblue','lightgreen','red','green','grey']
plt.pie(weights,labels = names,
explode = [0,0,0.1,0.075,0], #每一块离开饼中心的距离
colors = colors, #颜色
shadow = True, #阴影
autopct = '%.4f%%', #显示百分比(格式化输出)
startangle = -30, #绘制角度 (旋转)
counterclock = False, #指针方向 默认=ture为逆时针 False为顺时针
radius = 1.3, #饼图半径
labeldistance = 1.3, #label的位置 (与半径的比例)
pctdistance = 0.3, #显示百分比的文本的位置(与半径的比例)
textprops = None, #文本格式

)
plt.axis('equal') #显示为正圆
plt.legend(loc = 'lower left') #显示图例
pass

在这里插入图片描述
所有图例位置名称

:18: MatplotlibDeprecationWarning: Unrecognized location ‘lower dleft’.
Falling back on ‘best’; valid locations are
best
upper right
upper left
lower left
lower right
right
center left
center right
lower center
upper center
center
This will raise an exception in 3.3.

plt.savefig( ) 保存

条形图+饼图组合 Bar of pie

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
from matplotlib.patches import ConnectionPatch

fig,[ax0,ax1] = plt.subplots(ncols = 2,figsize = [12,6])

#画饼
names = ['AA','BB','CC','DD','EE']
weights = [200,300,500,159,66]
colors = ['lightblue','lightgreen','red','green','grey']
ax0.pie(weights,
labels = None,
explode = [0,0.1,0,0,0], #每一块离开饼中心的距离
colors = colors, #颜色
shadow = True, #阴影
autopct = '%.4f%%', #显示百分比(格式化输出)
startangle = 100, #绘制角度 (旋转)
counterclock = False, #指针方向 默认=ture为逆时针 False为顺时针
radius = 1, #饼图半径
labeldistance = 0.85, #label的位置 (与半径的比例)
pctdistance = 0.65, #显示百分比的文本的位置(与半径的比例)
textprops = None, #文本格式

)
ax0.axis('equal') #显示为正圆
ax0.legend(names,loc = 'upper left') #显示图例
ax0.set(title = 'Big')

#画柱
rates = [0.33,0.56,0.07,0.04]
posX = 0
floor = 0
width = 0.2

for i in range(len(rates)):
height = rates[i]
ax1.bar(posX,height,width,bottom = floor)
posY = ax1.patches[i].get_height() / 2 + floor
ax1.text(posX,posY,'%d%%' % (rates[i]*100), ha = 'center')
floor += ax1.patches[i].get_height()

ax1.set(ylim = [0,1],xlim = [-0.5,2],title = 'Small')
ax1.axis('off') #边框消失
ax1.legend(['B1','B2','B3','B4'],loc = 'center')

#划线
top = 1
startangle = 0 #曾经多此一举加上了起始角度
center, r = ax0.patches[2].center, ax0.patches[2].r
theta1,theta2 = ax0.patches[2].theta1+startangle,ax0.patches[2].theta2+startangle

xb,yb = np.cos(np.pi / 180 * theta2) *r + center[0], np.sin(np.pi / 180 * theta2) *r + center[1]
con = ConnectionPatch(xyA = (-width/2,top), coordsA = ax1.transData, xyB = (xb,yb), coordsB = ax0.transData)
ax1.add_artist(con)

xb,yb = np.cos(np.pi / 180 * theta1) *r + center[0], np.sin(np.pi / 180 * theta1) *r + center[1]
con = ConnectionPatch(xyA = (-width/2,0), coordsA = ax1.transData, xyB = (xb,yb), coordsB = ax0.transData)
ax1.add_artist(con)
con.set_color([1,0.5,0])
con.set_linewidth(5)

pass

在这里插入图片描述

嵌套环形图+条形图组合 实例

太急于实现写的乱起八糟,以后可以复习思路
2021年2月9号东八区凌晨画完的,于同日中午放上来

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from matplotlib.patches import ConnectionPatch
angle = 120
fig,[ax0,ax1,ax2] = plt.subplots(ncols = 3,figsize = [27,9])
names = ['Index','Finance','Resources','Policy','Thought']
weights = [0.2,0.16,0.12,0.28,0.24]
cc = ['yellow','#ff8040','lightgreen','#00ffff','#b15bff']
cc.extend(['red']*4)

ax1.pie(weights,labels = names,
explode = [0,0,0,0,0], #每一块离开饼中心的距离
colors = cc, #颜色
shadow = False, #阴影
autopct = '%.2f%%', #显示百分比(格式化输出)
startangle = angle, #绘制角度 (旋转)
counterclock = False, #指针方向 默认=ture为逆时针 False为顺时针
radius = 0.75, #饼图半径
labeldistance = 0.75, #label的位置 (与半径的比例)
pctdistance = 0.6, #显示百分比的文本的位置(与半径的比例)
textprops = None, #文本格式
wedgeprops = dict(width = 0.2,edgecolor = 'white')
)
ax1.legend(loc = 'center')
#color = ['red'] *23
color = ['#ffffaa','#ffff6f','#f9f900','#d9b300']
color.extend(['#ffbd9d','#ff8f59','#ff5809','#ff5151','#ea0000','#ff5809'])
color.extend(['#d7ffee','#96fed1','#4efeb3','#02f78e','#28ff28','#00db00','#007500'])
color.extend(['#bbffff','#80ffff','#00e3e3','#00aeae'])
color.extend(['#ca8eef','#d200d2'])
small_n = ['','','','','','','','','','','','','','','','','','','','','','','']
small = [6.667,6.667,4.444,2.222,1.333,3.111,2.667,2.222,3.556,3.111,2.043,2.043,2.809,1.787,1.021,1.021,1.277,9.882,6.588,8.235,3.294,6,18]
ax1.pie(small,
labels = small_n,
explode = [0]*23, #每一块离开饼中心的距离
colors = color, #颜色
shadow = False, #阴影
autopct = '%.2f%%', #显示百分比(格式化输出)
startangle = angle, #绘制角度 (旋转)
counterclock = False, #指针方向 默认=ture为逆时针 False为顺时针
radius = 1, #饼图半径
labeldistance = 0.85, #label的位置 (与半径的比例)
pctdistance = 1.1, #显示百分比的文本的位置(与半径的比例)
textprops = None, #文本格式
wedgeprops = dict(width = 0.22,edgecolor = 'white')
)
ax1.axis('equal') #显示为正圆

#画柱
lll = [1.333,3.111,2.667,2.222,3.556,3.111,2.043,2.043,2.809,1.787,1.021,1.021,1.277]
colorssss = ['#ffbd9d','#ff8f59','#ff5809','#ff5151','#ea0000','#ff5809','#d7ffee'
,'#96fed1','#4efeb3','#02f78e','#28ff28','#00db00','#007500']
colorssss.reverse()
lll.reverse()
ttt = ['Proportion of tuition fees in total family income'
,'Pass rate of economic aid'
,'Average amount of undergraduate assistance'
,'Proportion of aid amount to tuition fee'
,'Proportion of education funds in the budget'
,'Education funds'
,'Difficulty of curriculum design'
,'Professional discipline depth'
,'Rationality of teaching plan'
,'Teaching Staff level'
,'Scholarships and grants'
,'Number of teaching staff'
,'Average salary of teaching staff']
tttr = ttt.copy()
tttr.reverse()
rates = (np.array(lll)/sum(lll)).tolist()
posX = -0.1
floor = 0
width = 0.2

for i in range(len(rates)):
height = rates[i]
ax2.bar(posX,height,width,bottom = floor,color = colorssss[i])
posY = ax2.patches[i].get_height() / 2 + floor
ax2.text(posX-0.07,posY,'%.2f%% ' % (lll[i])+ tttr[i], ha = 'left')
floor += ax2.patches[i].get_height()

ax2.set(ylim = [0,1],xlim = [-0.5,2],title = '')
ax2.axis('off') #边框消失

#画柱2
#lll = [1.333,3.111,2.667,2.222,3.556,3.111,2.043,2.043,2.809,1.787,1.021,1.021,1.277]
lll = [6.667,6.667,4.444,2.222,9.882,6.588,8.235,3.294,6,18]
#colorssss = ['#ffbd9d','#ff8f59','#ff5809','#ff5151','#ea0000','#ff5809','#d7ffee'
# ,'#96fed1','#4efeb3','#02f78e','#28ff28','#00db00','#007500']
colorssss = ['#ffffaa','#ffff6f','#f9f900','#d9b300','#bbffff','#80ffff','#00e3e3','#00aeae','#ca8eef','#d200d2']
colorssss.reverse()
lll.reverse()
#Z1 : Proportion of bachelor\'s degree education
ttt = ['Proportion of bachelor\'s degree education'
,'Registration rate of secondary school graduates'
,'Graduation rate of College Students'
,'Number of postgraduate examination'
,'Education policy support'
,'Teaching supervision system'
,'Regional differences in teaching quality'
,'Education supporting industry'
,'Environmental driving role'
,'People\'s cognitive attitude towards education']

tttr = ttt.copy()
tttr.reverse()
rates = (np.array(lll)/sum(lll)).tolist()
posX = 1.7
floor = 0
width = 0.2

for i in range(len(rates)):
height = rates[i]
ax0.bar(posX,height,width,bottom = floor,color = colorssss[i])
posY = ax0.patches[i].get_height() / 2 + floor
ax0.text(posX+0.1,posY,ttt[i]+' %.2f%%' % (lll[i]), ha = 'right')
floor += ax0.patches[i].get_height()

ax0.set(ylim = [0,1],xlim = [-0.5,2],title = '')
ax0.axis('off') #边框消失
#ax1.legend(tttr,loc = 'center')

ax1.set_title('Weight of Every Factor',fontsize = 30)
plt.savefig('Weight of Every Factor.png')

在这里插入图片描述

搬砖案例

爬一点空洞骑士1.4.3.2+版本Any完成度速通世界纪录榜,并简单画点图

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# %matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams['font.family']=['SimHei']
plt.rcParams.update({"font.size":25})
import numpy as np
import pandas as pd
import requests
from bs4 import BeautifulSoup
import os

'''
数据爬取
'''

HK_Any_1432More_SpeedRun_WR_url = 'https://www.speedrun.com/ajax_leaderboard.php?vary=1662443390&timeunits=0&topn=1000&variable9968=144449&variable9966=33306&variable16235=53846&variable45548=156253&variable45549=156255&variable14661=48684&variable25811=86076&variable25810=86073&variable55472=193197&variable45538=156209&variable35461=119744&variable15554=51769&variable9838=32795&variable45550=156257&variable67895=238958&game=hollowknight&layout=new&verified=1&category=44046&platform=&variable11324=&loadtimes=0&video=&obsolete=&date='
headers = None
proxy = None
if not os.path.exists('out'): os.mkdir('out')

res = requests.get(HK_Any_1432More_SpeedRun_WR_url)
soup = BeautifulSoup(res.text, 'lxml')
df = pd.DataFrame(
columns = ['Name', 'TimeWithLoads', 'TimeWithoutLoads', 'date', 'Version', 'Nationality']
)

for i, each in enumerate(soup.find('tbody').findAll('tr')):
cap = each.findAll('td', class_ = 'nobr center hidden-xs')
nationality = each.find('img', class_ = 'flagicon')
lst = [
each.find('span', class_ = 'username').span.string,
cap[0].text,
cap[1].text,
cap[2].text,
each.find('td', class_ = 'nobr center hidden-xs hidden-lg-down').string,
'unknow' if nationality == None else nationality['src'].split('/')[-1][ : -4],
]
df.loc[i] = lst


df = pd.read_csv('HK_Any_1432More_SpeedRun_WR.csv')
df = df[df['Nationality'] != 'unknow']

'''
国籍分布图 pie + bar
'''

nc = df['Nationality'].value_counts()
new_nc = nc[nc >= 9]
new_nc['others'] = len(nc[nc < 9])
idx, val = new_nc.index, new_nc.values


fig, ax = plt.subplots(figsize=(10, 10))
size = 0.3

vals = val
label = idx

cmap = plt.colormaps["tab20c"]
outer_colors = cmap(np.arange(5)*4)
# inner_colors = cmap([1, 2, 5, 6, 9, 10])

ax.pie(vals, radius=1, colors=outer_colors, labels= label,
wedgeprops=dict(width=size, edgecolor='w'))

# ax.pie(vals.flatten(), radius=1-size, colors=inner_colors,
# wedgeprops=dict(width=size, edgecolor='w'))

ax.set(aspect="equal", title='空洞骑士速通排行榜国籍分布')
fig.savefig('out/HKWR榜国籍分布pie.jpg')
plt.clf()
new_nc.plot(kind = 'bar', figsize=(10, 10), title = '空洞骑士速通排行榜国籍分布').figure.savefig('out/HKWR榜国籍分布bar.jpg')
plt.clf()

'''
版本分布图 bar
'''

df['Version'].value_counts().plot(kind = 'bar', figsize=(12, 14), title = '空洞骑士速通排行榜版本分布').figure.savefig('out/HKWR榜版本分布bar.jpg')
plt.clf()

'''
通关时间分布图 bar
'''

class TM:
def __init__(self, data: str):
if 'h' not in data: data = '0h ' + data
self.H, self.M, self.S = data.split()
self.H, self.M, self.S = int(self.H[ : -1]), int(self.M[ : -1]), int(self.S[ : -1])

def __ge__(self, other):
if self.H == other.H:
if self.M == other.M:
return self.S >= other.S
return self.M > other.M
return self.H > other.H

def tostr(self) -> str:
return f'{self.H}h{self.M}m{self.S}s'

def add10M(x: TM):
x.M += 10
if x.M >= 60:
x.M -= 60
x.H += 1
return x

def per10M(x):
I, X = TM('0h 0m 0s'), TM(x)
while True:
I = add10M(I)
if I >= X:
return I.tostr()

df['TimeWithLoads'].map(per10M).value_counts(sort = False).plot(kind = 'bar', figsize=(25, 15), title = '空洞骑士速通排行榜通关时间分布').figure.savefig('out/HKWR榜通关时间分布bar.jpg')
plt.clf()

'''
计算与不计算加载时间的对比
'''

fig,ax = plt.subplots(figsize = (100, 300))

x = range(len(df['TimeWithLoads']))
ax.set_title('空洞骑士速通排行榜计算与不计算加载时间的对比')
ax.plot(x, df['TimeWithLoads'].values)
ax.plot(x, df['TimeWithoutLoads'].values, color = 'red')
plt.savefig('out/HKWR榜计算与不计算加载时间的差距对比bar.jpg')

【已重置】【学习记录】【python】【opencv】自学简要记录

说明 - 2022 - 05 - 06

已经完成人工重置,发布时间设定在1444年11月11日,表示这篇博文写于博客建立之前,并于为重置的远古文章区分。

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

自学记录 不专业

001 基础-读取与保存

读取图像

1
2
3
4
5
6
7
8
9
#读入图像 cv2.imread(filepath,flags)
'''
flags参数的取值:
cv2.IMREAD_COLOR:默认,载入一个彩色图像,忽略透明度 可用1代替
cv2.IMREAD_GRAYSCALE:载入一个灰阶图像 可用0代替
cv2.IMREAD_UNCHANGED:载入一个包含 Alpha 通道(透明度)的图像 可用-1代替
'''
img1=cv2.imread('imgs/lx.jpg',0)

显示图像

1
2
3
4
5
6
7
8
9
10
11
12
#显示图像   cv2.imshow(wname,img)
'''
wname 窗口的名字 window name
img 要显示的图像 窗口他大小为自动调整图片大小
'''
cv2.imshow('lxSHOW',img1)
key=cv2.waitKey(0)
cv2.destroyWindow('lxSHOW')
#cv2.destroyAllWindows()
if key==27:
print('Key_ESC has been pressed')

保存图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#保存图像 cv2.imwrite(file,img,num)
'''
file 文件名
img 要保存的图像
'''
#其中可选参数num
'''
它针对特定的格式:对于JPEG,其表示的是图像的质量,用0 - 100的整数表示,默认95;对于png ,第三个参数表示的是压缩级别。默认为3.
cv2.IMWRITE_JPEG_QUALITY类型为 long ,必须转换成 int
cv2.IMWRITE_PNG_COMPRESSION, 从0到9 压缩级别越高图像越小。
'''
cv2.imwrite('imgs_save/lxGrey1.jpg',img1,[int(cv2.IMWRITE_JPEG_QUALITY),95])
cv2.imwrite('imgs_save/lxGrey2.png',img1,[int(cv2.IMWRITE_PNG_COMPRESSION),3])
# jpg属于有损压缩,是以图片的清晰度为代价的,数字越小,压缩比越高,图片质量损失越严重
# png属于无损压缩,数字0-9,数字越低,压缩比越低

读取视频文件

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
#读取视频 cv2.VideoCapture()
'''
参数为0 : 调用笔记本内置摄像头
参数为其他数字 : 不是很清楚,尝试用OBS开了个虚拟摄像头参数调成1可以调用
参数为路径 : 打开视频
'''
#检查摄像头初始化是否成功 cv2.VideoCapture.isOpened()
'''
成功返回Ture
'''
#读取视频的一帧 cv2.VideoCapture.read()
'''
每次调用便读取一帧 返回值为 ret,frame
ret : 布尔类型,如果正确读取便返回Ture
frame : 每一帧的图像,三维矩阵
'''
#释放摄像头 cv2.VideoCapture.release()

#vd = cv2.VideoCapture('videos/mgm.mkv')
vd = cv2.VideoCapture(1)
if vd.isOpened():
ret, frame = vd.read()
#frame = cv2.resize(frame,(1080,720))
else:
ret = False

while ret:
cv2.imshow('TheVideo',frame)
#利用waitKey设置每一帧的停顿时间 如果你按了ESC(27)则直接退出
#显然当waitKey为0时只有按键才会换帧
if cv2.waitKey(1) & 0xFF == 27:
break
ret, frame = vd.read()
#frame = cv2.resize(frame,(1080,720))

vd.release()
cv2.destroyAllWindows()

002基础-简单变换

裁剪

1
2
3
4
5
6
7
#图片裁剪
img_cliped = img[100:600,50:1050]

cv2.imshow('Clip', img_cliped)
cv2.waitKey(0)
cv2.destroyWindow('Clip')

改变图片大小

1
2
3
4
5
6
7
8
9
10
11
12
13
#图片改变大小 cv2.resize(img,(整形宽,整形高),fx,fy)
'''
可以给出固定的宽高
也可以给(0,0)按倍数变化(fx,fy)
'''
img_resized = cv2.resize(img, (1200,500)) #(宽1200,高500)
img_resized2 = cv2.resize(img, (0,0), fx = 0.4, fy = 0.4)

cv2.imshow('Resize', img_resized)
cv2.imshow('Resize2', img_resized2)
cv2.waitKey(0)
cv2.destroyAllWindows()

改变图片大小-自己实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#图片改变大小 自己实现
import numpy as np
import cv2

img=cv2.imread('imgs/lx.jpg',1)
rate=0.4

imgH,imgW,imgN=img.shape
newH,newW,newN=int(imgH*rate),int(imgW*rate),imgN

new=np.zeros((newH,newW,newN),np.uint8)

for i in range(newH):
for j in range(newW):
ii=int(i*(imgH*1.0/newH))
jj=int(j*(imgW*1.0/newW))
new[i,j]=img[ii,jj]

cv2.imshow('lxRESIZED',new)
#cv2.imshow('lxRESIZED',np.dstack((new[:,:,0],new[:,:,1],new[:,:,2])))
cv2.waitKey(0)
cv2.destroyAllWindows()

图片反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#图片反转 cv2.flip(img,flipcode)

'''
#flipcode=0 沿x轴翻转
#flipcode>0 沿y轴翻转
#flipcode<0 x,y同时翻转
'''
flipX=cv2.flip(img,0)
flipY=cv2.flip(img,1)
flipXY=cv2.flip(img,-1)

cv2.imshow('lxFLIPx',flipX)
cv2.imshow('lxFLIPy',flipY)
cv2.imshow('lxFLIPxy',flipXY)

cv2.waitKey(0)
cv2.destroyAllWindows()

颜色通道分离与合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#颜色通道分离与合并
img = cv2.imread('imgs/sky1.png')
imgC = img.copy()

B,G,R = cv2.split(img)
zero = np.zeros_like(B)
imgC[:,:,1] = 0
imgC[:,:,2] = 0

cv2.imshow('B1',np.dstack((B,zero,zero)))
cv2.imshow('B2',cv2.merge((B,zero,zero)))
cv2.imshow('B3',imgC)

cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.cvtColor 通道转换

1
2
3
4
5
6
7
8
img = cv2.imread('imgs/sky1.png',1)
imgBGR = cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
imgGRAY = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
fig, ax = plt.subplots(1,2)
ax[0].imshow(imgBGR)
ax[1].imshow(imgGRAY,cmap = 'gray')
plt.savefig('imgs_save/123.png')

在这里插入图片描述

简单数值计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#数值计算
'''
numpy(直接计算) 越界会%256
cv2.add(img1,img2) 越界会保留255
'''
img = cv2.imread('imgs/sky1.png')
another = np.full_like(img,225)
another_img = cv2.add(img,another)
cv2.imshow('Add_50',img+50)
cv2.imshow('Add_80',img+80)
cv2.imshow('another_img',another_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.addWeighted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#重叠两张图片
img1 = cv2.imread('imgs/sky1.png')
img2 = cv2.imread('imgs/lx.jpg')
img1_resized = cv2.resize(img1,(img2.shape[1],img2.shape[0]))
img_mix = cv2.addWeighted(img2,0.5,img1_resized,0.5,0)
cv2.imshow('MIX',img_mix)
cv2.imshow('MIX2',cv2.add((img1_resized*0.5).astype(np.int8),(img2*0.5).astype(np.int8))-120)
cv2.waitKey(0)
cv2.destroyAllWindows()
'''
图MIX2手动实现了一下结果太亮了,所以最后-120达到和函数差不多的效果
其中的原因改日探究
'''

003 基础-用matplotlib画图像

1
2
3
4
5
6
7
8
img = cv2.imread('imgs/lx.jpg',1)

#在matplotlib中画出图片 matplotlib.pyplot.imshow(img)
#vc2 是BGR模式,matplotlib.pyplot是RGB模式 所以颜色会不一样
B,G,R = cv2.split(img)
img_rgb = cv2.merge((R,G,B))
plt.imshow(img_rgb)

004 阈值

#def threshold(src, thresh, maxval, type, dst=None):
‘’’
设置固定级别的阈值应用于多通道矩阵
例如,将 灰度图像 变换二值图像,或去除指定级别的噪声,或过滤掉过小或者过大的像素点;
Argument:
src: 原图像
dst: 目标图像
thresh: 阈值
type: 指定阈值类型;下面会列出具体类型;
maxval: 当type指定为THRESH_BINARY或THRESH_BINARY_INV时,需要设置该值;
‘’’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gray = cv2.imread('imgs/sky1.png',0)
thresh = 127
maxval = 255
#以阈值分割二值化为0和maxval
ret, threshed1 = cv2.threshold(gray,thresh,maxval,cv2.THRESH_BINARY)

#以阈值分割二值化为0和maxval
ret, threshed2 = cv2.threshold(gray,thresh,maxval,cv2.THRESH_BINARY_INV)

#小于阈值变为0
ret, threshed3 = cv2.threshold(gray,thresh,maxval,cv2.THRESH_TOZERO)

#大于阈值变为0
ret, threshed4 = cv2.threshold(gray,thresh,maxval,cv2.THRESH_TOZERO_INV)

#大于阈值变为阈值
ret, threshed5 = cv2.threshold(gray,thresh,maxval,cv2.THRESH_TRUNC)

用matplotlib画图

1
2
3
4
5
6
7
8
fig, ax = plt.subplots(3,2,figsize = (10,10))
ax[0,0].imshow(threshed1,cmap = 'gray')
ax[0,1].imshow(threshed2,cmap = 'gray')
ax[1,0].imshow(threshed3,cmap = 'gray')
ax[1,1].imshow(threshed4,cmap = 'gray')
ax[2,0].imshow(threshed5,cmap = 'gray')
ax[2,1].imshow(gray,cmap = 'gray')

在这里插入图片描述

005 平滑/模糊处理 // 滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
img = cv2.imread('imgs/sky1.png',1)
lst = []
name = ['blur','boxFilter','GaussianBlur','madianBlur']

#均值滤波
lst.append( cv2.blur(img,(19,19)) )

#方框滤波 当normallize为True时等同于均值滤波
lst.append( cv2.boxFilter(img,-1,(19,19),normalize = False) )

#高斯滤波
lst.append( cv2.GaussianBlur(img,(19,19),sigmaX = 864) )

#中值滤波
lst.append( cv2.medianBlur(img,19) )

lst_save = lst.copy()

for i in range(len(lst)):
b,g,r = cv2.split(lst[i])
lst[i] = cv2.merge((r,g,b))

用matplotlib画图

1
2
3
4
5
6
fig, ax = plt.subplots(2,2,figsize = (10,7))
for i in range(len(lst)):
ax[i//2,i%2].imshow(lst[i])
ax[i//2,i%2].set_title(name[i])
plt.savefig('imgs_save/blur.png')

在这里插入图片描述

006 形态学-腐蚀/膨胀 开/闭/梯度运算

形态学操作一般作用于 二值化图 ,来连接相邻的元素或分离成独立的元素

腐蚀 erode:找局部最小
图像与核卷积,找核覆盖范围内最小值

膨胀 dilate:找局部最大
图像与核卷积,找核覆盖范围内最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
img = cv2.imread('imgs/plainstar.png',0)
#fig,ax = plt.subplots(2,1,figsize = (8,12))

kernel = np.ones([3,3],dtype = np.int8)#卷积核
'''
以kernel作为卷积核
参数iterations为迭代次数,iterations=2 代表进行两次腐蚀
'''
erosion = cv2.erode(img,kernel,iterations = 2)
#ax[0].imshow(erosion,cmap = 'gray')

dilation_after_erosion = cv2.dilate(erosion,kernel,iterations = 2)
#ax[1].imshow(dilation_after_erosion,cmap = 'gray')

tot = np.vstack([img,erosion,dilation_after_erosion])
cv2.imwrite('imgs_save/plainstar_open.png',tot)

原图+腐蚀图+腐蚀图的膨胀图:
在这里插入图片描述

开运算 :先腐蚀后膨胀
闭运算 :先膨胀后腐蚀
上方腐蚀与膨胀的示例代码就是一个开运算,下方代码时开运算+闭运算

1
2
3
4
5
6
7
8
9
10
11
12
13
img = cv2.imread('imgs/plainstar.png',0)
kernel = np.ones([5,5],dtype = np.int8)#卷积核
'''
以kernel作为卷积核
参数cv2.MORPH_OPEN表示开运算
参数cv2.MORPH_CLOSE表示闭运算
'''
opening = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)
closing = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)

tot = np.vstack([opening,closing])
cv2.imwrite('imgs_save/plainstar_opening_and_closing.png',tot)

开运算与闭运算效果:
在这里插入图片描述
梯度运算 :膨胀-腐蚀

1
2
3
4
5
6
7
8
9
10
11
img = cv2.imread('imgs/cloud.png')
kernel = np.ones([5,5],dtype = np.int8)#卷积核
'''
梯度运算:膨胀-腐蚀
参数cv2.MORPH_GRADIENT代表梯度运算
'''
gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)

tot = np.vstack([img,gradient])
cv2.imwrite('imgs_save/cloud_gradient.png',tot)

在这里插入图片描述
刚才的五角星进行梯度运算
在这里插入图片描述

007 梯度处理/边缘检测/轮廓检测

sobel算子梯度处理

cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[,
borderType]]]]])


前四个是必须的参数:
第一个参数是需要处理的图像;
第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
其后是可选的参数:


ksize是Sobel算子的大小,必须为1、3、5、7。
scale是缩放导数的比例常数,默认情况下没有伸缩系数;
delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。

cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])


可以用来合并两个方向的梯度图

cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])


Sobel函数求完导数后会有负值,还有会大于255的值,所以需要更大的数据类型(所以为什么深度的参数填的是数据类型???),最后用convertScaleAbs()函数将其转回原来的uint8形式

存疑⬆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cloud = cv2.imread('imgs/sky1.png',0)
cloud = cv2.resize(cloud,(0,0),fx = 0.3,fy = 0.3)

cloudX = cv2.Sobel(cloud,cv2.CV_64F,1,0)
cloudY = cv2.Sobel(cloud,cv2.CV_64F,0,1)

cloudAbsX = cv2.convertScaleAbs(cloudX)
cloudAbsY = cv2.convertScaleAbs(cloudY)

cloudXY = cv2.addWeighted(cloudX,0.5,cloudY,0.5,0)
cloudAbsXY = cv2.addWeighted(cloudAbsX,0.5,cloudAbsY,0.5,0)

tot = np.hstack([np.vstack([cloud,cloudX,cloudY,cloudXY]), np.vstack([cloud,cloudAbsX,cloudAbsY,cloudAbsXY])])
cv2.imshow('tot',tot)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('imgs_save/cloud_grad.png',tot)

在这里插入图片描述
此外还查到scharr算子laplacian算子 没有深入了解

Canny边缘检测
原理(查资料):https://www.cnblogs.com/techyan1990/p/7291771.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
town = cv2.imread('imgs/town.jpg',0)
town = cv2.resize(town,(0,0),fx = 0.3,fy = 0.3)

'''
见canny双阈值检测
后两个参数表示最小梯度minval、最大梯度maxval
if 梯度 >= maxval 则视为边界
elif 梯度 <= maxval 则不视为边界
elif maxval > 梯度 > minval:
if 这里与边界相连, 视为边界
else 不视为边界
'''
canny1 = cv2.Canny(town,80,130)
canny2 = cv2.Canny(town,105,155)
canny = np.hstack((canny1,canny2))
cv2.imwrite('imgs_save/town_canny1.png',canny)

在这里插入图片描述
在这里插入图片描述
轮廓检测

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
img = cv2.imread('imgs/mountain.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,176,255,cv2.THRESH_BINARY)

#轮廓检测
#contours,hierarchy = cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])
'''
参数:
mode
cv2.RETR_EXTERNAL 表示只检测外轮廓
cv2.RETR_LIST 检测的轮廓不建立等级关系
cv2.RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
cv2.RETR_TREE 建立一个等级树结构的轮廓。
method
cv2.CHAIN_APPROX_NONE 存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
cv2.CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS 使用teh-Chinl chain 近似算法
返回值:
contours 是列表,其中每个元素都是图像中的一个轮廓,用numpy中的ndarray表示
hierarchy 是一个ndarray,其中的元素个数和轮廓个数相同,每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0] ~hierarchy[i][3],
分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,则该值为负数。
'''
contour, hierarchy= cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

#画轮廓
#cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset ]]]]])
'''
参数:
image 绘制轮廓的图片 注意函数会在image原图上直接作修改
contours 轮廓
contourIdx 要绘制的轮廓的编号,-1为绘制所有
color 绘制轮廓用的颜色
thickness 线条宽度
'''
con = cv2.drawContours(img.copy(),contour,0,(255,0,255),4)
cv2.imshow('ss',con)
cv2.waitKey(0)
cv2.destroyAllWindows()

轮廓近似

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
#轮廓近似
x = contour[0]
epsilon1 = 0.05*cv2.arcLength(x,True) #计算轮廓周长
epsilon2 = 0.1*cv2.arcLength(x,True) #计算轮廓周长
'''
cv2.approxPolyDP()中给出一个轮廓contour和阈值epsilon
'''
approx1 = cv2.approxPolyDP(x,epsilon1,True)
approx2 = cv2.approxPolyDP(x,epsilon2,True)


'''
实测第二个参数形如approx结果只能在图上点点,若形如[approx]结果为点加连线
'''
con = cv2.drawContours(img.copy(),[approx1],-1,(255,0,255),5)
cv2.imshow('ss',con)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('imgs_save/mountain+contour1.png',con)
con = cv2.drawContours(img.copy(),[approx2],-1,(255,0,255),5)
cv2.imshow('ss',con)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('imgs_save/mountain+contour2.png',con)

在这里插入图片描述
在这里插入图片描述
资料很多都是cpp的暂时不明白最后一个 参数是干嘛的,一下为True(左)和False(右)的对比
在这里插入图片描述

008 模板匹配

参考资料:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/histograms/template_matching/template_matching.html

https://www.cnblogs.com/ssyfj/p/9271883.html

https://www.cnblogs.com/jiyanjiao-702521/p/10471032.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
method = [cv2.TM_CCOEFF,cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR, cv2.TM_CCORR_NORMED, cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]
lst = []
re = []

#获得match结果矩阵
for i in range(len(method)):
match = cv2.matchTemplate(img,template,method[i])
lst.append(match)
#找到最优匹配位置并画方框
for i in range(len(method)):
min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(lst[i])
TL = min_loc if method[i] in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED] else max_loc
BR = TL[0]+template.shape[1] , TL[1]+template.shape[0]
tt = img.copy()
cv2.rectangle(tt,TL,BR,(0,255,0),2)
re.append(tt)

#储存
fi = np.vstack(re)
fi = cv2.resize(fi,(0,0),fx = 0.45, fy = 0.45)
cv2.imwrite('imgs_save/chess_rec+\'str(cnt)+\'.png',fi)

模板
在这里插入图片描述
结果
在这里插入图片描述

009 直方图

获得直方图数据
直方图均衡化

获得直方图数据

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
#得到直方图数据
#cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]])
'''
imaes:输入的图像
channels:选择图像的通道
mask:掩膜,是一个大小和image一样的np数组,其中把需要处理的部分指定为1,不需要处理的部分指定为0,一般设置为None,表示处理整幅图像
histSize:使用多少个bin(柱子),一般为256
ranges:像素值的范围,一般为[0,255]表示0~255
后面两个参数基本不用管。
注意,除了mask,其他四个参数都要带[]号

返回值为hist,直方图;接着使用
matplotlib.pyplot.plot(hist,color)进行绘制
'''

gray = cv2.resize(cv2.imread('imgs/InLab1.jpg',0),(0,0),fx = 0.2,fy = 0.2)
img = cv2.resize(cv2.imread('imgs/InLab1.jpg'),(0,0),fx = 0.2,fy = 0.2)

hist = cv2.calcHist([gray],[0],None,[256],[0,256])
plt.plot(hist)
plt.savefig('imgs_save/InLab1_0.2_zhifangtuGRAY.png')

color = ('b','g','r')
for i,each in enumerate(color):
hist = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(hist,color = each)
plt.savefig('imgs_save/InLab1_0.2_zhifangtuRGB.png')

在这里插入图片描述
在这里插入图片描述
直方图均衡化
原理(大概):从小到大,像素依次变为像素的积累概率(这个像素以及比这个像素小的像素出现的总概率)*最大-最小?(255-0)
1.全局均衡化

1
2
3
4
5
6
7
8
9
10
11
#直方图均衡化
#cv2.equalizeHist(img)
'''
要求是灰度图
'''
equalized = cv2.equalizeHist(gray)
#both = np.hstack([gray,equalized])
cv2.imshow('asdf',both)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.限制对比度自适应直方图均衡 CLAHE算法

1
2
3
4
5
6
7
8
#自适应均衡化 分块+调整让块与块之间看起来没有界限
'''
clipLimit参数表示对比度的大小。
tileGridSize参数表示每次处理块的大小 。
'''
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahed = clahe.apply(gray)

效果: 1 灰度图原图 . 2全局均衡化 3.CLAHE
在这里插入图片描述

C语言程序(有一点pp)实现PVZ一代修改,探索记录 (随缘更新).md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

wscb 只用过CE

建议食用前先熟悉CE基本操作

游戏版本

Plants_Vs_Zombies_V1.0.0.1051_EN

0.找到PVZ进程

这一步是之后对内存操作的基础
导入windows.h头文件, 我们就可以使用winAPI,于是我们可以根据窗口标题找到游戏进程。

1.找到PVZ窗口的句柄(HWND类型)

使用winAPI FindWindow, 下方代码展现了他的一种用法

2.找到PVZ进程ID(DWORD类型)

DWORD即无符号int
使用API GetWindowThreadProcessId

3.找到进程句柄(HANDLE类型)

使用API OpenProcess

1
2
3
4
5
6
7
8
9
HANDLE CatchPvzProcessByTitle(LPCTSTR ProcessTitleName){
HWND hwnd = FindWindow(NULL, ProcessTitleName);
DWORD processID = 0;
HANDLE PVZprocess = NULL;
GetWindowThreadProcessId(hwnd, &processID);
if(!processID) return NULL;
PVZprocess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,processID);
return PVZprocess;
}

1.修改阳光

1.找地址

使用CE精确数值搜索,可以轻易确定阳光地址
1.5 好的我们来寻找基址, 找基址的方法是直接莽(因为真不会找), 然后就找到了!

2.找基址+偏移

找到阳光的基址和偏移, 发现是
[ [ [ p o p c a p g a m e 1. e x e + 2 A 9 E C 0 ] + 768 ] + 5560 ]
[[[popcapgame1.exe+2A9EC0] + 768] + 5560]
[[[popcapgame1.exe+2A9EC0]+768]+5560]
而pvz起始申请的地址固定是0x400000, 所以起始地址是62A9EC0

3.修改内存内容

我们可以调用两个winAPI来读写内存: 这里意识流地介绍一点用法, 具体建议自己去查,因为作者也不会

1
2
3
4
5
6
WriteProcessMemory
ReadProcessMemory
参数:
(pvz进程句柄,地址,新值/读取值保存地址,数据大小,NULL)
返回值:
bool类型, 读写失败会返回0

先读地址,确定目标地址是否可以访问, 若成功读取,则修改之。
(如果在选择游戏模式界面, 显然不能修改阳光值, 或者你的地址写错了,也无法修改,所以要先读,如果读取能成功,可以排除一部分问题)

4.没什么用的总结

若正确开始了一句游戏,找到了正确的阳光基址以及偏移量,正确调用修改内存的函数,便可以成功修改阳光数值。

2. 卡片无冷却

一些探索

1.

刚开始根据网上搜索的思路,以1字节为单位搜索,可以找到卡片是否在冷却的标志位,1代表准备就绪,0代表正在冷却。

2.

当你点击卡片,卡片就会变暗,这说明你点击卡片的同时,游戏修改了标志位的数值。

3.

生成反汇编代码,把mov [标志位地址], 00 的改为 01,这样点击卡片时,即标志位数值不变,即可让卡片无冷却。
但这样会遇到一些问题, 这种操作只是“ 不让卡片从就绪状态进入冷却状态” , 如果你先使用卡片,在卡片冷却是修改代码,
正在冷却的卡片是不会立刻准备就绪 的。

4.

遇到了另一个问题,在寻找基址的过程中,有一个中间步骤为[edi + 24], 但查找edi的值时无匹配项, 寻找基址无果。好在后来通过一个等价地址[eax

  • ecx + 70]寻得,至于为何用到了两个寄存器做偏移, 当时未知, 之后探索出来了。
5.

后来经过玄学+盯代码+猜+不严谨证明,习得了部分卡槽机制。

  • 每个卡槽都有一个“已经冷却了多长时间”的计时(即反汇编代码中的[edi + 24]), 这个计时会从冷却开始时增加。

  • 每个卡槽都会根据所选植物不同有一个冷却时长(即反汇编代码中的[edi + 28]),当卡槽冷却计时达到了这个冷却时长,则冷却计时置0, 卡片进入就绪状态。

  • 相邻卡槽冷却标志位地址做差,可以得到80(0x50), 相邻卡槽冷却计时的地址做差,可以得到80(0x50), 且对于同一卡槽,其冷却计时地址相对其冷却标志位地址的偏移为-36
    .

  • 可以推测出,储存卡槽信息的是一系列连续的结构体,每个结构体占用0x50Byte的空间, 而经验证,双偏移中的[eax + ecx + 70]eax是50的倍数,即使第几个卡槽,可以直接通过ecx找到基址

卡片无冷却的修改方法

找到了许多种,但未能深入理解代码

在反汇编代码中,
冷却计时每次加1,通过不断的类似这样的循环,实现冷却恢复。只要冷却计时小于等于(jle)目标时间,循环就继续,游戏中的卡片冷却进度条也不断刷新。

冷却计时+1
mov, eax , 冷却计时
cmp eax,目标时间
把eax sub成0

1. 修改反汇编代码,固定地址0x487290处3个字节

每次冷却计时+1, 把对应mov [xxx] 01改为 00,即冷却计时一直是0,可以实现卡片无冷却(貌似计时置0后循环就会跳出)。

2. 修改反汇编代码,固定地址0x487296处1个字节

把jle 改成ja(判大于), 直接结束循环,之后卡片冷却标志位置1,实现无冷却

3.修改反汇编代码以及内存数据

先修改“使得卡片标志位变为00”的mov代码为01,使卡片不会从就绪状态进入冷却状态,然后通过时间计数+固定偏移36(0x28)修改所有卡槽的标志位为01,令正在冷却中的卡片刷新。

没用的东西

作者在CE中使用了这三种方法,但只选择方法2进行了代码实现

3.阳光自动收集

0.

进行4Byte搜索, 阳光会有一个标志判断是否被点击, 没点击时为0, 点击后为1。(不过点了阳光马上暂停好像找不出来,需要等一会)。

1.

找到阳光是否被点击的标志位后, 查看“什么访问了这个地址”, 发现一些有趣的数据,(节选):

若干个(cmp byte ptr [ebx+50],00 出现次数:非常非常多次)
cmp byte ptr [ebx+50],00 出现次数:x (后跟jne)
mov byte ptr [ebp+50],01 出现次数:x

其中下方两行,每点击一次阳光,出现次数都加一。猜测时点击阳光时先判断阳光点击标志位是否=1(等价于标志位是否!=0),如果不为0,则跳转,
否则进行第三行操作把标志位置1。

2.

然后联想到还有很多个出现非常多次的“cmp byte ptr [ebx+50],00”, 说明游戏会经常检验阳光是否被点击,
那么猜想,检测到阳光被点击后,游戏应该会进行收集操作,很可能有一个cmp后会跟收集阳光操作。
于是把其中一个“cmp byte ptr [ebx+50],00”后面的jne改为je(由 if 标志位!=0-阳光被点击 则跳转到收集操作 改为
if 标志位==0-阳光不被点击 则跳转到收集操作), 或者改成jmp也行

3.

所以, 只要把popcapgame1.exe+3158F位置的jne改为je,即可实现阳光的自动收集

LCT

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

P4219 [BJOI2014]大融合

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
99
100
101
102
#include <cstdio>
#include <algorithm>
int n, m, op, a, b, x , y;
const int N = 3e5+4;
int top, sdk[N], son[N][2], fa[N],rev[N], sxor[N], sxor2[N];
inline void pushup(int x){
sxor[x] = sxor[son[x][0]] + sxor[son[x][1]] + sxor2[x] + 1;
}
inline void hpushdown(int x){
if(rev[x]){
rev[son[x][0]] ^= 1;
rev[son[x][1]] ^= 1;
rev[x] ^= 1;
std::swap(son[x][0], son[x][1]);
}
}
inline bool isroot(int x){
return son[fa[x]][0] != x && son[fa[x]][1] != x;
}
inline void pushdown(int x){
if(!isroot(x)) pushdown(fa[x]);
hpushdown(x);
}
void rotate(int x){
int y = fa[x], z = fa[y];
int z2y = son[z][1] == y, y2x = son[y][1] == x;
if(!isroot(y)) son[z][z2y] = x;
fa[x] = z, fa[y] = x, fa[son[x][y2x ^ 1]] = y;
son[y][y2x] = son[x][y2x ^ 1], son[x][y2x ^ 1] = y;
pushup(y); pushup(x);
}
void splay(int x){
top = 0; sdk[++top] = x;
for(int i=x; !isroot(i); i = fa[i]) sdk[++top] = fa[i];
for(int i=top; i; i--) pushdown(sdk[i]);
while(!isroot(x)){
int y = fa[x], z = fa[y];
if(!isroot(y)){
if((son[y][0] == x) ^ (son[z][0] == y)) rotate(x);
else rotate(y);
}
rotate(x);
}
}
void access(int x){
for(int t = 0; x; t = x, x = fa[x]){
splay(x);
sxor2[x] += sxor[son[x][1]] - sxor[t];
son[x][1] = t;
pushup(x);
}
}
void makeroot(int x){
access(x); splay(x); rev[x] ^= 1;
}
int find(int x){
access(x); splay(x);
while(son[x][0]) x = son[x][0];
return x;
}
void split(int x, int y){
makeroot(x); access(y); splay(y);
}
void cut(int x, int y){
split(x, y);
if(son[y][0] == x && son[x][1] == 0){
son[y][0] = 0;
fa[x] = 0;
}
}
void link(int x, int y){
makeroot(x);
fa[x] = y;
}
int main(){
scanf("%d%d",&n,&m);
while(m--){
scanf("%*c%c%d%d",&op,&x,&y);
if(op=='A')
{
makeroot(x);
makeroot(y);
fa[x]=y;
sxor2[y]+=sxor[x];
}
if(op=='Q')
{
makeroot(x); access(y); splay(y);
son[y][0] = fa[x] = 0;
pushup(x);
makeroot(x);
makeroot(y);
printf("%lld\n",(long long)(sxor[x]*sxor[y]));
makeroot(x);
makeroot(y);
fa[x]=y;
sxor2[y]+=sxor[x];
}
}

return 0;
}

Splay.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

luogu.com.cn P3391 文艺平衡树

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
#include <cstdio>
#include <algorithm>
const int N = 100005;
int n, m, rt, qx, qy;
struct SplayNode{
int fa, son[2], size, rev, rt;
}snode[N];
inline void pushup(int x){
int son0 = snode[x].son[0], son1 = snode[x].son[1];
snode[x].size = snode[son0].size + snode[son1].size + 1;
}
inline void pushdown(int x){
if(snode[x].rev){
std::swap(snode[x].son[0], snode[x].son[1]);
int son0 = snode[x].son[0], son1 = snode[x].son[1];
snode[son0].rev ^= 1;
snode[son1].rev ^= 1;
snode[x].rev = 0;
}
}
void rotate(int x, int &k){
int y = snode[x].fa, z = snode[y].fa;
int y2x = snode[y].son[1] == x, z2y = snode[z].son[1] == y;
if(y == k) k = x; else snode[z].son[z2y] = x;
snode[y].son[y2x] = snode[x].son[y2x^1];
snode[snode[y].son[y2x]].fa = y;
snode[x].son[y2x^1] = y;
snode[y].fa = x, snode[x].fa = z;
pushup(x), pushup(y);
}
void splay(int x, int &k){
while(x != k){
int y = snode[x].fa, z = snode[y].fa;
if(y != k){
if((snode[y].son[1] == x) ^ (snode[z].son[1] == y)) rotate(x, k);
else rotate(y, k);
}
rotate(x, k);
}
}
int find(int x, int k){
pushdown(x);
int sz = snode[snode[x].son[0]].size;
if(k == sz + 1) return x;
if(k <= sz) return find(snode[x].son[0],k);
else return find(snode[x].son[1], k - sz - 1);
}
void rev(int l, int r){
int x = find(rt, l), y = find(rt, r);
splay(x, rt);
splay(y, snode[x].son[1]);
int z = snode[y].son[0];
snode[z].rev ^= 1;
}
void build(int l, int r, int root){
if(l > r) return ;
int mid = (l + r) >> 1;
if(mid < root) snode[root].son[0] = mid;
else snode[root].son[1] = mid;
snode[mid].fa = root;
snode[mid].size = 1;
if(l == r) return ;
build(l, mid-1, mid);
build(mid+1, r, mid);
pushup(mid);
}
int main(){
scanf("%d%d",&n,&m);
rt = (n + 3) >> 1;
build(1, n+2, rt);
while(m--){
scanf("%d%d",&qx,&qy);
rev(qx, qy+2);
}
for(int i=2; i<=n+1; i++) printf("%d ",find(rt, i) - 1);
return 0;
}

luogu.com.cn P3369 普通平衡树

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <cstdio>
#include <iostream>

inline int read(){
int v = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){ if(ch == '-') f = -1; ch = getchar(); }
while(isdigit(ch)){ v = (v << 1) + (v << 3) + ch - 48; ch = getchar(); }
return v * f;
}

const int N = 1e5+4, HINF = 0x3fffffff;
int fa[N], cnt[N], son[N][2], val[N], size[N];
int root, idx, op, va, n;

inline void update(int x){
size[x] = size[son[x][0]] + size[son[x][1]] + cnt[x];
}

void rotate(int x){
int y = fa[x], z = fa[y];
int y2x = son[y][1] == x, z2y = son[z][1] == y;
son[z][z2y] = x, fa[x] = z;
son[y][y2x] = son[x][y2x ^ 1], fa[son[x][y2x ^ 1]] = y;
son[x][y2x ^ 1] = y , fa[y] = x;
update(y), update(x);
}

void splay(int x, int goal){
while(fa[x] != goal){
int y = fa[x], z = fa[y];
if(z != goal){
if((son[z][1] == y) ^ (son[y][1] == x)) rotate(x);
else rotate(y);
}
rotate(x);
}
if(!goal) root = x;
}

void find(int x){
if(int u = root){
while(son[u][x > val[u]] && x != val[u]){
u = son[u][x > val[u]];
}
splay(u, 0);
}
}

void insert(int x){
int u = root, ffa = 0;
while(u && val[u] != x){
ffa = u;
u = son[u][x > val[u]];
}
if(u) cnt[u] += 1;
else{
u = ++idx;
if(ffa) son[ffa][x > val[ffa]] = u;
son[u][0] = son[u][1] = 0;
fa[u] = ffa;
val[u] = x;
cnt[u] = size[u] = 1;
}
splay(u, 0);
}

int pre_next(int x, bool isnext){
find(x);
int u = root;
if(val[u] < x && !isnext) return u;
if(val[u] > x && isnext) return u;
u = son[u][isnext];
while(son[u][isnext ^ 1]) u = son[u][isnext ^ 1];
return u;
}

void _delete(int x){
int pre = pre_next(x, 0), next = pre_next(x, 1);
splay(pre, 0), splay(next, pre);
int todel = son[next][0];
if(cnt[todel] > 1){
cnt[todel] -= 1;
splay(todel, 0);
}
else son[next][0] = 0;
}

int Kth(int x){
int u = root;
if(size[u] < x) return 0;
while(true){
if(size[son[u][0]] + cnt[u] < x){
x -= size[son[u][0]] + cnt[u];
u = son[u][1];
}
else{
if(size[son[u][0]] >= x) u = son[u][0];
else return val[u];
}
}
}

int main(){
scanf("%d", &n);
insert(HINF); insert(-HINF);
while(n--){
scanf("%d%d", &op, &va);
if(op == 1) insert(va);
else if(op == 2) _delete(va);
else if(op == 3) {
find(va);
printf("%d\n", size[son[root][0]]);
}
else if(op == 4) printf("%d\n", Kth(va + 1));
else if(op == 5) printf("%d\n", val[pre_next(va, 0)]);
else if(op == 6) printf("%d\n", val[pre_next(va, 1)]);
}
return 0;
}

Ubuntu下求生之路2Linux服务器搭建(官方战役,三方药抗).md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

声明

本文章仅为萌新探索搭建服务器后写的初级总结,有问题请指出。更深层次的内容请自己去研究Sir大佬或者其他人的资料。

准备

首先你需要有服务器或者云服务器,我是2核4gUbuntu系统,据说1核2g带求生之路服务器就没问题。
我使用的Xshell远程连接服务器,使用Xftp从本地上传文件到服务器(不会的百度搜有教程)

资料

官方战役主要参考https://blog.csdn.net/qq_31555445/article/details/105616307
药抗插件资源和后期药抗搭建主要参考都是Sir大佬的<https://github.com/SirPlease/L4D2-Competitive-
Rework>

搭建过程

这里就直接完全搬运Sir大佬的教程了

1.在服务器上安装以下环境或工具等

screen 是一个托盘工具, 开服后screen -r 可以看服务器状态

dpkg --add-architecture i386 # enable multi-arch
apt-get update && apt-get upgrade
apt-get install libc6:i386 # install base 32bit libraries
apt-get install lib32z1
apt-get install screen

2.新建一个账户

其实直接用root用户也可以,新建一个用户可能安全一些。
这里Sir大佬创建的账户名称是steam,名称可以自定义,但之后要把相应路径中的steam换成你自定义的名称。
然后login登录。

adduser steam
adduser steam sudo
login

3.安装steamcmd并部署L4D2环境

下载steamcmd_linux.tar.gz

wget http://media.steampowered.com/installer/steamcmd_linux.tar.gz

把下载好的steamcmd_linux.tar.gz文件解压

tar -xvzf steamcmd_linux.tar.gz

进入Steam, 启动成功后你的屏幕上会有**Steam>**标志

./steamcmd.sh

匿名登录,有一些游戏不支持匿名登陆下载,但显然L4D2可以

login anonymous

指定安装路径
所以你文件的真实路径是/home/steam(这个是你的用户名)/Steam/steamapps/common/l4d2

force_install_dir ./Steam/steamapps/common/l4d2

安装求生之路

app_update 222860 validate

如果他提示你成功安装完成了,就可以退出了

quit

配置文件

首先需要先获取Sir大佬在Github上发布的资源,我在上面放过连接了,不会从Github下载东西的自行百度。

srcds1文件

在Sir大佬发布的资源中,这个文件的路径为:L4D2-Competitive-Rework-master\Dedicated Server Install
Guide。

第四行,改成自己的账户名

SRCDS_USER=“steam”

第10行 ,/home后面第一个小写的steam,改成你的账户名

DIR=/home/steam/Steam/steamapps/common/l4d2

第37行,改成你自己服务器的IP

IP=1.3.3.7

第40行,把-ip $IP删掉。Sir大佬的教程是没有这一步的,但是我不删掉的时候没法正常开服。

PARAMS="-game left4dead2 -ip $IP -port P O R T + s v c l o c k c o r r e c
t i o n m s e c s 25 − t i m e o u t 10 − t i c k r a t e 100 + m a p c 1 m
1 h o t e l − m a x p l a y e r s 32 + s e r v e r c f g f i l e s e r v e
r PORT +sv_clockcorrection_msecs 25 -timeout 10 -tickrate 100 +map
c1m1_hotel -maxplayers 32 +servercfgfile server
PORT+svc​lockcorrectionm​secs25−timeout10−tickrate100+mapc1m1h​otel−maxplayers32+servercfgfileserverSVNUM.cfg"

然后你就可以把这个scrds1文件放到这里(这是一个绝对路径)
这个文件应是个启动脚本,至于为什么要放到这里,放到别处行不行,我没有尝试。

/etc/init.d

serve.cfg文件

这个文件主要是配置游戏里的一些参数。
第6行是你的服务器名。

hostname “xxxxxxxxxxxx”

第11行意思是把你的服务器设置为私有(也就是只有加入特定steam组的人才可以搜索到,不过服务器进人后好像路人也能匹配进来)

sv_steamgroup_exclusive “1”

而第九行是服务器所属的steam组号

sv_steamgroup “xxxxxxxxx”

然后你需要 把这个文件改名为serve1.cfg 因为Sir大佬就是这么设置的(他考虑了开很多服,而这篇文章讨论开一个服)

进服后公告栏上显示的内容对应的文件

进入服务器后,服务器提供者对应host.txt,下方信息对应motd.txt
!match 进入药抗后 ,服务器提供者对应myhost.txt,下方信息对应mymotd.txt
你可以自由编辑

在服务器复制和替换文件

直接到home/steam(你的账户名)/Steam/steamapps/common/l4d2/left4dead2/把修改后的Sir大佬的资源全复制进去就行了。
我是现在本地windows修改了文件传上去的。(不会传的搜索Xshell上传文件到服务器)

启动服务器

先给srcds1文件加上可执行权限

sudo chmod +x /etc/init.d/srcds1

以下三种操作分别是重启服务器,启动服务器,停止服务器,分不清哪个是哪个的别开服务器了。

/etc/init.d/srcds1 restart
/etc/init.d/srcds1 start
/etc/init.d/srcds1 stop

某些问题的解决方案

这里有点记不清楚是哪个文件了,好像是srcds1这个脚本文件。
开服时遇到了这个错误

-bash: xxx: /bin/sh^M: bad interpreter: No such file or directory

由于我现在windows编辑文件又上传到服务器,一个文件用vi编辑器打开后,输入

:set ff

显示的是fileformat=dos,而正常应该是fileformat=unix,所以直接进行修改

set ff=unix

然后使用上述命令就可以开服了。

其他

关于托盘程序

开服后登录你的新账户是可以看你的服务器状态的

screen -r

进入之后如果你这样按会关服

Ctrl + C

如果你想退出来这样按

Ctrl + A 然后 Ctrl + D

你可以简化你开服的敲码量

比如你可以在某个顺手的目录写个脚本

vi st.sh

if [ $1 = “start” ]
then
/etc/init.d/srcds1 “start”
elif [ $1 = “stop” ]
then
/etc/init.d/srcds1 “stop”
elif [ $1 = “restart” ]
then
/etc/init.d/srcds1 “restart”
else
echo “Unknow command. arg should be [start/stop/restart]”
fi

然后就可以直接

./st.sh start

关于游戏中的一些参数

吹牛逼+讲故事

问题解决了,说一下解决过程吧,可能造福以后遇到类似问题的人。
没有大佬解答
于是自己开始玄学(我下的插件有很多版本,promod(好像是这个名字,太菜了没印象),zonemod2.1,zonemod1.9,apexmod啥的,我只修改了zonemod2.1。)
.
.

具体思路是暴力查找文件,ctrl+f搜索诸如bot、infect、ghost等关键词,最后在\cfg\cfgogl\zonemod\shared_cvars.cfg的文件中找到了confogl_addcvar
confogl_blockinfectedbots "0"和confogl_addcvar director_allow_infected_bots
“0”,遂把它们全改成1
。这时候进服务器可以看到字幕上出现了bommer的响声,linux服务器上也出现了含有jockey一堆英文,但跑了半张图都没见到特感,白高兴一场。于是怀疑还需要改其他的地方,就继续重新搜索了一遍,反而确定了跟特感有关的大概率就是这个文件。
.
.
回过头来仔细看,blockinfectedbots是“阻碍特感电脑”的意思(英语太渣一开始看的头皮发麻就没有仔细看),所以猜测可能是
director_allow_infected_bots设为1后导演系统允许特感刷新,但是由于blockinfectedbots把特感的刷新阻塞了,于是把confogl_addcvar
director_allow_infected_bots 改回0 “0”,进入服务器发现ai特感刷新,被牛+口水一顿乱锤,但是非常开心。
.
.

总结 : \cfg\cfgogl\zonemod\shared_cvars.cfg文件 ,把这玩意改成1就行了 confogl_addcvar
director_allow_infected_bots “0”

人话:
这里我只改了ZoneMod v2.1插件的一些配置。
许多参数都写在这个文件里 \cfg\cfgogl\zonemod\shared_cvars.cfg
我列举我找到且用到的几个:

限制连推的次数,下面代码表示连推10次进入冷却。而冷却时间是递增的,在15次之后达到最大值。

confogl_addcvar z_gun_swing_vs_min_penalty 10
confogl_addcvar z_gun_swing_vs_max_penalty 15

阻碍ai特感刷新

confogl_addcvar confogl_blockinfectedbots “0”

导演系统允许ai特感刷新

confogl_addcvar director_allow_infected_bots “1”

关于服务器管理员

这里我直接从网上找资料抄作业,没有深究。

先在服务器中找到文件addons\sourcemod\configs\core.cfg打开。找到第55行。这里的_password可以随便取名,可能是服务器的一个变量,假设我们把它改成
_aaaa(记好了后面要考)

“PassInfoVar” “_password”

然后打开文件addons\sourcemod\configs\admins_simple.ini,拉到最下面加入以下内容,就可以把相应的名称设置为管理员名称

“名称1” “99:z” “密码1”
“名称2” “99:z” “密码2”

名称1可以设置为你的steam用户名。
在连接服务器之前,你需要在控制台输入以下代码来修改一些信息,否则你用管理员名称登录,服务器检测到你密码不对会拒绝你进入。
密码要填写特定管理员名对应的密码

setinfo “_aaaa” “密码”

此外,如果有人尝试在游戏中通过setinfo name "xxx"改名刚好改成了管理员名,也会被服务器直接T掉,除非他提前修改了信息

setinfo “_aaaa” “他改成的管理员名称对应的密码”

只要你使用管理员名称,就有管理员权限。如果你把名称改成了别的,就没有管理员权限。

目前尚未解决的问题

  1. ai生还吃药不加血,没解决,有大佬会的话私信或者评论帮一下忙呗。

关于游戏更新

游戏更新后服务器是没法正常进入的(你可以尝试强行降低游戏版本,但估计不能正常游戏),所以要在服务器更新游戏版本。

./steamcmd.sh
login anonymous
app_update 222860 validate

如果开了插件服务器,则需要等作者更新发布之后,你下载下来在服务器更新

文章历史版本

先堆一点东西占个坑,日后补完

资源和主要参考都是Sir大佬的https://github.com/SirPlease/L4D2-Competitive-Rework

zonemod v2.1 打开ai特感总结:

问题解决了,说一下解决过程吧,可能造福以后遇到类似问题的人。
没有大佬解答
于是自己开始玄学(我下的插件有很多版本,promod(好像是这个名字,太菜了没印象),zonemod2.1,zonemod1.9,apexmod啥的,我只修改了zonemod2.1。)
.
.

具体思路是暴力查找文件,ctrl+f搜索诸如bot、infect、ghost等关键词,最后在\cfg\cfgogl\zonemod\shared_cvars.cfg的文件中找到了confogl_addcvar
confogl_blockinfectedbots "0"和confogl_addcvar director_allow_infected_bots
“0”,遂把它们全改成1
。这时候进服务器可以看到字幕上出现了bommer的响声,linux服务器上也出现了含有jockey一堆英文,但跑了半张图都没见到特感,白高兴一场。于是怀疑还需要改其他的地方,就继续重新搜索了一遍,反而确定了跟特感有关的大概率就是这个文件。
.
.
回过头来仔细看,blockinfectedbots是“阻碍特感电脑”的意思(英语太渣一开始看的头皮发麻就没有仔细看),所以猜测可能是
director_allow_infected_bots设为1后导演系统允许特感刷新,但是由于blockinfectedbots把特感的刷新阻塞了,于是把confogl_addcvar
director_allow_infected_bots 改回0 “0”,进入服务器发现ai特感刷新,被牛+口水一顿乱锤,但是非常开心。
.
.

总结 : \cfg\cfgogl\zonemod\shared_cvars.cfg文件 ,把这玩意改成1就行了 confogl_addcvar
director_allow_infected_bots “0”

完结撒花