【OpenCV】网络模型推理的简单流程分析(readNetFromONNX、setInput和forward等)
目录
- 1.模型读取(readNetFromONNX())
- 1.1 初始化解析函数(parseOperatorSet())
- 1.2 提取张量(getGraphTensors())
- 1.3 节点处理(handleNode())
- 2.数据准备(blobFromImage() & setInput())
- 3.模型推理(forward())
前面提到了如何使用imread()和imshow()函数处理图像文件,本文简单的记录以下OpenCV中使用dnn模块进行网络模型推理的流程,大致的流程为:
(1)模型读取
(2)输入数据准备
(3)模型推理
其中有一些实现的细节没有深入研究
1.模型读取(readNetFromONNX())
readNetFromONNX()用于从指定路径读取onnx模型,函数定义在sources/modules/dnn/src/onnx/onnx_importer.cpp中
Net readNetFromONNX(const String& onnxFile)
{return detail::readNetDiagnostic<ONNXImporter>(onnxFile.c_str());
}
detail::readNetDiagnostic<ONNXImporter>的定义位于sources/modules/dnn/src/common_dnn.hpp中,用于导入一个网络模型
template <typename Importer, typename ... Args>
Net readNetDiagnostic(Args&& ... args)
{Net maybeDebugNet = readNet<Importer>(std::forward<Args>(args)...);// DNN_DIAGNOSTICS_RUN默认为falseif (DNN_DIAGNOSTICS_RUN && !DNN_SKIP_REAL_IMPORT){// if we just imported the net in diagnostic mode, disable it and import againenableModelDiagnostics(false);Net releaseNet = readNet<Importer>(std::forward<Args>(args)...);enableModelDiagnostics(true);return releaseNet;}return maybeDebugNet;
}
readNet()读取了具体的网络模型
template <typename Importer, typename ... Args>
Net readNet(Args&& ... args)
{Net net;Importer importer(net, std::forward<Args>(args)...);return net;
}
如果是ONNX模型,调用的是ONNXImporter的构造函数
ONNXImporter::ONNXImporter(Net& net, const char *onnxFile): layerHandler(DNN_DIAGNOSTICS_RUN ? new ONNXLayerHandler(this) : nullptr), dstNet(net), onnx_opset(0), useLegacyNames(getParamUseLegacyNames())
{hasDynamicShapes = false;CV_Assert(onnxFile);CV_LOG_DEBUG(NULL, "DNN/ONNX: processing ONNX model from file: " << onnxFile);std::fstream input(onnxFile, std::ios::in | std::ios::binary);if (!input){CV_Error(Error::StsBadArg, cv::format("Can't read ONNX file: %s", onnxFile));}// 使用Protocol Buffers库来解析输入的文件流if (!model_proto.ParseFromIstream(&input)){CV_Error(Error::StsUnsupportedFormat, cv::format("Failed to parse ONNX model: %s", onnxFile));}// 前面GraphProto中解析出网络结构,将其转换为OpenCV DNN可运行的内部表示(即构建 dstNet)populateNet();
}
populateNet()的定义
void ONNXImporter::populateNet()
{CV_Assert(model_proto.has_graph());graph_proto = model_proto.mutable_graph();std::string framework_version;if (model_proto.has_producer_name())framework_name = model_proto.producer_name();if (model_proto.has_producer_version())framework_version = model_proto.producer_version();CV_LOG_INFO(NULL, "DNN/ONNX: loading ONNX"<< (model_proto.has_ir_version() ? cv::format(" v%d", (int)model_proto.ir_version()) : cv::String())<< " model produced by '" << framework_name << "'"<< (framework_version.empty() ? cv::String() : cv::format(":%s", framework_version.c_str()))<< ". Number of nodes = " << graph_proto->node_size()<< ", initializers = " << graph_proto->initializer_size()<< ", inputs = " << graph_proto->input_size()<< ", outputs = " << graph_proto->output_size());/* 1.解析操作符集合版本,初始化解析函数 */ parseOperatorSet();// 简化graphsimplifySubgraphs(*graph_proto);const int layersSize = graph_proto->node_size();CV_LOG_DEBUG(NULL, "DNN/ONNX: graph simplified to " << layersSize << " nodes");/* 2.提取张量 */// graph_proto表示ONNX图的原型对象,包含模型的所有信息,包括初始化器、输入输出等constBlobs = getGraphTensors(*graph_proto); // scan GraphProto.initializerstd::vector<String> netInputs; // map with network inputs (without const blobs)// Add all the inputs shapes. It includes as constant blobs as network's inputs shapes.for (int i = 0; i < graph_proto->input_size(); ++i){const opencv_onnx::ValueInfoProto& valueInfoProto = graph_proto->input(i);CV_Assert(valueInfoProto.has_name());const std::string& name = valueInfoProto.name();CV_Assert(valueInfoProto.has_type());const opencv_onnx::TypeProto& typeProto = valueInfoProto.type();CV_Assert(typeProto.has_tensor_type());const opencv_onnx::TypeProto::Tensor& tensor = typeProto.tensor_type();CV_Assert(tensor.has_shape());const opencv_onnx::TensorShapeProto& tensorShape = tensor.shape();int dim_size = tensorShape.dim_size();CV_CheckGE(dim_size, 0, ""); // some inputs are scalars (dims=0), e.g. in Test_ONNX_nets.Resnet34_kinetics testMatShape inpShape(dim_size);for (int j = 0; j < dim_size; ++j){const opencv_onnx::TensorShapeProto_Dimension& dimension = tensorShape.dim(j);if (dimension.has_dim_param()){CV_LOG_DEBUG(NULL, "DNN/ONNX: input[" << i << "] dim[" << j << "] = <" << dimension.dim_param() << "> (dynamic)");}// https://github.com/onnx/onnx/blob/master/docs/DimensionDenotation.md#denotation-definitionif (dimension.has_denotation()){CV_LOG_INFO(NULL, "DNN/ONNX: input[" << i << "] dim[" << j << "] denotation is '" << dimension.denotation() << "'");}inpShape[j] = dimension.dim_value();// NHW, NCHW(NHWC), NCDHW(NDHWC); do not set this flag if only N is dynamicif (dimension.has_dim_param() && !(j == 0 && inpShape.size() >= 3)){hasDynamicShapes = true;}}bool isInitialized = ((constBlobs.find(name) != constBlobs.end()));CV_LOG_IF_DEBUG(NULL, !isInitialized, "DNN/ONNX: input[" << i << " as '" << name << "'] shape=" << toString(inpShape));CV_LOG_IF_VERBOSE(NULL, 0, isInitialized, "DNN/ONNX: pre-initialized input[" << i << " as '" << name << "'] shape=" << toString(inpShape));if (dim_size > 0 && !hasDynamicShapes) // FIXIT result is not reliable for models with multiple inputs{inpShape[0] = std::max(inpShape[0], 1); // It's OK to have undetermined batch size}outShapes[valueInfoProto.name()] = inpShape;// fill map: push layer name, layer id and output idif (!isInitialized){netInputs.push_back(name);layer_id.insert(std::make_pair(name, LayerInfo(0, netInputs.size() - 1)));}}dstNet.setInputsNames(netInputs);if (!hasDynamicShapes){for (int i = 0; i < netInputs.size(); ++i)dstNet.setInputShape(netInputs[i], outShapes[netInputs[i]]);}// dump outputsfor (int i = 0; i < graph_proto->output_size(); ++i){dumpValueInfoProto(i, graph_proto->output(i), "output");}if (DNN_DIAGNOSTICS_RUN) {CV_LOG_INFO(NULL, "DNN/ONNX: start diagnostic run!");layerHandler->fillRegistry(*graph_proto);}/* 3.将节点转换成OpenCV内部层结构 */for(int li = 0; li < layersSize; li++) // layersSize表示一共有多少层{const opencv_onnx::NodeProto& node_proto = graph_proto->node(li);handleNode(node_proto);}// register outputsfor (int i = 0; i < graph_proto->output_size(); ++i){const std::string& output_name = graph_proto->output(i).name();if (output_name.empty()){CV_LOG_ERROR(NULL, "DNN/ONNX: can't register output without name: " << i);continue;}ConstIterLayerId_t layerIt = layer_id.find(output_name);if (layerIt == layer_id.end()){CV_LOG_ERROR(NULL, "DNN/ONNX: can't find layer for output name: '" << output_name << "'. Does model imported properly?");continue;}const LayerInfo& li = layerIt->second;int outputId = dstNet.registerOutput(output_name, li.layerId, li.outputId); CV_UNUSED(outputId);// no need to duplicate message from engine: CV_LOG_DEBUG(NULL, "DNN/ONNX: registered output='" << output_name << "' with id=" << outputId);}CV_LOG_DEBUG(NULL, (DNN_DIAGNOSTICS_RUN ? "DNN/ONNX: diagnostic run completed!" : "DNN/ONNX: import completed!"));
}
1.1 初始化解析函数(parseOperatorSet())
parseOperatorSet()的核心函数是buildDispatchMap_ONNX_AI(),用于构建解析函数map
void ONNXImporter::parseOperatorSet()
{// ...CV_LOG_INFO(NULL, "DNN/ONNX: ONNX opset version = " << onnx_opset);// 构建解析函数map,后续会基于这个map进行模型各模块的解析buildDispatchMap_ONNX_AI(onnx_opset);for (const auto& pair : onnx_opset_map){if (pair.first == str_domain_ai_onnx){continue; // done above}else if (pair.first == "com.microsoft"){buildDispatchMap_COM_MICROSOFT(pair.second);}else{CV_LOG_INFO(NULL, "DNN/ONNX: unknown domain='" << pair.first << "' version=" << pair.second << ". No dispatch map, you may need to register 'custom' layers.");}}
}
1.2 提取张量(getGraphTensors())
getGraphTensors()用于将文件中提取的张量数据转换成OpenCV的mat格式,便于后续处理
std::map<std::string, Mat> ONNXImporter::getGraphTensors(const opencv_onnx::GraphProto& graph_proto)
{std::map<std::string, Mat> layers_weights;for (int i = 0; i < graph_proto.initializer_size(); i++){const opencv_onnx::TensorProto& tensor_proto = graph_proto.initializer(i);// 打印tensor信息dumpTensorProto(i, tensor_proto, "initializer");// 将tensor信息转换成mat,便于后续构建OpenCV的神经网络Mat mat = getMatFromTensor(tensor_proto);// 丢弃掉已经处理过的数据releaseONNXTensor(const_cast<opencv_onnx::TensorProto&>(tensor_proto)); // drop already loaded dataif (DNN_DIAGNOSTICS_RUN && mat.empty())continue;// 数据插入队列layers_weights.insert(std::make_pair(tensor_proto.name(), mat));constBlobsExtraInfo.insert(std::make_pair(tensor_proto.name(), TensorInfo(tensor_proto.dims_size())));}return layers_weights;
}
getMatFromTensor()
Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto)
{if (tensor_proto.raw_data().empty() && tensor_proto.float_data().empty() &&tensor_proto.double_data().empty() && tensor_proto.int64_data().empty() &&tensor_proto.int32_data().empty())return Mat();opencv_onnx::TensorProto_DataType datatype = tensor_proto.data_type();Mat blob;std::vector<int> sizes;for (int i = 0; i < tensor_proto.dims_size(); i++) {sizes.push_back(tensor_proto.dims(i));}if (sizes.empty())sizes.assign(1, 1);if (datatype == opencv_onnx::TensorProto_DataType_FLOAT) {if (!tensor_proto.float_data().empty()) {const ::google::protobuf::RepeatedField<float> field = tensor_proto.float_data();Mat(sizes, CV_32FC1, (void*)field.data()).copyTo(blob);}else {char* val = const_cast<char*>(tensor_proto.raw_data().c_str());// 构建mat,将读取的信息传递给blobMat(sizes, CV_32FC1, val).copyTo(blob);}}// ...
1.3 节点处理(handleNode())
它的作用是 处理 ONNX 模型中的一个节点(node),也就是一个算子(operator),并将其转换为 OpenCV 内部可用的层(Layer)结构。这个函数是整个 ONNX 模型导入流程中的核心部分
void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto)
{CV_Assert(node_proto.output_size() >= 1);const std::string& name = extractNodeName(node_proto);const std::string& layer_type = node_proto.op_type();const std::string& layer_type_domain = getLayerTypeDomain(node_proto);const auto& dispatch = getDispatchMap(node_proto);CV_LOG_INFO(NULL, "DNN/ONNX: processing node with " << node_proto.input_size() << " inputs and "<< node_proto.output_size() << " outputs: "<< cv::format("[%s]:(%s)", layer_type.c_str(), name.c_str())<< cv::format(" from %sdomain='", onnx_opset_map.count(layer_type_domain) == 1 ? "" : "undeclared ")<< layer_type_domain << "'");// 检查是否有对应的解析器if (dispatch.empty()){CV_LOG_WARNING(NULL, "DNN/ONNX: missing dispatch map for domain='" << layer_type_domain << "'");}LayerParams layerParams;try{// FIXIT not all cases can be repacked into "LayerParams". Importer should handle such cases directly for each "layer_type"layerParams = getLayerParams(node_proto);layerParams.name = name;layerParams.type = layer_type;layerParams.set("has_dynamic_shapes", hasDynamicShapes);setParamsDtype(layerParams, node_proto);DispatchMap::const_iterator iter = dispatch.find(layer_type);if (iter != dispatch.end()){// 调用解析函数CALL_MEMBER_FN(*this, iter->second)(layerParams, node_proto);}else{parseCustomLayer(layerParams, node_proto);}}// ...
}
这里会根据具体的模块调用对应的解析函数,例如卷积模块(Conv)会调用parseConv()
void ONNXImporter::parseConv(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_)
{opencv_onnx::NodeProto node_proto = node_proto_;// /* 卷积层至少需要2个输入(1) 特征图 feature map(2) 输入权重 weight tensor(3) 偏置项 bias(可选)*/CV_Assert(node_proto.input_size() >= 2);// 表明类型为卷积层,后续据此构建对应的卷积层类layerParams.type = "Convolution";// 提取权重和偏置项for (int j = 1; j < node_proto.input_size(); j++) {// 查找是否是常量张量if (constBlobs.find(node_proto.input(j)) != constBlobs.end()){// 通过getBlob()获取对应的mat数据,送入blobs中layerParams.blobs.push_back(getBlob(node_proto, j));}}int outCn = layerParams.blobs.empty() ? outShapes[node_proto.input(1)][0] : layerParams.blobs[0].size[0];layerParams.set("num_output", outCn);addLayer(layerParams, node_proto);
}
2.数据准备(blobFromImage() & setInput())
cv::dnn::blobFromImage()的作用是将输入的mat格式数据转换成为4维的blob(batch,channel,height,width),便于后续的网络推理。函数的定义位于sources/modules/dnn/src/dnn_utils.cpp
void blobFromImage(InputArray image, OutputArray blob, double scalefactor,const Size& size, const Scalar& mean, bool swapRB, bool crop, int ddepth)
{CV_TRACE_FUNCTION();if (image.kind() == _InputArray::UMAT) {// UMat是OpenCL加速的图像表示std::vector<UMat> images(1, image.getUMat());blobFromImages(images, blob, scalefactor, size, mean, swapRB, crop, ddepth);} else {std::vector<Mat> images(1, image.getMat());blobFromImages(images, blob, scalefactor, size, mean, swapRB, crop, ddepth);}
}
经过多层调用之后,blobFromImagesNCHWImpl()用于进行实际的格式转换任务
// Tinp = uint8_t, Tout = float
template<typename Tinp, typename Tout>
void blobFromImagesNCHWImpl(const std::vector<Mat>& images, Mat& blob_, const Image2BlobParams& param)
{int w = images[0].cols;int h = images[0].rows;int wh = w * h;int nch = images[0].channels();CV_Assert(nch == 1 || nch == 3 || nch == 4);int sz[] = { (int)images.size(), nch, h, w};blob_.create(4, sz, param.ddepth);for (size_t k = 0; k < images.size(); ++k) // 遍历所有输入图像{CV_Assert(images[k].depth() == images[0].depth());CV_Assert(images[k].channels() == images[0].channels());CV_Assert(images[k].size() == images[0].size());// RGBA格式,A是透明度通道Tout* p_blob = blob_.ptr<Tout>() + k * nch * wh;Tout* p_blob_r = p_blob;Tout* p_blob_g = p_blob + wh;Tout* p_blob_b = p_blob + 2 * wh;Tout* p_blob_a = p_blob + 3 * wh;if (param.swapRB) // 默认为truestd::swap(p_blob_r, p_blob_b);for (size_t i = 0; i < h; ++i){const Tinp* p_img_row = images[k].ptr<Tinp>(i);if (nch == 1) // 灰度图像{for (size_t j = 0; j < w; ++j){p_blob[i * w + j] = p_img_row[j];}}else if (nch == 3) // RGB{/* p_img_row的存储格式 (RGB, 如果swapRB, 格式为BGR)r g b r g b r g b ...p_blob的存储格式r0 r1 r2 ... g0 g1 g2 ... b0 b1 b2 ...*/for (size_t j = 0; j < w; ++j){p_blob_r[i * w + j] = p_img_row[j * 3 ];p_blob_g[i * w + j] = p_img_row[j * 3 + 1];p_blob_b[i * w + j] = p_img_row[j * 3 + 2];}}else // if (nch == 4) -> RGBA{/* p_img_row的存储格式 (RGBA, 如果swapRB, 格式为BGRA)r g b a r g b a r g b a ...p_blob的存储格式r0 r1 r2 ... g0 g1 g2 ... b0 b1 b2 ... a0 a1 a2 ...*/for (size_t j = 0; j < w; ++j){p_blob_r[i * w + j] = p_img_row[j * 4 ];p_blob_g[i * w + j] = p_img_row[j * 4 + 1];p_blob_b[i * w + j] = p_img_row[j * 4 + 2];p_blob_a[i * w + j] = p_img_row[j * 4 + 3];}}}}if (param.mean == Scalar() && param.scalefactor == Scalar::all(1.0))return;CV_CheckTypeEQ(param.ddepth, CV_32F, "Scaling and mean substraction is supported only for CV_32F blob depth");/* 应用给定的均值减法和缩放因子 */for (size_t k = 0; k < images.size(); ++k){for (size_t ch = 0; ch < nch; ++ch){float cur_mean = param.mean[ch];float cur_scale = param.scalefactor[ch];Tout* p_blob = blob_.ptr<Tout>() + k * nch * wh + ch * wh;for (size_t i = 0; i < wh; ++i){p_blob[i] = (p_blob[i] - cur_mean) * cur_scale;}}}
}
setInput()用于为神经网络设置输入数据
void Net::setInput(InputArray blob, const String& name, double scalefactor, const Scalar& mean)
{CV_TRACE_FUNCTION();CV_TRACE_ARG_VALUE(name, "name", name.c_str());CV_Assert(impl);return impl->setInput(blob, name, scalefactor, mean);
}
impl->setInput()调用的是Impl::setInput()
void Net::Impl::setInput(InputArray blob, const String& name, double scalefactor, const Scalar& mean)
{// 性能优化手段,用来避免在浮点运算中处理极小值(denormal numbers),提高计算效率FPDenormalsIgnoreHintScope fp_denormals_ignore_scope;/* 举例:Input -> Conv1 -> ReLU -> Conv2 -> Output\ /\-> MaxPool如果 Conv2 的输入来自 ReLU 和 MaxPool,则需要两个 LayerPin:{lid_of_ReLU, 0}(ReLU的第1个输出){lid_of_MaxPool, 0}(MaxPool的第1个输出)*/// LayerPin表示某一层的输入或输出节点,类似于插槽LayerPin pin;// 0表示为当前网络的输入层,即当前的pin连接的是网络的输入端口pin.lid = 0; // 根据层的名称解析出对应的输出端口索引pin.oid = resolvePinOutputName(getLayerData(pin.lid), name);if (!pin.valid())CV_Error(Error::StsObjectNotFound, "Requested blob \"" + name + "\" not found");Mat blob_ = blob.getMat(); // can't use InputArray directly due MatExpr stuffMatShape blobShape = shape(blob_);// ...LayerData& ld = layers[pin.lid];const int numInputs = std::max(pin.oid + 1, (int)ld.requiredOutputs.size());ld.outputBlobs.resize(numInputs);ld.outputBlobsWrappers.resize(numInputs);netInputLayer->inputsData.resize(numInputs);netInputLayer->scaleFactors.resize(numInputs);netInputLayer->means.resize(numInputs);MatShape prevShape = shape(netInputLayer->inputsData[pin.oid]);bool oldShape = prevShape == blobShape;// 拷贝数据到输入层blob_.copyTo(netInputLayer->inputsData[pin.oid]);if (!oldShape)ld.outputBlobs[pin.oid] = netInputLayer->inputsData[pin.oid];if (!ld.outputBlobsWrappers[pin.oid].empty()){ld.outputBlobsWrappers[pin.oid]->setHostDirty();}netInputLayer->scaleFactors[pin.oid] = scalefactor;netInputLayer->means[pin.oid] = mean;netWasAllocated = netWasAllocated && oldShape;
}
3.模型推理(forward())
网络模型的推理由Net::forward()函数实现,定义在sources/modules/dnn/src/net.cpp中
void Net::forward(OutputArrayOfArrays outputBlobs,const std::vector<String>& outBlobNames)
{CV_TRACE_FUNCTION();CV_Assert(impl);CV_Assert(!empty());return impl->forward(outputBlobs, outBlobNames);
}// outBlobNames的输入由getUnconnectedOutLayersNames()给出,
// 用于获取网络模型中所有未连接输出的层的名称,这些层通常是指那些没有被其他层作为输入使用的输出层,
// 它们代表了神经网络的最终输出
std::vector<String> Net::getUnconnectedOutLayersNames() const
{CV_TRACE_FUNCTION();CV_Assert(impl);return impl->getUnconnectedOutLayersNames();
}
impl->forward()的定义
void Net::Impl::forward(OutputArrayOfArrays outputBlobs,const std::vector<String>& outBlobNames)
{CV_Assert(!empty());FPDenormalsIgnoreHintScope fp_denormals_ignore_scope;std::vector<LayerPin> pins;for (int i = 0; i < outBlobNames.size(); i++){// getPinByAlias()通过别名找到该层的输出端口信息,包括层ID和编号// 如果只有一个outBlobNames,只找到一个输出端口pins.push_back(getPinByAlias(outBlobNames[i]));}// 初始化网络结构setUpNet(pins);// 找到最后一个需要处理的层LayerPin out = getLatestLayerPin(pins);// 执行前向传播forwardToLayer(getLayerData(out.lid));// 遍历所有输出节点,提取每层的输出 blobstd::vector<Mat> matvec;for (int i = 0; i < pins.size(); i++){matvec.push_back(getBlob(pins[i]));}outputBlobs.create((int)matvec.size(), 1, CV_32F/*FIXIT*/, -1); // allocate vectoroutputBlobs.assign(matvec);
}
forwardToLayer()具体执行推理的任务,定义在net_impl.cpp中
void Net::Impl::forwardToLayer(LayerData& ld, bool clearFlags)
{CV_TRACE_FUNCTION();// clearFlags为true,表明要遍历网络中所有的层if (clearFlags){// 遍历所有层,将其中的标记状态置为0for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++)it->second.flag = 0;}// already was forwardedif (ld.flag)return;// forward parents// 遍历所有层,找到目标层之前的所有父层,确保所有父层都进行过推理// 由于函数输入层是最后一个输出层,所以这里会对前面网络的层都进行一遍推理for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end() && (it->second.id < ld.id); ++it){LayerData& ld = it->second;if (ld.flag)continue;forwardLayer(ld);}// forward itselfforwardLayer(ld);#ifdef HAVE_CUDAif (preferableBackend == DNN_BACKEND_CUDA)cudaInfo->context.stream.synchronize();
#endif
}
forwardLayer()调用的是net_impl.cpp中的文件,其中会调用layer->forward(inps, ld.outputBlobs, ld.internals);,后面会根据当前层类型调用推理函数,以Conv卷积层为例,会调用convolution_layer.cpp中的forward()函数,执行具体的fastConv()操作,卷积操作涉及的比较底层,不再深入分析。
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE
{CV_TRACE_FUNCTION();CV_TRACE_ARG_VALUE(name, "name", name.c_str()); // name为当前层名称,例如"model.0/Conv"CV_OCL_RUN(IS_DNN_OPENCL_TARGET(preferableTarget),forward_ocl(inputs_arr, outputs_arr, internals_arr))// ...{int nstripes = std::max(getNumThreads(), 1);int conv_dim = CONV_2D;if (inputs[0].dims == 3)conv_dim = CONV_1D;if (inputs[0].dims == 5)conv_dim = CONV_3D;// Initialization of FastCovn2d, pack weight.if (!fastConvImpl || variableWeight){int K = outputs[0].size[1];int C = inputs[0].size[1];// Winograd only works when input h and w >= 12.bool canUseWinograd = useWinograd && conv_dim == CONV_2D && inputs[0].size[2] >= 12 && inputs[0].size[3] >= 12;CV_Assert(outputs[0].size[1] % ngroups == 0);// 初始化fastConvfastConvImpl = initFastConv(weightsMat, &biasvec[0], ngroups, K, C, kernel_size, strides,dilations, pads_begin, pads_end, conv_dim,preferableTarget == DNN_TARGET_CPU_FP16, canUseWinograd);// This is legal to release weightsMat here as this is not used anymore for// OpenCV inference. If network needs to be reinitialized (new shape, new backend)// a new version of weightsMat is created at .finalize() from original weightsweightsMat.release();}// 执行fastConvrunFastConv(inputs[0], outputs[0], fastConvImpl, nstripes, activ, reluslope, fusedAdd);}
}
net.forward()使用举例,net.getUnconnectedOutLayersNames()会获取所有未连接输出的层的名称,这些层通常是指那些没有被其他层作为输入的层,通常就是输出层,这里表明会进行整个网络模型的推理。推理的结果会被存储到outputs中。
std::vector<cv::Mat> outputs;
net.forward(outputs, net.getUnconnectedOutLayersNames());
如果想要获得其中某些层,例如第{10, 20, 30}层的中间结果,可以修改net.forward()的入参
std::vector<cv::Mat> outputs;
net.forward(outputs, 第{10, 20, 30}层的名称);
相关文章:
【OpenCV】网络模型推理的简单流程分析(readNetFromONNX、setInput和forward等)
目录 1.模型读取(readNetFromONNX())1.1 初始化解析函数(parseOperatorSet())1.2 提取张量(getGraphTensors())1.3 节点处理(handleNode()) 2.数据准备(blobFromImage() …...
代码随想录算法训练营第三十九天
LeetCode题目: 115. 不同的子序列583. 两个字符串的删除操作72. 编辑距离 其他: 今日总结 往期打卡 115. 不同的子序列 跳转: 115. 不同的子序列 学习: 代码随想录公开讲解 问题: 给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。 测试用例保…...
InternVL3: 利用AI处理文本、图像、视频、OCR和数据分析
InternVL3推动了视觉-语言理解、推理和感知的边界。 在其前身InternVL 2.5的基础上,这个新版本引入了工具使用、GUI代理操作、3D视觉和工业图像分析方面的突破性能力。 让我们来分析一下是什么让InternVL3成为游戏规则的改变者 — 以及今天你如何开始尝试使用它。 InternVL…...
力扣刷题Day 48:盛最多水的容器(283)
1.题目描述 2.思路 学习了Krahets佬的双指针思路,初始化两个边界作为容器边界,然后逐个向数组内遍历,直到左右两指针相遇。 3.代码(Python3) class Solution:def maxArea(self, height: List[int]) -> int:left,…...
基于单应性矩阵变换的图像拼接融合
单应性矩阵变换 单应性矩阵是一个 3x3 的可逆矩阵,它描述了两个平面之间的投影变换关系。在图像领域,单应性矩阵可以将一幅图像中的点映射到另一幅图像中的对应点,前提是这两幅图像是从不同视角拍摄的同一平面场景。 常见的应用场景&#x…...
《驱动开发硬核特训 · 专题篇》:深入理解 I2C 子系统
关键词:i2c_adapter、i2c_client、i2c_driver、i2c-core、platform_driver、设备树匹配、驱动模型 本文目标:通过实际代码一步步讲清楚 I2C 子系统的结构与运行机制,让你不再混淆 platform_driver 与 i2c_driver 的职责。 🧩 一、…...
Spark缓存-cache
一、RDD持久化 1.什么时候该使用持久化(缓存) 2. RDD cache & persist 缓存 3. RDD CheckPoint 检查点 4. cache & persist & checkpoint 的特点和区别 特点 区别 二、cache & persist 的持久化级别及策略选择 Spark的几种持久化…...
tails os系统详解
一、起源与发展背景 1. 项目初衷与历史 创立时间:Tails 项目始于 2004 年,最初名为 “Anonymous Live CD”,2009 年正式更名为 “Tails”(The Amnesic Incognito Live System,“健忘的匿名实时系统”)。核…...
[洛谷刷题9]
P2376 [USACO09OCT] Allowance G(贪心) https://www.luogu.com.cn/problem/P2376 题目描述 作为创造产奶纪录的回报,Farmer John 决定开始每个星期给Bessie 一点零花钱。 FJ 有一些硬币,一共有 N ( 1 ≤ N ≤ 20 ) N (1 \le …...
数据挖掘入门-二手车交易价格预测
一、二手车交易价格预测 1-1 项目背景 随着二手车市场的快速发展,二手车交易价格的预测成为了一个热门研究领域。精准的价格预测不仅能帮助买卖双方做出更明智的决策,还能促进市场的透明度和公平性。对于买家来说,了解合理的市场价格可以避免…...
【PostgreSQL数据分析实战:从数据清洗到可视化全流程】金融风控分析案例-10.4 模型部署与定期评估
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 10.4 模型部署与定期评估10.4.1 模型部署架构设计1.1 模型存储方案1.2 实时预测接口 10.4.2 定期评估体系构建2.1 评估指标体系2.2 自动化评估流程2.3 模型衰退预警 10.4.3 …...
构建可信数据空间需要突破技术、规则和生态三大关键
构建可信数据空间需要突破技术、规则和生态三大关键:技术上要解决"可用不可见"的隐私计算难题,规则上要建立动态确权和跨境流动的治理框架,生态上要形成多方协同的标准体系。他强调,只有实现技术可控、规则可信、生态协…...
阳光学院【2020下】计算机网络原理-A卷-试卷-期末考试试卷
一、单选题(共25分,每空1分) 1.ICMP协议工作在TCP/IP参考模型的 ( ) A.主机-网络 B.网络互联层 C.传输层 D.应用层 2.下列关于交换技术的说法中,错误的是 ( ) A.电路交换适用于突发式通信 B.报文交换不能满足实时通信 C.报文…...
python: union()函数用法
在 Python 中,union() 是集合(set)类型的内置方法,用于返回两个或多个集合的并集(即所有元素的合集,自动去重)。以下是它的用法详解: 1. 基本语法 python 复制 下载 set.union(*…...
docker部署WeDataSphere开源大数据平台
GitHub:https://github.com/WeBankFinTech/WeDataSphere **WDS容器化版本是由Docker构建的一个能够让用户在半小时内完成所有组件安装部署并使用的镜像包。**无需再去部署Hadoop等基础组件,也不需要部署WDS的各功能组件,即可让您快速体验 WD…...
【计算机视觉】OpenCV项目实战:基于face_recognition库的实时人脸识别系统深度解析
基于face_recognition库的实时人脸识别系统深度解析 1. 项目概述2. 技术原理与算法设计2.1 人脸检测模块2.2 特征编码2.3 相似度计算 3. 实战部署指南3.1 环境配置3.2 数据准备3.3 实时识别流程 4. 常见问题与解决方案4.1 dlib安装失败4.2 人脸检测性能差4.3 误识别率高 5. 关键…...
uni-app学习笔记五-vue3响应式基础
一.使用ref定义响应式变量 在组合式 API 中,推荐使用 ref() 函数来声明响应式状态,ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回 示例代码: <template> <view>{{ num1 }}</view><vi…...
阿克曼-幻宇机器人系列教程2- 机器人交互实践(Topic)
在上一篇文章中,我们介绍了两种登录机器人的方式,接下来我们介绍登录机器人之后,我们如何通过topic操作命令实现与机器人的交互。 1. 启动 & 获取topic 在一个终端登录树莓派后,执行下列命令运行机器人 roslaunch huanyu_r…...
Windows系统事件查看器管理单元不可用
报错:Windows系统事件查看器管理单元不可用 现象原因:为误触关闭管理单元或者该模块卡死 解决办法:重启Windows server服务,若不行,则重启服务器即可...
milvus+flask山寨《从零构建向量数据库》第7章case2
继续流水账完这本书,这个案例是打造文字形式的个人知识库雏形。 create_context_db: # Milvus Setup Arguments COLLECTION_NAME text_content_search DIMENSION 2048 MILVUS_HOST "localhost" MILVUS_PORT "19530"# Inference Arguments…...
前端如何应对精确数字运算?用BigNumber.js解决JavaScript原生Number类型在处理大数或高精度计算时的局限性
目录 前端如何应对精确数字运算?用BigNumber.js解决JavaScript原生Number类型在处理大数或高精度计算时的局限性 一、BigNumber.js介绍 1、什么是 BigNumber.js? 2、作用领域 3、核心特性 二、安装配置与基础用法 1、引入 BigNumber.js 2、配置 …...
多目应用:三目相机在汽车智能驾驶领域的应用与技术创新
随着汽车智能驾驶技术不断完善,智能汽车也不断加速向全民普惠迈进,其中智驾“眼睛”三目视觉方案凭借低成本、高精度、强适配性成为众多汽车品牌关注的焦点。三目相机在汽车智能驾驶领域的创新应用,主要依托其多视角覆盖、高动态范围…...
webpack重构优化
好的,以下是一个关于如何通过重构 Webpack 构建策略来优化性能的示例。这个过程包括分析现有构建策略的问题、优化策略的制定以及具体的代码实现。 --- ### 项目背景 在参与公司的性能专项优化过程中,我发现现有的 Webpack 构建策略存在一些问题&#…...
MySQL 8.0 OCP(1Z0-908)英文题库(31-40)
目录 第31题题目分析正确答案 第32题题目分析正确答案 第33题题目分析正确答案: 第34题题目解析正确答案 第35题题目分析正确答案 第36题题目分析正确答案 第37题题目分析正确答案 第38题题目分析正确答案 第39题题目分析正确答案 第40题题目分析正确答案 第31题 Y…...
aardio - 虚表 —— vlistEx.listbar2 多层菜单演示
在 近我者赤 修改版的基础上,做了些许优化。 请升级到最新版本。 import win.ui; import godking.vlistEx.listbar2; import fonts.fontAwesome; /*DSG{{*/ mainForm win.form(text"多层折叠菜单";right1233;bottom713) mainForm.add({ custom{cls"…...
22.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--增加公共代码
在拆分服务之前,我们需要先提取一些公共代码。本篇将重点新增日志记录、异常处理以及Redis的通用代码。这些组件将被整合到一个共享类库中,便于在微服务架构中高效复用。 Tip:在后续的教程中我们会穿插多篇提取公共代码的文章,帮助…...
EasyOps®5月热力焕新:三大核心模块重构效能边界
在应用系统管理中,我们将管理对象从「服务实例」优化为「部署实例」,这一改变旨在提升管理效率与数据展示清晰度。 此前,系统以 “IP Port” 组合定义服务实例。当同一 IP 下启用多个进程或端口时,会产生多个服务实例。比如一台…...
基于深度学习的工业OCR数字识别系统架构解析
一、项目场景 春晖数字识别视觉检测系统专注于工业自动化生产监控、设备运行数据记录等关键领域。系统通过高精度OCR算法,能够实时识别设备上显示的关键数据(如温度、压力、计数等),并定时存储至Excel文件中。这些数据对于生产过…...
R语言绘图 | 渐变火山图
客户要求绘制类似文章中的这种颜色渐变火山图,感觉挺好看的。网上找了一圈,发现有别人已经实现的类似代码,拿来修改后即可使用,这里做下记录,以便后期查找。 简单实现 library(tidyverse)library(ggrepel)library(ggf…...
Go语言——docker-compose部署etcd以及go使用其服务注册
一、docker-compsoe.yml文件如下 version: "3.5"services:etcd:hostname: etcdimage: bitnami/etcd:latestdeploy:replicas: 1restart_policy:condition: on-failureprivileged: truevolumes:# 持久化 etcd 数据到宿主机- "/app/apisix/etcd/data:/bitnami/etc…...
Tomcat的调优
目录 一. JVM 1.1 JVM的组成 1.2 运行时数据区域的组成 二. 垃圾回收 2.1 如何确认垃圾 1. 引用计数法 2. 根搜索算法 2.2 垃圾回收基本算法 1. 标记-清除算法(Mark-Sweep) 2. 标记-压缩算法(Mark-Compact) 3. 复制算法…...
Tomcat和Nginx的主要区别
1、功能定位 Nginx:核心是高并发HTTP服务器和反向代理服务器,擅长处理静态资源(如HTML、图片)和负载均衡。Tomcat:是Java应用服务器,主要用于运行动态内容(如JSP、Servlet)…...
Python训练营打卡——DAY24(2025.5.13)
目录 一、元组 1. 通俗解释 2. 元组的特点 3. 元组的创建 4. 元组的常见用法 二、可迭代对象 1. 定义 2. 示例 3. 通俗解释 三、OS 模块 1. 通俗解释 2. 目录树 四、作业 1. 准备工作 2. 实战代码示例 3. 重要概念解析 一、元组 是什么:一种…...
【TDengine源码阅读】DLL_EXPORT
2025年5月13日,周二清晨 #ifdef WINDOWS #define DLL_EXPORT __declspec(dllexport) #else #define DLL_EXPORT #endif为啥Linux和MacOS平台时宏为空,难道Linux和mac不用定义导出函数吗? 这段代码是一个跨平台的宏定义,用于处理不…...
电子科技浪潮下的华秋电子:慕尼黑上海电子展精彩回顾
为期3天的2025慕尼黑上海电子展(electronica China 2025)于17日在上海新国际博览中心落下帷幕。 展会那规模,真不是吹的!本届展会汇聚了1,794家国内外行业知名品牌企业的展商来 “摆摊”,展览面积大得像个超级大迷宫&…...
TDengine编译成功后的bin目录下的文件的作用
2025年5月13日,周二清晨 以下是TDengine工具集中各工具的功能说明: 核心工具 taosd • TDengine的核心服务进程,负责数据存储、查询和集群管理。 taos • 命令行客户端工具,用于连接TDengine服务器并执行SQL操作。 taosBenchma…...
spark sql基本操作
Spark SQL 是 Apache Spark 的一个模块,用于处理结构化数据。它允许用户使用标准的 SQL 语法来查询数据,并且可以无缝地与 Spark 的其他功能(如 DataFrame、Dataset 和 RDD)结合使用。以下是 Spark SQL 的基本使用方法和一些常见操…...
采购流程规范化如何实现?日事清流程自动化助力需求、采购、财务高效协作
采购审批流程全靠人推进,内耗严重,效率低下? 花重金上了OA,结果功能有局限、不灵活? 问题出在哪里?是我们的要求太多、太苛刻吗?NO! 流程名称: 采购审批管理 流程功能…...
影刀RPA开发-CSS选择器介绍
影刀RPA网页自动化开发,很多时候需要我们查看页面源码,查找相关的元素属性,这就需要我们有必要了解CSS选择器。本文做了些简单的介绍。希望对大家有帮助! 1. CSS选择器概述 1.1 定义与作用 CSS选择器是CSS(层叠样式…...
DeepSeek、B(不是百度)AT、科大讯飞靠什么坐上中国Ai牌桌?
在国产AI舞台上,DeepSeek、阿里、字节、腾讯、讯飞群雄逐鹿,好不热闹。 这场堪称“军备竞赛”的激烈角逐,绝非简单的市场竞争,而是一场关乎技术、创新与未来布局的深度博弈。在竞赛中,五大模型各显神通,以…...
MySQL全局优化
目录 1 硬件层面优化 1.1 CPU优化 1.2 内存优化 1.3 存储优化 1.4 网络优化 2 系统配置优化 2.1 操作系统配置 2.2 MySQL服务配置 3 库表结构优化 4 SQL及索引优化 mysql可以从四个层面考虑优化,分别是 硬件系统配置库表结构SQL及索引 从成本和优化效果来看…...
【github】主页显示star和fork
数据收集:定期(例如每天)获取你所有仓库的 Star 和 Fork 总数。数据存储:将收集到的数据(时间戳、总 Star 数、总 Fork 数)存储起来。图表生成:根据存储的数据生成变化曲线图(通常是…...
网站遭受扫描攻击,大量爬虫应对策略
网站的日志里突然有很多访问路径不存在的,有些ip地址也是国外的,而且访问是在深夜且次数非常频繁紧密。判定就是不怀好意的扫描网站寻找漏洞。也有些是爬虫,且是国外的爬虫,有的也是不知道的爬虫爬取网站。网站的真实流量不多&…...
【 Redis | 实战篇 秒杀实现 】
目录 前言: 1.全局ID生成器 2.秒杀优惠券 2.1.秒杀优惠券的基本实现 2.2.超卖问题 2.3.解决超卖问题的方案 2.4.基于乐观锁来解决超卖问题 3.秒杀一人一单 3.1.秒杀一人一单的基本实现 3.2.单机模式下的线程安全问题 3.3.集群模式下的线程安全问题 前言&…...
手搓传染病模型(SEIARW)
在传染病传播的研究中,水传播途径是一个重要的考量因素。SEAIRW 模型(易感者 S - 暴露者 E - 感染者 I - 无症状感染者 A - 康复者 R - 水中病原体 W)综合考虑了人与人接触传播以及水传播的双重机制,为分析此类传染病提供了全面的…...
【C++】深入理解 unordered 容器、布隆过滤器与分布式一致性哈希
【C】深入理解 unordered 容器、布隆过滤器与分布式一致性哈希 在日常开发中,无论是数据结构优化、缓存设计,还是分布式架构搭建,unordered_map、布隆过滤器和一致性哈希都是绕不开的关键工具。它们高效、轻量,在性能与扩展性方面…...
第五天——贪心算法——射气球
1.题目 有一些球形气球贴在一个表示 XY 平面的平坦墙壁上。气球用一个二维整数数组 points 表示,其中 points[i] [xstart, xend] 表示第 i 个气球的水平直径范围从 xstart 到 xend。你并不知道这些气球的具体 y 坐标。 可以从 x 轴上的不同位置垂直向上࿰…...
麦肯锡110页PPT企业组织效能提升调研与诊断分析指南
“战略清晰、团队拼命、资源充足,但业绩就是卡在瓶颈期上不去……”这是许多中国企业面临的真实困境。表面看似健康的企业,往往隐藏着“组织亚健康”问题——跨部门扯皮、人才流失、决策迟缓、市场反应滞后……麦肯锡最新研究揭示:组织健康度…...
BFS算法篇——从晨曦到星辰,BFS算法在多源最短路径问题中的诗意航行(上)
文章目录 引言一、多源BFS的概述二、应用场景三、算法步骤四、代码实现五、代码解释六、总结 引言 在浩渺的图论宇宙中,图的每一条边、每一个节点都是故事的组成部分。每当我们站在一个复杂的迷宫前,开始感受它的深邃时,我们往往不再局限于从…...
理解 C# 中的各类指针
前言 变量可以理解成是一块内存位置的别名,访问变量也就是访问对应内存中的数据。 指针是一种特殊的变量,它存储了一个内存地址,这个内存地址代表了另一块内存的位置。 指针指向的可以是一个变量、一个数组元素、一个对象实例、一块非托管内存…...