JoyLau's Blog

JoyLau 的技术学习与思考

背景

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);

问题

启动 docker 容器时挂载容器以前存在的数据文件时出现了 Permission denied 的错误

解决

  1. 首先以为是挂载的文件夹有读写数据的权限问题 chmod -R 777 xxxx , 没有解决,依然报错
  2. 再分析是文件目录的所属者的问题: chown -R gname:uname xxxx , 没有解决,依然报错
  3. 这时我们进入容器之后 使用 ll 查看挂载的目录的所属者,发现组名和户名跟宿主机的组名和用户名不一致
  4. 原因在于,操作系统判断用户组和用户其实并不是根据名称来的,而是根据名称对应的 id 来的
  5. 查看用户组和用户名对象的 id, 可查看 /etc/passwd
  6. 此时,我们需要将宿主机的用户组用户的 ID 和 容器内挂在目录所需的用户组和用户的 ID 对应起来,写一直即可
  7. 举个例子
  8. redis 镜像产生的数据文件在 /var/lib/redis 中,并且该目录的用户组和用户都为 redis, 此时我们查看容器的 redis:redis 的 id , 假如是 102:103
  9. 此时我们宿主机挂载目录是 /opt/docker/redis/data ,我们改变这个目录的所属者 chown -R 102:103 /opt/docker/redis/data
  10. 不要管 102:103 在宿主机系统中有没有该用户组和用户
  11. 再次进入容器就可以看到 /var/lib/redis 目录的所属者是正确的了

mysql 和 mariaDB 的问题

这样的情况也发生在 mysql 和 mariaDB 上
按照上述的方法似乎没有奏效,确切的说奏效一半
因为 /var/lib/mysql 目录中文件夹可以看到,文件却没有权限看到
类似这样

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
190321 06:02:13 mysqld_safe Logging to '/var/lib/mysql/d240623581db.err'.
190321 06:02:13 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
chown: /var/lib/mysql/60689c28e4a1.err: Permission denied
chown: /var/lib/mysql/60689c28e4a1.pid: Permission denied
chown: /var/lib/mysql/aria_log.00000001: Permission denied
chown: /var/lib/mysql/aria_log_control: Permission denied
chown: /var/lib/mysql/ib_buffer_pool: Permission denied
chown: /var/lib/mysql/ibdata1: Permission denied
chown: /var/lib/mysql/ib_logfile0: Permission denied
chown: /var/lib/mysql/ib_logfile1: Permission denied
chown: /var/lib/mysql/ibtmp1: Permission denied
chown: /var/lib/mysql/multi-master.info: Permission denied
chown: /var/lib/mysql/mysql: Permission denied
chown: /var/lib/mysql/mysql-bin.000001: Permission denied
chown: /var/lib/mysql/mysql-bin.000002: Permission denied
chown: /var/lib/mysql/mysql-bin.000003: Permission denied
chown: /var/lib/mysql/mysql-bin.000004: Permission denied
chown: /var/lib/mysql/mysql-bin.000005: Permission denied
chown: /var/lib/mysql/mysql-bin.000006: Permission denied
chown: /var/lib/mysql/mysql-bin.000007: Permission denied
chown: /var/lib/mysql/mysql-bin.000008: Permission denied
chown: /var/lib/mysql/mysql-bin.000009: Permission denied
chown: /var/lib/mysql/mysql-bin.index: Permission denied
chown: /var/lib/mysql/owncloud: Permission denied
chown: /var/lib/mysql/performance_schema: Permission denied
chown: /var/lib/mysql: Permission denied
chown: /var/lib/mysql: Permission denied
190321 06:02:14 mysqld_safe Logging to '/var/lib/mysql/d240623581db.err'.
190321 06:02:14 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
chown: /var/lib/mysql/60689c28e4a1.err: Permission denied
chown: /var/lib/mysql/60689c28e4a1.pid: Permission denied
chown: /var/lib/mysql/aria_log.00000001: Permission denied
chown: /var/lib/mysql/aria_log_control: Permission denied
chown: /var/lib/mysql/ib_buffer_pool: Permission denied
chown: /var/lib/mysql/ibdata1: Permission denied
chown: /var/lib/mysql/ib_logfile0: Permission denied
chown: /var/lib/mysql/ib_logfile1: Permission denied
chown: /var/lib/mysql/ibtmp1: Permission denied
chown: /var/lib/mysql/multi-master.info: Permission denied
chown: /var/lib/mysql/mysql: Permission denied
chown: /var/lib/mysql/mysql-bin.000001: Permission denied
chown: /var/lib/mysql/mysql-bin.000002: Permission denied
chown: /var/lib/mysql/mysql-bin.000003: Permission denied
chown: /var/lib/mysql/mysql-bin.000004: Permission denied
chown: /var/lib/mysql/mysql-bin.000005: Permission denied
chown: /var/lib/mysql/mysql-bin.000006: Permission denied
chown: /var/lib/mysql/mysql-bin.000007: Permission denied
chown: /var/lib/mysql/mysql-bin.000008: Permission denied
chown: /var/lib/mysql/mysql-bin.000009: Permission denied
chown: /var/lib/mysql/mysql-bin.index: Permission denied
chown: /var/lib/mysql/owncloud: Permission denied
chown: /var/lib/mysql/performance_schema: Permission denied
chown: /var/lib/mysql: Permission denied
chown: /var/lib/mysql: Permission denied

原因分析是:
SELinux 造成的
有以下 4 中解决方法:

  1. setenforce 0 : 临时关闭
  2. vi /etc/selinux/config : 将 SELINUX=enforcing 改为 SELINUX=disabled ,重启
  3. 在docker run 中加入 --privileged=true 给容器加上特定权限
  4. 修改 SELinux 规则 chcon -t mysqld_db_t -R /opt/docker/mysql/data

配置前

配置前

@ 导包的类无法点击跳转,也不识别

配置

在项目根目录添加配置文件 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
/**
* 不是真实的 webpack 配置,仅为兼容 webstorm 和 intellij idea 代码跳转
*/

module.exports = {
resolve: {
alias: {
'@': require('path').resolve(__dirname, 'src'), // eslint-disable-line
},
},
};

然后,在 idea 的 preference -> language & frameworks -> javascript -> webpack 路径到更目录下的webpack.config.js

完成

为什么没有 Windows 下的编译安装

因为官网已经提供的编译好的 exe 包,双击运行就会解压到特定的目录了,除此之外官网还提供了 ios 版和 安卓版
这里着重记录下 CentOS , Ubuntu 和 Mac OS 下的安装,因为官网没有提供编译好的包

条件

  1. GCC 4.4.x or later
  2. CMake 2.8.7 or higher
  3. Git
  4. GTK+2.x or higher, including headers (libgtk2.0-dev)
  5. pkg-config
  6. Python 2.6 or later and Numpy 1.5 or later with developer packages (python-dev, python-numpy)
  7. ffmpeg or libav development packages: libavcodec-dev, libavformat-dev, libswscale-dev
  8. [optional] libtbb2 libtbb-dev
  9. [optional] libdc1394 2.x
  10. [optional] libjpeg-dev, libpng-dev, libtiff-dev, libjasper-dev, libdc1394-22-dev
  11. [optional] CUDA Toolkit 6.5 or higher

步骤

  1. 安装常用的开发编译工具包, Centos 的命令为: yum groupinstall “Development Tools”, Ubuntu 的命令为: apt-get install build-essential
  2. 安装 cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
  3. mkdir opencv4; cd opencv4
  4. git clone https://github.com/opencv/opencv.git
  5. git clone https://github.com/opencv/opencv_contrib.git
  6. cd opencv
  7. mkdir build
  8. cd build
  9. cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local .. (如果不工作的话,删除 -D的空格,cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..)
  10. make -j7 # runs 7 jobs in parallel 使用7个并行任务来编译
  11. 生成文档 cd ~/opencv/build/doc/; make -j7 doxygen
  12. make install

编译好的包

  1. centos7 版: http://cloud.joylau.cn:1194/s/kUoNelmj1SX810K 或者 https://pan.baidu.com/s/1qaZ-TbF0xP0DxaEJKbdt-A 提取码: jkir
  2. Ubuntu 16.04 版: http://cloud.joylau.cn:1194/s/TsNRKwxJhM0v0HE 或者 https://pan.baidu.com/s/1ha6nATLrSt5WPL1iQlmWSg 提取码: gduu
  3. java 调用所需 opencv-410.jar 包: //s3.joylau.cn:9000/blog/opencv-410.jar

Mac OS 上

  1. AppStore 上安装 XCode, 安装完成打开 XCode , 同意 license
  2. 安装 HomeBrew
  3. 安装必要依赖: Python 3, CMake and Qt 5
1
2
3
brew install python3
brew install cmake
brew install qt5
  1. 安装环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
mkdir ~/opencv4
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git

# 变量定义
cwd=$(pwd)
cvVersion="master"
QT5PATH=/usr/local/Cellar/qt/5.12.2

rm -rf opencv/build
rm -rf opencv_contrib/build

# Create directory for installation
mkdir -p installation/OpenCV-"$cvVersion"

sudo -H pip3 install -U pip numpy
# Install virtual environment
sudo -H python3 -m pip install virtualenv virtualenvwrapper
VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3
echo "VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3" >> ~/.bash_profile
echo "# Virtual Environment Wrapper" >> ~/.bash_profile
echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bash_profile
cd $cwd
source /usr/local/bin/virtualenvwrapper.sh

############ For Python 3 ############
# create virtual environment 由于 mac OS 本身使用的是 Python 2.7 , 而一些本身的应用依赖于 Python 2 ,为了不影响原来的环境,这里创建一个 Python3 的虚拟环境来进行编译
mkvirtualenv OpenCV-"$cvVersion"-py3 -p python3
workon OpenCV-"$cvVersion"-py3

# now install python libraries within this virtual environment
pip install cmake numpy scipy matplotlib scikit-image scikit-learn ipython dlib

# quit virtual environment
deactivate
######################################

cd opencv
mkdir build
cd build

cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=$cwd/installation/OpenCV-"$cvVersion" \
-D INSTALL_C_EXAMPLES=ON \
-D INSTALL_PYTHON_EXAMPLES=ON \
-D WITH_TBB=ON \
-D WITH_V4L=ON \
-D OPENCV_SKIP_PYTHON_LOADER=ON \
-D CMAKE_PREFIX_PATH=$QT5PATH \
-D CMAKE_MODULE_PATH="$QT5PATH"/lib/cmake \
-D OPENCV_PYTHON3_INSTALL_PATH=~/.virtualenvs/OpenCV-"$cvVersion"-py3/lib/python3.7/site-packages \
-D WITH_QT=ON \
-D WITH_OPENGL=ON \
-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
-D BUILD_EXAMPLES=ON ..

make -j$(sysctl -n hw.physicalcpu)
make install

  1. cmake 后输出如下:
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
--   OpenCV modules:
-- To be built: aruco bgsegm bioinspired calib3d ccalib core cvv datasets dnn dnn_objdetect dpm face features2d flann freetype fuzzy gapi hfs highgui img_hash imgcodecs imgproc java java_bindings_generator line_descriptor ml objdetect optflow phase_unwrapping photo plot python2 python3 python_bindings_generator quality reg rgbd saliency shape stereo stitching structured_light superres surface_matching text tracking ts video videoio videostab xfeatures2d ximgproc xobjdetect xphoto
-- Disabled: world
-- Disabled by dependency: -
-- Unavailable: cnn_3dobj cudaarithm cudabgsegm cudacodec cudafeatures2d cudafilters cudaimgproc cudalegacy cudaobjdetect cudaoptflow cudastereo cudawarping cudev hdf js matlab ovis sfm viz
-- Applications: tests perf_tests examples apps
-- Documentation: NO
-- Non-free algorithms: NO
--
-- GUI:
-- QT: YES (ver 5.12.2)
-- QT OpenGL support: YES (Qt5::OpenGL 5.12.2)
-- Cocoa: YES
-- OpenGL support: YES (/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/OpenGL.framework /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/OpenGL.framework)
-- VTK support: NO
--
-- Media I/O:
-- ZLib: build (ver 1.2.11)
-- JPEG: build-libjpeg-turbo (ver 2.0.2-62)
-- WEBP: build (ver encoder: 0x020e)
-- PNG: build (ver 1.6.36)
-- TIFF: build (ver 42 - 4.0.10)
-- JPEG 2000: build (ver 1.900.1)
-- OpenEXR: build (ver 1.7.1)
-- HDR: YES
-- SUNRASTER: YES
-- PXM: YES
-- PFM: YES
--
-- Video I/O:
-- DC1394: NO
-- FFMPEG: YES
-- avcodec: YES (58.35.100)
-- avformat: YES (58.20.100)
-- avutil: YES (56.22.100)
-- swscale: YES (5.3.100)
-- avresample: YES (4.0.0)
-- GStreamer: NO
-- AVFoundation: YES
-- v4l/v4l2: NO
--
-- Parallel framework: GCD
--
-- Trace: YES (with Intel ITT)
--
-- Other third-party libraries:
-- Intel IPP: 2019.0.0 Gold [2019.0.0]
-- at: /Users/joylau/opencv4/opencv/build/3rdparty/ippicv/ippicv_mac/icv
-- Intel IPP IW: sources (2019.0.0)
-- at: /Users/joylau/opencv4/opencv/build/3rdparty/ippicv/ippicv_mac/iw
-- Lapack: YES (/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Accelerate.framework)
-- Eigen: NO
-- Custom HAL: NO
-- Protobuf: build (3.5.1)
--
-- OpenCL: YES (no extra features)
-- Include path: NO
-- Link libraries: -framework OpenCL
--
-- Python 2:
-- Interpreter: /usr/bin/python2.7 (ver 2.7.10)
-- Libraries: /usr/lib/libpython2.7.dylib (ver 2.7.10)
-- numpy: /System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy/core/include (ver 1.8.0rc1)
-- install path: lib/python2.7/site-packages
--
-- Python 3:
-- Interpreter: /usr/local/bin/python3 (ver 3.7.2)
-- Libraries: /usr/local/Frameworks/Python.framework/Versions/3.7/lib/libpython3.7m.dylib (ver 3.7.2)
-- numpy: /usr/local/lib/python3.7/site-packages/numpy/core/include (ver 1.16.2)
-- install path: /Users/joylau/.virtualenvs/OpenCV-master-py3/lib/python3.7/site-packages
--
-- Python (for build): /usr/bin/python2.7
--
-- Java:
-- ant: /Users/joylau/dev/apache-ant-1.10.5/bin/ant (ver 1.10.5)
-- JNI: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/JavaVM.framework/Headers /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/JavaVM.framework/Headers /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/JavaVM.framework/Headers
-- Java wrappers: YES
-- Java tests: YES
--
-- Install to: /Users/joylau/opencv4/installation/OpenCV-master
-- -----------------------------------------------------------------
--
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/joylau/opencv4/opencv/build
  1. 编译好的安装包: http://cloud.joylau.cn:1194/s/6GMLl09ZAYNAUMU 或者: https://pan.baidu.com/s/1YBxUD_vB1zKOcxHeAtn6Xw 提取码: twsq

遇到的问题

CentOS 上 CMake 版本太低的解决方法

  1. yum 上安装的版本太低,先卸载掉版本低的,yum remove cmake

  2. cd /opt
    tar zxvf cmake-3.10.2-Linux-x86_64.tar.gz

  3. vim /etc/profile
    export CMAKE_HOME=/opt/cmake-3.10.2-Linux-x86_64
    export PATH=$PATH:$CMAKE_HOME/bin

  4. source /etc/profile

没有生成 opencv-410.jar

1
2
3
4
5
6
Java:                          
-- ant: /bin/ant (ver 1.9.4)
-- JNI: /usr/lib/jvm/java-1.8.0-openjdk/include /usr/lib/jvm/java-1.8.0-openjdk/include/linux /usr/lib/jvm/java-1.8.0-openjdk/include
-- Java wrappers: YES
-- Java tests: NO

需要 ant 环境,安装后即可, java 即可进行调用

IDEA 及 Spring Boot 项目中的使用

  1. 下载 opencv-410.jar 包,引入到项目中
1
2
3
4
5
6
7
8
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

compile fileTree(dir:'libs',include:['*.jar'])
}
  1. 配置动态库路径, vm options: -Djava.library.path=/home/joylau/opencv4/opencv/build/lib

vm options

mac os 下路径为: -Djava.library.path=/Users/joylau/opencv4/installation/OpenCV-master/share/java/opencv4

  1. 加载动态库
1
2
3
4
5
6
7
8
9
@SpringBootApplication
public class OpencvTestApplication {

public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
System.out.println(Core.VERSION);
SpringApplication.run(OpencvTestApplication.class, args);
}
}
  1. 脸部识别 demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static void testFace() {
// 1 读取OpenCV自带的人脸识别特征XML文件
CascadeClassifier facebook = new CascadeClassifier("/home/joylau/opencv4/opencv/data/haarcascades/haarcascade_frontalface_alt.xml");
// 2 读取测试图片
Mat image = Imgcodecs.imread("/home/joylau/图片/image-test-4.jpg");
// 3 特征匹配
MatOfRect face = new MatOfRect();
facebook.detectMultiScale(image, face);
// 4 匹配 Rect 矩阵 数组
Rect[] rects = face.toArray();
System.out.println("匹配到 " + rects.length + " 个人脸");
// 5 为每张识别到的人脸画一个框
for (int i = 0; i < rects.length; i++) {
Imgproc.rectangle(image,new Point(rects[i].x, rects[i].y), new Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height), new Scalar(0, 0, 255));
Imgproc.putText(image,"face-" + i, new Point(rects[i].x, rects[i].y),Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(0, 255, 0),1,Imgproc.LINE_AA,false);
}
// 6 展示图片
HighGui.imshow("人脸-匹配", image);
HighGui.waitKey(0);
}

test_face

注: 图片来自微博

  1. 边缘检测 demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static void testContours() {
//1 获取原图
Mat src = Imgcodecs.imread("/home/joylau/图片/image-test.jpg");
//2 图片灰度化
Mat gary = new Mat();
Imgproc.cvtColor(src, gary, Imgproc.COLOR_RGB2GRAY);
//3 图像边缘处理
Mat edges = new Mat();
Imgproc.Canny(gary, edges, 200, 500, 3, false);
//4 发现轮廓
List<MatOfPoint> list = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(edges, list, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);
//5 绘制轮廓
for (int i = 0, len = list.size(); i < len; i++) {
Imgproc.drawContours(src, list, i, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA);
}
HighGui.imshow("边缘检测", src);
HighGui.waitKey(0);
}

test_source
test_contours

  1. 实时人脸识别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* OpenCV-4.0.0 实时人脸识别
*
*/
public static void videoFace() {
VideoCapture capture=new VideoCapture(0);
Mat image=new Mat();
int index=0;
if (capture.isOpened()) {
do {
capture.read(image);
HighGui.imshow("实时人脸识别", getFace(image));
index = HighGui.waitKey(1);
} while (index != 27);
}
}

/**
* OpenCV-4.0.0 人脸识别
* @param image 待处理Mat图片(视频中的某一帧)
* @return 处理后的图片
*/
public static Mat getFace(Mat image) {
// 1 读取OpenCV自带的人脸识别特征XML文件
CascadeClassifier facebook=new CascadeClassifier("/Users/joylau/opencv4/opencv/data/haarcascades/haarcascade_frontalface_alt.xml");
// 2 特征匹配类
MatOfRect face = new MatOfRect();
// 3 特征匹配
facebook.detectMultiScale(image, face);
Rect[] rects=face.toArray();
log.info("匹配到 "+rects.length+" 个人脸");
// 4 为每张识别到的人脸画一个圈
for (Rect rect : rects) {
Imgproc.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0));
Imgproc.putText(image, "Human", new Point(rect.x, rect.y), Imgproc.FONT_HERSHEY_SIMPLEX, 2.0, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA, false);
//Mat dst=image.clone();
//Imgproc.resize(image, image, new Size(300,300));
}
return image;
}

背景

使用 docker stack 部署一组服务时,docker 会根据集群的每个节点的资源的情况来进行分配,作为使用者无法参与其中的分配,该怎么解决呢?

环境

  1. docker 1.13.0+
  2. compose version 3+

deploy mode

  1. replicated 默认模式,可自定义服务的副本数,此模式不能决定服务部署到哪个节点上
1
2
3
deploy:
mode: replicated
replicas: 2
  1. global 定义每个节点均部署一个服务的副本
1
2
deploy:
mode: global

node labels

该方法是通过给节点添加标签,然后在 yaml 文件里通过配置标签来决定服务部署到哪些节点

  1. docker node ls 查看节点
  2. docker node update –label-add role=service-1 nodeId 给 nodeId 的节点添加 label role=service-1, label 的形式是 map 的键值对形式
  3. docker node inspect nodeId 查看节点的 labels 信息
  4. docker node update –label-rm role=service-1 nodeId 删除 label

service 部署

1
2
3
4
docker service create \
--name nginx \
--constraint 'node.labels.role == service-1' \
nginx

stack 部署

1
2
3
4
deploy:
placement:
constraints:
- node.labels.role == service-2

constraints 填写多个时,它们之间的关系是 AND;constraints 可以匹配 node 标签和 engine 标签
例如

1
2
3
deploy:
placement:
constraints: [node.role == manager]
1
2
3
4
5
deploy:
placement:
constraints:
- node.role == manager
- engine.labels.operatingsystem == ubuntu 14.04

环境

  1. docker 18.09

说明

  1. 本篇文章中的搭建过程有多台物理机,如果说是自己测试使用的话,或者只有一台机器,可以使用 docker-machine 来创建多个 docker 主机
  2. 比如创建一个主机名为 work 的 docker 主机 : docker-machine create -d virtualbox worker
  3. 之后进入刚才创建的主机 : docker-machine ssh worker
  4. 然后就当成是一台独立机器来执行以下的操作

步骤

  1. 初始化 swarm 集群 : docker swarm init --advertise-addr 34.0.7.183
    1. 机器有多个网卡的指定 IP 地址 –advertise-addr
    2. 默认创建的是管理节点
  2. 加入刚才创建 swarm 集群
1
docker swarm join --token SWMTKN-1-1o1yfsquxasw7c7ah4t7lmd4i89i62u74tutzhtcbgb7wx6csc-1hf4tjv9oz9vpo937955mi0z2 34.0.7.183:2377

如果说忘了集群管理节点的 token, 可以使用 docker swarm join-token work/manage 来查看加入该集群的命令

  1. 查看集群节点: docker node list

服务部署

  1. 单服务部署 docker service create --name nginx -p 80:80 --replaces 4 containAddress
    上述命令部署了4个 nginx 服务,如果集群有2台主机的话,会在每台主机上部署 2 个服务

  2. 多服务部署, 使用 yml 配置文件,具体语法参看 https://docs.docker.com/compose/compose-file/

命令

docker swarm

docker swarm init 初始化集群
docker swarm join-token worker 查看工作节点的 token
docker swarm join-token manager 查看管理节点的 token
docker swarm join 加入集群中

docker stack

docker stack deploy 部署新的服务或更新现有服务
docker stack ls 列出现有服务
docker stack ps 列出服务中的任务
docker stack rm 删除服务
docker stack services 列出服务中的具体项
docker stack down 移除某个服务(不会删除数据)

docker node

docker node ls 查看所有集群节点
docker node rm 删除某个节点(-f强制删除)
docker node inspect 查看节点详情
docker node demote 节点降级,由管理节点降级为工作节点
docker node promote 节点升级,由工作节点升级为管理节点
docker node update 更新节点
docker node ps 查看节点中的 Task 任务

docker service

docker service create 部署服务
docker service inspect 查看服务详情
docker service logs 产看某个服务日志
docker service ls 查看所有服务详情
docker service rm 删除某个服务(-f强制删除)
docker service scale 设置某个服务个数
docker service update 更新某个服务

docker machine

docker-machine create 创建一个 Docker 主机(常用-d virtualbox)
docker-machine ls 查看所有的 Docker 主机
docker-machine ssh SSH 到主机上执行命令
docker-machine env 显示连接到某个主机需要的环境变量
docker-machine inspect 输出主机更多信息
docker-machine kill 停止某个主机
docker-machine restart 重启某台主机
docker-machine rm 删除某台主机
docker-machine scp 在主机之间复制文件
docker-machine start 启动一个主机
docker-machine status 查看主机状态
docker-machine stop 停止一个主机

swarm 集群节点可视化工具

portainer : 很强大的工具,可以监控本机和远程服务器或者集群环境,远程 docker 主机的话需要远程 docker 主机开启在 2375 端口的服务

https://www.portainer.io/installation/

1
2
3
4
5
6
7
8
9
10
version: '3'
services:
portainer:
image: 34.0.7.183:5000/joylau/portainer:latest
container_name: portainer
ports:
- 80:9000
restart: always
volumes:
- /home/liufa/portainer/data:/data

@Valid 和 @Validated

  1. @Valid@Validated 注解都用于字段校验
  2. @Valid 所属包为:javax.validation.Valid ; @Validated 所属包为 org.springframework.validation.annotation.Validated
  3. @Validated@Valid 的一次封装,是Spring提供的校验机制使用。@Valid 不提供分组功能

@Validated的特殊用法

当一个实体类需要多种验证方式时,例:对于一个实体类的id来说,新增的时候是不需要的,对于更新时是必须的

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
public class Attachment {
@Id
@NotBlank(message = "id can not be blank!", groups = {All.class, Update.class})
private String id;

@NotBlank(message = "fileName can not be blank!", groups = {All.class})
private String fileName;

@NotBlank(message = "filePath can not be blank!", groups = {All.class})
private String filePath;

@Field
private byte[] data;

@NotBlank(message = "metaData can not be empty!", groups = {All.class})
private String metaData;

@NotBlank(message = "uploadTime can not be blank!", groups = {All.class})
private String uploadTime;

public Attachment(@NotBlank(message = "id can not be blank!", groups = {All.class, Update.class}) String id) {
this.id = id;
}

public interface All {
}

public interface Update {
}
}

单独对 groups 进行校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 添加附件
*/
@PostMapping("addAttachment")
public MessageBody addAttachment(@RequestParam("file") final MultipartFile multipartFile,
@Validated(Attachment.All.class) Attachment attachment,
BindingResult results){
return attachmentApiService.addAttachment(multipartFile,attachment,results);
}

/**
* 更新单个附件
*/
@PostMapping("updateAttachment")
public MessageBody updateAttachment(@RequestParam(value = "file",required = false) final MultipartFile multipartFile,
@Validated(Attachment.Update.class) Attachment attachment){
return attachmentApiService.updateAttachment(multipartFile,attachment);
}

使用注意

  1. 校验的注解中不分配 groups,默认每次都要进行验证
  2. @Validated 没有添加 groups 属性时,默认验证没有分组的验证属性
  3. @Validated 添加特定 groups 属性时,只校验该注解中分配了该 groups 的属性
  4. 一个功能方法上处理多个模型对象时,需添加多个验证结果对象,如下所示
1
2
3
@RequestMapping("/addPeople")  
public @ResponseBody String addPeople(@Validated People p,BindingResult result,@Validated Person p2,BindingResult result2) {
}
0%