转自:https://www.douban.com/note/265479171/

cv::Mat
depth/dims/channels/step/data/elemSize
The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store (Mat类的对象用于表示一个多维度的单通道或者多通道稠密数组,它可以用来存储以下东西)
real or complex-valued vectors or matrices (实数值或复合值向量、矩阵)
grayscale or color images (灰度图或者彩色图)
voxel volumes (立体元素)
vector fields (矢量场)
point clouds (点云)
tensors (张量)
histograms (though, very high-dimensional histograms may be better stored in a SparseMat ) (直方图,高纬度的最好存放在SparseMat中)
旧版本的OpenCV中的C结构体有 CvMat 和 CvMatND,目前我用的是 2.3 版,里面的文档指出 CvMat 和 CvMatND 弃用了,在C++封装中用 Mat 代替,另外旧版还有一个 IplImage,同样用 Mat 代替(可以参考博文 OpenCV中的结构体、类与Emgu.CV的对应表).
矩阵 (M) 中数据元素的地址计算公式:
addr(Mi0,i1,…im-1) = M.data + M.step[0] * i0 + M.step[1] * i1 + … + M.step[m-1] * im-1 (其中 m = M.dims M的维度)

data:Mat对象中的一个指针,指向内存中存放矩阵数据的一块内存 (uchar* data)
dims:Mat所代表的矩阵的维度,如 3 * 4 的矩阵为 2 维, 3 * 4 * 5 的为3维
channels:通道,矩阵中的每一个矩阵元素拥有的值的个数,比如说 3 * 4 矩阵中一共 12 个元素,如果每个元素有三个值,那么就说这个矩阵是 3 通道的,即 channels = 3。常见的是一张彩色图片有红、绿、蓝三个通道。
depth:深度,即每一个像素的位数(bits),在opencv的Mat.depth()中得到的是一个 0 – 6 的数字,分别代表不同的位数:enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 可见 0和1都代表8位, 2和3都代表16位,4和5代表32位,6代表64位;
step:是一个数组,定义了矩阵的布局,具体见下面图片分析,另外注意 step1 (step / elemSize1),M.step[m-1] 总是等于 elemSize,M.step1(m-1)总是等于 channels;
elemSize : 矩阵中每一个元素的数据大小,如果Mat中的数据的数据类型是 CV_8U 那么 elemSize = 1,CV_8UC3 那么 elemSize = 3,CV_16UC2 那么 elemSize = 4;记住另外有个 elemSize1 表示的是矩阵中数据类型的大小,即 elemSize / channels 的大小
图片分析1:考虑二维情况(stored row by row)按行存储

 

上面是一个 3 X 4 的矩阵,假设其数据类型为 CV_8U,也就是单通道的 uchar 类型

这是一个二维矩阵,那么维度为 2 (M.dims == 2);
M.rows == 3; M.cols == 4;
sizeof(uchar) = 1,那么每一个数据元素大小为 1 (M.elemSize() == 1, M.elemSize1() == 1);
CV_8U 得到 M.depth() == 0, M.channels() == 1;
因为是二维矩阵,那么 step 数组只有两个值, step[0] 和 step[1] 分别代表一行的数据大小和一个元素的数据大小,则 M.step[0] == 4, M.step[1] == 1;
M.step1(0) == M.cols = 4; M.step1(1) == 1;
假设上面的矩阵数据类型是 CV_8UC3,也就是三通道

M.dims == 2; M.channels() == 3;M.depth() == 0;
M.elemSize() == 3 (每一个元素包含3个uchar值) M.elemSize1() == 1 (elemSize / channels)
M.step[0] == M.cols * M.elemSize() == 12, M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;
M.step(0) == M.cols * M.channels() == 12 ; M.step(1) == M.channels() == 3;
图片分析2:考虑三维情况(stored plane by plane)按面存储

 

上面是一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型

M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;
M.rows == M.cols == –1;
M.elemSize() == M.elemSize1() * M.channels() == M.step[M.dims-1] == M.step[2] == 2 * 4 == 8;
M.step[0] == 4 * 6 * M.elemSize() == 192;
M.step[1] == 6 * M.elemSize() == 48;
M.step[2] == M.elemSize() == 8;
M.step1(0) == M.step[0] / M.elemSize() == 48 / 2 == 96 (第一维度(即面的元素个数) * 通道数);
M.step1(1) == M.step[1] / M.elemSize() == 12 / 2 == 24(第二维度(即行的元素个数/列宽) * 通道数);
M.step1(2) == M.step[2] / M.elemSize() == M.channels() == 4(第三维度(即元素) * 通道数);
End :

Author : Ggicci

本文讲解Mat 的一些基本的初始化

// m为3*5的矩阵,float型的单通道,把每个点都初始化为1
Mat m(3, 5, CV_32FC1, 1);
或者 Mat m(3, 5, CV_32FC1, Scalar(1));
cout<<m;
输出为:
[1, 1, 1, 1, 1;
  1, 1, 1, 1, 1;
  1, 1, 1, 1, 1]

// m为3*5的矩阵,float型的2通道,把每个点都初始化为1 2
 Mat m(3, 5, CV_32FC2, Scalar(1, 2));
cout<<m;
输出为
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
  1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
  1, 2, 1, 2, 1, 2, 1, 2, 1, 2]

// m为3*5的矩阵,float型的3通道,把每个点都初始化为1 2 3
Mat m(3, 5, CV_32FC3, Scalar(1, 2, 3));
cout << m;
输出为
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
  1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
  1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

// 从已有的数据源初始化
double *data = new double[15];
for (int i = 0; i < 15; i++)
{
   data[i] = 1.2;
}
Mat m(3, 5, CV_32FC1, data);
cout << m;
输出为:
[1.2, 1.2, 1.2, 1.2, 1.2;
  1.2, 1.2, 1.2, 1.2, 1.2;
  1.2, 1.2, 1.2, 1.2, 1.2]

如果接着
delete [] data;
cout << m;
输出为:
[-1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144]
可见,这里只是进行了浅拷贝,当数据源不在的时候,Mat里的数据也就是乱码了。

// 从图像初始化
 Mat m = imread("1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
 cout<< "channels ="<<m.channels()<<endl;
 cout << "cols ="<<m.cols<<endl;
 cout << "rows ="<<m.rows<<endl;
 cout << m;
输出为:
channels =1
cols =13
rows =12
[179, 173, 175, 189, 173, 163, 148, 190, 68, 14, 19, 31, 22;
  172, 172, 172, 180, 172, 177, 162, 190, 64, 13, 19, 30, 17;
  177, 180, 176, 175, 169, 184, 165, 181, 58, 12, 23, 38, 25;
  181, 183, 178, 178, 170, 181, 163, 182, 52, 8, 23, 37, 23;
  176, 173, 173, 184, 175, 178, 164, 195, 60, 14, 24, 35, 16;
  179, 175, 176, 187, 176, 175, 158, 191, 70, 21, 28, 37, 20;
  182, 183, 180, 184, 174, 179, 155, 174, 54, 1, 5, 15, 2;
  173, 182, 178, 176, 173, 191, 165, 169, 157, 101, 100, 107, 93;
  181, 182, 180, 177, 177, 177, 171, 162, 183, 185, 186, 185, 182;
  178, 180, 179, 177, 178, 179, 174, 167, 172, 174, 175, 174, 172;
  175, 178, 179, 178, 180, 182, 179, 173, 172, 174, 175, 175, 174;
  175, 179, 181, 180, 181, 183, 181, 177, 178, 180, 182, 183, 182]

内容来自《OpenCV 2 Computer Vision Application Programming Cookbook》

OpenCV2 访问图像的各个像素有各种方法

我们来用各种方法来实现减少图像的颜色数量

color = color/div*div +div/2;

若div为8,则原来RGB每个通道的256种颜色减少为32种。

若div为64,则原来RGB每个通道的256种颜色减少为4种,此时三通道所有能表示的颜色有4×4×4 = 64 种

首先,我们来看一个函数

C++: uchar* Mat::ptr(int i=0)
i 是行号,返回的是该行数据的指针。
在OpenCV中,一张3通道图像的一个像素点是按BGR的顺序存储的。
先来看看第一种访问方案
void colorReduce1(cv::Mat& image, cv::Mat& result, int div=64){
    int nrow = image.rows;
    int ncol = image.cols * image.channels();
    for(int i=0; i<nrow; i++){
        uchar* data = image.ptr<uchar>(i);
        uchar* data_out = result.ptr<uchar>(i);
        for(int j=0; j<ncol; j++){
            data_out[j] = data[j]/div*div +div/2;
        }
    }
}

第二种方案:

先来看如下函数:

C++: bool Mat::isContinuous() const

C++: Mat Mat::reshape(int cn, int rows=0) const

出于性能方面的考虑,在图像每一行的最后可能会填充一些像素,这样图像的数据就不是连续的了

我们可以用函数isContinuous()来判断图像的数据是否连续

reshape函数的作用如下:

Changes the shape and/or the number of channels of a 2D matrix without copying the data.

这样,我们就提出了对第一种方法的改进

void colorReduce2(cv::Mat& image, cv::Mat& result, int div){
    if(image.isContinuous()){
        image.reshape(1,image.cols*image.rows);
    }
    int nrow = image.rows;
    int ncol = image.cols * image.channels();
    for(int i=0; i<nrow; i++){
        uchar* data = image.ptr<uchar>(i);
        uchar* data_out = result.ptr<uchar>(i);
        for(int j=0; j<ncol; j++){
            data_out[j] = data[j]/div*div +div/2;
        }
    }
}
第三种方案:
先来看看下面的函数
C++: template<typename T> T& Mat::at(int i, int j)
其作用是Returns a reference to the specified array element.
void colorReduce3(cv::Mat& image, cv::Mat& result, int div){
    int nrow = image.rows;
    int ncol = image.cols * image.channels();
    for(int i=0; i<nrow; i++){
        for(int j=0; j<ncol; j++){
            image.at<cv::Vec3b>(j,i)[0]= image.at<cv::Vec3b>(j,i)[0]/div*div + div/2;
            image.at<cv::Vec3b>(j,i)[1]= image.at<cv::Vec3b>(j,i)[1]/div*div + div/2;
            image.at<cv::Vec3b>(j,i)[2]= image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
        }
    }
}
第四种方案是使用迭代器
会使用到如下函数:
C++: template<typename _Tp> MatIterator_<_Tp> Mat::begin()
C++: MatIterator_<_Tp> Mat::end()
void colorReduce4(cv::Mat& image, cv::Mat& result, int div){
    cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::iterator itout = result.begin<cv::Vec3b>();
    for(; it!=itend; ++it,++itout){
        (*itout)[0] = (*it)[0]/div*div + div/2;
        (*itout)[1] = (*it)[1]/div*div + div/2;
        (*itout)[2] = (*it)[2]/div*div + div/2;
    }
}
OpenCV中矩阵数据的访问(二)(Learning OpenCV第三章3)

2009-08-14 21:45:19| 分类: 科研学习 |字号 订阅
上一篇文章提到了访问矩阵中元素的前两种方式,下面讲第三种方式:正确的访问矩阵中数据的方式:

正确的方式
前面介绍的一些读取和写入矩阵数据的方式,实际上,你可能很少会使用它们。因为,在大多数情况下,你需要使用最有效率的方式来访问矩阵中的数据。如果使用以上的函数界面来访问数据,效率比较低,你应该使用指针方式来直接访问矩阵中数据。特别是,如果你想遍历矩阵中所有元素时,就更需要这样做了。
在用指针直接访问矩阵元素时,就需要格外注意矩阵结构体中的step成员。该成员是以字节为单位的每行的长度。而矩阵结构体的cols或width就不适合此时使用,因为为了访问效率,矩阵中的内存分配上,是以每四个字节做为最小单位的。因此如果一个矩阵的宽度是三个字节,那么就会在宽度上分配四个字节,而此时每行最后一个字节会被忽略掉。所以我们用step则会准确地按行访问数据。
我们可以通过以下例子,看一下rows,cols,height,width,step的数据,你可以通过改变矩阵的元素类型定义,来查看step的改变:
#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
    //矩阵元素为三通道8位浮点数
    CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
    printf("rows=%d,cols=%d,height=%d,width=%d,step=%d\n",mat->rows,mat->cols,mat->height,mat->width,mat->step);

}
如果我们的矩阵存储的是浮点型(或整数类型)数据,此时矩阵中每个元素占4字节,则如果我们用float类型指针指向下一行时,我们实际上要用float类型指针挪动step/4的长度,因为float类型指针每挪动一个单位就是4个字节长度。
如果我们的矩阵存储的是double类型数据,此时矩阵中每个元素占8字节,则如果我们用double类型指针指向下一行时,我们实际上要用double类型指针挪动step/8的长度,因为double类型指针每挪动一个单位就是8个字节长度。
我们重新看一下CvMat类型的数据结构定义,其中,data就是数据部分,指向data的指针可以是多种数据类型的:
typedef struct CvMat {
    int type;
    int step;
    int* refcount; // for internal use only
    union {
         uchar* ptr;
         short* s;
         int* i;
         float* fl;
         double* db;
    } data;//数据部分
    union {
         int rows;
         int height;
    };
    union {
         int cols;
         int width;
    };
} CvMat;

我们可以通过为矩阵赋值,和读取的例子,查看怎样使用step:
#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
    //矩阵元素为三通道8位浮点数
    CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
    float *p;
    int row,col;
    for(row=0; row< mat->rows; row++)
    {
        p = mat->data.fl + row * (mat->step/4);
        for(col = 0; col < mat->cols; col++)
        {
            *p = (float) row+col;
            *(p+1) = (float) row+col+1;
            *(p+2) =(float) row+col+2;
            p+=3;
        }
    }

for(row = 0; row < mat->rows; row++)
    {
        p = mat->data.fl + row * (mat->step/4);
        for(col = 0; col < mat->cols; col++)
        {
            printf("%f,%f,%f\t",*p,*(p+1),*(p+2));
            p+=3;
        }
        printf("\n");
    }
}

如果我们使用的指针类型为uchar*类型,则事情可能会简单一些,不用考虑step/4,step/8等类似情况,我们推荐用这种方式。如下例所示:

#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
    //矩阵元素为三通道8位浮点数
    CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
    float *p;
    int row,col;
    for(row=0; row< mat->rows; row++)
    {
        p = (float*)(mat->data.ptr + row * mat->step);
        for(col = 0; col < mat->cols; col++)
        {
            *p = (float) row+col;
            *(p+1) = (float) row+col+1;
            *(p+2) =(float) row+col+2;
            p+=3;
        }
    }

for(row = 0; row < mat->rows; row++)
    {
        p = (float*)(mat->data.ptr + row * mat->step);
        for(col = 0; col < mat->cols; col++)
        {
            printf("%f,%f,%f\t",*p,*(p+1),*(p+2));
            p+=3;
        }
        printf("\n");
    }
}

最后要注意一下,我们在每行都要使用step重新计算一下指针的位置,这好象不如从首指针从头到尾一直指下去,如我们上一文章的例子一样


#pragma comment( lib, "cxcore.lib" )
#include "cv.h"
#include <stdio.h>
void main()
{
    //矩阵元素为三通道浮点数
    CvMat* mat = cvCreateMat(3,3,CV_32FC3);
    cvZero(mat);//将矩阵置0
    //为矩阵元素赋值

//获得矩阵元素(0,0)的指针
    float *p = (float*)cvPtr2D(mat, 0, 0);
    //为矩阵赋值
    for(int i = 0; i < 9; i++)
    {
        //为每个通道赋值
        *p = (float)i*10;
        p++;
        *p = (float)i*10+1;
        p++;
        *p = (float)i*10+2;
        p++;
    }

//打印矩阵的值
    p = (float*)cvPtr2D(mat, 0, 0);

for(i = 0; i < 9; i++)
    {
        printf("%2.1f,%2.1f,%2.1f\t",*p,*(p+1),*(p+2));
        p+=3;
        if((i+1) % 3 == 0)
            printf("\n");
    }
}

但是一定要注意了,这个例子其实是不对的!因为我们说过,分配矩阵内存空间时,是以四字节为最小单位的,这就很有可能有不到四个字节而取成四个字节的情况,所以,如果用矩阵首地址从头到尾指下去访问数据,就很有可能访问到不是数据的字节上去!这一点请务必牢记!!
综上所述,如果要直接访问矩阵中数据,请记住使用step的方案。

另一个需要知道的情况是,我们需要了解一个多维数组(矩阵)和一个一维,但是包含高维数据的数组之间的区别。假设,你有n个点(每个点有x,y,z坐标值)需要保存到CvMat*中,你其实有四种方式可以使用,但这四种方式的存储形式不同。你可能使用一个二维矩阵,矩阵大小为n行3列,数据类型为CV32FC1。你还可以使用一个二维矩阵,矩阵大小为3行n列,数据类型为CV32FC1;第三种可能性是,你使用一个一维矩阵,n行1列,数据类型为CV32FC3;最后,你还可以使用1行三列,数据类型为CV32FC3.这几种方式,在内存分配上,有些是相同的,有些是不同的,如下所示:

n个点的集合(n=5);
(x0 y0 z0) (x1 y1 z1) (x2 y2 z2) (x3 y3 z3) (x4 y4 z4)

n行1列时(数据类型CV32FC3)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4

1行n列时(数据类型CV32FC3)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4

n行3列时(数据类型CV32FC1)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4

3行n列时(数据类型CV32FC1)内存分配情况
x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 z0 z1 z2 z3 z4

我们可以看出,前三种的内存分配情况相同,但最后一种的内存分配不同。更复杂的是,如果有n维数组,每个数组的元素是c维(c可能是通道数)时。所以,多维数组(矩阵)和一个一维但包含多维数据的数组一般是不同的。

对于一个Rows行Cols列,通道数为Channels的矩阵,访问其中第row行,第col列,第channel通道的数据,可以使用如下公式:
数据地址偏移量=row*Cols*Channels+col*Channels+channel

OpenCV中对Mat里面depth,dims,channels,step,data,elemSize和数据地址计算的理解相关推荐

  1. OpenCV中图像Mat,二维指针和CxImage类之间的转换

    在做图像处理中,常用的函数接口有Opencv中的Mat图像类,有时候需要直接用二维指针开辟内存直接存储图像数据,有时候需要用到CxImage类存储图像.本文主要是总结下这三类存储方式之间的图像数据的转 ...

  2. opencv中查看mat位图的像素幅度(Cv::matStep)

    实例 其中step里的 ,其中数据指针首地址是p=0x000000000028d7b0,1280是每行数据所占的字节数,1是每个元素的字节数. Mat的作用 The class Mat represe ...

  3. 如何将OpenCV中的Mat类绑定为OpenGL中的纹理

    https://blog.csdn.net/TTTTzTTTT/article/details/53456324 如果要调用外接的USB摄像头获取图像通常使用OpenCV来调用,如何调用摄像头请参考本 ...

  4. 遍历opencv中的mat像素的几种方法和概念

    今天在看矩形滤波的时候忽然脑子短路,把一些概念全弄混了,现总结一下,以便下次再混的时候可以参考确认下,自己的理解,有错的地方还请指正. 首先,在Opencv2中基本上都是用的Mat来表示图像了,C++ ...

  5. opencv中的Mat图使用CDC显示

    需求:MFC显示opencv读取的Mat图 代码: 1.中间转化的函数: //************************************ // 函数名称: Show2DC // 访问权限 ...

  6. OpenCV中图像Mat存储格式和MATLAB中图像Mat存储格式的区别

    首先,看一下图像中的宽高与笛卡尔坐标系之间的关系如下图所示,即x与width(cols)对应,y与height(rows)对应,x是按列来进行变化,y按行变化. OpenCV读入图像以Mat形式存储时 ...

  7. C语言使用指针处理opencv中的Mat图像数据

    1.在处理图像时,一般直接使用opencv中的imread函数获取图片,但是获取到图片后没有用到opencv中的其他算法时,直接用图片处理就会出现耗时严重的情况,所以需要将图片形式转换成指针数组形式处 ...

  8. c++版opencv中的Mat数据类型的说明

    一直使用mat,很好用,但是细扣又说不清楚到底是怎样的一种数据类型,今天学习下. 一.先上硬货结论: 浅拷贝:拷贝构造函数和赋值运算符只复制信息头,即实际上还是同个图像数据.mat中存储同个数据地址: ...

  9. 利用FreeImage将gif图像转为opencv中的Mat

    原文:http://www.cnblogs.com/monkeyhey/p/3927857.html 网上有将gif转为iplimg的版本,只是用惯了C++的接口,所以就写了个转Mat的版本,代码比较 ...

  10. opencv中的Mat类型

    Mat类型主要是跟matlab中的数据类型一样.故用起来很方便. Mat最大的优势跟STL很相似,都是对内存进行动态的管理,不需要之前用户手动的管理内存,对于一些大型的开发,有时候投入的lpImage ...

最新文章

  1. 解决:无法解析的外部符号__iob_func
  2. 加速SaaS规模化演进,餐道基于K8s的云上创新底座
  3. 面向对象的C语言编程-DynamicLinkageGenericFunctions--C语言中的偷梁换柱
  4. 成都内推 | 腾讯游戏王者荣耀算法团队招聘算法实习生
  5. 8 list切片_Python中14个切片操作,你常用哪几个?
  6. Redis 为什么是单线程的?
  7. vb6 判断打印机是否有效_吊打面试官 | 算法之如何判断括号是否有效?
  8. Flutter Duration详细概述
  9. oracle as sydba,Oracle的操作系统认证(/ as sydba 登录方式)
  10. 309. 最佳买卖股票时机含冷冻期
  11. 网络编程(基于udp协议的套接字/socketserver模块/进程简介)
  12. Oracle 角色权限表
  13. 服务器上装的hadoop系统,在Ubuntu Server 18.04.1中安装Hadoop系统环境
  14. java-乐观锁与悲观锁
  15. win10微信卡顿_终于找到Win10卡顿病根了!看完秒懂
  16. java高级工程师认证考试_Java高级软件工程师人才缺口巨大
  17. 鹅厂java面试真题汇总
  18. ECharts之阶梯瀑布柱状图
  19. Python 打包exe
  20. 中文技术文档写作规范【转载】

热门文章

  1. Python 使用Protobuf(struct模块)
  2. 倒置链表(递归方式)
  3. FISCO BCOS(一)———搭建单群组FISCO BCOS联盟链
  4. Python如何使用生成器得到斐波那契数列
  5. iphone小圆点在哪儿设置_iPhone终于自带长截屏了?苹果手机这些截图方式,你用过几种?...
  6. python语法使用方法_Python语法基础
  7. java中面向对象6_Java面向对象
  8. feign调用https接口_SpringCloudFeign远程调用
  9. SQL:postgis中计算距离和面积
  10. 面向对象(Python):学习笔记之模块和包