爱来自ZZULI❤drluo

C/C++

图像经过bitset处理为二进制后再转为uchar格式便于串口发送,并支持解码为uchar数组(240 * 320)(row * col)

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
 class UCHARFrame {
public:
UCHARFrame() {

}
UCHARFrame(const cv::Mat bi_frame) {
biFrameToUCHARarr(bi_frame);
this->uframe = (uchar*)malloc(sizeof(uchar) * 320 * 240);
}

~UCHARFrame() {
free(this->uframe);
}
// 320列 240行图像 二进制转换为uchar array
void biFrameToUCHARarr(const cv::Mat bi_frame) {
std::bitset<8> f[240][40];
for (int r = 0; r < 240; r++) {
for (int c = 0; c < 320; c++) {
int data = *(bi_frame.data + c + r * 320);
if (data == 0) {
f[r][c / 8].reset(c % 8);
}
//白色 置为1
else {
f[r][c / 8].set(c % 8);
}
}
}

for (int r = 0; r < 240; r++) {
for (int i = 0; i < 40; i++) {
this->frame_uchar_data[r][i] = f[r][i].to_ulong();
}
}
}

// 320列 240行图像 二进制转换为uchar array
uchar* ucharArrToBiFrame() {
for (int r = 0; r < 240; r++) {
for (int i = 0; i < 40; i++) {
int index = i * 8;
for (int b = 1; b < (1 << 8); b = b << 1) {
// 当前二进制位为1 白色
if (this->frame_uchar_data[r][i] & b) {
*(this->uframe + r * 320 + index) = 255;
}
else {
*(this->uframe + r * 320 + index) = 0;
}
index++;
}
}
}
return this->uframe;
}


private:
uchar frame_uchar_data[240][40];
uchar* uframe;
};


int main() {
VideoCapture cap;
cap.open("../video/result_best.mp4");
cap.set(CAP_PROP_FRAME_WIDTH, 320); //设置宽度
cap.set(CAP_PROP_FRAME_HEIGHT, 240); //设置长度
cap.set(CAP_PROP_FOURCC, VideoWriter::fourcc('M', 'J', 'P', 'G'));//视频流格式
Mat frame;
while (1) {
cap >> frame;
cvtColor(frame, frame, COLOR_BGR2GRAY);
threshold(frame, frame, 0, 255, THRESH_OTSU);
UCHARFrame uFrame(frame);
uchar* uframe = uFrame.ucharArrToBiFrame();

for (int r = 0; r < 240; r++) {
for (int c = 0; c < 320; c++) {
// 黑色点
if ((int)(uframe + r * 240 + c) == 0) {
cout << 0;
}
// 白色点
else {
cout << 1;
}
}
cout << endl;
}

waitKey(100000);
}
return 0;
}

拼音排序

unicode_to_hanyu_pinyin.txt下载地址

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
std::map<int, std::string> pinyinMap;
void readPinYinData() {
FILE* fin;
fopen_s(&fin, "unicode_to_hanyu_pinyin.txt", "r");

if (fin == NULL) {
printf("拼音文件打开失败!\n汉语排序可能出错!");
MessageBox(NULL, _T("拼音文件打开失败!\n汉语排序可能出错!"), _T(""), MB_OK | MB_SYSTEMMODAL);
}
else {
int hexNum;
char* pinyinStr = new char[100];
while (!feof(fin)) {
fscanf_s(fin, "%x,%s", &hexNum, pinyinStr, 10);
getc(fin);
//printf("%x %s\n", hexNum, pinyinStr);
pinyinMap.insert(std::pair<int, std::string>{hexNum, pinyinStr});
}

printf("成功载入拼音数据:%zd份", pinyinMap.size());
fclose(fin);
}
}

bool compare(const std::wstring& a, const std::wstring& b) {
for (size_t i = 0; i < a.size() && i < b.size(); ++i) {
if (a[i] != b[i]) {
return strcmp(pinyinMap[(int)a[i]].c_str(), pinyinMap[(int)b[i]].c_str()) < 0 ? 1 : 0;
}
}
return a.size() < b.size();
}

bool cmpWithName(Drug a, Drug b) {
return compare(a.name, b.name);
}

void sortByName(std::vector<Drug>& drugList) {
std::sort(drugList.begin(), drugList.end(), cmpWithName);
}

Python

mp42img

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
# 导入所需要的库
import cv2

# 定义保存图片函数
# image:要保存的图片名字
# addr;图片地址与相片名字的前部分
# num: 相片,名字的后缀。int 类型x
def save_image(image,addr,num):
address = addr + str(num)+ '.jpg'
cv2.imwrite(address,image)

# 读取视频文件
videoCapture = cv2.VideoCapture("sample.mp4")
# 通过摄像头的方式
# videoCapture=cv2.VideoCapture(1)

#读帧
success, frame = videoCapture.read()
i = 1000
timeF = 3
j=0
while success :
i = i + 1
if (i % timeF == 0):
j = j + 1
save_image(frame,'./frame/',j)
print('save image:',i)
success, frame = videoCapture.read()

img2mp4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import cv2

fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 设置输出视频为mp4格式

# cap_fps是帧率,可以根据随意设置
cap_fps = 30

# 注意!!!
# size要和图片的size一样,但是通过img.shape得到图像的参数是(height,width,channel),但是此处的size要传的是(width,height),这里一定要注意注意不然结果会打不开,比如通过img.shape得到常用的图片尺寸
size = (320, 240)

# 设置输出视频的参数,如果是灰度图,可以加上 isColor = 0 这个参数
# video = cv2.VideoWriter('results/result.avi',fourcc, cap_fps, size, isColor=0)
video = cv2.VideoWriter('result_best.mp4', fourcc, cap_fps, size)

# 这里直接读取py文件所在目录下的pics目录所有图片。
path = './image/bestf/'
for i in range(1,1000):
filename = str(i) + ".jpg"
img = cv2.imread(path + filename)
video.write(img)

视频加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2

vdop = ".\\lx2.mp4"#输入视频路径
cap = cv2.VideoCapture(vdop)
fps = cap.get(cv2.CAP_PROP_FPS) #获取输入视频的帧率
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))#获取输入视频的大小
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 设置输出视频为mp4格式

out_path = ".\\fast.mp4" #输出2倍速的avi格式的视频路径
output_viedo = cv2.VideoWriter()
fps = 2*fps #2倍速处理
#isColor:如果该位值为Ture,解码器会进行颜色框架的解码,否则会使用灰度进行颜色架构
output_viedo.open(out_path , fourcc, fps, size, isColor=True)
rval = True
while rval:
rval, img = cap.read()#逐帧读取原视频
output_viedo.write(img)#写入视频帧
output_viedo.release()
cap.release()

图像RGB均值/方差计算

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
import os
import numpy as np
import cv2

files_dir = './Images/'
files = os.listdir(files_dir)

R = 0.
G = 0.
B = 0.
R_2 = 0.
G_2 = 0.
B_2 = 0.
N = 0


for file in files:
img = cv2.imread(files_dir+file)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = np.array(img)
h, w, c = img.shape
N += h*w

R_t = img[:, :, 0]
R += np.sum(R_t)
R_2 += np.sum(np.power(R_t, 2.0))

G_t = img[:, :, 1]
G += np.sum(G_t)
G_2 += np.sum(np.power(G_t, 2.0))

B_t = img[:, :, 2]
B += np.sum(B_t)
B_2 += np.sum(np.power(B_t, 2.0))

R_mean = R/N
G_mean = G/N
B_mean = B/N

R_std = np.sqrt(R_2/N - R_mean*R_mean)
G_std = np.sqrt(G_2/N - G_mean*G_mean)
B_std = np.sqrt(B_2/N - B_mean*B_mean)

print("R_mean: %f, G_mean: %f, B_mean: %f" % (R_mean, G_mean, B_mean))
print("R_std: %f, G_std: %f, B_std: %f" % (R_std, G_std, B_std))

VOC数据集列表获取

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
def get_annotations(label_list, datadir, Annotations = 'annotations', Images = 'images'):
filenames = os.listdir(os.path.join(datadir, Annotations))
records = []
ct = 0
for fname in filenames:
fid = fname.split('.')[0]
fpath = os.path.join(datadir, Annotations, fname)
img_file = os.path.join(datadir, Images, fid + '.jpg')
tree = ET.parse(fpath)

if tree.find('id') is None:
im_id = np.array([ct])
else:
im_id = np.array([int(tree.find('id').text)])

objs = tree.findall('object')
im_w = float(tree.find('size').find('width').text)
im_h = float(tree.find('size').find('height').text)
gt_bbox = np.zeros((len(objs), 4), dtype=np.float32)
gt_class = np.zeros((len(objs), ), dtype=np.int32)
is_crowd = np.zeros((len(objs), ), dtype=np.int32)
difficult = np.zeros((len(objs), ), dtype=np.int32)
for i, obj in enumerate(objs):
cname = obj.find('name').text
gt_class[i] = label_list[cname]
_difficult = int(obj.find('difficult').text)
x1 = float(obj.find('bndbox').find('xmin').text)
y1 = float(obj.find('bndbox').find('ymin').text)
x2 = float(obj.find('bndbox').find('xmax').text)
y2 = float(obj.find('bndbox').find('ymax').text)
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(im_w - 1, x2)
y2 = min(im_h - 1, y2)
# 这里使用xywh格式来表示目标物体真实框
gt_bbox[i] = [(x1+x2)/2.0 , (y1+y2)/2.0, x2-x1+1., y2-y1+1.]
is_crowd[i] = 0
difficult[i] = _difficult

voc_rec = {
'im_file': img_file,
'im_id': im_id,
'h': im_h,
'w': im_w,
'is_crowd': is_crowd,
'gt_class': gt_class,
'gt_bbox': gt_bbox,
'gt_poly': [],
'difficult': difficult
}
if len(objs) != 0:
records.append(voc_rec)
ct += 1
return records

'''
record格式:
{'im_file': 'Car2024\\Images\\block1.jpg', 'im_id': array([0]), 'h': 240.0, 'w': 320.0, 'is_crowd': array([0, 0, 0]), 'gt_class': array([11, 8, 8]), 'gt_bbox': array([[110.5, 21.5, 36. , 30. ],
[ 99.5, 70. , 32. , 41. ],
[266. , 164.5, 49. , 70. ]], dtype=float32), 'gt_poly': [], 'difficult': array([0, 0, 0])}
'''


# E.g
label_list = {'spy': 0, 'safety': 1, 'bridge': 2, 'danger': 3, 'tumble': 4, 'thief': 5, 'evil': 6, 'bomb': 7, 'cone': 8, 'crosswalk': 9, 'prop': 10, 'block': 11, 'patient': 12}
records = get_annotations(label_list, train_dir, Annotations = 'Annotations', Images = 'Images')

VOC数据类别统计

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
"""
#数据集预处理.类别统计
# !/usr/bin/env python
# encoding: utf-8
"""

import xml.dom.minidom as xmldom
import os


def voc_label_statistics(annotation_path):
"""
VOC 数据集类别统计
:param annotation_path: voc数据集的标签文件夹
:return: {'class1':'count',...}
"""
count = 0
annotation_names = [
os.path.join(annotation_path, i) for i in os.listdir(annotation_path)
]

labels = dict()
for names in annotation_names:
names_arr = names.split(".")
file_type = names_arr[-1]
if file_type != "xml":
continue
file_size = os.path.getsize(names)
if file_size == 0:
continue

count = count + 1
# print('process:', names)
xmlfilepath = names
domobj = xmldom.parse(xmlfilepath)
# 得到元素对象
elementobj = domobj.documentElement
# 获得子标签
subElementObj = elementobj.getElementsByTagName("object")
for s in subElementObj:
label = s.getElementsByTagName("name")[0].firstChild.data

label_count = labels.get(label, 0)
labels[label] = label_count + 1

print("文件标注个数:", count)
return labels


if __name__ == "__main__":
annotation_path = "C:/Users/drluo/Desktop/total train data/Annotations"

label = voc_label_statistics(annotation_path)
print(label)

VOC数据分配

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
"""
这是随机划分VOC数据集的脚本
"""

import os
import numpy as np


def random_split_voc_dataset(voc_dataset_dir, train_ratio=0.8):
"""
这是随机划分VOC数据集为训练集和验证集的函数
:param voc_dataset_dir: VOC数据集地址
:param train_ratio: 训练集比例,默认为0.8
:return:
"""
# 初始化相关文件和文件夹路径
# voc_main_dir = os.path.join(voc_dataset_dir,'ImageSets','Main')
voc_main_dir = voc_dataset_dir
voc_image_dir = os.path.join(voc_dataset_dir, 'Images')
train_txt_path = os.path.join(voc_main_dir, 'train.txt')
trainval_txt_path = os.path.join(voc_main_dir, 'trainval.txt')
val_txt_path = os.path.join(voc_main_dir, 'val.txt')
if not os.path.exists(voc_main_dir):
os.makedirs(voc_main_dir)

# 遍历图像文件夹,获取所有图像
image_name_list = []
for image_name in os.listdir(voc_image_dir):
image_name_list.append(image_name)
image_name_list = np.array(image_name_list)
image_name_list = np.random.permutation(image_name_list)

# 划分训练集和测试集
size = len(image_name_list)
random_index = np.random.permutation(size)
train_size = int(size * train_ratio)
train_image_name_list = image_name_list[random_index[0:train_size]]
val_image_name_list = image_name_list[random_index[train_size:]]

# 生成trainval
with open(trainval_txt_path, 'w') as f:
for image_name in image_name_list:
fname, ext = os.path.splitext(image_name)
fname = "./Images/" + fname + ".jpg" + "," + "./Annotations/" + fname + ".xml"
f.write(fname + "\n")
# 生成train
with open(train_txt_path, 'w') as f:
for image_name in train_image_name_list:
fname, ext = os.path.splitext(image_name)
fname = "./Images/" + fname + ".jpg" + "," + "./Annotations/" + fname + ".xml"
f.write(fname + "\n")
# 生成val
with open(val_txt_path, 'w') as f:
for image_name in val_image_name_list:
fname, ext = os.path.splitext(image_name)
fname = "./Images/" + fname + ".jpg" + "," + "./Annotations/" + fname + ".xml"
f.write(fname + "\n")


def run_main():
"""
这是主函数
"""
train_ratio = 0.8
# voc_dataset_dir = os.path.abspath("./"),
voc_dataset_dir = './'
random_split_voc_dataset(voc_dataset_dir, train_ratio)


if __name__ == '__main__':
run_main()

Paddle模型预测(mobilenet_ssd)

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
import cv2
import numpy as np
# 引用 paddle inference 预测库
import paddle.inference as paddle_infer
from PIL import Image

# 创建 config
config = paddle_infer.Config("./models/mobilenet_ssd/mobilenet-ssd-model",
"./models/mobilenet_ssd/mobilenet-ssd-params")

# 根据 config 创建 predictor
predictor = paddle_infer.create_predictor(config)

# 获取输入 Tensor
input_names = predictor.get_input_names()
input_tensor = predictor.get_input_handle(input_names[0])

print(input_names)
print(input_tensor)

# 从 CPU 获取数据,设置到 Tensor 内部
imgPath = './frame/15.jpg'

cap = cv2.VideoCapture('./models/mobilenet_ssd/sample.mp4')
frame = []
while True:
ret, frame = cap.read()
if ret == False:
break
# cv2.imwrite("./frame/frame.jpg", frame)
# imgPath = './frame/frame.jpg'
im = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))# Image.open(imgPath)
im = im.resize((300, 300), Image.BICUBIC)

# img = cv2.cvtColor(numpy.asarray(im.resize((320, 240), Image.BICUBIC)), cv2.COLOR_RGB2BGR)#cv2.cvtColor(, cv2.COLOR_RGB2BGR) # 转换代码
# cv2.imshow('img', img) # opencv显示
# cv2.waitKey()

im = np.array(im, dtype=np.float32).transpose(2, 0, 1).reshape((1, 3, 300, 300))
mean = [121.636447, 125.696666, 124.807721]
# mean = [127.5, 127.5, 127.5]
scale = [0.007843,
0.007843,
0.007843]
std = [ 69.480526,
66.625320,
50.616609]

for ri in range(0, 300):
im[0][0][ri] = (im[0][0][ri] / 255)# - (scale[0] - mean[0] / 255)
for gi in range(0, 300):
im[0][1][gi] = (im[0][1][gi] / 255)# - (scale[1] - mean[1] / 255)
for bi in range(0, 300):
im[0][2][bi] = (im[0][2][bi] / 255)# - (scale[2] - mean[2] / 255)

fake_input = im

input_tensor1 = predictor.get_input_handle(input_names[0])
input_tensor1.copy_from_cpu(im)
#
# im = np.asarray([[320.0/240.0, 320.0/320.0]], dtype=np.float32)
# input_tensor2 = predictor.get_input_handle(input_names[1])
# input_tensor2.copy_from_cpu(im)
#
# im = np.asarray([[608, 608]], dtype=np.float32)
# input_tensor2 = predictor.get_input_handle(input_names[2])
# input_tensor2.copy_from_cpu(im)

# 执行预测
predictor.run()

# 获取输出 Tensor
output_names = predictor.get_output_names()
output_tensor = predictor.get_output_handle(output_names[0])

# 从 Tensor 中获取数据到 CPU
output_data = output_tensor.copy_to_cpu()

# 获取 Tensor 的维度信息
output_shape = output_tensor.shape()

# 获取 Tensor 的数据类型
output_type = output_tensor.type()

results = []
i = 0
img = frame
label_list = ["background", "cone", "bridge", "pig", "tractor", "corn", "bump", "crosswalk", " "]
label_list = ["background", "cone", "granary", "bridge", "tractor", "corn", "pig", "crosswalk", "bump"]
img = cv2.resize(img, dsize=(300, 300), fx=1, fy=1, interpolation=cv2.INTER_LINEAR)
if len(output_data) > 1:
for data in output_data:
tmp = {}
if data[1] > 0.9:
tmp['type'] = data[0]
tmp['score'] = data[1]
tmp['x'] = data[2] * 300
tmp['y'] = data[3] * 300
tmp['width'] = data[4] * 300 - tmp['x']
tmp['height'] = data[5] * 300 - tmp['y']
results.insert(i, tmp)
# print(tmp)
i = i + 1
for i in results:
x = int(i['x'])
xmax = int(i['x'] + i['width'])
y = int(i['y'])
ymax = int(i['y'] + i['height'])
# print((x, y), (xmax, ymax))
cv2.rectangle(img, (x, y), (xmax, ymax), (0, 0, 255), 2)
# cv2.rectangle(img, (x, ymax), (xmax, y), (255, 0, 0), 2)
cv2.putText(img,
str(label_list[int(i['type'])]),
(int(x), int(y)),
cv2.FONT_HERSHEY_SIMPLEX,
0.75,
(0, int(i['type']) * 30, 255),
2)
img = cv2.resize(img, dsize=(320, 240), fx=1, fy=1, interpolation=cv2.INTER_LINEAR)
cv2.imshow('src', img)
cv2.waitKey(1)

# 释放中间 Tensor
predictor.clear_intermediate_tensor()

# 释放内存池中的所有临时 Tensor
predictor.try_shrink_memory()

xml文件解析(paddle官方数据标注格式下)

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
import xml.dom.minidom

def getXMLData(xml_file):
# 打开xml文档
dom = xml.dom.minidom.parse(xml_file)

# 得到文档元素对象
root = dom.documentElement

filename = root.getElementsByTagName('filename')[0].firstChild.data

# object_num = root.getElementsByTagName('object_num')[0].firstChild.data

object = []
for i in root.getElementsByTagName('object'):
name = i.getElementsByTagName('name')[0].firstChild.data
# difficult = i.getElementsByTagName('difficult')[0].firstChild.data
bndbox = i.getElementsByTagName('bndbox')[0]
xmin = bndbox.getElementsByTagName('xmin')[0].firstChild.data
ymin = bndbox.getElementsByTagName('ymin')[0].firstChild.data
xmax = bndbox.getElementsByTagName('xmax')[0].firstChild.data
ymax = bndbox.getElementsByTagName('ymax')[0].firstChild.data

data = {'name': name,
# 'difficult': difficult,
'xmin': xmin,
'ymin': ymin,
'xmax': xmax,
'ymax': ymax}

object.append(data)
# print(root.nodeName)
# print (filename)
# print(object)
return filename, object

if __name__ == '__main__':
data_path = './Car2024/'
xml_file = data_path + "/Annotations/block10.xml"
filename, object = getXMLData(xml_file)
print(len(object))

JAVA

Pair<Key, Value>

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
package util;

public final class Pair<K, V> {

private K key;
private V value;

private Pair(K key, V value) {
this.key = key;
this.value = value;
}

public static <K, V> Pair<K, V> of(K key, V value) {
return new Pair<>(key, value);
}

public K getKey() {
return key;
}

public V getValue() {
return value;
}

@Override
public String toString() {
if (key == null && value == null) return "";
if (key == null && value instanceof String) return value.toString();
if (value == null && key instanceof String) return key.toString();
if (key == null || value == null) return "";
return key.toString() + value.toString();
}

public boolean equals(Pair<K, V> obj) {
if (obj == null) return false;
return obj.getKey().equals(getKey()) && obj.getValue().equals(getValue());
}
}

字符串分段排序

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
package common;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Sort {
/**
* sort StringList
*
* @param strArr split array.
* @param seqStr For split strings, if you need take some content before the seqStr, then this item needs to be adjusted.
* @param interval inserting content between results.
* @return sorted ans.
*/
public static String stringSort(String[] strArr, String seqStr, String interval){
//分割后字符串子串列表
//[P1012, John Li, 01/01/2000, C001, T001]
List<List<String>> splitList = new ArrayList<>();
List<String> stringList = new ArrayList<>();

for (String str : strArr){
splitList.add(List.of(str.split(seqStr)));
stringList.add(str);
}

quickSort(stringList,splitList,0,stringList.size()-1);

WordUtil words = new WordUtil(interval, stringList, false);
return words.getResult();
}

private static void quickSort(List<String> stringList, List<List<String>> splitList, int left, int right){
if(left < right){
int pivot = partition(stringList, splitList, left, right);
quickSort(stringList, splitList, left, pivot-1);
quickSort(stringList, splitList, pivot+1, right);
}
}

private static int partition(List<String> stringList, List<List<String>> splitList, int left, int right){
List<String> pivot = splitList.get(left);
int i = left + 1, j = right;
while(i <= j){
while(i <= j && strListCmp(splitList.get(i),pivot) <= 0) i++;
while(i <= j && strListCmp(splitList.get(j),pivot) > 0) j--;
if(i < j){
Collections.swap(splitList, i, j);
Collections.swap(stringList, i, j);
}
}
Collections.swap(splitList, left, j);
Collections.swap(stringList, left, j);
return j;
}

private static int strListCmp(List<String> str1, List<String> str2){
if(str1.size() > str2.size()){
return 1;
}
if(str1.size() < str2.size()){
return -1;
}

for(int i = 0; i < str1.size(); i++) {
int error = str1.get(i).compareTo(str2.get(i));
if (error != 0) {
return error;
}
}

return 0;
}

}

HTML

midi播放可视化1

参考文章:

Pixi + Tone 实现简单midi音频可视化_tone.js节拍器-CSDN博客

代码实现效果:

网页详情

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Midi可视化</title>

<script type="text/javascript" src="./js/Tone.js"></script>
<script type="text/javascript" src="./js/Midi.js"></script>
<script type="text/javascript" src="./js/pixi.js"></script>
</head>
<body>
<input type="file" id="file">
<input type="button" id="play" value="播放"/>

<script>
const Application = PIXI.Application; // 应用类,快速创建PIXI应用
const Sprite = PIXI.Sprite; // 精灵类
const Graphics = PIXI.Graphics; // 图形类
// 创建应用程序并挂载
const pixi = new Application({
width: 1000,
height: 600,
backgroundColor: 0x000000
})
// pixi.view 代表画布,是一个canvas元素
document.body.appendChild(pixi.view);

const config = {
speed: 1,
leftColor: 0xFF69B4,
rightColor: 0x00BFFF,
color1: 0x66A9C9,
color2: 0xF0C9CF,
color3: 0xE2C17C,
color4: 0x363433,
color5: 0xFF4500,
}
// 定义灯光类作为音符的可视化
class Light extends Graphics {
constructor(color, height, x) {
super();
this.beginFill(color);
this.drawRect(x, 600, 10, height);
this.endFill();
// pixijs的定时器,可以实现每帧执行一次,并且十分稳定
pixi.ticker.add(() => {
this.y -= config.speed * 5;
});
}
}
// pixi.stage 代表舞台,所有的物体必须挂载在舞台上才可以显示。

const input = document.querySelector('#file');
input.addEventListener('change', (e) => {
console.log(e.target.files[0]);
parseMidi(e.target.files[0]);
})

// 读取midi文件
function parseMidi(file) {
// 创建文件读取器
const reader = new FileReader();
// 读取文件
reader.readAsArrayBuffer(file);
// 文件读取完成后将文件转化为json对象
reader.addEventListener('load', (e) => {
currentMidi = new Midi(e.target.result);
console.log(currentMidi);
})
}

play.addEventListener('click', (e) => {
console.log(currentMidi);
// 如果未加载midi文件
if(!currentMidi) {
alert('未加载文件');
return;
}

const now = Tone.now() + 0.5; // 获取当前时间
const synths = []; // 存储合成器
// 遍历midi文件中的轨道
currentMidi.tracks.forEach(track => {
// 创建合成器作为音轨并连接至出口,音色使用Tonejs的默认音色
const synth = new Tone.PolySynth(Tone.Synth, {
envelope: {
// 声音的生命周期:按下按键 - 渐入 - 攻击阶段 - 衰减阶段 - 衰减结束 - 松开按键 - 声音消逝

<!-- // 旧 -->
<!-- attack: 0.02, // 渐入时间 -->
<!-- decay: 0.1, // 攻击阶段(最大音量)持续时间 -->
<!-- sustain: 0.3, // 衰减结束后的最小声音 -->
<!-- release: 1, // 从松开按键到声音彻底消失所需的时间 -->

// Piano
attack: 0.005, // 渐入时间
decay: 1.5, // 攻击阶段(最大音量)持续时间
sustain: 0.05, // 衰减结束后的最小声音
release: 0.005, // 从松开按键到声音彻底消失所需的时间
},
}).toDestination();
// 将合成器存储起来,为之后停止播放的功能留下接口。
synths.push(synth);

// 遍历轨道中的每个音符
track.notes.forEach(note => {
// 合成器发声
note.velocity = note.velocity/10;
synth.triggerAttackRelease(
note.name, // 音名
note.duration, // 持续时间
note.time + now, // 开始发声时间
note.velocity // 音量
);

// 在播放按钮的事件中,遍历音符时,创建音频调度,实现音画同步
Tone.Transport.schedule((time) => {
// 根据音调划分颜色,(其实应该根据轨道来划分的)
var color = config.color1;
if(note.midi < 15) {
color = config.color1;
} else if (note.midi < 30){
color = config.color2;
} else if (note.midi < 45){
color = config.color3;
} else if (note.midi < 60){
color = config.color4;
} else {
color = config.color5;
}
//color = '0x'+ Math.random().toString(16).substr(2,6);
pixi.stage.addChild(new Light(color, note.duration * 150 * config.speed, (note.midi - 20) * 10))

}, note.time + now);

// 在代码最外层设置音频调度的模式,并启动音频调度。
Tone.context.latencyHint = 'fastest';
Tone.Transport.start();

});
});
})



</script>
</body>
</html>


Tone.js文件,Midi.js文件,pixi.js文件:

https://www.drluo.top/friends/self/midivisualization/js/Tone.js

https://www.drluo.top/friends/self/midivisualization/js/Midi.js

https://www.drluo.top/friends/self/midivisualization/js/pixi.js

midi播放可视化2

由于https://storage.googleapis.com网站国内可能无法访问,所以此代码可能因为缺失音效而无法播放,暂未找到相关镜像替代,请等待后续修改。

由于上面的代码效果不好,找了几圈,看到了html-midi-player | Play and display MIDI files online (cifkao.github.io)

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Midi可视化</title>

<link rel="stylesheet" href="./css/style.css">
<script type="text/javascript" src="./js/core.js"></script>
<script type="text/javascript" src="./js/index.js"></script>
</head>
<body>
<main id="content" class="main-content" role="main">
<div>
<midi-visualizer type="piano-roll" id="mainVisualizer"
src="./mid/弱虫だって 咩栗X呜米.mid">
</midi-visualizer>

<midi-player
src="./mid/弱虫だって 咩栗X呜米.mid"
sound-font="" visualizer="#mainVisualizer" id="mainPlayer" data-js-focus-visible="">


<!-- 播放/暂停进度条 -->
<div class="controls stopped" part="control-panel">
<button class="play" part="play-button">
<span class="icon play-icon"><svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m4.4979 3.175-2.1167 1.5875v-3.175z" stroke-width=".70201"></path>
</svg>
</span>
<span class="icon stop-icon"><svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m1.8521 1.5875v3.175h0.92604v-3.175zm1.7198 0v3.175h0.92604v-3.175z" stroke-width=".24153"></path>
</svg>
</span>
<span class="icon error-icon"><svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path transform="scale(.26458)" d="m12 3.5a8.4993 8.4993 0 0 0-8.5 8.5 8.4993 8.4993 0 0 0 8.5 8.5 8.4993 8.4993 0 0 0 8.5-8.5 8.4993 8.4993 0 0 0-8.5-8.5zm-1.4062 3.5h3v6h-3v-6zm0 8h3v2h-3v-2z"></path>
</svg>
</span>
</button>
<div part="time"><span class="current-time" part="current-time">0:00</span> / <span class="total-time" part="total-time">0:15</span></div>
<input type="range" min="0" max="15.999984" value="0" step="any" class="seek-bar" part="seek-bar">
<div class="overlay loading-overlay" part="loading-overlay"></div>
</div>

<!-- 播放/暂停进度条 -->
</midi-player>

</div>

<p>
<label for="midiFile">选择Midi文件:</label>
<input type="file" id="midiFile" name="midiFile" accept="audio/midi, audio/x-midi">
</p>

</main>


</body>
</html>

代码实现效果:

网页详情

由于https://storage.googleapis.com网站国内可能无法访问,所以此代码可能因为缺失音效而无法播放,暂未找到相关镜像替代,请等待后续修改。

style.css文件,core.js文件,index.js文件:

https://www.drluo.top/friends/self/midivisualization/css/style.css

https://www.drluo.top/friends/self/midivisualization/js/core.js

https://www.drluo.top/friends/self/midivisualization/js/index.js