From ecf3067b1e71655dbe2f78b7bc2fbdcb8018b88e Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Sun, 15 May 2016 01:51:46 +0800 Subject: [PATCH 01/11] Make HDF5 optional --- CMakeLists.txt | 14 ++ Makefile | 13 +- Makefile.config.example | 1 + cmake/ConfigGen.cmake | 4 + cmake/Dependencies.cmake | 9 +- cmake/Modules/FindProtobuf.cmake | 231 ++++++++++++++++++++++ cmake/Summary.cmake | 1 + cmake/Templates/caffe_config.h.in | 1 + docs/installation.md | 4 +- include/caffe/util/hdf5.hpp | 2 + include/caffe/util/math_functions.hpp | 1 + src/caffe/layers/hdf5_data_layer.cpp | 2 + src/caffe/layers/hdf5_data_layer.cu | 2 + src/caffe/layers/hdf5_output_layer.cpp | 2 + src/caffe/layers/hdf5_output_layer.cu | 2 + src/caffe/net.cpp | 11 ++ src/caffe/solvers/sgd_solver.cpp | 10 + src/caffe/test/test_hdf5_output_layer.cpp | 2 + src/caffe/test/test_hdf5data_layer.cpp | 2 + src/caffe/util/hdf5.cpp | 2 + 20 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 cmake/Modules/FindProtobuf.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index da7142c9b3c..b3647e3c4f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,19 @@ set(CAFFE_TARGET_VERSION "1.0.0-rc3" CACHE STRING "Caffe logical version") set(CAFFE_TARGET_SOVERSION "1.0.0-rc3" CACHE STRING "Caffe soname version") add_definitions(-DCAFFE_VERSION=${CAFFE_TARGET_VERSION}) +# Search packages for host system instead of packages for target system +# in case of cross compilation these macro should be defined by toolchain file +if(NOT COMMAND find_host_package) + macro(find_host_package) + find_package(${ARGN}) + endmacro() +endif() +if(NOT COMMAND find_host_program) + macro(find_host_program) + find_program(${ARGN}) + endmacro() +endif() + # ---[ Using cmake scripts and modules list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) @@ -38,6 +51,7 @@ caffe_option(USE_OPENCV "Build with OpenCV support" ON) caffe_option(USE_LEVELDB "Build with levelDB" ON) caffe_option(USE_LMDB "Build with lmdb" ON) caffe_option(ALLOW_LMDB_NOLOCK "Allow MDB_NOLOCK when reading LMDB files (only if necessary)" OFF) +caffe_option(USE_HDF5 "Build with hdf5" ON) # ---[ Dependencies include(cmake/Dependencies.cmake) diff --git a/Makefile b/Makefile index 403e00a38a1..d86f556a9f1 100644 --- a/Makefile +++ b/Makefile @@ -178,11 +178,12 @@ ifneq ($(CPU_ONLY), 1) LIBRARIES := cudart cublas curand endif -LIBRARIES += glog gflags protobuf boost_system boost_filesystem m hdf5_hl hdf5 +LIBRARIES += glog gflags protobuf boost_system boost_filesystem m # handle IO dependencies USE_LEVELDB ?= 1 USE_LMDB ?= 1 +USE_HDF5 ?= 1 USE_OPENCV ?= 1 ifeq ($(USE_LEVELDB), 1) @@ -191,13 +192,16 @@ endif ifeq ($(USE_LMDB), 1) LIBRARIES += lmdb endif +ifeq ($(USE_HDF5), 1) + LIBRARIES += hdf5_hl hdf5 +endif ifeq ($(USE_OPENCV), 1) - LIBRARIES += opencv_core opencv_highgui opencv_imgproc + LIBRARIES += opencv_core opencv_highgui opencv_imgproc ifeq ($(OPENCV_VERSION), 3) LIBRARIES += opencv_imgcodecs endif - + endif PYTHON_LIBRARIES ?= boost_python python2.7 WARNINGS := -Wall -Wno-sign-compare @@ -341,6 +345,9 @@ ifeq ($(ALLOW_LMDB_NOLOCK), 1) COMMON_FLAGS += -DALLOW_LMDB_NOLOCK endif endif +ifeq ($(USE_HDF5), 1) + COMMON_FLAGS += -DUSE_HDF5 +endif # CPU-only configuration ifeq ($(CPU_ONLY), 1) diff --git a/Makefile.config.example b/Makefile.config.example index 07bed63ae40..2ec9800c410 100644 --- a/Makefile.config.example +++ b/Makefile.config.example @@ -11,6 +11,7 @@ # USE_OPENCV := 0 # USE_LEVELDB := 0 # USE_LMDB := 0 +# USE_HDF5 := 0 # uncomment to allow MDB_NOLOCK when reading LMDB files (only if necessary) # You should not set this flag if you will be reading LMDBs with any diff --git a/cmake/ConfigGen.cmake b/cmake/ConfigGen.cmake index 056371110b5..fa85037922f 100644 --- a/cmake/ConfigGen.cmake +++ b/cmake/ConfigGen.cmake @@ -71,6 +71,10 @@ function(caffe_generate_export_configs) list(APPEND Caffe_DEFINITIONS -DUSE_LEVELDB) endif() + if(USE_HDF5) + list(APPEND Caffe_DEFINITIONS -DUSE_HDF5) + endif() + if(NOT HAVE_CUDNN) set(HAVE_CUDNN FALSE) else() diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index c7b6a17aa69..d07c6074c49 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -24,9 +24,12 @@ list(APPEND Caffe_LINKER_LIBS ${GFLAGS_LIBRARIES}) include(cmake/ProtoBuf.cmake) # ---[ HDF5 -find_package(HDF5 COMPONENTS HL REQUIRED) -include_directories(SYSTEM ${HDF5_INCLUDE_DIRS} ${HDF5_HL_INCLUDE_DIR}) -list(APPEND Caffe_LINKER_LIBS ${HDF5_LIBRARIES}) +if(USE_HDF5) + find_package(HDF5 COMPONENTS HL REQUIRED) + include_directories(SYSTEM ${HDF5_INCLUDE_DIRS} ${HDF5_HL_INCLUDE_DIR}) + list(APPEND Caffe_LINKER_LIBS ${HDF5_LIBRARIES}) + add_definitions(-DUSE_HDF5) +endif() # ---[ LMDB if(USE_LMDB) diff --git a/cmake/Modules/FindProtobuf.cmake b/cmake/Modules/FindProtobuf.cmake new file mode 100644 index 00000000000..e8fe5299d17 --- /dev/null +++ b/cmake/Modules/FindProtobuf.cmake @@ -0,0 +1,231 @@ +# Locate and configure the Google Protocol Buffers library. +# +# The following variables can be set and are optional: +# +# PROTOBUF_SRC_ROOT_FOLDER - When compiling with MSVC, if this cache variable is set +# the protobuf-default VS project build locations +# (vsprojects/Debug & vsprojects/Release) will be searched +# for libraries and binaries. +# +# PROTOBUF_IMPORT_DIRS - List of additional directories to be searched for +# imported .proto files. (New in CMake 2.8.8) +# +# Defines the following variables: +# +# PROTOBUF_FOUND - Found the Google Protocol Buffers library (libprotobuf & header files) +# PROTOBUF_INCLUDE_DIRS - Include directories for Google Protocol Buffers +# PROTOBUF_LIBRARIES - The protobuf libraries +# [New in CMake 2.8.5] +# PROTOBUF_PROTOC_LIBRARIES - The protoc libraries +# PROTOBUF_LITE_LIBRARIES - The protobuf-lite libraries +# +# The following cache variables are also available to set or use: +# PROTOBUF_LIBRARY - The protobuf library +# PROTOBUF_PROTOC_LIBRARY - The protoc library +# PROTOBUF_INCLUDE_DIR - The include directory for protocol buffers +# PROTOBUF_PROTOC_EXECUTABLE - The protoc compiler +# [New in CMake 2.8.5] +# PROTOBUF_LIBRARY_DEBUG - The protobuf library (debug) +# PROTOBUF_PROTOC_LIBRARY_DEBUG - The protoc library (debug) +# PROTOBUF_LITE_LIBRARY - The protobuf lite library +# PROTOBUF_LITE_LIBRARY_DEBUG - The protobuf lite library (debug) +# +# ==================================================================== +# Example: +# +# find_package(Protobuf REQUIRED) +# include_directories(${PROTOBUF_INCLUDE_DIRS}) +# +# include_directories(${CMAKE_CURRENT_BINARY_DIR}) +# PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS foo.proto) +# add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS}) +# target_link_libraries(bar ${PROTOBUF_LIBRARIES}) +# +# NOTE: You may need to link against pthreads, depending +# on the platform. +# +# NOTE: The PROTOBUF_GENERATE_CPP macro & add_executable() or add_library() +# calls only work properly within the same directory. +# +# ==================================================================== +# +# PROTOBUF_GENERATE_CPP (public function) +# SRCS = Variable to define with autogenerated +# source files +# HDRS = Variable to define with autogenerated +# header files +# ARGN = proto files +# +# ==================================================================== + + +#============================================================================= +# Copyright 2009 Kitware, Inc. +# Copyright 2009-2011 Philip Lowman +# Copyright 2008 Esben Mose Hansen, Ange Optimization ApS +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +function(PROTOBUF_GENERATE_CPP SRCS HDRS) + if(NOT ARGN) + message(SEND_ERROR "Error: PROTOBUF_GENERATE_CPP() called without any proto files") + return() + endif() + + if(PROTOBUF_GENERATE_CPP_APPEND_PATH) + # Create an include path for each file specified + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(ABS_PATH ${ABS_FIL} PATH) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + else() + set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + if(DEFINED PROTOBUF_IMPORT_DIRS) + foreach(DIR ${PROTOBUF_IMPORT_DIRS}) + get_filename_component(ABS_PATH ${DIR} ABSOLUTE) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + endif() + + set(${SRCS}) + set(${HDRS}) + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + + list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc") + list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h" + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL} + DEPENDS ${ABS_FIL} + COMMENT "Running C++ protocol buffer compiler on ${FIL}" + VERBATIM ) + endforeach() + + set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) + set(${SRCS} ${${SRCS}} PARENT_SCOPE) + set(${HDRS} ${${HDRS}} PARENT_SCOPE) +endfunction() + +# Internal function: search for normal library as well as a debug one +# if the debug one is specified also include debug/optimized keywords +# in *_LIBRARIES variable +function(_protobuf_find_libraries name filename) + find_library(${name}_LIBRARY + NAMES ${filename} + PATHS ${PROTOBUF_SRC_ROOT_FOLDER}/vsprojects/Release) + mark_as_advanced(${name}_LIBRARY) + + find_library(${name}_LIBRARY_DEBUG + NAMES ${filename} + PATHS ${PROTOBUF_SRC_ROOT_FOLDER}/vsprojects/Debug) + mark_as_advanced(${name}_LIBRARY_DEBUG) + + if(NOT ${name}_LIBRARY_DEBUG) + # There is no debug library + set(${name}_LIBRARY_DEBUG ${${name}_LIBRARY} PARENT_SCOPE) + set(${name}_LIBRARIES ${${name}_LIBRARY} PARENT_SCOPE) + else() + # There IS a debug library + set(${name}_LIBRARIES + optimized ${${name}_LIBRARY} + debug ${${name}_LIBRARY_DEBUG} + PARENT_SCOPE + ) + endif() +endfunction() + +# Internal function: find threads library +function(_protobuf_find_threads) + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + find_package(Threads) + if(Threads_FOUND) + list(APPEND PROTOBUF_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + set(PROTOBUF_LIBRARIES "${PROTOBUF_LIBRARIES}" PARENT_SCOPE) + endif() +endfunction() + +# +# Main. +# + +# By default have PROTOBUF_GENERATE_CPP macro pass -I to protoc +# for each directory where a proto file is referenced. +if(NOT DEFINED PROTOBUF_GENERATE_CPP_APPEND_PATH) + set(PROTOBUF_GENERATE_CPP_APPEND_PATH TRUE) +endif() + + +# Google's provided vcproj files generate libraries with a "lib" +# prefix on Windows +if(MSVC) + set(PROTOBUF_ORIG_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") + set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "") + + find_path(PROTOBUF_SRC_ROOT_FOLDER protobuf.pc.in) +endif() + +# The Protobuf library +_protobuf_find_libraries(PROTOBUF protobuf) +#DOC "The Google Protocol Buffers RELEASE Library" + +_protobuf_find_libraries(PROTOBUF_LITE protobuf-lite) + +# The Protobuf Protoc Library +_protobuf_find_libraries(PROTOBUF_PROTOC protoc) + +# Restore original find library prefixes +if(MSVC) + set(CMAKE_FIND_LIBRARY_PREFIXES "${PROTOBUF_ORIG_FIND_LIBRARY_PREFIXES}") +endif() + +if(UNIX) + _protobuf_find_threads() +endif() + +# Find the include directory +find_path(PROTOBUF_INCLUDE_DIR + google/protobuf/service.h + PATHS ${PROTOBUF_SRC_ROOT_FOLDER}/src +) +mark_as_advanced(PROTOBUF_INCLUDE_DIR) + +# Find the protoc Executable +find_host_program(PROTOBUF_PROTOC_EXECUTABLE + NAMES protoc + DOC "The Google Protocol Buffers Compiler" + PATHS + ${PROTOBUF_SRC_ROOT_FOLDER}/vsprojects/Release + ${PROTOBUF_SRC_ROOT_FOLDER}/vsprojects/Debug +) +mark_as_advanced(PROTOBUF_PROTOC_EXECUTABLE) + + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PROTOBUF DEFAULT_MSG + PROTOBUF_LIBRARY PROTOBUF_INCLUDE_DIR) + +if(PROTOBUF_FOUND) + set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIR}) +endif() diff --git a/cmake/Summary.cmake b/cmake/Summary.cmake index ba025cf81e0..b6c069c5db3 100644 --- a/cmake/Summary.cmake +++ b/cmake/Summary.cmake @@ -118,6 +118,7 @@ function(caffe_print_configuration_summary) caffe_status(" USE_LEVELDB : ${USE_LEVELDB}") caffe_status(" USE_LMDB : ${USE_LMDB}") caffe_status(" ALLOW_LMDB_NOLOCK : ${ALLOW_LMDB_NOLOCK}") + caffe_status(" USE_HDF5 : ${USE_HDF5}") caffe_status("") caffe_status("Dependencies:") caffe_status(" BLAS : " APPLE THEN "Yes (vecLib)" ELSE "Yes (${BLAS})") diff --git a/cmake/Templates/caffe_config.h.in b/cmake/Templates/caffe_config.h.in index 8a31b43cabf..f6f907ff136 100644 --- a/cmake/Templates/caffe_config.h.in +++ b/cmake/Templates/caffe_config.h.in @@ -36,3 +36,4 @@ #cmakedefine USE_LEVELDB #cmakedefine USE_LMDB #cmakedefine ALLOW_LMDB_NOLOCK +#cmakedefine USE_HDF5 diff --git a/docs/installation.md b/docs/installation.md index 4aac7c42d27..0a23cd5ddf5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -34,12 +34,12 @@ Caffe has several dependencies: * 5.5, and 5.0 are compatible but considered legacy * [BLAS](http://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms) via ATLAS, MKL, or OpenBLAS. * [Boost](http://www.boost.org/) >= 1.55 -* `protobuf`, `glog`, `gflags`, `hdf5` +* `protobuf`, `glog`, `gflags` Optional dependencies: * [OpenCV](http://opencv.org/) >= 2.4 including 3.0 -* IO libraries: `lmdb`, `leveldb` (note: leveldb requires `snappy`) +* IO libraries: `lmdb`, `leveldb` (note: leveldb requires `snappy`), `hdf5` * cuDNN for GPU acceleration (v5) Pycaffe and Matcaffe interfaces have their own natural needs. diff --git a/include/caffe/util/hdf5.hpp b/include/caffe/util/hdf5.hpp index ce568c5eb0d..3ed29d31e1c 100644 --- a/include/caffe/util/hdf5.hpp +++ b/include/caffe/util/hdf5.hpp @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 #ifndef CAFFE_UTIL_HDF5_H_ #define CAFFE_UTIL_HDF5_H_ @@ -37,3 +38,4 @@ string hdf5_get_name_by_idx(hid_t loc_id, int idx); } // namespace caffe #endif // CAFFE_UTIL_HDF5_H_ +#endif // USE_HDF5 diff --git a/include/caffe/util/math_functions.hpp b/include/caffe/util/math_functions.hpp index 6f6d3feeae2..52811a9f65d 100644 --- a/include/caffe/util/math_functions.hpp +++ b/include/caffe/util/math_functions.hpp @@ -3,6 +3,7 @@ #include #include // for std::fabs and std::signbit +#include // for memset #include "glog/logging.h" diff --git a/src/caffe/layers/hdf5_data_layer.cpp b/src/caffe/layers/hdf5_data_layer.cpp index 2f13dc641df..2f54b159a94 100644 --- a/src/caffe/layers/hdf5_data_layer.cpp +++ b/src/caffe/layers/hdf5_data_layer.cpp @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 /* TODO: - load file in a separate thread ("prefetch") @@ -164,3 +165,4 @@ INSTANTIATE_CLASS(HDF5DataLayer); REGISTER_LAYER_CLASS(HDF5Data); } // namespace caffe +#endif // USE_HDF5 diff --git a/src/caffe/layers/hdf5_data_layer.cu b/src/caffe/layers/hdf5_data_layer.cu index 595d2230220..a8b467c10e0 100644 --- a/src/caffe/layers/hdf5_data_layer.cu +++ b/src/caffe/layers/hdf5_data_layer.cu @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 /* TODO: - only load parts of the file, in accordance with a prototxt param "max_mem" @@ -48,3 +49,4 @@ void HDF5DataLayer::Forward_gpu(const vector*>& bottom, INSTANTIATE_LAYER_GPU_FUNCS(HDF5DataLayer); } // namespace caffe +#endif // USE_HDF5 diff --git a/src/caffe/layers/hdf5_output_layer.cpp b/src/caffe/layers/hdf5_output_layer.cpp index f8f1edcd18e..28c453a20fd 100644 --- a/src/caffe/layers/hdf5_output_layer.cpp +++ b/src/caffe/layers/hdf5_output_layer.cpp @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 #include #include "hdf5.h" @@ -72,3 +73,4 @@ INSTANTIATE_CLASS(HDF5OutputLayer); REGISTER_LAYER_CLASS(HDF5Output); } // namespace caffe +#endif // USE_HDF5 diff --git a/src/caffe/layers/hdf5_output_layer.cu b/src/caffe/layers/hdf5_output_layer.cu index c1685cd34a7..891aea03862 100644 --- a/src/caffe/layers/hdf5_output_layer.cu +++ b/src/caffe/layers/hdf5_output_layer.cu @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 #include #include "hdf5.h" @@ -37,3 +38,4 @@ void HDF5OutputLayer::Backward_gpu(const vector*>& top, INSTANTIATE_LAYER_GPU_FUNCS(HDF5OutputLayer); } // namespace caffe +#endif // USE_HDF5 diff --git a/src/caffe/net.cpp b/src/caffe/net.cpp index f0bf594936c..ef3b7314325 100644 --- a/src/caffe/net.cpp +++ b/src/caffe/net.cpp @@ -5,7 +5,9 @@ #include #include +#ifdef USE_HDF5 #include "hdf5.h" +#endif // USE_HDF5 #include "caffe/common.hpp" #include "caffe/layer.hpp" @@ -795,6 +797,7 @@ void Net::CopyTrainedLayersFromBinaryProto( template void Net::CopyTrainedLayersFromHDF5(const string trained_filename) { +#ifdef USE_HDF5 hid_t file_hid = H5Fopen(trained_filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); CHECK_GE(file_hid, 0) << "Couldn't open " << trained_filename; @@ -841,6 +844,10 @@ void Net::CopyTrainedLayersFromHDF5(const string trained_filename) { } H5Gclose(data_hid); H5Fclose(file_hid); +#else + LOG(FATAL) << "CopyTrainedLayersFromHDF5 requires hdf5;" + << " compile with USE_HDF5."; +#endif // USE_HDF5 } template @@ -857,6 +864,7 @@ void Net::ToProto(NetParameter* param, bool write_diff) const { template void Net::ToHDF5(const string& filename, bool write_diff) const { +#ifdef USE_HDF5 hid_t file_hid = H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); CHECK_GE(file_hid, 0) @@ -910,6 +918,9 @@ void Net::ToHDF5(const string& filename, bool write_diff) const { H5Gclose(diff_hid); } H5Fclose(file_hid); +#else + LOG(FATAL) << "ToHDF5 requires hdf5; compile with USE_HDF5."; +#endif // USE_HDF5 } template diff --git a/src/caffe/solvers/sgd_solver.cpp b/src/caffe/solvers/sgd_solver.cpp index f30f316d1a0..0c4900cdb3c 100644 --- a/src/caffe/solvers/sgd_solver.cpp +++ b/src/caffe/solvers/sgd_solver.cpp @@ -278,6 +278,7 @@ void SGDSolver::SnapshotSolverStateToBinaryProto( template void SGDSolver::SnapshotSolverStateToHDF5( const string& model_filename) { +#ifdef USE_HDF5 string snapshot_filename = Solver::SnapshotFilename(".solverstate.h5"); LOG(INFO) << "Snapshotting solver state to HDF5 file " << snapshot_filename; @@ -299,6 +300,10 @@ void SGDSolver::SnapshotSolverStateToHDF5( } H5Gclose(history_hid); H5Fclose(file_hid); +#else + LOG(FATAL) << "SnapshotSolverStateToHDF5 requires hdf5;" + << " compile with USE_HDF5."; +#endif // USE_HDF5 } template @@ -323,6 +328,7 @@ void SGDSolver::RestoreSolverStateFromBinaryProto( template void SGDSolver::RestoreSolverStateFromHDF5(const string& state_file) { +#ifdef USE_HDF5 hid_t file_hid = H5Fopen(state_file.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); CHECK_GE(file_hid, 0) << "Couldn't open solver state file " << state_file; this->iter_ = hdf5_load_int(file_hid, "iter"); @@ -344,6 +350,10 @@ void SGDSolver::RestoreSolverStateFromHDF5(const string& state_file) { } H5Gclose(history_hid); H5Fclose(file_hid); +#else + LOG(FATAL) << "RestoreSolverStateFromHDF5 requires hdf5;" + << " compile with USE_HDF5."; +#endif // USE_HDF5 } INSTANTIATE_CLASS(SGDSolver); diff --git a/src/caffe/test/test_hdf5_output_layer.cpp b/src/caffe/test/test_hdf5_output_layer.cpp index 3833ebff78e..618d16fe373 100644 --- a/src/caffe/test/test_hdf5_output_layer.cpp +++ b/src/caffe/test/test_hdf5_output_layer.cpp @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 #include #include @@ -119,3 +120,4 @@ TYPED_TEST(HDF5OutputLayerTest, TestForward) { } } // namespace caffe +#endif // USE_HDF5 diff --git a/src/caffe/test/test_hdf5data_layer.cpp b/src/caffe/test/test_hdf5data_layer.cpp index 8884ce95a23..37b65e03686 100644 --- a/src/caffe/test/test_hdf5data_layer.cpp +++ b/src/caffe/test/test_hdf5data_layer.cpp @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 #include #include @@ -134,3 +135,4 @@ TYPED_TEST(HDF5DataLayerTest, TestRead) { } } // namespace caffe +#endif // USE_HDF5 diff --git a/src/caffe/util/hdf5.cpp b/src/caffe/util/hdf5.cpp index 7730e76ab87..a428d43c4d0 100644 --- a/src/caffe/util/hdf5.cpp +++ b/src/caffe/util/hdf5.cpp @@ -1,3 +1,4 @@ +#ifdef USE_HDF5 #include "caffe/util/hdf5.hpp" #include @@ -185,3 +186,4 @@ string hdf5_get_name_by_idx(hid_t loc_id, int idx) { } } // namespace caffe +#endif // USE_HDF5 From bf43d91d2d47e36ce02dbc2550cd13c7081f68d9 Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Thu, 18 Jun 2015 15:52:59 +0800 Subject: [PATCH 02/11] added Eigen support --- Makefile | 5 + Makefile.config.example | 3 +- cmake/ConfigGen.cmake | 4 + cmake/Dependencies.cmake | 4 + cmake/Modules/FindEigen.cmake | 32 ++++++ include/caffe/util/mkl_alternate.hpp | 38 +++++++ src/caffe/util/math_functions.cpp | 158 ++++++++++++++++++++++++++- 7 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 cmake/Modules/FindEigen.cmake diff --git a/Makefile b/Makefile index d86f556a9f1..1a7200ae1f2 100644 --- a/Makefile +++ b/Makefile @@ -374,6 +374,11 @@ ifeq ($(BLAS), mkl) MKLROOT ?= /opt/intel/mkl BLAS_INCLUDE ?= $(MKLROOT)/include BLAS_LIB ?= $(MKLROOT)/lib $(MKLROOT)/lib/intel64 +else ifeq ($(BLAS), eigen) + # Eigen + COMMON_FLAGS += -DUSE_EIGEN + EIGEN_DIR ?= /opt/eigen + BLAS_INCLUDE ?= $(EIGEN_DIR) else ifeq ($(BLAS), open) # OpenBLAS LIBRARIES += openblas diff --git a/Makefile.config.example b/Makefile.config.example index 2ec9800c410..ad089c03fd2 100644 --- a/Makefile.config.example +++ b/Makefile.config.example @@ -42,10 +42,11 @@ CUDA_ARCH := -gencode arch=compute_20,code=sm_20 \ # BLAS choice: # atlas for ATLAS (default) +# eigen for Eigen # mkl for MKL # open for OpenBlas BLAS := atlas -# Custom (MKL/ATLAS/OpenBLAS) include and lib directories. +# Custom (MKL/Eigen/ATLAS/OpenBLAS) include and lib directories. # Leave commented to accept the defaults for your choice of BLAS # (which should work)! # BLAS_INCLUDE := /path/to/your/blas diff --git a/cmake/ConfigGen.cmake b/cmake/ConfigGen.cmake index fa85037922f..54945e3aeef 100644 --- a/cmake/ConfigGen.cmake +++ b/cmake/ConfigGen.cmake @@ -85,6 +85,10 @@ function(caffe_generate_export_configs) list(APPEND Caffe_DEFINITIONS -DUSE_MKL) endif() + if(BLAS STREQUAL "Eigen" OR BLAS STREQUAL "eigen") + list(APPEND Caffe_DEFINITIONS -DUSE_EIGEN) + endif() + configure_file("cmake/Templates/CaffeConfig.cmake.in" "${PROJECT_BINARY_DIR}/CaffeConfig.cmake" @ONLY) # Add targets to the build-tree export set diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index d07c6074c49..d9aa0938d87 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -100,6 +100,10 @@ if(NOT APPLE) include_directories(SYSTEM ${MKL_INCLUDE_DIR}) list(APPEND Caffe_LINKER_LIBS ${MKL_LIBRARIES}) add_definitions(-DUSE_MKL) + elseif(BLAS STREQUAL "Eigen" OR BLAS STREQUAL "eigen") + find_package(Eigen REQUIRED) + include_directories(SYSTEM ${EIGEN_INCLUDE_DIR}) + add_definitions(-DUSE_EIGEN) endif() elseif(APPLE) find_package(vecLib REQUIRED) diff --git a/cmake/Modules/FindEigen.cmake b/cmake/Modules/FindEigen.cmake new file mode 100644 index 00000000000..c28bc5bd5c6 --- /dev/null +++ b/cmake/Modules/FindEigen.cmake @@ -0,0 +1,32 @@ +SET(EIGEN_INCLUDE_SEARCH_PATHS + /usr/include + /usr/include/eigen3 + /usr/local/include + /usr/local/include/eigen3 + $ENV{EIGEN_HOME} +) + +FIND_PATH(EIGEN_INCLUDE_DIR NAMES Eigen/Dense PATHS ${EIGEN_INCLUDE_SEARCH_PATHS}) + +SET(EIGEN_FOUND ON) + +# Check include files +IF(NOT EIGEN_INCLUDE_DIR) + SET(EIGEN_FOUND OFF) + MESSAGE(STATUS "Could not find EIGEN include. Turning EIGEN_FOUND off") +ENDIF() + +IF (EIGEN_FOUND) + IF (NOT EIGEN_FIND_QUIETLY) + MESSAGE(STATUS "Found EIGEN include: ${EIGEN_INCLUDE_DIR}") + ENDIF (NOT EIGEN_FIND_QUIETLY) +ELSE (EIGEN_FOUND) + IF (EIGEN_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find EIGEN") + ENDIF (EIGEN_FIND_REQUIRED) +ENDIF (EIGEN_FOUND) + +MARK_AS_ADVANCED( + EIGEN_INCLUDE_DIR + EIGEN +) diff --git a/include/caffe/util/mkl_alternate.hpp b/include/caffe/util/mkl_alternate.hpp index 3355b6658a3..ab85c96f0eb 100644 --- a/include/caffe/util/mkl_alternate.hpp +++ b/include/caffe/util/mkl_alternate.hpp @@ -7,9 +7,43 @@ #else // If use MKL, simply include the MKL header +#ifdef USE_EIGEN + +#include + +using Eigen::Map; +using Eigen::VectorXf; +using Eigen::VectorXd; +using Eigen::Dynamic; +using Eigen::Matrix; +using Eigen::RowMajor; +using Eigen::InnerStride; + +enum CBLAS_ORDER { CblasRowMajor = 101, CblasColMajor = 102 }; +enum CBLAS_TRANSPOSE { CblasNoTrans = 111, CblasTrans = 112, CblasConjTrans = 113 }; + +#define MAP_SVECTOR(name, ptr, N) Map name(ptr, N) +#define MAP_CONST_SVECTOR(name, ptr, N) Map name(ptr, N) +#define MAP_CONST_SVECTOR_STRIDE(name, ptr, N, S) Map > name(ptr, N, InnerStride<>(S)) +#define MAP_DVECTOR(name, ptr, N) Map name(ptr, N) +#define MAP_CONST_DVECTOR(name, ptr, N) Map name(ptr, N) +#define MAP_CONST_DVECTOR_STRIDE(name, ptr, N, S) Map > name(ptr, N, InnerStride<>(S)) +typedef Matrix MatXf; +typedef Matrix MatXd; + +#define MAP_SMATRIX(name, ptr, M, N) Map name(ptr, M, N) +#define MAP_CONST_SMATRIX(name, ptr, M, N) Map name(ptr, M, N) +#define MAP_DMATRIX(name, ptr, M, N) Map name(ptr, M, N) +#define MAP_CONST_DMATRIX(name, ptr, M, N) Map name(ptr, M, N) + +#else + extern "C" { #include } + +#endif // USE_EIGEN + #include // Functions that caffe uses but are not present if MKL is not linked. @@ -77,6 +111,8 @@ DEFINE_VSL_BINARY_FUNC(Sub, y[i] = a[i] - b[i]); DEFINE_VSL_BINARY_FUNC(Mul, y[i] = a[i] * b[i]); DEFINE_VSL_BINARY_FUNC(Div, y[i] = a[i] / b[i]); +#ifndef USE_EIGEN + // In addition, MKL comes with an additional function axpby that is not present // in standard blas. We will simply use a two-step (inefficient, of course) way // to mimic that. @@ -93,5 +129,7 @@ inline void cblas_daxpby(const int N, const double alpha, const double* X, cblas_daxpy(N, alpha, X, incX, Y, incY); } +#endif // USE_EIGEN + #endif // USE_MKL #endif // CAFFE_UTIL_MKL_ALTERNATE_H_ diff --git a/src/caffe/util/math_functions.cpp b/src/caffe/util/math_functions.cpp index 71c02274a75..eb5231e4d10 100644 --- a/src/caffe/util/math_functions.cpp +++ b/src/caffe/util/math_functions.cpp @@ -14,10 +14,32 @@ void caffe_cpu_gemm(const CBLAS_TRANSPOSE TransA, const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const float alpha, const float* A, const float* B, const float beta, float* C) { +#ifdef USE_EIGEN + MAP_SMATRIX(eC, C, M, N); + eC *= beta; + if (TransA == CblasNoTrans && TransB == CblasNoTrans) { + MAP_CONST_SMATRIX(eA, A, M, K); + MAP_CONST_SMATRIX(eB, B, K, N); + eC.noalias() += alpha * (eA * eB); + } else if (TransA == CblasNoTrans && TransB == CblasTrans) { + MAP_CONST_SMATRIX(eA, A, M, K); + MAP_CONST_SMATRIX(eB, B, N, K); + eC.noalias() += alpha * (eA * eB.transpose()); + } else if (TransA == CblasTrans && TransB == CblasNoTrans) { + MAP_CONST_SMATRIX(eA, A, K, M); + MAP_CONST_SMATRIX(eB, B, K, N); + eC.noalias() += alpha * (eA.transpose() * eB); + } else { + MAP_CONST_SMATRIX(eA, A, K, M); + MAP_CONST_SMATRIX(eB, B, N, K); + eC.noalias() += alpha * (eA.transpose() * eB.transpose()); + } +#else int lda = (TransA == CblasNoTrans) ? K : M; int ldb = (TransB == CblasNoTrans) ? N : K; cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B, ldb, beta, C, N); +#endif } template<> @@ -25,33 +47,101 @@ void caffe_cpu_gemm(const CBLAS_TRANSPOSE TransA, const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const double alpha, const double* A, const double* B, const double beta, double* C) { +#ifdef USE_EIGEN + MAP_DMATRIX(eC, C, M, N); + eC *= beta; + if (TransA == CblasNoTrans && TransB == CblasNoTrans) { + MAP_CONST_DMATRIX(eA, A, M, K); + MAP_CONST_DMATRIX(eB, B, K, N); + eC.noalias() += alpha * (eA * eB); + } else if (TransA == CblasNoTrans && TransB == CblasTrans) { + MAP_CONST_DMATRIX(eA, A, M, K); + MAP_CONST_DMATRIX(eB, B, N, K); + eC.noalias() += alpha * (eA * eB.transpose()); + } else if (TransA == CblasTrans && TransB == CblasNoTrans) { + MAP_CONST_DMATRIX(eA, A, K, M); + MAP_CONST_DMATRIX(eB, B, K, N); + eC.noalias() += alpha * (eA.transpose() * eB); + } else { + MAP_CONST_DMATRIX(eA, A, K, M); + MAP_CONST_DMATRIX(eB, B, N, K); + eC.noalias() += alpha * (eA.transpose() * eB.transpose()); + } +#else int lda = (TransA == CblasNoTrans) ? K : M; int ldb = (TransB == CblasNoTrans) ? N : K; cblas_dgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B, ldb, beta, C, N); +#endif } template <> void caffe_cpu_gemv(const CBLAS_TRANSPOSE TransA, const int M, const int N, const float alpha, const float* A, const float* x, const float beta, float* y) { +#ifdef USE_EIGEN + MAP_CONST_SMATRIX(eA, A, M, N); + if (TransA == CblasNoTrans) { + MAP_SVECTOR(eY, y, M); + eY *= beta; + MAP_CONST_SVECTOR(eX, x, N); + eY.noalias() += alpha * (eA * eX); + } else { + MAP_SVECTOR(eY, y, N); + eY *= beta; + MAP_CONST_SVECTOR(eX, x, M); + eY.noalias() += alpha * (eA.transpose() * eX); + } +#else cblas_sgemv(CblasRowMajor, TransA, M, N, alpha, A, N, x, 1, beta, y, 1); +#endif } template <> void caffe_cpu_gemv(const CBLAS_TRANSPOSE TransA, const int M, const int N, const double alpha, const double* A, const double* x, const double beta, double* y) { +#ifdef USE_EIGEN + MAP_CONST_DMATRIX(eA, A, M, N); + if (TransA == CblasNoTrans) { + MAP_DVECTOR(eY, y, M); + eY *= beta; + MAP_CONST_DVECTOR(eX, x, N); + eY.noalias() += alpha * (eA * eX); + } else { + MAP_DVECTOR(eY, y, N); + eY *= beta; + MAP_CONST_DVECTOR(eX, x, M); + eY.noalias() += alpha * (eA.transpose() * eX); + } +#else cblas_dgemv(CblasRowMajor, TransA, M, N, alpha, A, N, x, 1, beta, y, 1); +#endif } template <> void caffe_axpy(const int N, const float alpha, const float* X, - float* Y) { cblas_saxpy(N, alpha, X, 1, Y, 1); } + float* Y) { +#ifdef USE_EIGEN + MAP_SVECTOR(eY, Y, N); + MAP_CONST_SVECTOR(eX, X, N); + eY = alpha * eX + eY; +#else + cblas_saxpy(N, alpha, X, 1, Y, 1); +#endif +} template <> void caffe_axpy(const int N, const double alpha, const double* X, - double* Y) { cblas_daxpy(N, alpha, X, 1, Y, 1); } + double* Y) { +#ifdef USE_EIGEN + MAP_DVECTOR(eY, Y, N); + MAP_CONST_DVECTOR(eX, X, N); + eY = alpha * eX + eY; +#else + cblas_daxpy(N, alpha, X, 1, Y, 1); +#endif +} template void caffe_set(const int N, const Dtype alpha, Dtype* Y) { @@ -106,24 +196,46 @@ template void caffe_copy(const int N, const double* X, double* Y); template <> void caffe_scal(const int N, const float alpha, float *X) { +#ifdef USE_EIGEN + MAP_SVECTOR(eX, X, N); + eX *= alpha; +#else cblas_sscal(N, alpha, X, 1); +#endif } template <> void caffe_scal(const int N, const double alpha, double *X) { +#ifdef USE_EIGEN + MAP_DVECTOR(eX, X, N); + eX *= alpha; +#else cblas_dscal(N, alpha, X, 1); +#endif } template <> void caffe_cpu_axpby(const int N, const float alpha, const float* X, const float beta, float* Y) { +#ifdef USE_EIGEN + MAP_SVECTOR(eY, Y, N); + MAP_CONST_SVECTOR(eX, X, N); + eY = alpha * eX + beta * eY; +#else cblas_saxpby(N, alpha, X, 1, beta, Y, 1); +#endif } template <> void caffe_cpu_axpby(const int N, const double alpha, const double* X, const double beta, double* Y) { +#ifdef USE_EIGEN + MAP_DVECTOR(eY, Y, N); + MAP_CONST_DVECTOR(eX, X, N); + eY = alpha * eX + beta * eY; +#else cblas_daxpby(N, alpha, X, 1, beta, Y, 1); +#endif } template <> @@ -328,13 +440,29 @@ void caffe_rng_bernoulli(const int n, const float p, unsigned int* r); template <> float caffe_cpu_strided_dot(const int n, const float* x, const int incx, const float* y, const int incy) { +#ifdef USE_EIGEN + int lx = (n + incx - 1) / incx; + int ly = (n + incy - 1) / incy; + MAP_CONST_SVECTOR_STRIDE(eX, x, lx, incx); + MAP_CONST_SVECTOR_STRIDE(eY, y, ly, incy); + return eX.dot(eY); +#else return cblas_sdot(n, x, incx, y, incy); +#endif } template <> double caffe_cpu_strided_dot(const int n, const double* x, const int incx, const double* y, const int incy) { +#ifdef USE_EIGEN + int lx = (n + incx - 1) / incx; + int ly = (n + incy - 1) / incy; + MAP_CONST_DVECTOR_STRIDE(eX, x, lx, incx); + MAP_CONST_DVECTOR_STRIDE(eY, y, ly, incy); + return eX.dot(eY); +#else return cblas_ddot(n, x, incx, y, incy); +#endif } template @@ -350,26 +478,52 @@ double caffe_cpu_dot(const int n, const double* x, const double* y); template <> float caffe_cpu_asum(const int n, const float* x) { +#ifdef USE_EIGEN + float *y = new float[n]; + vsAbs(n, x, y); + MAP_SVECTOR(eY, y, n); + return eY.sum(); +#else return cblas_sasum(n, x, 1); +#endif } template <> double caffe_cpu_asum(const int n, const double* x) { +#ifdef USE_EIGEN + double *y = new double[n]; + vdAbs(n, x, y); + MAP_DVECTOR(eY, y, n); + return eY.sum(); +#else return cblas_dasum(n, x, 1); +#endif } template <> void caffe_cpu_scale(const int n, const float alpha, const float *x, float* y) { +#ifdef USE_EIGEN + memcpy(y, x, sizeof(float)*n); + MAP_SVECTOR(eY, y, n); + eY *= alpha; +#else cblas_scopy(n, x, 1, y, 1); cblas_sscal(n, alpha, y, 1); +#endif } template <> void caffe_cpu_scale(const int n, const double alpha, const double *x, double* y) { +#ifdef USE_EIGEN + memcpy(y, x, sizeof(double)*n); + MAP_DVECTOR(eY, y, n); + eY *= alpha; +#else cblas_dcopy(n, x, 1, y, 1); cblas_dscal(n, alpha, y, 1); +#endif } } // namespace caffe From 6581926f3c0b729daebc8a1a3ff13319e158bed3 Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Tue, 20 Oct 2015 14:37:20 +0800 Subject: [PATCH 03/11] Added jni lib for android --- CMakeLists.txt | 5 ++ android/CMakeLists.txt | 17 +++++++ android/caffe_jni.cpp | 100 ++++++++++++++++++++++++++++++++++++++ android/caffe_mobile.cpp | 101 +++++++++++++++++++++++++++++++++++++++ android/caffe_mobile.hpp | 27 +++++++++++ cmake/Utils.cmake | 18 +++++++ 6 files changed, 268 insertions(+) create mode 100644 android/CMakeLists.txt create mode 100644 android/caffe_jni.cpp create mode 100644 android/caffe_mobile.cpp create mode 100644 android/caffe_mobile.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b3647e3c4f9..dd0894403cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,10 @@ if(USE_libstdcpp) message("-- Warning: forcing libstdc++ (controlled by USE_libstdcpp option in cmake)") endif() +if(ANDROID) + caffe_enable_cpp11_support() +endif() + add_definitions(-DGTEST_USE_OWN_TR1_TUPLE) # ---[ Warnings @@ -86,6 +90,7 @@ add_subdirectory(src/gtest) add_subdirectory(src/caffe) add_subdirectory(tools) add_subdirectory(examples) +add_subdirectory(android) add_subdirectory(python) add_subdirectory(matlab) add_subdirectory(docs) diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 00000000000..44526b55bb1 --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8) + +if(ANDROID) + add_library(caffe_jni SHARED caffe_jni.cpp caffe_mobile.cpp) + #add_executable(caffe_jni caffe_jni.cpp caffe_mobile.cpp) + target_link_libraries(caffe_jni ${Caffe_LINK}) + caffe_default_properties(caffe_jni) + + # set back RUNTIME_OUTPUT_DIRECTORY + set_target_properties(caffe_jni PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/android") + + caffe_set_solution_folder(caffe_jni android) + + # install + install(TARGETS caffe_jni DESTINATION lib) +endif() diff --git a/android/caffe_jni.cpp b/android/caffe_jni.cpp new file mode 100644 index 00000000000..3695621e773 --- /dev/null +++ b/android/caffe_jni.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +#include "caffe/caffe.hpp" +#include "caffe_mobile.hpp" + +#define LOG_TAG "caffe-mobile" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__) + +#ifdef __cplusplus +extern "C" { +#endif + +caffe::CaffeMobile *caffe_mobile; + +int getTimeSec(); + +__NDK_FPABI__ void JNIEXPORT JNICALL +Java_com_sh1r0_caffe_1android_1demo_CaffeMobile_enableLog(JNIEnv* env, jobject thiz, jboolean enabled) +{ +} + +__NDK_FPABI__ jint JNIEXPORT JNICALL +Java_com_sh1r0_caffe_1android_1demo_CaffeMobile_loadModel(JNIEnv* env, jobject thiz, jstring modelPath, jstring weightsPath) +{ + const char *model_path = env->GetStringUTFChars(modelPath, 0); + const char *weights_path = env->GetStringUTFChars(weightsPath, 0); + caffe_mobile = new caffe::CaffeMobile(string(model_path), string(weights_path)); + env->ReleaseStringUTFChars(modelPath, model_path); + env->ReleaseStringUTFChars(weightsPath, weights_path); + return 0; +} + +__NDK_FPABI__ jint JNIEXPORT JNICALL +Java_com_sh1r0_caffe_1android_1demo_CaffeMobile_predictImage(JNIEnv* env, jobject thiz, jstring imgPath) +{ + const char *img_path = env->GetStringUTFChars(imgPath, 0); + caffe::vector top_k = caffe_mobile->predict_top_k(string(img_path), 3); + LOGD("top-1 result: %d", top_k[0]); + + env->ReleaseStringUTFChars(imgPath, img_path); + + return top_k[0]; +} + +int getTimeSec() { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (int) now.tv_sec; +} +/* +JavaVM *g_jvm = NULL; +jobject g_obj = NULL; + +void JNIEXPORT JNICALL +Java_com_sh1r0_caffe_1android_1demo_MainActivity_MainActivity_setJNIEnv(JNIEnv* env, jobject obj) +{ + env->GetJavaVM(&g_jvm); + g_obj = env->NewGlobalRef(obj); +} +*/ +jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { + LOGE("GetEnv failed!"); + return result; + } + + return JNI_VERSION_1_6; +} + +#ifdef __cplusplus +} +#endif + +int main(int argc, char const *argv[]) +{ + string usage("usage: main "); + if (argc < 4) { + std::cerr << usage << std::endl; + return 1; + } + + caffe_mobile = new caffe::CaffeMobile(string(argv[1]), string(argv[2])); + caffe::vector top_3 = caffe_mobile->predict_top_k(string(argv[3])); + for (auto k : top_3) { + std::cout << k << std::endl; + } + return 0; +} + diff --git a/android/caffe_mobile.cpp b/android/caffe_mobile.cpp new file mode 100644 index 00000000000..61553a2b7bd --- /dev/null +++ b/android/caffe_mobile.cpp @@ -0,0 +1,101 @@ +#include +#include "caffe_mobile.hpp" + +using std::string; +using std::static_pointer_cast; +using std::clock; +using std::clock_t; + +using caffe::Blob; +using caffe::Caffe; +using caffe::Datum; +using caffe::Net; +using caffe::shared_ptr; +using caffe::vector; +using caffe::MemoryDataLayer; + +namespace caffe { + +template +vector ordered(vector const& values) { + vector indices(values.size()); + std::iota(begin(indices), end(indices), static_cast(0)); + + std::sort( + begin(indices), end(indices), + [&](size_t a, size_t b) { return values[a] > values[b]; } + ); + return indices; +} + +CaffeMobile::CaffeMobile(string model_path, string weights_path) { + CHECK_GT(model_path.size(), 0) << "Need a model definition to score."; + CHECK_GT(weights_path.size(), 0) << "Need model weights to score."; + + Caffe::set_mode(Caffe::CPU); + + clock_t t_start = clock(); + caffe_net = new Net(model_path, caffe::TEST); + caffe_net->CopyTrainedLayersFrom(weights_path); + clock_t t_end = clock(); + VLOG(1) << "Loading time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC << " ms."; +} + +CaffeMobile::~CaffeMobile() { + free(caffe_net); + caffe_net = NULL; +} + +int CaffeMobile::test(string img_path) { + CHECK(caffe_net != NULL); + + Datum datum; + CHECK(ReadImageToDatum(img_path, 0, 256, 256, true, &datum)); + const shared_ptr> memory_data_layer = + static_pointer_cast>( + caffe_net->layer_by_name("data")); + memory_data_layer->AddDatumVector(vector({datum})); + + vector* > dummy_bottom_vec; + float loss; + clock_t t_start = clock(); + const vector*>& result = caffe_net->Forward(dummy_bottom_vec, &loss); + clock_t t_end = clock(); + VLOG(1) << "Prediction time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC << " ms."; + + const float* argmaxs = result[1]->cpu_data(); + for (int i = 0; i < result[1]->num(); i++) { + for (int j = 0; j < result[1]->height(); j++) { + LOG(INFO) << " Image: "<< i << " class:" + << argmaxs[i*result[1]->height() + j]; + } + } + + return argmaxs[0]; +} + +vector CaffeMobile::predict_top_k(string img_path, int k) { + CHECK(caffe_net != NULL); + + Datum datum; + CHECK(ReadImageToDatum(img_path, 0, 256, 256, true, &datum)); + const shared_ptr> memory_data_layer = + static_pointer_cast>( + caffe_net->layer_by_name("data")); + memory_data_layer->AddDatumVector(vector({datum})); + + float loss; + vector* > dummy_bottom_vec; + clock_t t_start = clock(); + const vector*>& result = caffe_net->Forward(dummy_bottom_vec, &loss); + clock_t t_end = clock(); + VLOG(1) << "Prediction time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC << " ms."; + + const vector probs = vector(result[1]->cpu_data(), result[1]->cpu_data() + result[1]->count()); + CHECK_LE(k, probs.size()); + vector sorted_index = ordered(probs); + + return vector(sorted_index.begin(), sorted_index.begin() + k); +} + +} // namespace caffe diff --git a/android/caffe_mobile.hpp b/android/caffe_mobile.hpp new file mode 100644 index 00000000000..c2f5c4fa1b5 --- /dev/null +++ b/android/caffe_mobile.hpp @@ -0,0 +1,27 @@ +#ifndef CAFFE_MOBILE_HPP_ +#define CAFFE_MOBILE_HPP_ + +#include +#include "caffe/caffe.hpp" + +using std::string; + +namespace caffe { + +class CaffeMobile +{ +public: + CaffeMobile(string model_path, string weights_path); + ~CaffeMobile(); + + int test(string img_path); + + vector predict_top_k(string img_path, int k=3); + +private: + Net *caffe_net; +}; + +} // namespace caffe + +#endif diff --git a/cmake/Utils.cmake b/cmake/Utils.cmake index 653de5fdf89..da4d1048b58 100644 --- a/cmake/Utils.cmake +++ b/cmake/Utils.cmake @@ -380,3 +380,21 @@ function(caffe_detect_darwin_version output_var) set(${output_var} "" PARENT_SCOPE) endif() endfunction() + +################################################################################################ +# Helper function to add appropriate c++11 flags to CMAKE_CXX_FLAGS +# Usage: +# caffe_enable_cpp11_support() +include(CheckCXXCompilerFlag) + +function(caffe_enable_cpp11_support) + set(__flags "-std=c++11" "-std=c++0x" "/Qstd=c++0x") + + foreach(__f ${__flags}) + check_cxx_compiler_flag(${__f} __result) + if(__result) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${__f}" PARENT_SCOPE) + break() + endif() + endforeach() +endfunction() From 14cab593ef40be707eadf12a5381919cef5aa71e Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Thu, 24 Dec 2015 00:14:28 +0800 Subject: [PATCH 04/11] Revised jni interfaces adapted from examples/cpp_classification add ExtractFeatures reformat code in consistency with caffe style --- android/caffe_jni.cpp | 160 +++++++++++--------- android/caffe_mobile.cpp | 307 ++++++++++++++++++++++++++++++--------- android/caffe_mobile.hpp | 41 +++++- 3 files changed, 363 insertions(+), 145 deletions(-) diff --git a/android/caffe_jni.cpp b/android/caffe_jni.cpp index 3695621e773..6c9fda0c640 100644 --- a/android/caffe_jni.cpp +++ b/android/caffe_jni.cpp @@ -2,99 +2,121 @@ #include #include #include +#include #include "caffe/caffe.hpp" #include "caffe_mobile.hpp" -#define LOG_TAG "caffe-mobile" -#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG, __VA_ARGS__) -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__) -#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__) -#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__) - #ifdef __cplusplus extern "C" { #endif -caffe::CaffeMobile *caffe_mobile; - -int getTimeSec(); +using std::string; +using std::vector; +using caffe::CaffeMobile; -__NDK_FPABI__ void JNIEXPORT JNICALL -Java_com_sh1r0_caffe_1android_1demo_CaffeMobile_enableLog(JNIEnv* env, jobject thiz, jboolean enabled) -{ +int getTimeSec() { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (int)now.tv_sec; } -__NDK_FPABI__ jint JNIEXPORT JNICALL -Java_com_sh1r0_caffe_1android_1demo_CaffeMobile_loadModel(JNIEnv* env, jobject thiz, jstring modelPath, jstring weightsPath) -{ - const char *model_path = env->GetStringUTFChars(modelPath, 0); - const char *weights_path = env->GetStringUTFChars(weightsPath, 0); - caffe_mobile = new caffe::CaffeMobile(string(model_path), string(weights_path)); - env->ReleaseStringUTFChars(modelPath, model_path); - env->ReleaseStringUTFChars(weightsPath, weights_path); - return 0; +string jstring2string(JNIEnv *env, jstring jstr) { + const char *cstr = env->GetStringUTFChars(jstr, 0); + string str(cstr); + env->ReleaseStringUTFChars(jstr, cstr); + return str; } -__NDK_FPABI__ jint JNIEXPORT JNICALL -Java_com_sh1r0_caffe_1android_1demo_CaffeMobile_predictImage(JNIEnv* env, jobject thiz, jstring imgPath) -{ - const char *img_path = env->GetStringUTFChars(imgPath, 0); - caffe::vector top_k = caffe_mobile->predict_top_k(string(img_path), 3); - LOGD("top-1 result: %d", top_k[0]); - - env->ReleaseStringUTFChars(imgPath, img_path); +JNIEXPORT void JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_enableLog(JNIEnv *env, + jobject thiz, + jboolean enabled) {} - return top_k[0]; +JNIEXPORT jint JNICALL Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_loadModel( + JNIEnv *env, jobject thiz, jstring modelPath, jstring weightsPath) { + CaffeMobile::Get(jstring2string(env, modelPath), + jstring2string(env, weightsPath)); + return 0; } -int getTimeSec() { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - return (int) now.tv_sec; +JNIEXPORT void JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setMeanWithMeanFile( + JNIEnv *env, jobject thiz, jstring meanFile) { + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + caffe_mobile->SetMean(jstring2string(env, meanFile)); } -/* -JavaVM *g_jvm = NULL; -jobject g_obj = NULL; - -void JNIEXPORT JNICALL -Java_com_sh1r0_caffe_1android_1demo_MainActivity_MainActivity_setJNIEnv(JNIEnv* env, jobject obj) -{ - env->GetJavaVM(&g_jvm); - g_obj = env->NewGlobalRef(obj); + +JNIEXPORT void JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setMeanWithMeanValues( + JNIEnv *env, jobject thiz, jfloatArray meanValues) { + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + int num_channels = env->GetArrayLength(meanValues); + jfloat *ptr = env->GetFloatArrayElements(meanValues, 0); + vector mean_values(ptr, ptr + num_channels); + caffe_mobile->SetMean(mean_values); } -*/ -jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) -{ - JNIEnv* env = NULL; - jint result = -1; - - if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { - LOGE("GetEnv failed!"); - return result; - } - return JNI_VERSION_1_6; +JNIEXPORT void JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setScale(JNIEnv *env, + jobject thiz, + jfloat scale) { + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + caffe_mobile->SetScale(scale); } -#ifdef __cplusplus +JNIEXPORT jintArray JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_predictImage(JNIEnv *env, + jobject thiz, + jstring imgPath, + jint k) { + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + vector top_k = + caffe_mobile->PredictTopK(jstring2string(env, imgPath), k); + + jintArray result; + result = env->NewIntArray(k); + if (result == NULL) { + return NULL; /* out of memory error thrown */ + } + // move from the temp structure to the java structure + env->SetIntArrayRegion(result, 0, k, &top_k[0]); + return result; } -#endif -int main(int argc, char const *argv[]) -{ - string usage("usage: main "); - if (argc < 4) { - std::cerr << usage << std::endl; - return 1; +JNIEXPORT jobjectArray JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_extractFeatures( + JNIEnv *env, jobject thiz, jstring imgPath, jstring blobNames) { + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + vector> features = caffe_mobile->ExtractFeatures( + jstring2string(env, imgPath), jstring2string(env, blobNames)); + + jobjectArray array2D = + env->NewObjectArray(features.size(), env->FindClass("[F"), NULL); + for (size_t i = 0; i < features.size(); ++i) { + jfloatArray array1D = env->NewFloatArray(features[i].size()); + if (array1D == NULL) { + return NULL; /* out of memory error thrown */ } + // move from the temp structure to the java structure + env->SetFloatArrayRegion(array1D, 0, features[i].size(), &features[i][0]); + env->SetObjectArrayElement(array2D, i, array1D); + } + return array2D; +} - caffe_mobile = new caffe::CaffeMobile(string(argv[1]), string(argv[2])); - caffe::vector top_3 = caffe_mobile->predict_top_k(string(argv[3])); - for (auto k : top_3) { - std::cout << k << std::endl; - } - return 0; +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env = NULL; + jint result = -1; + + if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) { + LOG(FATAL) << "GetEnv failed!"; + return result; + } + + return JNI_VERSION_1_6; } +#ifdef __cplusplus +} +#endif diff --git a/android/caffe_mobile.cpp b/android/caffe_mobile.cpp index 61553a2b7bd..429e4cb2c3f 100644 --- a/android/caffe_mobile.cpp +++ b/android/caffe_mobile.cpp @@ -1,101 +1,270 @@ +#include #include +#include + +#include "boost/algorithm/string.hpp" + +#include "caffe/caffe.hpp" +#include "caffe/layers/memory_data_layer.hpp" + #include "caffe_mobile.hpp" -using std::string; -using std::static_pointer_cast; +#include +#include +#include + using std::clock; using std::clock_t; +using std::string; +using std::vector; using caffe::Blob; using caffe::Caffe; using caffe::Datum; using caffe::Net; -using caffe::shared_ptr; -using caffe::vector; using caffe::MemoryDataLayer; namespace caffe { -template -vector ordered(vector const& values) { - vector indices(values.size()); - std::iota(begin(indices), end(indices), static_cast(0)); +template vector argmax(vector const &values, int N) { + vector indices(values.size()); + std::iota(indices.begin(), indices.end(), static_cast(0)); + std::partial_sort(indices.begin(), indices.begin() + N, indices.end(), + [&](size_t a, size_t b) { return values[a] > values[b]; }); + return vector(indices.begin(), indices.begin() + N); +} + +CaffeMobile *CaffeMobile::caffe_mobile_ = 0; +string CaffeMobile::model_path_ = ""; +string CaffeMobile::weights_path_ = ""; + +CaffeMobile *CaffeMobile::Get() { + CHECK(caffe_mobile_); + return caffe_mobile_; +} - std::sort( - begin(indices), end(indices), - [&](size_t a, size_t b) { return values[a] > values[b]; } - ); - return indices; +CaffeMobile *CaffeMobile::Get(const string &model_path, + const string &weights_path) { + if (!caffe_mobile_ || model_path != model_path_ || + weights_path != weights_path_) { + caffe_mobile_ = new CaffeMobile(model_path, weights_path); + model_path_ = model_path; + weights_path_ = weights_path; + } + return caffe_mobile_; } -CaffeMobile::CaffeMobile(string model_path, string weights_path) { - CHECK_GT(model_path.size(), 0) << "Need a model definition to score."; - CHECK_GT(weights_path.size(), 0) << "Need model weights to score."; +CaffeMobile::CaffeMobile(const string &model_path, const string &weights_path) { + CHECK_GT(model_path.size(), 0) << "Need a model definition to score."; + CHECK_GT(weights_path.size(), 0) << "Need model weights to score."; + + Caffe::set_mode(Caffe::CPU); + + clock_t t_start = clock(); + net_.reset(new Net(model_path, caffe::TEST)); + net_->CopyTrainedLayersFrom(weights_path); + clock_t t_end = clock(); + VLOG(1) << "Loading time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC + << " ms."; - Caffe::set_mode(Caffe::CPU); + CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input."; + CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output."; - clock_t t_start = clock(); - caffe_net = new Net(model_path, caffe::TEST); - caffe_net->CopyTrainedLayersFrom(weights_path); - clock_t t_end = clock(); - VLOG(1) << "Loading time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC << " ms."; + Blob *input_layer = net_->input_blobs()[0]; + num_channels_ = input_layer->channels(); + CHECK(num_channels_ == 3 || num_channels_ == 1) + << "Input layer should have 1 or 3 channels."; + input_geometry_ = cv::Size(input_layer->width(), input_layer->height()); + + scale_ = 0.0; } -CaffeMobile::~CaffeMobile() { - free(caffe_net); - caffe_net = NULL; +CaffeMobile::~CaffeMobile() { net_.reset(); } + +void CaffeMobile::SetMean(const vector &mean_values) { + CHECK_EQ(mean_values.size(), num_channels_) + << "Number of mean values doesn't match channels of input layer."; + + cv::Scalar channel_mean(0); + double *ptr = &channel_mean[0]; + for (int i = 0; i < num_channels_; ++i) { + ptr[i] = mean_values[i]; + } + mean_ = cv::Mat(input_geometry_, (num_channels_ == 3 ? CV_32FC3 : CV_32FC1), + channel_mean); } -int CaffeMobile::test(string img_path) { - CHECK(caffe_net != NULL); - - Datum datum; - CHECK(ReadImageToDatum(img_path, 0, 256, 256, true, &datum)); - const shared_ptr> memory_data_layer = - static_pointer_cast>( - caffe_net->layer_by_name("data")); - memory_data_layer->AddDatumVector(vector({datum})); - - vector* > dummy_bottom_vec; - float loss; - clock_t t_start = clock(); - const vector*>& result = caffe_net->Forward(dummy_bottom_vec, &loss); - clock_t t_end = clock(); - VLOG(1) << "Prediction time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC << " ms."; - - const float* argmaxs = result[1]->cpu_data(); - for (int i = 0; i < result[1]->num(); i++) { - for (int j = 0; j < result[1]->height(); j++) { - LOG(INFO) << " Image: "<< i << " class:" - << argmaxs[i*result[1]->height() + j]; - } - } - - return argmaxs[0]; +void CaffeMobile::SetMean(const string &mean_file) { + BlobProto blob_proto; + ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto); + + /* Convert from BlobProto to Blob */ + Blob mean_blob; + mean_blob.FromProto(blob_proto); + CHECK_EQ(mean_blob.channels(), num_channels_) + << "Number of channels of mean file doesn't match input layer."; + + /* The format of the mean file is planar 32-bit float BGR or grayscale. */ + std::vector channels; + float *data = mean_blob.mutable_cpu_data(); + for (int i = 0; i < num_channels_; ++i) { + /* Extract an individual channel. */ + cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data); + channels.push_back(channel); + data += mean_blob.height() * mean_blob.width(); + } + + /* Merge the separate channels into a single image. */ + cv::Mat mean; + cv::merge(channels, mean); + + /* Compute the global mean pixel value and create a mean image + * filled with this value. */ + cv::Scalar channel_mean = cv::mean(mean); + mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean); +} + +void CaffeMobile::SetScale(const float scale) { + CHECK_GT(scale, 0); + scale_ = scale; } -vector CaffeMobile::predict_top_k(string img_path, int k) { - CHECK(caffe_net != NULL); +void CaffeMobile::Preprocess(const cv::Mat &img, + std::vector *input_channels) { + /* Convert the input image to the input image format of the network. */ + cv::Mat sample; + if (img.channels() == 3 && num_channels_ == 1) + cv::cvtColor(img, sample, cv::COLOR_BGR2GRAY); + else if (img.channels() == 4 && num_channels_ == 1) + cv::cvtColor(img, sample, cv::COLOR_BGRA2GRAY); + else if (img.channels() == 4 && num_channels_ == 3) + cv::cvtColor(img, sample, cv::COLOR_BGRA2BGR); + else if (img.channels() == 1 && num_channels_ == 3) + cv::cvtColor(img, sample, cv::COLOR_GRAY2BGR); + else + sample = img; + + cv::Mat sample_resized; + if (sample.size() != input_geometry_) + cv::resize(sample, sample_resized, input_geometry_); + else + sample_resized = sample; + + cv::Mat sample_float; + if (num_channels_ == 3) + sample_resized.convertTo(sample_float, CV_32FC3); + else + sample_resized.convertTo(sample_float, CV_32FC1); - Datum datum; - CHECK(ReadImageToDatum(img_path, 0, 256, 256, true, &datum)); - const shared_ptr> memory_data_layer = - static_pointer_cast>( - caffe_net->layer_by_name("data")); - memory_data_layer->AddDatumVector(vector({datum})); + cv::Mat sample_normalized; + if (!mean_.empty()) { + cv::subtract(sample_float, mean_, sample_normalized); + } else { + sample_normalized = sample_float; + } - float loss; - vector* > dummy_bottom_vec; - clock_t t_start = clock(); - const vector*>& result = caffe_net->Forward(dummy_bottom_vec, &loss); - clock_t t_end = clock(); - VLOG(1) << "Prediction time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC << " ms."; + if (scale_ > 0.0) { + sample_normalized *= scale_; + } - const vector probs = vector(result[1]->cpu_data(), result[1]->cpu_data() + result[1]->count()); - CHECK_LE(k, probs.size()); - vector sorted_index = ordered(probs); + /* This operation will write the separate BGR planes directly to the + * input layer of the network because it is wrapped by the cv::Mat + * objects in input_channels. */ + cv::split(sample_normalized, *input_channels); - return vector(sorted_index.begin(), sorted_index.begin() + k); + CHECK(reinterpret_cast(input_channels->at(0).data) == + net_->input_blobs()[0]->cpu_data()) + << "Input channels are not wrapping the input layer of the network."; +} + +void CaffeMobile::WrapInputLayer(std::vector *input_channels) { + Blob *input_layer = net_->input_blobs()[0]; + + int width = input_layer->width(); + int height = input_layer->height(); + float *input_data = input_layer->mutable_cpu_data(); + for (int i = 0; i < input_layer->channels(); ++i) { + cv::Mat channel(height, width, CV_32FC1, input_data); + input_channels->push_back(channel); + input_data += width * height; + } +} + +vector CaffeMobile::Forward(const string &filename) { + cv::Mat img = cv::imread(filename, -1); + CHECK(!img.empty()) << "Unable to decode image " << filename; + + Blob *input_layer = net_->input_blobs()[0]; + input_layer->Reshape(1, num_channels_, input_geometry_.height, + input_geometry_.width); + /* Forward dimension change to all layers. */ + net_->Reshape(); + + vector input_channels; + WrapInputLayer(&input_channels); + + Preprocess(img, &input_channels); + + clock_t t_start = clock(); + net_->ForwardPrefilled(); + clock_t t_end = clock(); + VLOG(1) << "Forwarding time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC + << " ms."; + + /* Copy the output layer to a std::vector */ + Blob *output_layer = net_->output_blobs()[0]; + const float *begin = output_layer->cpu_data(); + const float *end = begin + output_layer->channels(); + return vector(begin, end); +} + +vector CaffeMobile::PredictTopK(const string &img_path, int k) { + const vector probs = Forward(img_path); + k = std::min(std::max(k, 1), probs.size()); + return argmax(probs, k); +} + +vector> +CaffeMobile::ExtractFeatures(const string &img_path, + const string &str_blob_names) { + Forward(img_path); + + vector blob_names; + boost::split(blob_names, str_blob_names, boost::is_any_of(",")); + + size_t num_features = blob_names.size(); + for (size_t i = 0; i < num_features; i++) { + CHECK(net_->has_blob(blob_names[i])) << "Unknown feature blob name " + << blob_names[i]; + } + + vector> features; + for (size_t i = 0; i < num_features; i++) { + const shared_ptr> &feat = net_->blob_by_name(blob_names[i]); + features.push_back( + vector(feat->cpu_data(), feat->cpu_data() + feat->count())); + } + + return features; } } // namespace caffe + +using caffe::CaffeMobile; + +int main(int argc, char const *argv[]) { + string usage("usage: main "); + if (argc < 5) { + std::cerr << usage << std::endl; + return 1; + } + + CaffeMobile *caffe_mobile = + CaffeMobile::Get(string(argv[1]), string(argv[2])); + caffe_mobile->SetMean(string(argv[3])); + vector top_3 = caffe_mobile->PredictTopK(string(argv[4]), 3); + for (auto i : top_3) { + std::cout << i << std::endl; + } + return 0; +} diff --git a/android/caffe_mobile.hpp b/android/caffe_mobile.hpp index c2f5c4fa1b5..c4bae171f2e 100644 --- a/android/caffe_mobile.hpp +++ b/android/caffe_mobile.hpp @@ -2,24 +2,51 @@ #define CAFFE_MOBILE_HPP_ #include +#include #include "caffe/caffe.hpp" +#include using std::string; +using std::vector; namespace caffe { -class CaffeMobile -{ +class CaffeMobile { public: - CaffeMobile(string model_path, string weights_path); - ~CaffeMobile(); + ~CaffeMobile(); - int test(string img_path); + static CaffeMobile *Get(); + static CaffeMobile *Get(const string &model_path, const string &weights_path); - vector predict_top_k(string img_path, int k=3); + void SetMean(const string &mean_file); + + void SetMean(const vector &mean_values); + + void SetScale(const float scale); + + vector PredictTopK(const string &img_path, int k); + + vector> ExtractFeatures(const string &img_path, + const string &str_blob_names); private: - Net *caffe_net; + static CaffeMobile *caffe_mobile_; + static string model_path_; + static string weights_path_; + + CaffeMobile(const string &model_path, const string &weights_path); + + void Preprocess(const cv::Mat &img, vector *input_channels); + + void WrapInputLayer(std::vector *input_channels); + + vector Forward(const string &filename); + + shared_ptr> net_; + cv::Size input_geometry_; + int num_channels_; + cv::Mat mean_; + float scale_; }; } // namespace caffe From 4cfa9a9888573b56eccbcabce2d350e899d23ec5 Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Sat, 2 Jan 2016 00:34:52 +0800 Subject: [PATCH 05/11] Add setNumThreads for Eigen/OpenBLAS --- android/caffe_jni.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/android/caffe_jni.cpp b/android/caffe_jni.cpp index 6c9fda0c640..b08738eb316 100644 --- a/android/caffe_jni.cpp +++ b/android/caffe_jni.cpp @@ -4,6 +4,12 @@ #include #include +#ifdef USE_EIGEN +#include +#else +#include +#endif + #include "caffe/caffe.hpp" #include "caffe_mobile.hpp" @@ -28,6 +34,17 @@ string jstring2string(JNIEnv *env, jstring jstr) { return str; } +JNIEXPORT void JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setNumThreads( + JNIEnv *env, jobject thiz, jint numThreads) { + int num_threads = numThreads; +#ifdef USE_EIGEN + omp_set_num_threads(num_threads); +#else + openblas_set_num_threads(num_threads); +#endif +} + JNIEXPORT void JNICALL Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_enableLog(JNIEnv *env, jobject thiz, From 0bb07cd2c33ae988e197a76ca3106d954c18921e Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Wed, 3 Feb 2016 11:17:59 +0800 Subject: [PATCH 06/11] Prefer static libraries rather than shared libraries --- CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd0894403cd..e160fbeeb71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,11 @@ caffe_option(USE_LMDB "Build with lmdb" ON) caffe_option(ALLOW_LMDB_NOLOCK "Allow MDB_NOLOCK when reading LMDB files (only if necessary)" OFF) caffe_option(USE_HDF5 "Build with hdf5" ON) +if(ANDROID) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") + caffe_enable_cpp11_support() +endif() + # ---[ Dependencies include(cmake/Dependencies.cmake) @@ -68,10 +73,6 @@ if(USE_libstdcpp) message("-- Warning: forcing libstdc++ (controlled by USE_libstdcpp option in cmake)") endif() -if(ANDROID) - caffe_enable_cpp11_support() -endif() - add_definitions(-DGTEST_USE_OWN_TR1_TUPLE) # ---[ Warnings From 03cda7f7f37e615115d9e65f128685a843af8194 Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Thu, 11 Feb 2016 12:32:12 +0800 Subject: [PATCH 07/11] Added GetConfidenceScore function --- android/caffe_jni.cpp | 34 ++++++++++++++++++++++++---------- android/caffe_mobile.cpp | 4 ++++ android/caffe_mobile.hpp | 2 ++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/android/caffe_jni.cpp b/android/caffe_jni.cpp index b08738eb316..4677952138a 100644 --- a/android/caffe_jni.cpp +++ b/android/caffe_jni.cpp @@ -35,8 +35,9 @@ string jstring2string(JNIEnv *env, jstring jstr) { } JNIEXPORT void JNICALL -Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setNumThreads( - JNIEnv *env, jobject thiz, jint numThreads) { +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setNumThreads(JNIEnv *env, + jobject thiz, + jint numThreads) { int num_threads = numThreads; #ifdef USE_EIGEN omp_set_num_threads(num_threads); @@ -45,10 +46,8 @@ Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setNumThreads( #endif } -JNIEXPORT void JNICALL -Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_enableLog(JNIEnv *env, - jobject thiz, - jboolean enabled) {} +JNIEXPORT void JNICALL Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_enableLog( + JNIEnv *env, jobject thiz, jboolean enabled) {} JNIEXPORT jint JNICALL Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_loadModel( JNIEnv *env, jobject thiz, jstring modelPath, jstring weightsPath) { @@ -74,14 +73,29 @@ Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setMeanWithMeanValues( caffe_mobile->SetMean(mean_values); } -JNIEXPORT void JNICALL -Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setScale(JNIEnv *env, - jobject thiz, - jfloat scale) { +JNIEXPORT void JNICALL Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_setScale( + JNIEnv *env, jobject thiz, jfloat scale) { CaffeMobile *caffe_mobile = CaffeMobile::Get(); caffe_mobile->SetScale(scale); } +JNIEXPORT jfloatArray JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_getConfidenceScore( + JNIEnv *env, jobject thiz, jstring imgPath) { + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + vector conf_score = + caffe_mobile->GetConfidenceScore(jstring2string(env, imgPath)); + + jfloatArray result; + result = env->NewFloatArray(conf_score.size()); + if (result == NULL) { + return NULL; /* out of memory error thrown */ + } + // move from the temp structure to the java structure + env->SetFloatArrayRegion(result, 0, conf_score.size(), &conf_score[0]); + return result; +} + JNIEXPORT jintArray JNICALL Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_predictImage(JNIEnv *env, jobject thiz, diff --git a/android/caffe_mobile.cpp b/android/caffe_mobile.cpp index 429e4cb2c3f..97326c88278 100644 --- a/android/caffe_mobile.cpp +++ b/android/caffe_mobile.cpp @@ -218,6 +218,10 @@ vector CaffeMobile::Forward(const string &filename) { return vector(begin, end); } +vector CaffeMobile::GetConfidenceScore(const string &img_path) { + return Forward(img_path); +} + vector CaffeMobile::PredictTopK(const string &img_path, int k) { const vector probs = Forward(img_path); k = std::min(std::max(k, 1), probs.size()); diff --git a/android/caffe_mobile.hpp b/android/caffe_mobile.hpp index c4bae171f2e..d93e0729e14 100644 --- a/android/caffe_mobile.hpp +++ b/android/caffe_mobile.hpp @@ -24,6 +24,8 @@ class CaffeMobile { void SetScale(const float scale); + vector GetConfidenceScore(const string &img_path); + vector PredictTopK(const string &img_path, int k); vector> ExtractFeatures(const string &img_path, From 91ecc0b31b8d6ad837d80280617bb0fbb8488f8f Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Sun, 6 Mar 2016 10:27:29 +0800 Subject: [PATCH 08/11] Fix for the changes of deploy-like prototxts Refer to https://github.com/BVLC/caffe/commit/2cc3844cb2a4a72de10d321781dc8f994bef95c7 --- android/caffe_mobile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/caffe_mobile.cpp b/android/caffe_mobile.cpp index 97326c88278..478c143032b 100644 --- a/android/caffe_mobile.cpp +++ b/android/caffe_mobile.cpp @@ -206,7 +206,7 @@ vector CaffeMobile::Forward(const string &filename) { Preprocess(img, &input_channels); clock_t t_start = clock(); - net_->ForwardPrefilled(); + net_->Forward(); clock_t t_end = clock(); VLOG(1) << "Forwarding time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC << " ms."; From 2daa41445e8b445848422835abb737191060044b Mon Sep 17 00:00:00 2001 From: sh1r0 Date: Sun, 15 May 2016 00:55:12 +0800 Subject: [PATCH 09/11] Use glog to redirect logs to android logcat --- android/caffe_jni.cpp | 3 +++ android/caffe_mobile.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/android/caffe_jni.cpp b/android/caffe_jni.cpp index 4677952138a..4a649255864 100644 --- a/android/caffe_jni.cpp +++ b/android/caffe_jni.cpp @@ -145,6 +145,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return result; } + FLAGS_redirecttologcat = true; + FLAGS_android_logcat_tag = "caffe_jni"; + return JNI_VERSION_1_6; } diff --git a/android/caffe_mobile.cpp b/android/caffe_mobile.cpp index 478c143032b..daee61e6ba0 100644 --- a/android/caffe_mobile.cpp +++ b/android/caffe_mobile.cpp @@ -64,8 +64,8 @@ CaffeMobile::CaffeMobile(const string &model_path, const string &weights_path) { net_.reset(new Net(model_path, caffe::TEST)); net_->CopyTrainedLayersFrom(weights_path); clock_t t_end = clock(); - VLOG(1) << "Loading time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC - << " ms."; + LOG(INFO) << "Loading time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC + << " ms."; CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input."; CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output."; @@ -208,8 +208,8 @@ vector CaffeMobile::Forward(const string &filename) { clock_t t_start = clock(); net_->Forward(); clock_t t_end = clock(); - VLOG(1) << "Forwarding time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC - << " ms."; + LOG(INFO) << "Forwarding time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC + << " ms."; /* Copy the output layer to a std::vector */ Blob *output_layer = net_->output_blobs()[0]; From 2d5c3447cf761770bd96d1bd3fd06b670e516687 Mon Sep 17 00:00:00 2001 From: woodthom2 Date: Fri, 29 Jul 2016 09:07:46 +0100 Subject: [PATCH 10/11] Add files via upload --- android/caffe_jni.cpp | 49 ++++++++++++++++++++++++++++++++++++++++ android/caffe_mobile.cpp | 33 +++++++++++++++++++++++++++ android/caffe_mobile.hpp | 4 ++++ 3 files changed, 86 insertions(+) diff --git a/android/caffe_jni.cpp b/android/caffe_jni.cpp index 4a649255864..428feba5ebc 100644 --- a/android/caffe_jni.cpp +++ b/android/caffe_jni.cpp @@ -12,6 +12,12 @@ #include "caffe/caffe.hpp" #include "caffe_mobile.hpp" +#include +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { @@ -151,6 +157,49 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_VERSION_1_6; } +void getBGRFromYUV(void* data, const int width, const int height, cv::Mat& bgr) + +{ + + cv::Mat yuv(height+height/2, width, CV_8UC1, data); + + bgr = cv::Mat(height, width, CV_8UC4); + + cv::cvtColor(yuv, bgr, CV_YUV420sp2BGR ); +} + + +JNIEXPORT jfloatArray JNICALL +Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_getConfidenceScore( + JNIEnv *env, jobject thiz, jbyteArray image, jint width, jint height, jint roiX, jint roiY, jint roiWidth, jint roiHeight) { + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + jbyte* image_ptr = env->GetByteArrayElements(image, 0); + cv::Mat previewBGR; + getBGRFromYUV(reinterpret_cast(image_ptr), width, height, previewBGR); + + cv::Rect roi((int) roiX, (int) roiY, (int) roiWidth, (int) roiHeight); + + // Need to rotate 270 degrees -> do a rotate and flip. + cv::Mat croppedImage = previewBGR(roi); + cv::Mat croppedRotated90; + cv::transpose(croppedImage, croppedRotated90); + cv::flip(croppedRotated90, croppedRotated90, -1); + + CaffeMobile *caffe_mobile = CaffeMobile::Get(); + +vector conf_score = + caffe_mobile->GetConfidenceScore(croppedRotated90); + + jfloatArray result; + result = env->NewFloatArray(conf_score.size()); + if (result == NULL) { + return NULL; /* out of memory error thrown */ + } + + env->SetFloatArrayRegion(result, 0, conf_score.size(), &conf_score[0]); + return result; +} + #ifdef __cplusplus } #endif diff --git a/android/caffe_mobile.cpp b/android/caffe_mobile.cpp index daee61e6ba0..b94bd506b2a 100644 --- a/android/caffe_mobile.cpp +++ b/android/caffe_mobile.cpp @@ -252,6 +252,39 @@ CaffeMobile::ExtractFeatures(const string &img_path, return features; } +// Overloads to allow us to pass image as Mat instead of file path. +vector CaffeMobile::GetConfidenceScore(cv::Mat &img) { + return Forward(img); +} + + +vector CaffeMobile::Forward(cv::Mat &img) { + + + Blob *input_layer = net_->input_blobs()[0]; + input_layer->Reshape(1, num_channels_, input_geometry_.height, + input_geometry_.width); + /* Forward dimension change to all layers. */ + net_->Reshape(); + + vector input_channels; + WrapInputLayer(&input_channels); + + Preprocess(img, &input_channels); + + clock_t t_start = clock(); + net_->Forward(); + clock_t t_end = clock(); + LOG(INFO) << "Forwarding time: " << 1000.0 * (t_end - t_start) / CLOCKS_PER_SEC + << " ms."; + + /* Copy the output layer to a std::vector */ + Blob *output_layer = net_->output_blobs()[0]; + const float *begin = output_layer->cpu_data(); + const float *end = begin + output_layer->channels(); + return vector(begin, end); +} + } // namespace caffe using caffe::CaffeMobile; diff --git a/android/caffe_mobile.hpp b/android/caffe_mobile.hpp index d93e0729e14..99e0c9d1e79 100644 --- a/android/caffe_mobile.hpp +++ b/android/caffe_mobile.hpp @@ -26,6 +26,8 @@ class CaffeMobile { vector GetConfidenceScore(const string &img_path); + vector GetConfidenceScore(cv::Mat &img); + vector PredictTopK(const string &img_path, int k); vector> ExtractFeatures(const string &img_path, @@ -44,6 +46,8 @@ class CaffeMobile { vector Forward(const string &filename); + vector Forward(cv::Mat &img); + shared_ptr> net_; cv::Size input_geometry_; int num_channels_; From eccbbeb5c9a7d7a0a7f4537dfb1082f0a12e7ec1 Mon Sep 17 00:00:00 2001 From: woodthom2 Date: Fri, 29 Jul 2016 14:00:36 +0100 Subject: [PATCH 11/11] Add files via upload --- android/caffe_jni.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/caffe_jni.cpp b/android/caffe_jni.cpp index 428feba5ebc..fd82fdd443f 100644 --- a/android/caffe_jni.cpp +++ b/android/caffe_jni.cpp @@ -172,7 +172,7 @@ void getBGRFromYUV(void* data, const int width, const int height, cv::Mat& bgr) JNIEXPORT jfloatArray JNICALL Java_com_sh1r0_caffe_1android_1lib_CaffeMobile_getConfidenceScore( JNIEnv *env, jobject thiz, jbyteArray image, jint width, jint height, jint roiX, jint roiY, jint roiWidth, jint roiHeight) { - CaffeMobile *caffe_mobile = CaffeMobile::Get(); + jbyte* image_ptr = env->GetByteArrayElements(image, 0); cv::Mat previewBGR; getBGRFromYUV(reinterpret_cast(image_ptr), width, height, previewBGR);