转:视频解码原理及ffmpeg MP4转YUV420P-程序员宅基地

本文为CSDN博主「涂文远」的原创文章,原文链接:https://blog.csdn.net/daividtu/article/details/84141849

  1. 为什么要进行视频压缩?
    ● 未经压缩的数字视频的数据量巨大
    ● 存储困难
    ○ 一G只能存储几秒钟的未压缩数字视频。
    ● 传输困难
    ○ 1兆的带宽传输一秒的数字电视视频需要大约4分钟。

为什么可以压缩
● 去除冗余信息
○ 空间冗余:图像相邻像素之间有较强的相关性
○ 时间冗余:视频序列的相邻图像之间内容相似
○ 编码冗余:不同像素值出现的概率不同
○ 视觉冗余:人的视觉系统对某些细节不敏感
○ 知识冗余:规律性的结构可由先验知识和背景知识得到
3. 数据压缩分类
● 无损压缩(Lossless)
○ 压缩前解压缩后图像完全一致X=X’
○ 压缩比低(2:1~3:1)
○ 例如:Winzip,JPEG-LS
● 有损压缩(Lossy)
○ 压缩前解压缩后图像不一致X≠X’
○ 压缩比高(10:1~20:1)
○ 利用人的视觉系统的特性
○ 例如:MPEG-2,H.264/AVC,AVS
人类视觉系统HVS
● HVS特点:
○ 对高频信息不敏感
○ 对高对比度更敏感
○ 对亮度信息比色度信息更敏感
○ 对运动的信息更敏感X
● RGB转化到YUV空间
亮度分量Y与三原色有如下关系:

主流的编解码标准的压缩对象都是YUV图像

解协议的作用,就是将流媒体协议的数据,解析为标准的相应的封装格式数据。视音频在网络上传播的时候,常常采用各种流媒体协议,例如HTTP,RTMP,或是MMS等等。这些协议在传输视音频数据的同时,也会传输一些信令数据。
信令数据包括对播放的控制(播放,暂停,停止),或者对网络状态的描述等。解协议的过程中会去除掉信令数据而只保留视音频数据。例如,采用RTMP协议传输的数据,经过解协议操作后,输出FLV格式的数据。

解封装的作用,就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类很多,例如MP4,MKV,RMVB,TS,FLV,AVI等等,它的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
解码的作用,就是将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC,MP3,AC-3等等,视频的压缩编码标准则包含H.264,MPEG2,VC-1等等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P,RGB等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。
视音频同步的作用,就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的视频和音频数据,并将视频音频数据送至系统的显卡

由表可见,除了AVI之外,其他封装格式都支持流媒体,即可以“边下边播”。有些格式更“万能”一些,支持的视音频编码标准多一些,比如MKV。而有些格式则支持的相对比较少,比如说RMVB。
这些封装格式都有相关的文档,在这里就不一一例举了。
我自己也做过辅助学习的小项目:

视频编码
视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。视频编码是视音频技术中最重要的技术之一。视频码流的数据量占了视音频总数据量的绝大部分。高效率的视频编码在同等的码率下,可以获得更高的视频质量。

音频编码
音频编码的主要作用是将音频采样数据(PCM等)压缩成为音频码流,从而降低音频的数据量。音频编码也是互联网视音频技术中一个重要的技术。但是一般情况下音频的数据量要远小于视频的数据量,因而即使使用稍微落后的音频编码标准,而导致音频数据量有所增加,也不会对视音频的总数据量产生太大的影响。高效率的音频编码在同等的码率下,可以获得更高的音质。
音频编码的简单原理

YUV420数据格式
YUV简介
YUV定义:分为三个分量,
“Y”表示明亮度(Luminance或Luma)也就是灰度值
而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

YUV存储:格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,
YUV特点:也是一种颜色编码方法,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样 可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传 输,所以用YUV方式传送占用极少的频宽。

在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB2YUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)。但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。如下:
YV420: 亮度(行×列) + V(行×列/4) + U(行×列/4)
以后提取每个像素的YUV分量会用到。

  1. YUV 4:4:4采样,每一个Y对应一组UV分量。
  2. YUV 4:2:2采样,每两个Y共用一组UV分量。
  3. YUV 4:2:0采样,每四个Y共用一组UV分量。

-----------------------------------函数作用----------------------------------------------------------------------------------
av_register_all
基于ffmpeg的应用程序中 几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器才能起作用,必须调用此函数。
一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Byte,RGB32的size=width×heigth×4,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Byte。

AVFormatContext是包含码流参数较多的结构体。本文将会详细分析一下该结构体里每个变量的含义和作用。
struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据

avformat_find_stream_info
该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。我们大致浏览一下这个函数的代码,会发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程。

从avcodec_decode_video2()主要做了以下几个方面的工作:
(1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。
(2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。
(3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。


FFmpeg并没有垃圾回收机制,所分配的空间都需要自己维护。而由于视频处理过程中数据量是非常大,对于动态内存的使用更要谨慎。
AVFormatContext 在FFmpeg中有很重要的作用,描述一个多媒体文件的构成及其基本信息,存放了视频编解码过程中的大部分信息。通常该结构体由avformat_open_input分配
存储空间,在最后调用avformat_input_close关闭。

AVStream 描述一个媒体流,在解码的过程中,作为AVFormatContext的一个字段存在,不需要单独的处理。
AVpacket 用来存放解码之前的数据,它只是一个容器,其data成员指向实际的数据缓冲区,在解码的过程中可有av_read_frame创建和填充AVPacket中的数据缓冲区,
当数据缓冲区不再使用的时候可以调用av_free_apcket释放这块缓冲区。
AVFrame 存放从AVPacket中解码出来的原始数据,其必须通过av_frame_alloc来创建,通过av_frame_free来释放。和AVPacket类似,AVFrame中也有一块数据缓存空间,
在调用av_frame_alloc的时候并不会为这块缓存区域分配空间,需要使用其他的方法。在解码的过程使用了两个AVFrame,这两个AVFrame分配缓存空间的方法也不相同
● 一个AVFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调avcodec_decode_video分配和填充。
● 另一个AVFrame用来存放将解码出来的原始数据变换为需要的数据格式(例如RGB,RGBA)的数据,这个AVFrame需要手动的分配数据缓存空间。

sws_getContext
srcW:源图像的宽
srcH:源图像的高
srcFormat:源图像的像素格式
dstW:目标图像的宽
dstH:目标图像的高
dstFormat:目标图像的像素格式
flags:设定图像拉伸使用的算法

在摄像头之类编程经常是会碰到YUV格式,而非大家比较熟悉的RGB格式. 我们可以把YUV看成是一个RGB的变种来理解.

YUV的原理是把亮度与色度分离,研究证明,人眼对亮度的敏感超过色度。利用这个原理,可以把色度信息减少一点,人眼也无法查觉这一点。

YUV三个字母中,其中"Y"表示明亮度(Lumina nce或Luma),也就是灰阶值;而"U"和"V"表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。用这个三个字母好象就是通道命令

使用YUV的优点有两个:
一.彩色YUV图像转黑白YUV图像转换非常简单,这一特性用在于电视信号上。

二.YUV是数据总尺寸小于RGB格式

  1. RGB 转换成 YUV
  2. Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16
  3. Cr = V = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
  4. Cb = U = -( 0.148 * R) - (0.291 * G) + (0.439 * B) + 128
  5. YUV 转换成 RGB
  6. B = 1.164(Y - 16) + 2.018(U - 128)
  7. G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
  8. R = 1.164(Y - 16) + 1.596(V - 128)

RGB格式中,一个24bpp像素要占用4字节空间。在YUV格式中,可以对于UV分量的数据压缩,但是对图像整体质量影响不大,这样YUV所占的空间就比RGB要小一些
YUV的存储中与RGB格式最大不同在于,RGB格式每个点的数据是连继保存在一起的。即R,G,B是前后不间隔的保存在2-4byte空间中。而YUV的数据中为了节约空间,U,V分量空间会减小。每一个点的Y分量独立保存,但连续几个点的U,V分量是保存在一起的,(反正人眼一般也看不出区别).这几个点合起来称
为macro-pixel, 这种存储格式称为Packed格式。
另外一种存储格式是把一幅图像中Y,U,V分别用三个独立的数组表示。这种模式称为planar模式。

播放器
G:\ScreenCapture\ffmpeg-20170915-6743351-win64-static\bin

下载地址
http://ffmpeg.zeranoe.com/builds/

命令
ffplay -f rawvideo -video_size 480x208 X:\Users\twy\Desktop\output.yuv

实现代码

public class MainActivity extends AppCompatActivity {
    
    static{
    
        System.loadLibrary("avcodec-56");
        System.loadLibrary("avdevice-56");
        System.loadLibrary("avfilter-5");
        System.loadLibrary("avformat-56");
        System.loadLibrary("avutil-54");
        System.loadLibrary("postproc-53");
        System.loadLibrary("swresample-1");
        System.loadLibrary("swscale-3");
        System.loadLibrary("native-lib");
    }
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public native void open(String inputStr,String outStr);
 
    public void load(View view){
    
        load();
 
    }
 
    private void load(){
    
        String[] permissions1 = checkPermission(this);
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
    
            if(permissions1.length<=0){
    
                File inputFile = new File(Environment.getExternalStorageDirectory(),"input.mp4");
                String input = inputFile.getAbsolutePath();
                String output  = new File(Environment.getExternalStorageDirectory(),"output.yuv").getAbsolutePath();
                Log.i("twy",input +"******"+ inputFile.exists()+inputFile.length());
                open(input,output);
            }else{
    
                //申请权限
                ActivityCompat.requestPermissions(this, permissions, 100);
            }
        }else{
    //6.0以下不需要申请权限
            File inputFile = new File(Environment.getExternalStorageDirectory(),"input.mp4");
            String input = inputFile.getAbsolutePath();
            String output  = new File(Environment.getExternalStorageDirectory(),"output.yuv").getAbsolutePath();
            Log.i("twy",input +"******"+ inputFile.exists()+inputFile.length());
            open(input,output);
        }
    }
 
    //需要申请的权限
    private static String[] permissions = new String[]{
    
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
    };
 
    //检测权限
    public static String[] checkPermission(Context context){
    
        List<String> data = new ArrayList<>();//存储未申请的权限
        for (String permission : permissions) {
    
            int checkSelfPermission = ContextCompat.checkSelfPermission(context, permission);
            if(checkSelfPermission == PackageManager.PERMISSION_DENIED){
    //未申请
                data.add(permission);
            }
        }
        return data.toArray(new String[data.size()]);
    }
 
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == 100){
    
            boolean flag = true;
            for(int i :  grantResults){
    
                if(i != PackageManager.PERMISSION_GRANTED){
    
                    flag = false;
                    break;
                }
            }
            if(flag){
    
                load();
            }else{
    
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }else{
    
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C"{
    
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
}
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"jason",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"jason",FORMAT,##__VA_ARGS__);
extern "C" JNIEXPORT jstring
JNICALL
Java_com_ican_ffmpegdemo1_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    
    std::string hello = "Hello from C++";
    av_register_all();
    return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_ican_ffmpegdemo1_MainActivity_open(JNIEnv *env, jobject instance, jstring inputStr_,
                                            jstring outStr_) {
    
    const char *inputStr = env->GetStringUTFChars(inputStr_, 0);
    const char *outStr = env->GetStringUTFChars(outStr_, 0);
    //注册各大组件
    av_register_all();
 
    //打开文件
 
    AVFormatContext *pContext = avformat_alloc_context();
    if(avformat_open_input(&pContext,inputStr,NULL,NULL)<0){
    
        LOGE("打开失败");
        return;
    }
 
    //给AVFormatContext填充数据
    if(avformat_find_stream_info(pContext,NULL)<0){
    
        LOGE("获取信息失败");
        return;
    }
    int vedio_stream_idx = -1;
    LOGE("size::%d",pContext->nb_streams);
    //找到视频流
    for(int i = 0;i<pContext->nb_streams;i++){
    
        LOGE("循环 %d",i);
        //streams包含了视频流 音频流 字母流 codex 每一个流 对应的解码上下文 code_type 流的类型
        if(pContext->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
    
            vedio_stream_idx = i;
        }
    }
 
    //获取到解码器上下文
    AVCodecContext *avCodectCtx = pContext->streams[vedio_stream_idx]->codec;
    //解码器
    AVCodec *pCodex = avcodec_find_decoder(avCodectCtx->codec_id);
    if(avcodec_open2(avCodectCtx,pCodex,NULL)<0){
    
        LOGE("解码失败");
        return;
    }
 
    //分配内存
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //初始化结构体
    av_init_packet(packet);
 
    AVFrame * frame = av_frame_alloc();
    //声明一个yuv fram
    AVFrame * yuvframe = av_frame_alloc();
    //给yuvframe 的缓冲区 初始化
    uint8_t  * out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,avCodectCtx->width,avCodectCtx->height));
    int re = avpicture_fill((AVPicture *)yuvframe,out_buffer,AV_PIX_FMT_YUV420P,avCodectCtx->width,avCodectCtx->height);
 
    //avCodectCtx->pix_fmt原文件的封装格式
    SwsContext * swsContext = sws_getContext(avCodectCtx->width,avCodectCtx->height,avCodectCtx->pix_fmt,avCodectCtx->width,avCodectCtx->height,AV_PIX_FMT_YUV420P,SWS_BILINEAR,NULL,NULL,NULL);
 
    int frameCount = 0;
    FILE * fp_yuv = fopen(outStr,"wb");
 
    //packet 入参 出参对象 >=0 不满足条件代表读到尾
    int got_fram;
    while (av_read_frame(pContext,packet)>=0){
    
        LOGE("解码 %d",frameCount++);
        //解封装
        //根据fram进行原生绘制 会把packet放入frame
        avcodec_decode_video2(avCodectCtx,frame,&got_fram,packet);
 
 
        if(got_fram>0){
    
            //fram数据拿到 视频像素数据 yuv 三个rgb r g b 数据量大 三个通道
            // r g b 1824  yuv1970 y 亮度  u v   4:1:1
            sws_scale(swsContext,(const uint8_t *const *)frame->data,frame->linesize,0,frame->height,yuvframe->data,yuvframe->linesize);
            int y_size = avCodectCtx->width*avCodectCtx->height;
            //y亮度信息写完了
            fwrite(yuvframe->data[0],1,y_size,fp_yuv);
            fwrite(yuvframe->data[1],1,y_size/4,fp_yuv);
            fwrite(yuvframe->data[2],1,y_size/4,fp_yuv);
        }
 
        av_free_packet(packet);
 
    }
 
    fclose(fp_yuv);
    av_frame_free(&frame);
    av_frame_free(&yuvframe);
    avcodec_close(avCodectCtx);
    avformat_free_context(pContext);
 
    env->ReleaseStringUTFChars(inputStr_, inputStr);
    env->ReleaseStringUTFChars(outStr_, outStr);
}

在这里插入图片描述
在这里插入图片描述
播放器
G:\ScreenCapture\ffmpeg-20170915-6743351-win64-static\bin

下载地址
http://ffmpeg.zeranoe.com/builds/

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43451928/article/details/106498526

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan