目录

  • 引言
  • 1.进程间通信
  • 2.基于共享内存的视频传输
    • 2.1 C++之间的通信
      • 2.1.1 接口函数
      • 2.1.2 创建数据格式和共享内存信息
      • 2.1.3 C++之间共享内存通信
      • 2.1.4 C++之间共享内存通信视频测试结果
    • 2.2 C++和python间视频通信
      • 2.2.1 接口函数
      • 2.2.1 C++与python之间共享内存通信
  • 3.基于Socket的视频传输
    • 3.1 cpp端socket
    • 3.2 python端
    • 3.3 CMakeList
    • 3.4 测试结果
  • 4 结论

引言

本案例旨在实现跨语言(C++和python间)视频的实时通信。这一工作内容在实际工程中很常见。由于python语言支持很多第三方库,对于开发深度学习项目很方便,验真算法速度快,很多开源算法也大多基于python实现。这时可能就会出现C++的代码借助python语言做一些图像处理(包括目标检测、姿态估计、目标跟踪等任务)的需求。

平台环境:

  • Win10
  • VS2019
  • OpenCV

进程间通信方式:共享内存

1.进程间通信

进程间通信方式有很多种。工程上最常用的是共享内存socket机制。前者效率高,基本思想就是开辟一块公共的内存空间,供两个或多个进程之间使用。为了标识这个公共空间,要给它起个名。但是共享内存的方式不支持多平台。而socket刚好就是支持多平台间进程通信的方式。当然这种方式也会慢一些。

在本案例中,分别尝试了两种方式。虽然最终共享内存的方式写内存帧率只达到15fps左右,但是要比socket快了近20倍(大概0.5-1fps左右)。下面将介绍这两种机制的具体实现过程。

2.基于共享内存的视频传输

2.1 C++之间的通信

2.1.1 接口函数

首先验证C++之间能通信。这里使用的是CreateFileMapping和MapViewOfFile进行共享内存的创建和映射。

其中CreateFileMapping的接口为,参数含义详解请点击链接。

HANDLE CreateFileMapping(HANDLE                hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttributes,DWORD                 flProtect,DWORD                 dwMaximumSizeHigh,DWORD                 dwMaximumSizeLow,LPCSTR                lpName
);

MapViewOfFile的接口为

LPVOID MapViewOfFile(HANDLE hFileMappingObject,DWORD  dwDesiredAccess,DWORD  dwFileOffsetHigh,DWORD  dwFileOffsetLow,SIZE_T dwNumberOfBytesToMap
);

2.1.2 创建数据格式和共享内存信息

首先需要一个图像的头部

typedef struct {int width;int height;int type;
}ImgInf;       //图像信息

由于sizeof(int)=4,所以这里ImgInf结构体大小为12B。在进行共享内存映射时,我们需要这个大小去做偏移量,找到图像数据。

接下来要定义图像的数据信息

#define FRAME_NUMBER         1               // 图像路数
#define FRAME_W              1920
#define FRAME_H              1080
#define FRAME_W_H            FRAME_W*FRAME_H
// 图像分辨率:彩色图(3通道)+图像信息结构体
#define FRAME_SIZE           FRAME_W_H*sizeof(unsigned char)*3+sizeof(ImgInf)#define MEMORY_SIZE          FRAME_NUMBER*FRAME_SIZE

定义共享内存类SHAREDMEMORY

class SHAREDMEMORY
{public:SHAREDMEMORY();~SHAREDMEMORY();//void SendBox(TrackBox& BOX);//void RecBox(TrackBox& BOX);//void SendVectorBox(vector<TrackBox>& VTrackBox);//void RecieveVectorBox(vector<TrackBox>& VTrackBox);void SendMat(cv::Mat img, char indexAddress);cv::Mat  ReceiveMat(char indexAddress);void SendStr(const char data[]);  char* ReceiveStr();public:int state;
private:HANDLE hShareMem;                               //共享内存句柄TCHAR sShareMemName[30] = TEXT("CppPytonSharedFrame"); // 共享内存名称LPCTSTR pBuf;
};

其中SendMat为图像数据发送,ReceiveMat为图像接收。
SendStr为字符串发送,ReceiveStr为字符串接收。

最后的ShareMemory.h文件如下:

#pragma once
// ShareMemory.h : 此文件包含共享内存数据定义、大小确定、位置分配、信息定义
// Author : Jiejing.Ma
// Update : 2020/11/27
#ifndef ShareMemory_H
#define ShareMemory_H#include <opencv2/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>  // cv::Canny()
#include <opencv2/opencv.hpp>#include <Windows.h>//=================================共享内存数据定义=================================
typedef struct {int width;int height;int type;
}ImgInf;       //图像信息
//=================================共享内存大小确定=================================
// 为图像分配空间
#define FRAME_NUMBER         1               // 图像路数
#define FRAME_W              1920
#define FRAME_H              1080
#define FRAME_W_H            FRAME_W*FRAME_H
// 图像分辨率:彩色图(3通道)+图像信息结构体
#define FRAME_SIZE           FRAME_W_H*sizeof(unsigned char)*3+sizeof(ImgInf)#define MEMORY_SIZE          FRAME_NUMBER*FRAME_SIZE//=================================共享内存信息定义=================================
#define INITSUCCESS      0
#define CREATEMAPFAILED  1
#define MAPVIEWFAILED    2class SHAREDMEMORY
{public:SHAREDMEMORY();~SHAREDMEMORY();void SendMat(cv::Mat img, char indexAddress);cv::Mat  ReceiveMat(char indexAddress);void SendStr(const char data[]);char* ReceiveStr();public:int state;
private:HANDLE hShareMem;                               //共享内存句柄TCHAR sShareMemName[30] = TEXT("ShareMedia");   // 共享内存名称LPCTSTR pBuf;
};#endif // !ShareMemory_H

对应的ShareMemory.cpp文件为类的实现。

#pragma once
// ShareMemory.cpp : 此文件包含信息定义SHAREDMEMOR类的实现
// Author : MJJ
// Update : 2020/11/27
#ifndef ShareMemory_CPP
#define ShareMemory_CPP#include "ShareMemory.h"
#include <iostream>using namespace cv;
using namespace std;/*************************************************************************************
FuncName  :SHAREDMEMORY::~SHAREDMEMORY()
Desc      :构造函数创建共享内存
Input     :None
Output    :None
**************************************************************************************/
SHAREDMEMORY::SHAREDMEMORY() {hShareMem = CreateFileMapping(INVALID_HANDLE_VALUE,  // use paging fileNULL,                  //default securityPAGE_READWRITE,        //read/write access0,                     // maximum object size(high-order DWORD)MEMORY_SIZE,           //maximum object size(low-order DWORD)sShareMemName);        //name of mapping objectif (hShareMem) {//  映射对象视图,得到共享内存指针,设置数据pBuf = (LPTSTR)MapViewOfFile(hShareMem,           //handle to map objectFILE_MAP_ALL_ACCESS, // read/write permission0,0,MEMORY_SIZE);cout << "memory size:" << MEMORY_SIZE<< endl;// 若映射失败退出if (pBuf == NULL){std::cout << "Could not map view of framebuffer file." << GetLastError() << std::endl;CloseHandle(hShareMem);state = MAPVIEWFAILED;}}else{std::cout << "Could not create file mapping object." << GetLastError() << std::endl;state = CREATEMAPFAILED;}state = INITSUCCESS;
}/*************************************************************************************
FuncName  :SHAREDMEMORY::~SHAREDMEMORY()
Desc      :析构函数释放
Input     :None
Output    :None
**************************************************************************************/
SHAREDMEMORY::~SHAREDMEMORY() {std::cout << "unmap shared addr." << std::endl;UnmapViewOfFile(pBuf); //释放;CloseHandle(hShareMem);
}/*************************************************************************************
FuncName  :void SHAREDMEMORY::SendMat(cv::Mat img, char indexAddress)
Desc      :发送Mat数据
Input     :Mat img               发送图像char indexAddress     共享内存中起始位置,若只有一路视频则无偏移
Output    :None
**************************************************************************************/
void SHAREDMEMORY::SendMat(cv::Mat img, char indexAddress) {ImgInf img_head;img_head.width = img.cols;img_head.height = img.rows;img_head.type = img.type();if (img_head.type == CV_64FC1) {memcpy((char*)pBuf + indexAddress, &img_head, sizeof(ImgInf));memcpy((char*)pBuf + indexAddress + sizeof(ImgInf),        // Address of dstimg.data,                                              // Src dataimg.cols * img.rows * img.channels() * sizeof(double)  // size of data);}else{memcpy((char*)pBuf + indexAddress, &img_head, sizeof(ImgInf));memcpy((char*)pBuf + indexAddress + sizeof(ImgInf),        // Address of dstimg.data,                                              // Src dataimg.cols * img.rows * img.channels()                   // size of data);       }cout << "write shared mem successful." << endl;
}/*************************************************************************************
FuncName  :cv::Mat SHAREDMEMORY::ReceiveMat(char indexAddress)
Desc      :接收Mat数据
Input     :char indexAddress     共享内存中起始位置,若只有一路视频则无偏移
Output    :Mat图像
**************************************************************************************/
cv::Mat SHAREDMEMORY::ReceiveMat(char indexAddress)
{ImgInf img_head;cv::Mat img;memcpy(&img_head, (char*)pBuf + indexAddress, sizeof(ImgInf));img.create(img_head.height, img_head.width, img_head.type);if (img_head.type == CV_64FC1){memcpy(img.data, (char*)pBuf + indexAddress + sizeof(ImgInf), img.cols * img.rows * img.channels() * sizeof(double));}else{memcpy(img.data, (char*)pBuf + indexAddress + sizeof(ImgInf), img.cols * img.rows * img.channels());}return img;
}/*************************************************************************************
FuncName  :void SHAREDMEMORY::SendStr(cv::Mat img, char indexAddress)
Desc      :发送str数据
Input     :Mat img               发送图像char indexAddress     共享内存中起始位置,若只有一路视频则无偏移
Output    :None
**************************************************************************************/
void SHAREDMEMORY::SendStr(const char data[]) {memcpy((char*)pBuf, data, sizeof(data));cout << "write shared mem successful." << endl;getchar();
}/*************************************************************************************
FuncName  :void SHAREDMEMORY::ReceiveStr()
Desc      :接收str数据
Input     :None
Output    :获取的字符串
**************************************************************************************/
char* SHAREDMEMORY::ReceiveStr(){char* str= (char*)pBuf;cout << "receive is:"<< str << endl;return str;
}
#endif // !ShareMemory_CPP

2.1.3 C++之间共享内存通信

创建一个新的工程,导入上面两个文件,并创建WriteMem.cpp文件

// WriteMem.cpp : 此文件为写共享内存
// Author : Jiejing.Ma
// Update : 2020/11/27#include <iostream>
#include "ShareMemory.h"using namespace std;
using namespace cv;// 读图片或视频
void send_img(SHAREDMEMORY sharedsend)
{int index = 0;int64 t0 = cv::getTickCount();;int64 t1 = 0;string fps = "";int nFrames = 0;cv::Mat frame;cout << "Opening video..." << endl;VideoCapture cap("test.flv");while (cap.isOpened()) {cap >> frame;if (frame.empty()){std::cerr << "ERROR: Can't grab video frame." << endl;break;}resize(frame, frame, Size(FRAME_W, FRAME_H));nFrames++;if (!frame.empty()) {if (nFrames % 10 == 0){const int N = 10;int64 t1 = cv::getTickCount();fps = " Send FPS:" + to_string((double)getTickFrequency() * N / (t1 - t0)) + "fps";    t0 = t1;}cv::putText(frame, fps, Point(100, 100), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 255, 255),1);}sharedsend.SendMat(frame, index * FRAME_NUMBER);if ((waitKey(1) & 0xFF) == 'q') break;}
}int main()
{SHAREDMEMORY sharedmem;//char str[] = "hello";if (sharedmem.state == INITSUCCESS) send_img(sharedmem);//if (sharedmem.state == INITSUCCESS) sharedmem.SendStr(str);return 0;
}

创建一个新的工程,导入上面两个文件,并创建ReadMem.cpp文件

// ReadMem.cpp : 此文件为读共享内存
// Author : Jiejing.Ma
// Update : 2020/11/27#include <Windows.h>
#include <iostream>
#include "ShareMemory.h"
#include <string>using namespace std;
using namespace cv;int main(int argc, char** argv)
{int index=0;SHAREDMEMORY sharemem;if (sharemem.state == INITSUCCESS) {// read video frame from shared memory.sint64 t0 = cv::getTickCount();;int64 t1 = 0;string fps = "";int nFrames = 0;namedWindow("ReadMemShow", 0);while (true){nFrames++;Mat frame = sharemem.RecieveMat(index * FRAME_NUMBER);if (!frame.empty()) {if (nFrames % 10 == 0){const int N = 10;int64 t1 = cv::getTickCount();fps = " Average FPS:" + to_string((double)getTickFrequency() * N / (t1 - t0)) + "fps";t0 = t1;}cv::putText(frame, fps, Point(100, 200), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(100, 200, 200),1);imshow("ReadMemShow", frame);                }if((waitKey(1) & 0xFF) == 'q') break;}//char* str = sharemem.RecieveStr();}destroyAllWindows();return 0;
}

同时开启两个工程,则可以接收视频了。

2.1.4 C++之间共享内存通信视频测试结果

这里看到,写共享内存速度为15fps,读共享内存速度为65fps(超实时),写速度主要的影响因素与opecv有关。如果优化,还需改视频编解码部分。

2.2 C++和python间视频通信

这里以C++作为发送端,python作为接受端。逆向过程还有待测试。网上有教程提到python不能创建共享内存作为发送端,这种说法是错的。本人已测试过,只是发送数据都是字符串型,对于图像数据还有待研究。

2.2.1 接口函数

这里主要用到的是mmapnumpy的frombuffer.

关于mmap,请参考官网的接口说明。十分详细,不再赘述。

frombuffer:

numpy.frombuffer(buffer, dtype=float, count=-1, offset=0)

Interpret a buffer as a 1-dimensional array.

Parameters
bufferbuffer_like
An object that exposes the buffer interface.

dtypedata-type, optional
Data-type of the returned array; default: float.

countint, optional
Number of items to read. -1 means all data in the buffer.

offsetint, optional
Start reading the buffer from this offset (in bytes); default: 0.

参考官网

2.2.1 C++与python之间共享内存通信

前面已经实现C++代码。不需要改动。只需启动写共享内存工程即可。

python代码如下:

import mmap
import os
import cv2
import numpy as np# -----------------Define info in ShareMemory.h-----------------
IMG_HEAD_OFFSET = 12
# typedef struct {#   int width;
#   int height;
#   int type;
# }ImgInf;       //图像信息12字节FRAME_NUMBER = 1
FRAME_W = 1920
FRAME_H = 1080
FRAME_W_H = FRAME_W * FRAME_H
FRAME_SIZE = FRAME_W_H * 3
MEMORY_SIZE = (FRAME_SIZE + IMG_HEAD_OFFSET) * FRAME_NUMBERsShareMemName = "ShareMedia"if __name__ == "__main__":fpx = mmap.mmap(-1, FRAME_SIZE+IMG_HEAD_OFFSET, sShareMemName)# Read img as numpycv2.namedWindow("python_sharedmem_show",0)t0 = cv2.getTickCount()N = 50nFrame = 0fps = 0while 1:nFrame += 1img = np.frombuffer(fpx, dtype=np.uint8)img = img[IMG_HEAD_OFFSET:FRAME_SIZE+IMG_HEAD_OFFSET]img = img.reshape((FRAME_H,FRAME_W,3))# Print Average  FPSif nFrame % 50 == 0:t1 = cv2.getTickCount()fps = N*cv2.getTickFrequency() / (t1 - t0)t0 = t1cv2.putText(img, "Average FPS:" + str(fps) + "fps", (100, 200), cv2.FONT_HERSHEY_COMPLEX, 1, (100, 200, 200), 1)cv2.imshow("python_sharedmem_show", img)img = Noneif cv2.waitKey(1) & 0xFF == ord('q'):breakcv2.destroyAllWindows()

3.基于Socket的视频传输

这里是基于Linux开发的Socket通信。而共享内存是基于Windows平台。

3.1 cpp端socket

cppsocket.cpp

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>#include <errno.h>
#include <string>
#include <iostream>
#include <vector>#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main(int argc, char **argv)
{// 定义socket信息char *servInetAddr = "192.168.113.173";int servPort = 8081;int connfd;struct sockaddr_in addr;// 创建socketconnfd = socket(AF_INET,SOCK_STREAM, 0);if (connfd == -1){cout<<"socket创建失败"<<endl;exit(-1);}// 准备通信地址addr.sin_family=AF_INET;addr.sin_port=htons(servPort);addr.sin_addr.s_addr = inet_addr(servInetAddr);// bindint res = connect(connfd,(struct sockaddr*)&addr,sizeof(addr));if(res==-1){cout<<"bind连接失败"<<endl;exit(-1);}cout<<"bind连接成功"<<endl;// 获取视频帧并发送Mat img;VideoCapture capture("./test.flv");vector<uchar> data_encode;while(capture.isOpened()){if(!capture.read(img)) break;imencode(".jpg",img,data_encode);int len_encode = data_encode.size();string len = to_string(len_encode);int length = len.length(); for (int i=0;i<16-length;i++) len=len+' ';// 发送数据send(connfd,len.c_str(),strlen(len.c_str()),0);char send_char[1];for (int i=0;i<len_encode;i++){send_char[0]=data_encode[i];send(connfd,send_char,1,0);}// 接收返回信息char recvBuf[32] = "";if(recv(connfd, recvBuf,32,0)) cout<<recvBuf<<endl;}close(connfd);return 0;}

3.2 python端

pysocket.py

import socket
import cv2
import numpy
import timedef recv_size(sock, count):buf=b''while count:newbuf = sock.recv(count)if not newbuf: return Nonebuf +=newbufcount -= len(newbuf)return bufdef recv_all(sock, count):buf = b''while count:newbuf = sock.recv(1)if not newbuf:return Nonebuf +=newbufcount -= len(newbuf)return buf# 创建socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 准备通信地址
address = ('192.168.113.173',8081)
s.bind(address)
s.listen(True)
print('Waiting for images...')
# 接受TCP链接并返回(conn, addr),其中conn是新的套接字对象,可以用来接收和发送数据,addr是链接客户端的地址。
conn, addr = s.accept()
n = 0while 1:n +=1length = recv_size(conn,16).decode()t0=time.time()if isinstance(length,str):  # 若成功接收指定大小信息,进一步接收整张图string_data = recv_all(conn,int(length))data = numpy.fromstring(string_data,dtype='uint8')decimg = cv2.imdecode(data,1)cv2.namedWindow('python-recv')cv2.imshow('python-recv',decimg)if cv2.waitKey(1) & 0xFF == ord('q'):print("111111")breakt1 = time.time()print('Image recieved successfully!fps:'+str(1/(t1-t0)))conn.send('recieved messages!'.encode())t0=t1if cv2.waitKey(1) & 0xFF == ord('q'):breaks.close()
cv2.destroyAllWindows()

3.3 CMakeList

Linux 下编译CPP文件,这里使用CMakeList:

cmake_minimum_required(VERSION 3.0.0)
project(client VERSION 0.1.0)include(CTest)
enable_testing()# find opencv and link
find_package(OpenCV REQUIRED)
message(STATUS "Opencv library status:")
message(STATUS " version:${OpenCV_VERSION}")
message(STATUS " libraries:${OpenCV_LIBS}")
message(STATUS " include path:${OpenCV_INCLUDE_DIRS}")
include_directories(${OpenCV_INCLUDE_DIRS})
link_libraries(${OpenCV_LIBS})add_executable(client cppsocket.cpp)set(CMAKE_CXX_FLAGE "${CMAKE_CXX_FLAGE} -g")

3.4 测试结果

结果就是速度超级慢,大概一秒多一帧。

4 结论

C++和python之间通信,可以采用C++调python的方式。请参考之前的文章。Ubuntu下C++调python
这种方式,从架构的角度来讲,最简单。工程量和已有经验的角度,emm可能坑比较多。速度也应该最快(推测)

也可以使用进程间通信。当然这个成本就高了。有两种方式,一是共享内存机制,一是socket通信。前者更快,但只能在一个平台上。后者慢,可以支持不同电脑间通信。

最后关于基于共享内存方式,影响速度的主要是写共享内存,而这又与opencv读视频有关,与视频编解码有关。想要提高写内存速度,需要从底层修改视频编解码。可以参考UE4的相关插件解决。

在C++与python间传视频帧相关推荐

  1. python批量提取视频帧

    python批量提取视频帧 python批量提取视频帧,两种提取方式: 按帧数提取,每个视频提取固定帧数,若所取帧数超过视频总帧数,则截取视频所有帧 按时间间隔提取,每个time提取一帧 1. 使用示 ...

  2. Python 抽取剔除视频帧工具

    前言 正好有人问我怎么把视频某几帧去掉,正好有时间,正好写了,正好发出来. 大家想用的话可以拿去参考. 代码 下面是我使用opencv对视频中间几帧抽取的方法. 主要的思路是在读取frame的时候,顺 ...

  3. python快速检测视频跳过帧_使用Python实现跳帧截取视频帧

    本文实例为大家分享了Python跳帧截取视频帧的具体代码,供大家参考,具体内容如下 可以自由设定时长来截取视频,经实测效果理想.期间遇到的一个麻烦是我的视频文件在D:盘,在原视频D盘目录上不能保存截取 ...

  4. 视频抽帧并存图 python_使用Python实现跳帧截取视频帧

    本文实例为大家分享了Python跳帧截取视频帧的具体代码,供大家参考,具体内容如下 可以自由设定时长来截取视频,经实测效果理想.期间遇到的一个麻烦是我的视频文件在D:盘,在原视频D盘目录上不能保存截取 ...

  5. Python快速从视频中提取视频帧(多线程)

    Python快速提取视频帧(多线程) 今天介绍一种从视频中抽取视频帧的方法,由于单线程抽取视频帧速度较慢,因此这里我们增加了多线程的方法. 1.抽取视频帧 抽取视频帧主要使用了 Opencv 模块. ...

  6. 解决Python OpenCV 读取视频并抽帧出现error while decoding的问题

    解决Python OpenCV 读取视频抽帧出现error while decoding的问题 1. 问题 2. 解决 3. 源代码 参考 1. 问题 读取H264视频,抽帧视频并保存,报错如下: [ ...

  7. 使用OpenCV和Python高效计算视频的总帧数

    使用OpenCV和Python高效计算视频的总帧数 1. 效果图 2. 源码 参考 这篇博客将介绍两种使用OpenCV和Python计算视频文件中帧数的方法. 超级快,它依靠OpenCV的视频属性功能 ...

  8. python使用方法视频-python读取视频流提取视频帧的两种方法

    本文实例为大家分享了python读取视频流提取视频帧的具体代码,供大家参考,具体内容如下 方法一:通过imageio库和skimage库 1. 安装环境: pip install imageio pi ...

  9. python opencv按照一定间隔保存视频帧

    python opencv按照一定间隔保存视频帧 文章目录: 一.获取视频流的相关参数 二.设置间隔保存视频帧 想实现opencv读取视频帧,按照一定的时间间隔然后保存图片下来,因为所有的帧都保存下来 ...

最新文章

  1. 基于 Python 的 8 种常用抽样方法
  2. 一个判断字符是不是10进制数的函数------isdigit()
  3. mysql创建数据库时候同时创建表空间_MySQL 创建InnoDB表空间_编程学问网
  4. ubuntu nfs
  5. apply()与call()的区别
  6. unreal4怎么设置游戏模式_ue4(虚幻4)基础 Unreal4 服务器模式详细介绍
  7. ES6/03/函数的定义方式和调用方式,函数内的this指向,改变函数中this指向的三个方法(call(),apply(),bind())
  8. 《深入理解 Spring Cloud 与微服务构建》第九章 熔断器 Hystrix
  9. 深入理解java的异常处理机制
  10. Java WebService视频教程
  11. java 代码行数统计工具_代码行数统计工具
  12. 小飞鱼通达二开 通达OA2017集成MongoDB(图文)
  13. ffmpeg 命令转vp9
  14. JSAPI微信支付java
  15. DDoS 报告攻击类型占比
  16. Java中的Constants类
  17. html页面字体飞入飞出特效,JS网页特效:星空飞入效果
  18. 正则表达式,和python re模块
  19. Bias-variance trade off
  20. 网站URL如何SEO优化

热门文章

  1. vertica 数据库 linux,CentOS 7下安装vertica记录
  2. OpenShift 4 - 使用 Debezium 捕获变化数据,实现MySQL到PostgreSQL数据库同步(附视频)
  3. idea选中多行的一列、一竖(不是多行的全部内容)
  4. 网易2018校园招聘编程题真题集合
  5. 算术,逻辑左移右移(转)
  6. 【开发工具】【perf】性能分析工具perf的编译和使用说明
  7. 【“到此一游”系列】(菜鸡参加“美亚杯” 电子取证大赛感受)
  8. python精选04集(选择语句)
  9. CCF关于举办CSP-J1 CSP-S1 初赛的报名通知
  10. Powermill汽车件模具五轴数控CNC编程视频教程