PCL点云库入门——PCL库点云特征之PFH点特征直方图(Point Feature Histograms -PHF)

1、算法原理

        PFH点(Point Feature Histogram)特征直方图的原理涉及利用参数化查询点与邻域点之间的空间差异,并构建一个多维直方图以捕捉点的k邻域几何属性。这个高维超空间为特征表示提供了一个可度量的信息空间,对于点云对应曲面的六维姿态,它具有不变性,并且在不同的采样密度或邻域噪声水平下表现出鲁棒性。PFH特征描述表示法基于点与其k邻域之间的关系以及它们的估计法线,简而言之,它考虑了所有估计法线方向之间的相互作用,试图捕捉最佳的样本表面变化情况,以描述样本的几何特征。因此,合成特征超空间依赖于每个点的表面法线估计质量。图1展示了查询点Pq的PFH计算影响区域,Pq以红色标记并置于圆球中心,半径为r,所有与Pq的距离小于半径r的k邻元素(即所有点)都相互连接在一个网络中。最终的PFH特征描述子是通过计算邻域内所有点对之间关系得到的直方图,公用有K*(K+1)/2个点对。

 图1 

        PFH特征描述实现的步骤如下:

        第1步:构建局部坐标系

        为了描述两点Ps和Pt及与它们对应的法线Ns和Nt之间的相对偏差,我们在其中一个点上定义一个固定的 Darboux标系,为了使该坐标架被唯一的确定,如图2所示。

图2 

        其中u,v,w具体如下:

        其中,Ps被定义为源点,Pt被定义为目标点。源点即Ps的选择是使得其关联的法线与连接两点的直线之间的角度最小。 

        第2步:特征描述

        在第1步中的vuw坐标系下,法线Ns和Nt之间的相对偏差可以用一组角度特征描述,定义为下:

        其中α和ϕ为单位向量的内积,取值范围为[-1,1],θ为角度,取值范围[-π/2,π/2];d为两点欧式距离,取值范围为(0,2r)。Pq点的PFH特征为α,θ,ϕ,d组成的四元特征。

        第3步:特征编码:

        为计算查询点Pq的PFH特征,根据点的四元特征(α,θ,ϕ,d)构建频率直方图,具体而言,先将<α,θ,ϕ,d>中的每个特征值范围划分为b个子区间(默认为5,可自行计算并设置),则4个特征共有b^4个区间,然后,统计对应特征值落在每个子区间的点的数目,由于<α,ϕ,θ>三个特征都是法线之间的角度息,则它们可归为同一个空间,最后,计算每一个点对的特征值,直方图中对应于该点对四个特征值区间的统计个数加1,效果图3所示。

图3 

在实际工作中距离特征d通常跟设备捕获2.5D深度数据是相关的,临近点的距离d是从视点开始递增的,在不同视角下,这些值的变化意义不大,所以在扫描局部点密度影响特征时,省略距离d效果会更好。在PCL 点云库中 FPH 特征描述只用了三个角度特征,因而特征矢量是5^3=125 维,对应PCL中的pcl::PFHSignature125数据类型。内容如下:

  struct PFHSignature125{float histogram[125] = {0.f};static constexpr int descriptorSize () { return detail::traits::descriptorSize_v<PFHSignature125>; }inline PFHSignature125 () = default;friend std::ostream& operator << (std::ostream& os, const PFHSignature125& p);};

 2、主要成员函数和变量

        1、主要成员变量

         1)、每个角度特征区间的划分数,默认为5。

    int nr_subdiv_;

          2)、点的PFH特征描述的占位符。

    Eigen::VectorXf pfh_histogram_;

          3)、PFH的4元特征的占位符。

    Eigen::Vector4f pfh_tuple_;

          4)、map数据类型,用于优化冗余计算的效率。

    std::map<std::pair<int, int>, Eigen::Vector4f, std::less<>,Eigen::aligned_allocator<std::pair<const std::pair<int, int>, Eigen::Vector4f> > > feature_map_;

        5)、中间计算结果保存的对队列。

     std::queue<std::pair<int, int> > key_list_;

       6)、设置最大内部缓存大小。

      unsigned int max_cache_size_;

       7)、设置为true表示使用缓存中间计算结果,减少冗余计算。

     bool use_cache_;

        2、主要成员函数

        1)、设置最大内部缓存大小。默认值为2GB。

 inline voidsetMaximumCacheSize (unsigned int cache_size);

        2)、设置是否使用内部缓存机制减少冗余计算。

inline voidsetUseInternalCache (bool use_cache);

        3)、计算点三个角度和两点之间的距离的4元特征。

 bool computePairFeatures (const pcl::PointCloud<PointInT> &cloud, const pcl::PointCloud<PointNT> &normals,int p_idx, int q_idx, float &f1, float &f2, float &f3, float &f4);

        4)、基于具有法线的三维点的空间邻域,估计给定点的三个角(f1, f2, f3)特征的PFH特征直方图。

    void computePointPFHSignature (const pcl::PointCloud<PointInT> &cloud, const pcl::PointCloud<PointNT> &normals,const std::vector<int> &indices, int nr_split, Eigen::VectorXf &pfh_histogram);

3、主要部分代码注解

        1、计算点对的四元特征

//1、计算点对的四元特征
//
template <typename PointInT, typename PointNT, typename PointOutT> bool
pcl::PFHEstimation<PointInT, PointNT, PointOutT>::computePairFeatures(const pcl::PointCloud<PointInT>& cloud, const pcl::PointCloud<PointNT>&  normals,int p_idx, int q_idx, float& f1, float& f2, float& f3, float& f4)
{pcl::computePairFeatures(cloud.points[p_idx].getVector4fMap(),  normals.points[p_idx].getNormalVector4fMap(),cloud.points[q_idx].getVector4fMap(),  normals.points[q_idx].getNormalVector4fMap(),f1, f2, f3, f4);return (true);
}
//算法具体实现,对应1中第2步的公式;
bool pcl::computePairFeatures(const Eigen::Vector4f& p1, const Eigen::Vector4f& n1,const Eigen::Vector4f& p2, const Eigen::Vector4f& n2,float& f1, float& f2, float& f3, float& f4)
{//距离特征dEigen::Vector4f dp2p1 = p2 - p1;dp2p1[3] = 0.0f;f4 = dp2p1.norm();if (f4 == 0.0f){PCL_DEBUG("[pcl::computePairFeatures] Euclidean distance between points  is 0!\n");f1 = f2 = f3 = f4 = 0.0f;return (false);}//计算ϕ角度特征Eigen::Vector4f n1_copy = n1,n2_copy = n2;n1_copy[3] = n2_copy[3] = 0.0f;float angle1 = n1_copy.dot(dp2p1) / f4;// 根据角度大小来选择源点Ps,选择角度最小的为Ps点,std::acos是递减函数float angle2 = n2_copy.dot(dp2p1) / f4;if (std::acos(std::fabs(angle1)) > std::acos(std::fabs(angle2))){// switch p1 and p2n1_copy = n2;n2_copy = n1;n1_copy[3] = n2_copy[3] = 0.0f;dp2p1 *= (-1);f3 = -angle2;}elsef3 = angle1;//创建 Darboux 坐标系 u-v-w// u = n1; v = (p_idx - q_idx) x u / || (p_idx - q_idx) x u ||; w = u x vEigen::Vector4f v = dp2p1.cross3(n1_copy);v[3] = 0.0f;float v_norm = v.norm();if (v_norm == 0.0f){PCL_DEBUG("[pcl::computePairFeatures] Norm of Delta x U is 0!\n");f1 = f2 = f3 = f4 = 0.0f;return (false);}// Normalize vv /= v_norm;Eigen::Vector4f w = n1_copy.cross3(v);// Do not have to normalize w - it is a unit vector by construction//计算θ精度特征v[3] = 0.0f;f2 = v.dot(n2_copy);w[3] = 0.0f;//计算α角度特征 4元特征(α,θ,ϕ,d)// Compute f1 = arctan (w * n2, u * n2) i.e. angle of n2 in the x=u, y=w  coordinate systemf1 = std::atan2(w.dot(n2_copy), n1_copy.dot(n2_copy)); // @todo optimize thisreturn (true);
}

        2、计算单点PFH特征信息

//2、计算单点PFH特征信息
//
template <typename PointInT, typename PointNT, typename PointOutT> void
pcl::PFHEstimation<PointInT, PointNT, PointOutT>::computePointPFHSignature(const pcl::PointCloud<PointInT>& cloud, const pcl::PointCloud<PointNT>&  normals,const std::vector<int>& indices, int nr_split, Eigen::VectorXf& pfh_histogram)
{int h_index, h_p;//清楚上一次的PFH结果pfh_histogram.setZero();// 分解常数float hist_incr = 100.0f / static_cast<float> (indices.size() * (indices.size()  - 1) / 2);std::pair<int, int> key;bool key_found = false;// Iterate over all the points in the neighborhoodfor (std::size_t i_idx = 0; i_idx < indices.size(); ++i_idx){for (std::size_t j_idx = 0; j_idx < i_idx; ++j_idx){// 无序点检测,是则跳过if (!isFinite(cloud.points[indices[i_idx]]) ||  !isFinite(cloud.points[indices[j_idx]]))continue;//使用缓存来加速计算if (use_cache_){// 创建map需要的键值,为点云的索引int p1, p2;//      if (indices[i_idx] >= indices[j_idx])//      {p1 = indices[i_idx];p2 = indices[j_idx];//      }//      else//      {//        p1 = indices[j_idx];//        p2 = indices[i_idx];//      }key = std::pair<int, int>(p1, p2);// 检查是否已经在map中已经估计的点对,就不需要重新计算,避免重复计算std::map<std::pair<int, int>, Eigen::Vector4f,  std::less<>, Eigen::aligned_allocator<std::pair<const std::pair<int, int>,  Eigen::Vector4f> > >::iterator fm_it = feature_map_.find(key);if (fm_it != feature_map_.end()){//已经有计算的直接用计算的结果pfh_tuple_ = fm_it->second;key_found = true;}else{//没有被估计过,需要计算,这对新的点对的4元特征if (!computePairFeatures(cloud, normals,  indices[i_idx], indices[j_idx],pfh_tuple_[0], pfh_tuple_[1],  pfh_tuple_[2], pfh_tuple_[3]))continue;key_found = false;}}else//不使用缓存,直接计算所有两对点之间的4元特征if (!computePairFeatures(cloud, normals, indices[i_idx],  indices[j_idx],pfh_tuple_[0], pfh_tuple_[1], pfh_tuple_[2],  pfh_tuple_[3]))continue;// 将f1, f2, f3特征归一化,并将它们存储到直方图f_index_[0] = static_cast<int> (std::floor(nr_split *  ((pfh_tuple_[0] + M_PI) * d_pi_)));if (f_index_[0] < 0)         f_index_[0] = 0;if (f_index_[0] >= nr_split) f_index_[0] = nr_split - 1;f_index_[1] = static_cast<int> (std::floor(nr_split *  ((pfh_tuple_[1] + 1.0) * 0.5)));if (f_index_[1] < 0)         f_index_[1] = 0;if (f_index_[1] >= nr_split) f_index_[1] = nr_split - 1;f_index_[2] = static_cast<int> (std::floor(nr_split *  ((pfh_tuple_[2] + 1.0) * 0.5)));if (f_index_[2] < 0)         f_index_[2] = 0;if (f_index_[2] >= nr_split) f_index_[2] = nr_split - 1;// 特征直方图统计h_index = 0;h_p = 1;for (const int& d : f_index_){h_index += h_p * d;h_p *= nr_split;}//统计直方图结果pfh_histogram[h_index] += hist_incr;//缓存新计算点对的4元特征if (use_cache_ && !key_found){//保存4元特征信息到map中,根据Key由点的索引值feature_map_[key] = pfh_tuple_;// 将计算过的点对保存起来,为了防止冗余计算key_list_.push(key);// 如果缓存的量操过设置的最大值,需要清除最先缓存的值if (key_list_.size() > max_cache_size_){//删除最先缓存的元素。feature_map_.erase(key_list_.front());key_list_.pop();}}}}
}

        3、计算所有点的PFH特征信息

//3、计算所有点的PFH特征信息
//
template <typename PointInT, typename PointNT, typename PointOutT> void
pcl::PFHEstimation<PointInT, PointNT, PointOutT>::computeFeature(PointCloudOut&  output)
{// 清除特征map信息feature_map_.clear();std::queue<std::pair<int, int> > empty;std::swap(key_list_, empty);//初始化PFH特征维度大小5^3=125,并初始化为{0}pfh_histogram_.setZero(nr_subdiv_ * nr_subdiv_ * nr_subdiv_);//分配需要参数vector的大小std::vector<int> nn_indices(k_);std::vector<float> nn_dists(k_);output.is_dense = true;// 稠密点云数据if (input_->is_dense){// 对每一个索引值进行遍历执行for (std::size_t idx = 0; idx < indices_->size(); ++idx){//点的最近邻域搜索if (this->searchForNeighbors((*indices_)[idx],  search_parameter_, nn_indices, nn_dists) == 0){//如果最近邻域搜索失败,则将该点的PFH值重置为NAN无效值,跳过该点for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)output.points[idx].histogram[d] =  std::numeric_limits<float>::quiet_NaN();output.is_dense = false;continue;}// 估计每个patch的PFH特征computePointPFHSignature(*surface_, *normals_, nn_indices,  nr_subdiv_, pfh_histogram_);// 将PFH特征特征信息拷贝到输出的数据中for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)output.points[idx].histogram[d] = pfh_histogram_[d];}}else{// Iterating over the entire index vectorfor (std::size_t idx = 0; idx < indices_->size(); ++idx){//点的最近邻域搜索和无效值判断if (!isFinite((*input_)[(*indices_)[idx]]) ||this->searchForNeighbors((*indices_)[idx],  search_parameter_, nn_indices, nn_dists) == 0){//如果最近邻域搜索失败,则将该点的PFH值重置为NAN无效值,跳过该点for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)output.points[idx].histogram[d] =  std::numeric_limits<float>::quiet_NaN();output.is_dense = false;continue;}// 估计每个patch的PFH特征computePointPFHSignature(*surface_, *normals_, nn_indices,  nr_subdiv_, pfh_histogram_);// 将PFH特征特征信息拷贝到输出的数据中for (Eigen::Index d = 0; d < pfh_histogram_.size(); ++d)output.points[idx].histogram[d] = pfh_histogram_[d];}}
}

4、算法使用示例

/*****************************************************************//**
* \file   PCLPFHFeaturemain.cpp
* \brief  
*
* \author YZS
* \date   January 2025
*********************************************************************/
#include<iostream>
#include <vector>
#include <ctime>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/auto_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/pfh.h>//包含PFH计算的模块
using namespace std;
void PCLPFHFeature()
{//加载点云数据pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new  pcl::PointCloud<pcl::PointXYZRGB>());std::string fileName = "E:/fragment.pcd";pcl::io::load(fileName, *cloud);std::cout << "Cloud Size:" << cloud->points.size() << std::endl;//计算点云数据的法向量pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> ne;ne.setInputCloud(cloud);pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new  pcl::search::KdTree<pcl::PointXYZRGB>());ne.setSearchMethod(tree);pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new  pcl::PointCloud<pcl::Normal>);ne.setRadiusSearch(0.02);ne.compute(*cloud_normals);//计算点云数据的PFH特征信息//pcl::PFHEstimation PFH特征估计对象pcl::PFHEstimation<pcl::PointXYZRGB, pcl::Normal, pcl::PFHSignature125>  pfh_estimation;// 设置需要计算PFH的点云数据pfh_estimation.setInputCloud(cloud);// 设置需要计算PFH的点云数据对应的法向量pfh_estimation.setInputNormals(cloud_normals);//设置最近KDtree方法pfh_estimation.setSearchMethod(tree);//创建保存PFH特征信息的对象pfh_featurespcl::PointCloud<pcl::PFHSignature125>::Ptr pfh_features(new  pcl::PointCloud<pcl::PFHSignature125>);//设置搜索的邻域半径pfh_estimation.setRadiusSearch(0.08);//设置启用缓存计算结果,加快速度pfh_estimation.setUseInternalCache(true);// 计算PFH特征信息将结构保存到pfh_features中pfh_estimation.compute(*pfh_features);// 输出索引为2000的PFH特征信息pcl::PFHSignature125 descriptor = (*pfh_features)[2000];std::cout << descriptor << std::endl;
}
int main(int argc, char* argv[])
{PCLPFHFeature();std::cout << "Hello PCL World!" << std::endl;std::system("pause");return 0;
}

结果:只输出部分

5、PFH特征的的优缺点

1、PFH的优点

        1、该方法的优点在于对噪声和遮挡具有较强的抗干扰能力,适用于复杂场景下的三维物体识别与分类。

2、PFH的缺点:

        1、计算复杂度较高。假定点云P由n个点构成,且点云分布均匀,每个点在半径为r的邻域内平均能找到k个近邻点。那么,对于每个点计算点特征直方图(PFH)的时间复杂度为O(k²),因此,整个点云的PFH理论计算复杂度为O(nk²)。在实时或接近实时的应用场景中,对于密集点云的PFH计算,O(nk²)的计算复杂度显然不尽如人意,成为了一个显著的性能瓶颈。

        2、存在大量重复计算。在计算PFH的三个特征元素时,邻域内任意两点都需要分别计算其三个特征元素值。即便相邻点的邻域可能不完全相同,但它们往往包含许多相同的近邻点(随着邻域半径的增加,这种重复的近邻点会更多)。这些共同的近邻点会被重复配对并计算特征元素值。尽管相关论文和PCL库中引入了高速缓存机制,将重复计算的开销转化为查找开销,但整体效率提升并不显著。

        至此完成第十四节PCL库点云特征之PFH点特征直方图学习,下一节我们将进入《PCL库中点云特征之FPFH特征描述》的学习。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/503025.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qml PathView详解

1、概述 PathView 是 Qt Quick 中一个非常强大的视图组件&#xff0c;它基于一个 Path 来展示视图项&#xff08;如 Item、Rectangle 等&#xff09;。PathView 可以让你按照定义的路径动态地显示多个元素&#xff0c;并且支持动画、滑动等功能。这个视图控件的最大特点是能够…

网络协议安全的攻击手法

1.使用SYN Flood泛洪攻击&#xff1a; SYN Flood(半开放攻击)是最经典的ddos攻击之一&#xff0c;他利用了TCP协议的三次握手机制&#xff0c;攻击者通常利用工具或控制僵尸主机向服务器发送海量的变源端口的TCP SYN报文&#xff0c;服务器响应了这些报文后就会生成大量的半连…

前端学习DAY31(子元素溢出父元素)

.box1{width: 200px;height: 200px;background-color: chocolate;} 子元素是在父元素的内容区中排列的&#xff0c;如果子元素的大小超过了父元素&#xff0c;则子元素会从 父元素中溢出&#xff0c;使用overflow属性设置父元素如何处理溢出的子元素 可选值&#xff1a;visible…

机器人手眼标定

机器人手眼标定 一、机器人手眼标定1. 眼在手上标定基本原理2. 眼在手外标定基本原理 二、眼在手外标定实验三、标定精度分析 一、机器人手眼标定 要实现由图像目标点到实际物体上抓取点之间的坐标转换&#xff0c;就必须拥有准确的相机内外参信息。其中内参是相机内部的基本参…

【前端下拉框】获取国家国旗

一、先看效果 二、代码实现&#xff08;含国旗&#xff09; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…

Timer、Ticker使用及其注意事项

Timer、Ticker使用及其注意事项 在刚开始学习golang语言的时候就听说Timer、Ticker的使用要尤其注意&#xff0c;很容易出现问题&#xff0c;这次就来一探究竟。 本文主要脉络&#xff1a; 介绍定时器体系&#xff0c;并介绍常用使用方式和错误使用方式源码解读 timer、tic…

C++11——2:可变模板参数

一.前言 C11引入了可变模板参数&#xff08;variadic template parameters&#xff09;的概念&#xff0c;它允许我们在模板定义中使用可变数量的参数。这样&#xff0c;我们就可以处理任意数量的参数&#xff0c;而不仅限于固定数量的参数。 二.可变模板参数 我们早在C语言…

君正T41交叉编译ffmpeg、opencv并做h264软解,利用君正SDK做h264硬件编码

目录 1 交叉编译ffmpeg----错误解决过程&#xff0c;不要看 1.1 下载源码 1.2 配置 1.3 编译 安装 1.3.1 报错&#xff1a;libavfilter/libavfilter.so: undefined reference to fminf 1.3.2 报错&#xff1a;error: unknown type name HEVCContext; did you mean HEVCPr…

感知器的那些事

感知器的那些事 历史背景Rosenblatt和Minsky关于感知机的争论弗兰克罗森布拉特简介提出感知器算法Mark I感知机争议与分歧马文明斯基简介单层感知器工作原理训练过程多层感知器工作原理单层感知机 vs 多层感知机感知器模型(Perceptron),是由心理学家Frank Rosenblatt在1957年…

C语言:枚举类型

一、枚举类型的声明 枚举顾名思义就是一一列举。我们可以把可能的取值一一列举。比如我们现实生活中&#xff1a; 星期一到星期日是有限的7天&#xff0c;可以一一列举 &#xff1b;性别有&#xff1a;男、女、保密&#xff0c;也可以一一列举 &#xff1b;月份有12个月&#x…

25/1/6 算法笔记<强化学习> 初玩V-REP

我们安装V-REP之后&#xff0c;使用的是下面Git克隆的项目。 git clone https://github.com/deep-reinforcement-learning_book/Chapter16-Robot-Learning-in-Simulation.git 项目中直接组装好了一个机械臂。 我们先来分析下它的对象树 DefaultCamera:摄像机&#xff0c;用于…

CODESYS MODBUS TCP通信(AM400PLC作为主站通信)

禾川Q1 PLC MODBUS-TCP通信 禾川Q1 PLC MODBUS-TCP通信(CODESYS平台完整配置+代码)-CSDN博客文章浏览阅读17次。MATLAB和S7-1200PLC水箱液位高度PID控制联合仿真(MODBUSTCP通信)_将matlab仿真导入plc-CSDN博客文章浏览阅读722次。本文详细介绍了如何使用MATLAB与S7-1200PLC进行…

OSPF - 影响OSPF邻居建立的因素

总结为这么10种 routerID 冲突区域id不一致认证MA网络掩码需一致区域类型(特殊区域)hello、dead时间MTU(如果开启检查)静默接口网络类型不匹配MA网络中路由器接口优先级全为0 如何建立邻居可以查看上一篇文章&#xff0c;可以直接专栏找&#xff08;&#x1f92b;挂链接会没流…

【大数据】(选修)实验4 安装熟悉HBase数据库并实践

实验4 安装熟悉HBase数据库并实践 1、实验目的 (1)理解HBase在Hadoop体系结构中的角色; (2)熟练使用HBase操作常用的Shell命令; (3)熟悉HBase操作常用的Java API。 2、实验平台 操作系统:Linux Hadoop版本:2.6.0或以上版本 HBase版本:1.1.2或以上版本 JDK版…

windeployqt.exe打包qt程序总结(MSVC)

文章目录 前言打包步骤问题 前言 打包环境&#xff1a;windows10VS2017QT5.12.12 参考&#xff1a;Qt 打包发布程序&#xff0c;解决找不到msvcp140.dll等动态库问题正确方案 打包步骤 运行Qt5.12.12&#xff08;MSVC 2017 64-bits&#xff09; 在开始软件菜单里找到Qt文件夹…

算法的学习笔记—不用常规控制语句求 1 到 n 的和

&#x1f600;前言 在算法编程中&#xff0c;有时我们会遇到一些特殊的限制条件&#xff0c;这些限制会迫使我们跳出常规思维。本文讨论的问题就是一个典型案例&#xff1a;在不能使用基本控制语句的情况下&#xff0c;如何求解 1 到 n 的和。这个问题不仅考验编程技巧&#xf…

计算机网络 (27)IP多播

前言 IP多播&#xff08;也称多址广播或组播&#xff09;技术是一种允许一台或多台主机&#xff08;多播源&#xff09;发送单一数据包到多台主机&#xff08;一次性的、同时的&#xff09;的TCP/IP网络技术。 一、基本概念 定义&#xff1a;多播作为一点对多点的通信&#xff…

CSS 学习之正确看待 CSS 世界里的 margin 合并

一、什么是 margin 合并 块级元素的上外边距(margin-top)与下外边距(margin-bottom)有时会合并为单个外边距&#xff0c;这样的现象称为“margin 合并”。从此定义上&#xff0c;我们可以捕获两点重要的信息。 块级元素&#xff0c;但不包括浮动和绝对定位元素&#xff0c;尽…

小程序组件 —— 28 组件案例 - 推荐商品区域 - 实现结构样式

这一节目标是实现底部推荐商品的结构和样式&#xff0c;由于这里要求横向滚动&#xff0c;所以需要使用上节介绍的 scroll-view 功能&#xff0c;并使用 scroll-x 属性支持横向滚动&#xff0c;推荐商品区域中的每一个商品是一个单独的 view&#xff0c;每个view 中需要写三个组…

单片机-LED点阵实验

要将第一个点点亮&#xff0c;则 1 脚接高电平 a 脚接低电平&#xff0c;则第一个点就亮了&#xff1b;如果要将第一行点亮&#xff0c;则第 1 脚要接高电平&#xff0c;而&#xff08;a、b、c、d、e、f、g、h &#xff09;这些引脚接低电平&#xff0c;那么第一行就会点亮&…