JoyLau's Blog

JoyLau 的技术学习与思考

背景

城市版交通路况的分析基于高速公路路况的基础上以合肥为试点城市进行的研究课题.

记录

目前该研究课题已完成:

  1. 筛选出合肥市在百度地图 17 等级下的路况瓦片 62354 张
  2. 对合肥二环路内圈的路况进行分析,目前分析出的路况开发截图

分析结果: 用蓝色的线表示,画线时未区分颜色,实际分析已区分

目前已分析提取出的数据有:

  1. 拥堵段 2 端点的经纬度(百度,WGS84坐标系)

  2. 拥堵段距离,精确到到 0.1 m

  3. 拥堵段的拥堵状态 (缓行,拥堵,严重拥堵)

  4. 拥堵段的 WKT 空间信息, 单段 拥堵为 lineString格式, 近距离多段拥堵为 multiLineString 格式, 此信息可直接绘制在地图上

  5. 提取出瓦片的拥堵信息,再根据我们自己项目中使用的地图的比例尺渲染成适合我们地图的路况图层

目前该研究课题存在的问题:

  1. 有些复杂路口分析不正确,例如:

进行分析后,会变成下面这样:

还有这样的:

结果 :

目前的分析算法对于单张瓦片多处拥堵,且拥堵路段呈直线状分析结果准确,对于拥堵路段为曲线状且涉及多个路段时分析的结果不如人意,这是目前分析算法的原因,尚待改进.

性能问题,我目前测试了合肥二环路内圈的路况耗时日志结果如下:

该测试结果是我在笔记本上运行的结果,其中参数配置是:

  1. CPU : 2.8 GHz Intel Core i7-4 * 2核心, RAM: 16 GB 2133 MHz LPDDR3 , Graphics Card: Radeon Pro 555 2 GB

  2. 程序运行核心线程数 2 , 最大线程数 5, 运行过程中 CPU 使用率 50% ~ 60%

  3. 测试时间 4 月 16 日下午 16 点左右

测试结果: 单机运行, 12685 张图片耗时 714 s, 平均处理速度 18 张/秒 左右

相对于安徽高速,城市版的瓦片分析已经从 Java 的图片处理切换到 OpenCV,有个因素是 OpenCV 绝大数 API 是支持图形卡加速的,我本机的开发环境是开启了图形卡加速,但是一般的服务器是没有图形卡的.

未知因素: 获取合肥市内路网信息,获取后可进一步分析出以下信息:

  1. 获取详细的拥堵文字描述信息

  2. 按照某一条路进行分组,方便对一条路上多段拥堵进行合并

  3. 分析出拥堵路段涉及的行政区划

  4. 分析出该次拥堵的状态(首次拥堵,持续拥堵,结束拥堵),及拥堵持续时间

注解

在类上加入注解
@JsonInclude(JsonInclude.Include.NON_EMPTY)

解释

Include.Include.ALWAYS: 默认
Include.NON_DEFAULT: 属性为默认值不序列化
Include.NON_EMPTY: 属性为 空(””) 或者为 NULL 都不序列化
Include.NON_NULL: 属性为NULL 不序列化

页面

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
const formData = new FormData();

fileList.forEach(file => {
formData.append('file', file.originFileObj);
});


// 每个表单是否填写完成
let params = [];

.....


let data = {};
data.filePath = "";
data.markers = params;
formData.append("params", data);

$.ajax({
url: "/marker/file",
type: "POST",
processData: false,
contentType: false,
data: formData,
success: function (data) {
},
error: function (err) {
}
});

spring boot 处理

1
2
3
4
@PostMapping("/file")
public Object markerFile(@RequestParam("file") MultipartFile multipartFile, Params params){
return markerService.marker(multipartFile,params);
}

注意

  1. antd 上传组件里,真正的文件是 file.originFileObj
  2. params 是复杂的对象的话, spring boot 接受的 Params 对象需要使用 String 字符串进行序列化成对象; 或者将 Params 对象的属性分开来写, 如果某个属性又是复杂对象的话通用需要序列化

背景

上一篇文章记录了因为远程桌面连接把 Ubuntu 的 /home 弄坏了
好一番折腾。。。。
其实这个远程桌面我早就想重新配置了,今天我终于受不了它了,于是我觉得仔细研究一番找到适合我自己的方式来操作

以前的方式

之前我的远程配置是 xrdp + tightvncserver
然后我每次都是使用 Windows 上的 mstsc 来连接的
连接上后会出现 xrpd 的登录选项
每次我都选第一个 sesman-Xvnc 然后输入用户名密码即可

可这样的连接方式有个很不好的方面,就是这种方式是多用户的,想回家继续没干完的事情
连接上发现是一个新的桌面
都不知道做到什么地方了

这也就算了

最大的问题远程操作操作这就没响应了,鼠标的指针变成了 × 号,所有的东西都不能点,而且第二天到公司桌面卡死不动,只能重启桌面或重启系统,很多打开软件和工具都会还原

这是我最不能忍的地方

决定改变

我决定不使用这种方式来进行远程,远程 teamviewer 是比较合适的选择,但是工作由于连接的终端太多,被检测商用,每次连接都是只有 1 min 的操作时间
很尴尬…

最后决定使用轻量级的 vnc 服务来解决这个问题,并且搭配 xrdpany vnc 来使用 mstsc 远程连接

重新配置

x11vnc

  1. 卸载以前的 vnc 服务端
1
2
3
sudo apt remove tigervncserver
sudo apt remove tightvncserver
systemcrl auto remove
  1. 安装 x11vnc ,并进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sudo apt install x11vnc -y

sudo x11vnc -storepasswd /etc/x11vnc.pass # 配置访问密码并存储

vim /lib/systemd/system/x11vnc.service # 创建系统服务

# 服务配置
[Unit]
Description=Start x11vnc at startup.
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/bin/x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /etc/x11vnc.pass -rfbport 5900 -shared
[Install]
WantedBy=multi-user.target

systemctl enable x11vnc.service
systemctl start x11vnc.service

  1. 解决复制粘贴的问题
    • sudo apt install autocutsel 安装 autocutsel
    • autocutsel -f 后台运行

问题及解决

下载 vnc-view 新建一个连接发现连不上…
尴尬。。。
检查 5900 端口,是开放的

1
2
3
joylau@joylau-work-192:~$ sudo netstat -tnlp | grep :5900
tcp 0 0 0.0.0.0:5900 0.0.0.0:* LISTEN 4022/vino-server
tcp6 0 0 :::5900 :::* LISTEN 4022/vino-server

但是使用的进程是 vino-server ,这是 Ubuntu 自带程序开启的服务
原来端口被占用了
关闭服务 : 找到桌面共享,关闭 允许其他人查看您的桌面
重启 x11vnc 服务
连接成功

最后

现在有 4 中方式使用

  1. 使用 vnc-view 使用是单用户的,类似 teamviewer 那样,2 边操作都能互相看见
  2. 使用 mstsc , 连接到 xrdp 后,再选中 any vnc 使用 vnc 协议连接,效果和第一种是一样的,只不过不需要客户端了
  3. 浏览器直接远程,这种最方便,下面有说明
  4. 以前的那种使用方式, 多用户的,估计我是不会再用了

补充

第三种多用户方式连接,没连接一次就生成一个新的桌面,这个很烦,想连接回上次的桌面,可修改配置 /etc/xrdp/xrdp.ini

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
[globals]

bitmap_cache=yes 位图缓存

bitmap_compression=yes 位图压缩

port=3389 xrdp监听的端口(重要)

crypt_level=low 加密程度(low为40位,high为128位,medium为双40位)

channel_code=1

max_bpp=24 XRDP最大连接数

[xrdp1]

name=sesman-Xvnc XRDP的连接模式

lib=libvnc.so

username=ask

password=ask

ip=127.0.0.1

port=-1

修改 port 为 固定端口号或者 ask-1
下次连接不修改即可

注:再记录下 sesman.ini 的配置

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
[Globals]

ListenAddress=127.0.0.1 监听ip地址(默认即可)

ListenPort=3350 监听端口(默认即可)

EnableUserWindowManager=1 1为开启,可让用户自定义自己的启动脚本

UserWindowManager=startwm.sh

DefaultWindowManager=startwm.sh

[Security]

AllowRootLogin=1 允许root登陆

MaxLoginRetry=4 最大重试次数

TerminalServerUsers=tSUSErs 允许连接的用户组(如果不存在则默认全部用户允许连接)

TerminalServerAdmins=tsadmins 允许连接的超级用户(如果不存在则默认全部用户允许连接)

[Sessions]

MaxSessions=10 每个用户最大会话数

KillDisconnected=0 是否立即关闭断开的连接(如果为1,则断开连接后会自动注销)

IdleTimeLimit=0 空闲会话时间限制(0为没有限制)

DisconnectedTimeLimit=0 断开连接的存活时间(0为没有限制)

[Logging]

LogFile=./sesman.log 登陆日志文件

LogLevel=DEBUG 登陆日志记录等级(级别分别为,core,error,warn,info,debug)

EnableSyslog=0 是否开启日志

SyslogLevel=DEBUG 系统日志记录等级

使用浏览器来远程桌面

像阿里云等云服务提供商一样直接在浏览器上进行远程操作

1
docker run  -e REMOTE_HOST=192.168.10.192 -e REMOTE_PORT=5900 -p 8081:8081 -d --restart always --name novnc dougw/novnc

打开浏览器 http://host:8081/vnc.html

秀啊!!!

背景

在家里使用 vnc 协议远程连接公司的 Ubuntu 电脑
导致桌面卡死
期间还遇到了 搜狗输入法崩溃,提示我删除用户目录下的一个文件然后重启
鼠标可以动
界面上的任何东西都无法点击
没再操作
等第二天到公司解决

不重启解决 Ubuntu 桌面卡死

这样的情况遇到很多了
ctrl + alt + f1

1
2
3
4
5
6
7
ps -t tty7

PID TTY TIME CMD
1758 tty7 00:00:55 Xorg

kill -9 1758

之后桌面上的应用都会被关闭,回到登录界面

重启进入紧急模式

之后我想着电脑很久没关机了,想重启一下,顺便去倒杯水
回来之后发现系统正在进行磁盘检测并且之后进入了紧急模式

journalctl -xb 查看启动日志

一直往下翻

发现 /dev/sdb6 分区出现问题导致系统无法启动

使用 lsblk 查看分区挂载情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
joylau@joylau-work-192:~$ lsblk -f
NAME FSTYPE LABEL UUID MOUNTPOINT
loop1 squashfs /snap/core/6405
sdb
├─sdb2
├─sdb5 ext4 4ff695c6-b2ef-46d9-8501-c7e8ee61edda /
├─sdb1 ext4 98f0eb66-3d90-4bc5-a1f0-d3117de87809 /boot
└─sdb6 ext4 76ad5dc1-37b7-4624-830b-d923dac8ac48
loop4 squashfs /snap/redis-desktop-manager/191
loop2 squashfs /snap/core/6673
loop0 squashfs /snap/redis-desktop-manager/156
sdc
├─sdc2 ntfs 新加卷 AE5CA91F5CA8E2F7 /media/extra
└─sdc1 ntfs 新加卷 0CBC9840BC9825EC
sda
├─sda2 ntfs 5E68EE4D68EE240D
├─sda7 ntfs EAD67107D670D573
├─sda5 ntfs 9E14908214905ED9
├─sda3
├─sda1 ntfs 系统保留 A27AE98B7AE95C93
└─sda6 ntfs E2D84A6BD84A3E53 /media/extra_2
loop3 squashfs

解决问题

上面看到 sdb6 没有挂载点,实际上是有的,只不过现在出问题了没有挂载上
可以找 UUID 76ad5dc1-37b7-4624-830b-d923dac8ac48

查看 /etc/fstab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/sdb5 during installation
UUID=4ff695c6-b2ef-46d9-8501-c7e8ee61edda / ext4 errors=remount-ro 0 1
# /boot was on /dev/sdb1 during installation
UUID=98f0eb66-3d90-4bc5-a1f0-d3117de87809 /boot ext4 defaults 0 2
# /home was on /dev/sdb6 during installation
UUID=76ad5dc1-37b7-4624-830b-d923dac8ac48 /home ext4 defaults 0 2
# swap was on /dev/sdc5 during installation
#UUID=a99b0d98-9282-4e52-8f49-74b9b1f2ed8e none swap sw 0 0

UUID=AE5CA91F5CA8E2F7 /media/extra ntfs defaults 0 0
UUID=E2D84A6BD84A3E53 /media/extra_2 ntfs defaults 0 0

查看到 76ad5dc1-37b7-4624-830b-d923dac8ac48 对应挂载的 /home 目录

后面的 pass 写的是 2 ,就是说开机进行磁盘检查,并且数值越小,越先检查

这里有个临时的解决方式就是将 /home 的 pass 改为 0 ,也就是开机不进行检查,该分区有问题并不代表分区不可用

改完后依然可以访问 /home 目录

磁盘修复

强迫症让我不能就这么将就
我觉得修复这个磁盘错误
使用命令修复这个错误如下

1
fsck -y /dev/sdb6

结果提示 分区已挂载,操作被终止

修改 fstab 将 sdb6 挂载的那行注释
重启
进入紧急模式
运行 fsck -y /dev/sdb6
这时会打印很多日志
重复执行,直到没有日志打印

这时在修改 fstab, 去掉注释,pass 改为 2
重启
解决

背景

Docker 启动报错: Error starting daemon: Error initializing network controller: list bridge addresses failed: no available network

错误详情

查看错误日志: journalctl -xe | grep docker

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
[root@lenovo docker]# journalctl -xe | grep docker
-- Subject: Unit docker.socket has begun start-up
-- Unit docker.socket has begun starting up.
-- Subject: Unit docker.socket has finished start-up
-- Unit docker.socket has finished starting up.
-- Subject: Unit docker.service has begun start-up
-- Unit docker.service has begun starting up.
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.909025064+08:00" level=info msg="parsed scheme: \"unix\"" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.909923898+08:00" level=info msg="scheme \"unix\" not registered, fallback to default scheme" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.910865280+08:00" level=info msg="parsed scheme: \"unix\"" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.910909267+08:00" level=info msg="scheme \"unix\" not registered, fallback to default scheme" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.928785984+08:00" level=info msg="ccResolverWrapper: sending new addresses to cc: [{unix:///run/containerd/containerd.sock 0 <nil>}]" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.928902169+08:00" level=info msg="ClientConn switching balancer to \"pick_first\"" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.929039549+08:00" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc420606e00, CONNECTING" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.937533340+08:00" level=info msg="ccResolverWrapper: sending new addresses to cc: [{unix:///run/containerd/containerd.sock 0 <nil>}]" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.937601232+08:00" level=info msg="ClientConn switching balancer to \"pick_first\"" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.937707487+08:00" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc42015bf00, CONNECTING" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.950807950+08:00" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc42015bf00, READY" module=grpc
4月 08 16:42:09 lenovo dockerd[1742]: time="2019-04-08T16:42:09.952160247+08:00" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc420606e00, READY" module=grpc
4月 08 16:42:10 lenovo dockerd[1742]: time="2019-04-08T16:42:10.216864045+08:00" level=info msg="Graph migration to content-addressability took 0.00 seconds"
4月 08 16:42:10 lenovo dockerd[1742]: time="2019-04-08T16:42:10.218710988+08:00" level=info msg="Loading containers: start."
4月 08 16:42:10 lenovo dockerd[1742]: Error starting daemon: Error initializing network controller: list bridge addresses failed: no available network
4月 08 16:42:10 lenovo systemd[1]: docker.service: main process exited, code=exited, status=1/FAILURE
-- Subject: Unit docker.service has failed
-- Unit docker.service has failed.
4月 08 16:42:10 lenovo systemd[1]: Unit docker.service entered failed state.
4月 08 16:42:10 lenovo systemd[1]: docker.service failed.
4月 08 16:42:13 lenovo systemd[1]: docker.service holdoff time over, scheduling restart.
-- Subject: Unit docker.socket has begun shutting down
-- Unit docker.socket has begun shutting down.
-- Subject: Unit docker.socket has begun start-up
-- Unit docker.socket has begun starting up.
-- Subject: Unit docker.socket has finished start-up
-- Unit docker.socket has finished starting up.
-- Subject: Unit docker.service has begun start-up
-- Unit docker.service has begun starting up.

看到这样一句话: Error starting daemon: Error initializing network controller: list bridge addresses failed: no available network

查看本机网络: ip a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@lenovo docker]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp7s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
link/ether b8:70:f4:24:61:a7 brd ff:ff:ff:ff:ff:ff
3: wlp8s0b1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether cc:af:78:25:31:51 brd ff:ff:ff:ff:ff:ff
inet 192.168.10.145/24 brd 192.168.10.255 scope global noprefixroute wlp8s0b1
valid_lft forever preferred_lft forever
inet6 fe80::8de1:5b7d:b7d7:2788/64 scope link noprefixroute
valid_lft forever preferred_lft forever
4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100
link/none
inet 192.168.255.10 peer 192.168.255.9/32 scope global tun0
valid_lft forever preferred_lft forever
inet6 fe80::e41d:195:f566:33e1/64 scope link flags 800
valid_lft forever preferred_lft forever

没有 docker0 的桥接网络

手动添加一个即可

解决

1
2
ip link add name docker0 type bridge
ip addr add dev docker0 172.17.0.1/16

再看一下,多了一个 docker0

1
2
3
4
5
5: docker0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether a6:7d:d7:94:ab:f3 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever

重启 docker 即可

背景

OpenCV 提供的 API 是直接根据路径读取图片的, 在实际生产环境中,可能大部分情况下都是直接读取网络图片

在内存就完成图片和 opencv 的 Mat 对象的转换

那么该如何读取 byte[] 的图片呢?

API

openCV 提供的 API

1
Mat src = Imgcodecs.imread("/static/img/17.png");

很简单的就转化为 Mat 对象

而 该方法后面还有一个参数, flags, 该参数可选项有:

  • IMREAD_UNCHANGED = -1,
  • IMREAD_GRAYSCALE = 0,
  • IMREAD_COLOR = 1,
  • IMREAD_ANYDEPTH = 2,
  • IMREAD_ANYCOLOR = 4,
  • IMREAD_LOAD_GDAL = 8,
  • IMREAD_REDUCED_GRAYSCALE_2 = 16,
  • IMREAD_REDUCED_COLOR_2 = 17,
  • IMREAD_REDUCED_GRAYSCALE_4 = 32,
  • IMREAD_REDUCED_COLOR_4 = 33,
  • IMREAD_REDUCED_GRAYSCALE_8 = 64,
  • IMREAD_REDUCED_COLOR_8 = 65,
  • IMREAD_IGNORE_ORIENTATION = 128;

IMREAD_UNCHANGED: 以图片原有的方式读入,不进行任何改变
IMREAD_GRAYSCALE: 以灰度图读取
IMREAD_COLOR: 以彩色图读取

过渡

为了支持 OpenCV 读取 byte[] 的图片,为此我查找了很多资料做了大量的实验,有很多失败报错了,也有读取成功的,下面我将一一列举出来….

读取失败

Converters 类

我留意到 opencv 提供的 api 里有一个 utils 包, 里面有个转换类 Converters, 可以将 Mat 和 一些 java 的基本数据类型进行互相转换,其中有这样 2 个方法: vector_uchar_to_Matvector_char_to_Mat
参数是 List<Byte>

1
2
3
4
5
6
    private static Mat testConvertChar2Mat(byte[] bytes){
@SuppressWarnings("unchecked")
List<Byte> bs = CollectionUtils.arrayToList(bytes);
return Converters.vector_uchar_to_Mat(bs);
// return Converters.vector_char_to_Mat(bs);
}

vector_uchar_to_Mat 指有符号

转换出来的图片是一个像素的竖直线,读取失败

new Mat

Mat 对象除了转化得到,还可以 new , 再利用 Mat 的 put 方法,来创建 Mat

1
2
3
4
5
private static Mat testNewMat(int height, int width, byte[] bytes) throws IOException {
Mat data = new Mat(height, width, CvType.CV_8UC3);
data.put(0, 0, bytes);
return data;
}

转换出来的图片也不对,一些花花绿绿的像素点

new BufferByte

Mat 对象还有个构造方法,最后一个参数是传入 BufferByte,这时只需要在上述步骤中再将 byte[] 转化为 BufferByte

1
2
3
4
private static Mat testNewBuffer(int height, int width, byte[] bytes){
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return new Mat(height, width, CvType.CV_8UC3,byteBuffer);
}

抛出异常: CvException [org.opencv.core.CvException: cv::Exception: OpenCV(4.1.0-pre) /Users/joylau/opencv4/opencv/modules/core/include/opencv2/core/mat.inl.hpp:548: error: (-215:Assertion failed) total() == 0 || data != NULL in function ‘Mat’

读取成功

BufferedImage 转换

一次我在调试代码时 发现 HighGui.waitKey(); 的实现是将 Mat 对象转化为 BufferedImage 的逻辑,于是我明白了,OpenCV 里操作的 Mat 在显示的时候也需要转化为 BufferedImage
源码里有这样一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static Image toBufferedImage(Mat m) {
int type = BufferedImage.TYPE_BYTE_GRAY;

if (m.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}

int bufferSize = m.channels() * m.cols() * m.rows();
byte[] b = new byte[bufferSize];
m.get(0, 0, b); // get all the pixels
BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);

final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(b, 0, targetPixels, 0, b.length);

return image;
}

此时,我逆向转化,将 byte[] 转 BufferedImage ,BufferedImage 再转 Mat 即可

1
2
3
4
5
6
7
8
9
10
11
private static byte[] getBufferedImageByte(byte[] bytes) throws IOException{
BufferedImage bImage = ImageIO.read(new ByteArrayInputStream(bytes));
return ((DataBufferByte) bImage.getRaster().getDataBuffer()).getData();
}

// 再将从 BufferedImage 得到的 byte[] 使用 new Mat 对象
private static Mat testNewMat(int height, int width, byte[] bytes) throws IOException {
Mat data = new Mat(height, width, CvType.CV_8UC3);
data.put(0, 0, bytes);
return data;
}

该方法成功读取显示了图片

于是又引发了我的思考: 为什么直接从文件读取的 byte[] 无法被转化,而 BufferedImage 中得到的 byte[] 却可以被转化

于是我将 BufferedImage 中得到的 byte[] 在使用,调用 Converters.vector_char_to_Mat 方法

可惜却失败了…..

imdecode

Imgcodecs 类中有一个编码的方法 Imgcodecs.imdecode(Mat buf, int flags)
Mat 还有个子类 MatOfByte

1
2
3
private static Mat testImdecode(byte[] bytes){
return Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_COLOR);
}

该方法可成功转化

而且比上一个方法的优势是:

  1. byte[] 不需要再通过 BufferedImage 转化
  2. 不需要初始化 Mat 的长和宽

为此还可以逆向得出 Mat 转换成 byte[] 的方法

1
2
3
4
5
6
7
8
9
10
11
/**
* Mat转换成byte数组
*
* @param matrix 要转换的Mat
* @param fileExtension 格式为 ".jpg", ".png", etc
*/
public static byte[] mat2Byte(Mat matrix, String fileExtension) {
MatOfByte mob = new MatOfByte();
Imgcodecs.imencode(fileExtension, matrix, mob);
return mob.toArray();
}

最后

以下是全部测试代码

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
/**
* Created by liuf on 2019-04-01.
* cn.joylau.code
* liuf@ahtsoft.com
*/
@Slf4j
public class Byte2Mat {

public static void main(String[] args) throws Exception {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

// Mat mat = testImdecode(getImageByte());

// Mat mat = testConvertChar2Mat(getBufferedImageByte(getImageByte()));


// Mat mat = testNewBuffer(480,480,getImageByte());

// Mat mat = testNewMat(480,480,getImageByte());

Mat mat = testNewMat(480,480,getBufferedImageByte(getImageByte()));

log.info("{},{}",mat.rows(),mat.cols());
HighGui.imshow("byte2mat",mat);
HighGui.waitKey();
HighGui.destroyAllWindows();
}

private static byte[] getImageByte() throws IOException{
Resource resource = new FileSystemResource("/Users/joylau/work/anhui-project/traffic-service-layer/src/main/resources/static/img/1.jpg");
return IOUtils.toByteArray(resource.getInputStream());
}

private static byte[] getBufferedImageByte(byte[] bytes) throws IOException{
BufferedImage bImage = ImageIO.read(new ByteArrayInputStream(bytes));
return ((DataBufferByte) bImage.getRaster().getDataBuffer()).getData();
}


private static Mat testNewMat(int height, int width, byte[] bytes) throws IOException {
Mat data = new Mat(height, width, CvType.CV_8UC3);
data.put(0, 0, bytes);
return data;
}

private static Mat testNewBuffer(int height, int width, byte[] bytes){
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return new Mat(height, width, CvType.CV_8UC3,byteBuffer);
}


private static Mat testConvertChar2Mat(byte[] bytes){
@SuppressWarnings("unchecked")
List<Byte> bs = CollectionUtils.arrayToList(bytes);
return Converters.vector_uchar_to_Mat(bs);
// return Converters.vector_char_to_Mat(bs);
}

private static Mat testImdecode(byte[] bytes){
return Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_COLOR);
}





/**
* Mat转换成byte数组
*
* @param matrix 要转换的Mat
* @param fileExtension 格式为 ".jpg", ".png", etc
*/
public static byte[] mat2Byte(Mat matrix, String fileExtension) {
MatOfByte mob = new MatOfByte();
Imgcodecs.imencode(fileExtension, matrix, mob);
return mob.toArray();
}
}

一些概念

数字图像

数字图像指的是现在的图像都是以二维数字表示,每个像素的灰度值均由一个数字表示,范围为0-255(2^8)

二值图像

图像中每个像素的灰度值仅可取0或1,即不是取黑,就是取白,二值图像可理解为黑白图像

灰度图像

图像中每个像素可以由0-255的灰度值表示,具体表现为从全黑到全白中间有255个介于中间的灰色值可以取

彩色图像

每幅图像是由三幅灰度图像组合而成,依次表示红绿蓝三通道的灰度值,即我们熟知的RGB,此时彩色图像要视为三维的 [height,width, 3]

CvType

通道

OpenCV 中,图像可以分别为1,2,3,4 通道

  • 通道为灰度图;
  • 通道的图像是RGB555和RGB565。2通道图在程序处理中会用到,如傅里叶变换,可能会用到,一个通道为实数,一个通道为虚数,主要是编程方便。RGB555是16位的,2个字节,5+6+5,第一字节的前5位是R,后三位+第二字节是G,第二字节后5位是B,可见对原图像进行压缩了
  • 通道为彩色图(RGB);
  • 通道为 RGBA ,是RGB加上一个A通道,也叫alpha通道,表示透明度,PNG图像是一种典型的4通道图像。alpha通道可以赋值0到1,或者0到255,表示透明到不透明

常使用的是1,3,4通道; 2通道不常见

组合规则

CV_[bite](U|S|F)C[channels]

bite : 比特数,位数。 有 8bite,16bite,32bite,64bite,对应在 Mat 中,每个像素的所占的空间大小,8位即 CV_8

U|S|F :
- U : unsigned int , 无符号整形
- S : signed int , 有符号整形
- F : float , 单精度浮点型,float类型本身即有符号

这里的有符号、无符号是针对图像二进制编码来讲的。我在写的过程中大多数情况下都是使用的无符号,即 CV_8U ,CV_16U,当有计算时可能会介入有符号(存在负数),没学过 C++,对底层也一知半解,望高手解答。

C (channels):图像的通道数

比如: CV_8UC3 即 8位无符号的3通道(RGB 彩色)图像

参数说明

8U
- 说明:无符号的8位图
- 值:CV_8UC1,CV_8UC2,CV_8UC3,CV_8UC4
- 通道取值范围:0~255

8S
- 说明:有符号的8位图
- 值:CV_8SC1,CV_8SC2,CV_8SC3,CV_8SC4
- 通道取值范围:-128~127

16U
- 说明:无符号的16位图
- 值:CV_16UC1,CV_16UC2,CV_16UC3,CV_16UC4
- 通道取值范围:0~65535

16S
- 说明:有符号的16位图
- 值:CV_16SC1,CV_16SC2,CV_16SC3,CV_16SC4
- 通道取值范围:-32768~32767

32S
- 说明:无符号的32位图
- 值:CV_32SC1,CV_32SC2,CV_32SC3,CV_32SC4
- 通道取值范围:2147483648~2147483647

32F
- 说明:浮点型32位图
- 值:CV_32FC1,CV_32FC2,CV_32FC3,CV_32FC4
- 通道取值范围:1.18*(10(-38次方))~3.40*(10(38次方))

64F
- 说明:浮点型64位图
-值:CV_64FC1,CV_64FC2,CV_64FC3,CV_64FC4
- 通道取值范围:2.23*(10(-308次方))~1.79*(10(308次方))

1U
- 说明:1位
- 值:IPL_DEPTH_1U
- 通道取值范围:0~1

色彩空间

常见的色彩空间

  • RGB
  • HSV
  • HIS
  • YCRCB
  • YUV

HSV

HSV分别是色调(Hue),饱和度(Saturation)和亮度(Value)

H调整颜色;S越大,图像色彩越丰富,颜色越鲜艳;V越大,图像越亮

HSV颜色取值范围

  1. H:0— 180 : 之所以不是 360,是因为 8 位图 最大是 255,360 已经超出范围,以 180 为限定

  2. S: 0— 255

  3. V: 0— 255

记住下面这张图, 可使用这张图中的范围来查找某种颜色

HSV

转换方法

Imgproc.cvtColor(src,det,Imgproc.COLOR_BGR2HSV);

0%