【实验记录】CRFasRNN在VOC数据集上训练流程_不吃饭就会放大招的博客-程序员秘密

技术标签: # CRF  

官方地址:https://github.com/martinkersner/train-CRF-RNN


环境说明

Ubuntu 16.04
CUD 9.0
cuDNN 7.5.1
Caffe 版本:Caffe for crfasrnn 2018/2/23 by bittnt

重要!重要!重要!

在 CRF-RNN 官网的 caffe 版本不推荐使用,一是编译时错误太多太多,二是和 FCN 不兼容根本没法去做别的任务,所以在下载了官方的项目之后,将里面的 caffe 替换为上面环境说明里使用的 caffe,该 caffe 也是融合了 CRF 的。


编译安装

(1)创建 conda 环境

conda create -n crfasrnn

(2)进入 crfasrnn 环境下,下载 CRF-RNN 代码

git clone --recursive https://github.com/torrvision/crfasrnn.git

(3)下载新版的 caffe-crfasrnn,替换原工程中的 caffe

并放在 CRF-RNN 工程目录下:Caffe for crfasrnn 2018/2/23 by bittnt

此时 crfasrnn 工程目录下有两个 caffe,一个是自带的旧版本 caffe,另一个是我们自己下的 caffe-crfasrnn,不用管旧版本 caffe,只对新版本 caffe-crfasrnn 进行操作。

在 CRF-RNN 工程目录下,下载训练代码:

git clone --recursive https://github.com/martinkersner/train-CRF-RNN

进入 caffe-crfasrnn,进行编译,具体参考:【实验记录】CRF as RNN测试

数据准备: PASCAL VOC

这里只训练 3 个类

下载VOC数据集并解压:

wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
tar -xvf VOCtrainval_11-May-2012.tar

VOCdevkit/VOC2012/SegmentationClass 下有 2913 个标签
VOCdevkit/VOC2012/JPEGImages 下是对应的原始图像

创建软链接:

将数据链接到执行训练命令的文件夹下,比较方便,进入 voc-fcn8s 目录下:

# 修改 $DATASETS 是你保存 VOC 数据集的目录
ln -s /data/zyy/usr/local/anaconda/envs/crfasrnn/crfasrnn/caffe-crfrnn/data/VOCdevkit/VOC2012/SegmentationClass labels
ln -s /data/zyy/usr/local/anaconda/envs/crfasrnn/crfasrnn/caffe-crfrnn/data/VOCdevkit/VOC2012/JPEGImages images

划分数据集:

1.创建所有图片的 list 文件,用于后续的分割任务:

find labels/ -printf '%f\n' | sed 's/\.png//'  | tail -n +2 > train.txt

生成的 train.txt 为 2913 张图片的名称列表
labels/ 表示在 label 文件夹下
-printf ‘%f\n’ 表示在 txt 文件中输入 labels 文件夹下的文件名,且在后面添加换行符

2.确定需要训练的类别

在 filter_images.py 中指定(第15行),这里设置了鸟,瓶子和椅子类别
filter_images.py 脚本将会在当前目录下创建几个文本文件,其中包含了我们指定的类的图像,每个文件与 train.txt 文件结构相同

python filter_images.py labels/ train.txt # in a case you DID NOT RUN convert_labels.py script

创建LMDB文件:
CRF-RNN 训练图像大小为 500*500,但是图像的通道数由问题的不同而不同,通道数可以在 data2lmdb.py 文件的第 20 行进行修改
这里规定图像最大不超过 500 像素即可,因为图片不一定都是 500 * 500 的,对于小于 500 像素的图像,用 0 对其进行 pad 到 500 即可。

在 data2lmdb.py 文件的第 20 行进行修改通道数
在 21 行可以设置我们想要训练的类别
在 22 行可以设置验证数据占整个数据集的百分比,因为我们想要在训练过程中定期测试一下当前网络的性能

用下面的命令创建 4 个文件夹,分别是训练和验证数据的图像和标签:

python data2lmdb.py # in a case you DID NOT RUN convert_labels.py script

训练:
训练前先下载 CRF-RNN 的权重:

wget http://goo.gl/j7PrPZ -O TVG_CRFRNN_COCO_VOC.caffemodel
python solve.py 2>&1 | tee train.log

可视化:
训练期间可视化 loss 曲线,使用 loss_from_log.py,该脚本可以接受多个 log 文件

python loss_from_log.py train.log # 多个log文件直接跟在后面

测试报错:

WARNING: Logging before InitGoogleLogging() is written to STDERR
W0630 15:26:22.936763 17241 _caffe.cpp:139] DEPRECATION WARNING - deprecated use of Python interface
W0630 15:26:22.936839 17241 _caffe.cpp:140] Use this instead (with the named "weights" parameter):
W0630 15:26:22.936844 17241 _caffe.cpp:142] Net('TVG_CRFRNN_new_deploy.prototxt', 1, weights='TVG_CRFRNN_COCO_VOC.caffemodel')
[libprotobuf ERROR google/protobuf/text_format.cc:274] Error parsing text-format caffe.NetParameter: 664:25: Message type "caffe.MultiStageMeanfieldParameter" has no field named "spatial_filter_weight".
F0630 15:26:22.938649 17241 upgrade_proto.cpp:90] Check failed: ReadProtoFromTextFile(param_file, param) Failed to parse NetParameter file: TVG_CRFRNN_new_deploy.prototxt
*** Check failure stack trace: ***
Aborted (core dumped)

解决:

将TVG_CRFRNN_new_deploy.prototxt文件中最后一层的
spatial_filter_weight: 3
bilateral_filter_weight: 5
替换为
spatial_filter_weights_str: "3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3"
bilateral_filter_weights_str: "5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5"

测试在 crfasrnn_segmentation 和 crfasrnn_train 下都 OK


训练

!!!!!!报错:

[libprotobuf ERROR google/protobuf/text_format.cc:274] Error parsing text-format caffe.NetParameter: 728:31: Message type "caffe.LayerParameter" has no field named "multi_stage_meanfield_param".
F0630 13:26:31.979391  7718 upgrade_proto.cpp:90] Check failed: ReadProtoFromTextFile(param_file, param) Failed to parse NetParameter file: TVG_CRFRNN_COCO_VOC_TRAIN_3_CLASSES.prototxt

首先参考了以下回答,但是对我都没有用,郁闷了三天 …
https://github.com/torrvision/crfasrnn/issues/100
https://github.com/torrvision/crfasrnn/issues/125

后来突然发觉这可能是 make runtest 时有一个小错误的原因!!!!

之前出现了这个错误,但是不管他也能正常进行测试,所以并没有重视,现在看来刚好就是 MultiStageMeanfieldLayer 的问题!!!!看来这个错非解决不可。
在这里插入图片描述

MultiStageMeanfieldLayerTest/0.TestGradient, where TypeParam = caffe::CPUDevice<float>

换一个解决思路,再次参考:
https://github.com/BVLC/caffe/issues/3109
https://blog.csdn.net/u010454261/article/details/70236988

解决方案①:
在这里插入图片描述
在环境变量里添加下面的代码,指定仅 0 号 GPU 可见

vi ~/.bashrc
export CUDA_VISIBLE_DEVICES=0
source ~/.bashrc

【已尝试】无法解决。

解决方案②:

在 make runtest -j8 后加上 CUDA_VISIBLE_DEVICES=0

make runtest -j8 CUDA_VISIBLE_DEVICES=0

【已尝试】无法解决。

解决方案③:
参考:https://github.com/BVLC/caffe/issues/4836
在这里插入图片描述
修改 caffe 目录下 Makefile.config 文件:

BLAS := open

解决方案④:
参考:https://stackoverflow.com/questions/47073514/caffe-runtest-fails
在这里插入图片描述
在环境变量里添加:

vi ~/.bashrc
export LC_ALL="en_US.UTF-8"
source ~/.bashrc

【已尝试】

解决方案⑤:
参考:https://blog.csdn.net/striker_v/article/details/51983173
安装 Boot 1.55 版本

尝试更换到 Boost 1.55,安装参考:https://www.cnblogs.com/dylancao/p/9054821.html
下载 Boost 1.55:https://sourceforge.net/projects/boost/files/boost/1.55.0/
将 Boost 放在自己选定的目录下,并执行:

cd boost_1_55_0
./bootstrap.sh --with-toolset=clang 

再执行:

./b2 install --build-type=complete --layout=versioned threading=multi --prefix="/usr/lib/boost-1.55"

设置环境变量:

export CPLUS_INCLUDE_PATH=/usr/boost/include:$CPLUS_INCLUDE_PATH
export LIBRARY_PATH=/usr/boost/lib:$LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/boost/lib:$LD_LIBRARY_PATH

https://groups.google.com/d/topic/caffe-users/BD-lanZ9C50
https://groups.google.com/d/topic/caffe-users/DpltFQeIkx0

解决方案n:

更换 cuda 和 cudnn 版本,从 cuda 9.0 换到 cuda 8.0,从 cudnn 7.5.1 换到 cudnn 5.0

先安装 cuda 8.0 和 cudnn 5.0

【已尝试】无法解决

解决方案n:
https://segmentfault.com/a/1190000015191756
在这里插入图片描述

export MKL_CBWR=AUTO

要么 make runtest 时不要加 -j8 了,要么就 export MKL_CBWR=AUTO

【已尝试】

export LC_ALL=C

参考:
https://github.com/BVLC/caffe/issues/4083

报错:

WARNING: Logging before InitGoogleLogging() is written to STDERR
W0630 13:55:10.869971  8492 _caffe.cpp:139] DEPRECATION WARNING - deprecated use of Python interface
W0630 13:55:10.870028  8492 _caffe.cpp:140] Use this instead (with the named "weights" parameter):
W0630 13:55:10.870033  8492 _caffe.cpp:142] Net('TVG_CRFRNN_new_deploy.prototxt', 1, weights='TVG_CRFRNN_COCO_VOC.caffemodel')
[libprotobuf ERROR google/protobuf/text_format.cc:274] Error parsing text-format caffe.NetParameter: 657:31: Message type "caffe.LayerParameter" has no field named "multi_stage_meanfield_param".
F0630 13:55:10.871857  8492 upgrade_proto.cpp:90] Check failed: ReadProtoFromTextFile(param_file, param) Failed to parse NetParameter file: TVG_CRFRNN_new_deploy.prototxt
*** Check failure stack trace: ***
Aborted (core dumped)

https://blog.csdn.net/budf01/article/details/53205701
https://gist.github.com/mindcont/989dcba7feba14fb07781f8e655c339b
https://www.cnblogs.com/liumeng-blog/p/7978861.html
https://www.jianshu.com/p/8795b882ea67

参考:
https://github.com/Kitware/SMQTK/issues/306
https://github.com/shicai/DenseNet-Caffe/issues/10

caffe版本:
https://github.com/BVLC/caffe/releases/tag/rc5

caffe版本综合:
https://github.com/BVLC/caffe/releases

在 Pooling 中支持 ceil_mode 的 caffe 版本问题:
https://github.com/shicai/DenseNet-Caffe/issues/1

新版本的 caffe 没有 vision_layer.hpp

用官网自带caffe版本

cmake … 报错:
在这里插入图片描述

-- Boost version: 1.67.0
-- Found the following Boost libraries:
--   python
-- Found Doxygen: /usr/bin/doxygen (found version "1.8.11") 
CMake Error at CMakeLists.txt:50 (caffe_set_caffe_link):
  Unknown CMake command "caffe_set_caffe_link".

参考:https://github.com/torrvision/crfasrnn/issues/47

解决:将官方 Caffe /caffe-master/cmake 下的 Targets.cmake 替换对应文件
文件内容:

################################################################################################
# Defines global Caffe_LINK flag, This flag is required to prevent linker from excluding
# some objects which are not addressed directly but are registered via static constructors
macro(caffe_set_caffe_link)
  if(BUILD_SHARED_LIBS)
    set(Caffe_LINK caffe)
  else()
    if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
      set(Caffe_LINK -Wl,-force_load caffe)
    elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
      set(Caffe_LINK -Wl,--whole-archive caffe -Wl,--no-whole-archive)
    endif()
  endif()
endmacro()
################################################################################################
# Convenient command to setup source group for IDEs that support this feature (VS, XCode)
# Usage:
#   caffe_source_group(<group> GLOB[_RECURSE] <globbing_expression>)
function(caffe_source_group group)
  cmake_parse_arguments(CAFFE_SOURCE_GROUP "" "" "GLOB;GLOB_RECURSE" ${
    ARGN})
  if(CAFFE_SOURCE_GROUP_GLOB)
    file(GLOB srcs1 ${
    CAFFE_SOURCE_GROUP_GLOB})
    source_group(${
    group} FILES ${
    srcs1})
  endif()

  if(CAFFE_SOURCE_GROUP_GLOB_RECURSE)
    file(GLOB_RECURSE srcs2 ${
    CAFFE_SOURCE_GROUP_GLOB_RECURSE})
    source_group(${
    group} FILES ${
    srcs2})
  endif()
endfunction()

################################################################################################
# Collecting sources from globbing and appending to output list variable
# Usage:
#   caffe_collect_sources(<output_variable> GLOB[_RECURSE] <globbing_expression>)
function(caffe_collect_sources variable)
  cmake_parse_arguments(CAFFE_COLLECT_SOURCES "" "" "GLOB;GLOB_RECURSE" ${
    ARGN})
  if(CAFFE_COLLECT_SOURCES_GLOB)
    file(GLOB srcs1 ${
    CAFFE_COLLECT_SOURCES_GLOB})
    set(${
    variable} ${
    variable} ${
    srcs1})
  endif()

  if(CAFFE_COLLECT_SOURCES_GLOB_RECURSE)
    file(GLOB_RECURSE srcs2 ${
    CAFFE_COLLECT_SOURCES_GLOB_RECURSE})
    set(${
    variable} ${
    variable} ${
    srcs2})
  endif()
endfunction()

################################################################################################
# Short command getting caffe sources (assuming standard Caffe code tree)
# Usage:
#   caffe_pickup_caffe_sources(<root>)
function(caffe_pickup_caffe_sources root)
  # put all files in source groups (visible as subfolder in many IDEs)
  caffe_source_group("Include"        GLOB "${root}/include/caffe/*.h*")
  caffe_source_group("Include\\Util"  GLOB "${root}/include/caffe/util/*.h*")
  caffe_source_group("Include"        GLOB "${PROJECT_BINARY_DIR}/caffe_config.h*")
  caffe_source_group("Source"         GLOB "${root}/src/caffe/*.cpp")
  caffe_source_group("Source\\Util"   GLOB "${root}/src/caffe/util/*.cpp")
  caffe_source_group("Source\\Layers" GLOB "${root}/src/caffe/layers/*.cpp")
  caffe_source_group("Source\\Cuda"   GLOB "${root}/src/caffe/layers/*.cu")
  caffe_source_group("Source\\Cuda"   GLOB "${root}/src/caffe/util/*.cu")
  caffe_source_group("Source\\Proto"  GLOB "${root}/src/caffe/proto/*.proto")

  # source groups for test target
  caffe_source_group("Include"      GLOB "${root}/include/caffe/test/test_*.h*")
  caffe_source_group("Source"       GLOB "${root}/src/caffe/test/test_*.cpp")
  caffe_source_group("Source\\Cuda" GLOB "${root}/src/caffe/test/test_*.cu")

  # collect files
  file(GLOB test_hdrs    ${
    root}/include/caffe/test/test_*.h*)
  file(GLOB test_srcs    ${
    root}/src/caffe/test/test_*.cpp)
  file(GLOB_RECURSE hdrs ${
    root}/include/caffe/*.h*)
  file(GLOB_RECURSE srcs ${
    root}/src/caffe/*.cpp)
  list(REMOVE_ITEM  hdrs ${
    test_hdrs})
  list(REMOVE_ITEM  srcs ${
    test_srcs})

  # adding headers to make the visible in some IDEs (Qt, VS, Xcode)
  list(APPEND srcs ${
    hdrs} ${
    PROJECT_BINARY_DIR}/caffe_config.h)
  list(APPEND test_srcs ${
    test_hdrs})

  # collect cuda files
  file(GLOB    test_cuda ${
    root}/src/caffe/test/test_*.cu)
  file(GLOB_RECURSE cuda ${
    root}/src/caffe/*.cu)
  list(REMOVE_ITEM  cuda ${
    test_cuda})

  # add proto to make them editable in IDEs too
  file(GLOB_RECURSE proto_files ${
    root}/src/caffe/*.proto)
  list(APPEND srcs ${
    proto_files})

  # convert to absolute paths
  caffe_convert_absolute_paths(srcs)
  caffe_convert_absolute_paths(cuda)
  caffe_convert_absolute_paths(test_srcs)
  caffe_convert_absolute_paths(test_cuda)

  # propagate to parent scope
  set(srcs ${
    srcs} PARENT_SCOPE)
  set(cuda ${
    cuda} PARENT_SCOPE)
  set(test_srcs ${
    test_srcs} PARENT_SCOPE)
  set(test_cuda ${
    test_cuda} PARENT_SCOPE)
endfunction()

################################################################################################
# Short command for setting default target properties
# Usage:
#   caffe_default_properties(<target>)
function(caffe_default_properties target)
  set_target_properties(${
    target} PROPERTIES
    DEBUG_POSTFIX ${
    Caffe_DEBUG_POSTFIX}
    ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib"
    LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib"
    RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
  # make sure we build all external dependencies first
  if (DEFINED external_project_dependencies)
    add_dependencies(${
    target} ${
    external_project_dependencies})
  endif()
endfunction()

################################################################################################
# Short command for setting runtime directory for build target
# Usage:
#   caffe_set_runtime_directory(<target> <dir>)
function(caffe_set_runtime_directory target dir)
  set_target_properties(${
    target} PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${dir}")
endfunction()

################################################################################################
# Short command for setting solution folder property for target
# Usage:
#   caffe_set_solution_folder(<target> <folder>)
function(caffe_set_solution_folder target folder)
  if(USE_PROJECT_FOLDERS)
    set_target_properties(${
    target} PROPERTIES FOLDER "${folder}")
  endif()
endfunction()

################################################################################################
# Reads lines from input file, prepends source directory to each line and writes to output file
# Usage:
#   caffe_configure_testdatafile(<testdatafile>)
function(caffe_configure_testdatafile file)
  file(STRINGS ${
    file} __lines)
  set(result "")
  foreach(line ${
    __lines})
    set(result "${result}${PROJECT_SOURCE_DIR}/${line}\n")
  endforeach()
  file(WRITE ${
    file}.gen.cmake ${
    result})
endfunction()

################################################################################################
# Filter out all files that are not included in selected list
# Usage:
#   caffe_leave_only_selected_tests(<filelist_variable> <selected_list>)
function(caffe_leave_only_selected_tests file_list)
  if(NOT ARGN)
    return() # blank list means leave all
  endif()
  string(REPLACE "," ";" __selected ${
    ARGN})
  list(APPEND __selected caffe_main)

  set(result "")
  foreach(f ${
    ${
    file_list}})
    get_filename_component(name ${
    f} NAME_WE)
    string(REGEX REPLACE "^test_" "" name ${
    name})
    list(FIND __selected ${
    name} __index)
    if(NOT __index EQUAL -1)
      list(APPEND result ${
    f})
    endif()
  endforeach()
  set(${
    file_list} ${
    result} PARENT_SCOPE)
endfunction()

但是这样会报一堆关于 cudnn 的错…

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

智能推荐

Qt5使用自带的类实现自动识别串口代码。_pe.setcolor() 使用#ffffff_那白色蒲公英的博客-程序员秘密

.h#ifndef MAINWINDOW_H#define MAINWINDOW_H#include &lt;QMainWindow&gt;#include &lt;QSerialPortInfo&gt;#include &lt;QTimer&gt;namespace Ui {class MainWindow;}class MainWindow : public QMain...

mips计算机公司,MIPS (计算器)_yangyang0508的博客-程序员秘密

MIPS (计算器)Million Instructions per second(IPS)是一种计算计算机中央处理器速度的记量单位。大多数IPS的数值是在某些特定测量软件中取极限值所得,而较为可信的IPS值取决于测试软件的测试情境以及测试时间。内存阶层的效能也大大影响处理器的效能,也影响MIPS数值的真确性。由于以上问题,研发者发展了数套标准测试方案,例如SPECint以计算真实情况下软件使用情...

iOS中的混合编程_chidu8866的博客-程序员秘密

WebView混编介绍Hybrid编程:基于UIWebView的混合编程,它是指同时使用原生的控件和UIWebView加载HTML来展现应用界面, 既可以保证应用既有界面的流畅交互效果,又有Web界面良好的动态修改和多平台复用的优势(每个平台都有一个WebView)例子:微信公众号的...

架构简洁之道读书笔记--第三部分设计原则_架构简洁之道 设计原则_流水石板路的博客-程序员秘密

单一职责原则。该设计原则是某于康威圧律(Conway'sLaw)的一个推论——一个软件系统的最佳结构高度依赖于开发这个系统的组织的内部结构。这样,每个软件模块都有且只有一个需要被改变的理由。开闭原则。该设计原则是由BertrandMeyer在20世纪80年代大力推广的,其核心要素是如果软件系统想要更容易被改变,那么其设计就必须允许新增代码来修改系统行为,而非只能靠修改原来的代码。里氏替换原则。接口隔离原则。这项设计原则主要告诫软件设计师应该在设计中避免不必要的依赖。...

iPhone/iPad安装包的三种格式 deb、ipa 和 pxl的解释和说明_苹果的是安装包_沸腾的泪水05314的博客-程序员秘密

转载自:http://www.cnblogs.com/easonoutlook/archive/2012/09/03/2668888.html目前 iOS 平台上常见的安装包有三种,deb、ipa 和 pxl。转自链接:http://fanlb.blogbus.com/logs/80466716.html  多谢作者分享!其中 deb 格式是 Debian 系统(包含 Debi

女友回老家了!五一没啥事,手把手带你搭建一台服务器!_菜鸟学Python的博客-程序员秘密

这年头谁还没玩过服务器,都不好意思说自己是程序员。拥有一台服务器可以做很多有意思的事情。例如部署小程序,公众号的后台、搭建个人博客、搭建个人网盘、部署个爬虫,看谁不爽云攻击一下等等。最主要...

随便推点

android项目中自定义顶部标题栏,Android项目中自定义顶部标题栏_一笑奈何666的博客-程序员秘密

Android项目中自定义顶部标题栏下面给大家详细介绍android中自定义顶部标题栏的思路及实现方式先来图: 思路及实现步骤1.定义标题栏布局2.自定义TitleActivity控制标题栏按钮监听3.在TitleActivity中实现标题栏以下内容切换首先定义标题栏定义控制标题栏按钮和标题栏以下内容的布局注:此处使用 标签引入标题栏,且下方有定义一个空的FrameLayout的布...

MyBatis 学习笔记(八)---源码分析篇--SQL 执行过程详细分析_码农飞哥的博客-程序员秘密

在面试中我们经常会被到MyBatis中 #{} 占位符与`${}`占位符的区别。大多数的小伙伴都可以脱口而出#{} 会对值进行转义,防止SQL注入。而${}则会原样输出传入值,不会对传入值做任何处理。本文将通过源码层面分析为啥#{} 可以防止SQL注入。

nginx如何使用root和alias以及区别(nginx作为静态资源服务器,比如:作为web项目前端资源服务器)_nginx root alias 静态资源服务器_超爷_02的博客-程序员秘密

nginx的root和alias都是用来指定映射服务器静态资源文件的命令,例如访问的html文件或者图片文件等都可以通过这样命令配置访问。但使用方法虽然相似,但容易混淆,特别是root命令的使用方式,经常会被大家误解,下面就来介绍下如何使用和区分工具/原料 linux nginx 方法/步骤 1 连接linux服务器,然后在已有安装的nginx目录下面,创建一个s...

【学习笔记】【Datawhale12月】大话设计模式 - 设计准则_weixin_45805934的博客-程序员秘密

1994年,《设计模式:可复用面向对象软件的基础》一书正式将设计模式的概念引入到软件开发领域。设计模式是软件开发中一些常见问题的典型解决方案。设计模式的几点认知设计模式更类似于抽象的蓝图,而非具体的实现方法设计模式可应用范围广,小模块和整套软件系统均可以应用实际应用中,往往同时应用多个设计模式,部分模式可能只使用了其中一部分设计模式的适用性和功能、设计、背景等有关,彼此没有好坏面向设计对象的基本原则针对接口编程,而不是针对实现编程优先使用对象组合,而不是类继承。

在直播APP系统源码中基于腾讯视频云SDK制作简易版直播回放播放器_云豹科技官方的博客-程序员秘密

在直播App系统中,当主播结束之后,我们需要使用播放器观看直播的一些回放片段,在观看回放的过程中,我们会使用到开始,暂停,控制播放位置,进度监听,全屏等功能,然而腾讯点播并没有提供这些简单的控制功能,本文介绍下在直播APP系统源码如何基于腾讯点播制作一个简易版的播放器。腾讯视频云SDK说明:腾讯点播协议支持HLS,MP4,FLV格式。并且腾讯点播不会对播放地址的来源做限制,即可以用它来播放腾讯云...

推荐文章

热门文章

相关标签