热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

稀疏直接法视觉里程计中的特征点优化:基于光度误差最小化的灰度图像线性插值技术

在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。

特征点直接法 的 稀疏直接法 视觉里程计 最小化光度误差 灰度二维线性插值

* 空间点 P = (X, Y, Z) = (X, Y, Z, 1) 非齐次,齐次表示 (RGB-D获取3D坐标)

* 两帧图像上 对应 的像素坐标 p1 p2 = (u, v) = (u, v, 1) 非齐次,齐次表示

* 相机内参数 K

* 第一帧到第二帧的运动 旋转矩阵 R 平移向量t 也即变换矩阵 T 4*4 李代数形式 exp(f) 4*4

*

* p1 = K * P / Z1 , Z1为 P点在 第一帧图像下的深度

* p2 = K * (R*P + t) / Z2 = K * (T*P)前三列 / Z2 = K * exp(f)*P前三列 / Z2 , Z2为 P点在 第二帧图像下的深度

*

* 特征法中 通过匹配特征点描述子,可知道配对的 p1,p2像素位置,可以计算2D-2D 2D-3D 3D-3D 重投影误差得到 R t

* 而在 直接法 中 没有特征匹配,无从知道哪一个p2 与p1 匹配 (光流法中 通过灰度不变得到 匹配点对)

*

* 直接法的思路是 根据当前相机的位姿估计值和p1 来寻找 p2 的位置 最小化的不是重投影误差

* 而是 光度误差(灰度误差)与光流法一样 同一个点 灰度值相近(或不变)

* e = I(p1) - I2(p2)

* 有N个 空间点 Pi

* 最小化 sum(ei转置 * ei)

* e(f) = I(p1) - I2(p2) = I(K * P / Z1) - I2(K * exp(f)*P前三列 / Z2) ; 是 李代数形式 变换矩阵的 函数

* e(f 左乘 扰动∇f) = I(K * P / Z1) - I2(K * exp(∇f)*exp(f)*P)前三列 / Z2)

* = I(K * P / Z1) - I2(K * (1+∇f)exp(f)*P)前三列 / Z2)

* = I(K * P / Z1) - I2(K *exp(f)*P/ Z2 + K *∇f * exp(f)*P/ Z2)

*

* 记 Q = ∇f * exp(f)*P为 P在扰动之后 位于第二帧 相机坐标系下 的坐标

* u = K * Q/ Z2 为 其像素坐标

* 泰勒展开 f(x +∇x ) = f(x) + f'(x) * ∇x 链式求导法则

* e(f 左乘 扰动∇f) = I(K * P / Z1) - I2(K *exp(f)*P/ Z2 + u) = I(K * P / Z1) - I2(K *exp(f)*P/ Z2) - I2对u偏导 * u对Q偏导 * Q对 ∇f 偏导 * ∇f

* = e(f) - I2对u偏导 * u对Q偏导 * Q对 ∇f 偏导 * ∇f

* 注: I2对u偏导 为 u点处的 像素灰度梯度

* u对Q偏导 为投影方程 关于相机坐标系下 的三维点导数 Q = (X, Y, Z) u = K * Q/ Z

u [fx 0 cx X/Z

v = 0 fy cy * Y/Z

1 0 0 1] 1

利用第三行消去s(实际上就是 P'的深度)

u = fx * X/Z + cx

v = fy * Y/Z + cy

u 对Q的偏导数 = - [ u对X的偏导数 u对Y的偏导数 u对Z的偏导数;

v对X的偏导数 v对Y的偏导数 v对Z的偏导数]

= - [ fx/Z 0 -fx * X/Z ^2

0 fy/Z -fy * Y/Z ^2]

* Q对∇f的偏导数 = [ I -Q叉乘矩阵] 3*6大小 平移在前 旋转在后

* = [ 1 0 0 0 Z -Y

* 0 1 0 -Z 0 X

* 0 0 1 Y -X 0]

* 有向量 t = [ a1 a2 a3] 其

* 叉乘矩阵 = [0 -a3 a2;

* a3 0 -a1;

* -a2 a1 0 ]

u对∇f的偏导数 = u对Q偏导 * Q对∇f的偏导数 2*6 矩阵 与图像无关

* = 两者相乘得到

* = - [fx/Z 0 -fx * X/Z ^2 -fx * X*Y/Z^2 fx + fx * X^2/Z^2 -fx*Y/Z

* 0 fy/Z -fy* Y/Z^2 -fy -fy* Y^2/Z^2 fy * X*Y'/Z^2 fy*X/Z ]

* 如果是 旋转在前 平移在后 调换前三列 后三列

// 旋转在前 平移在后 g2o 负号乘了进去

*= [ fx *X*Y/Z^2 -fx *(1 + X^2/Z^2) fx*Y/Z -fx/Z 0 fx * X/Z^2

* fy *(1 + Y^2/Z^2) -fy * X*Y/Z^2 -fy*X/Z 0 -fy/Z fy* Y/Z^2 ]


得到 误差e 对李代数的导数 雅克比矩阵

J = I2对u偏导 * u对 ∇f 偏导

// 前者计算 图像灰度梯度可以得到(x方向梯度 y方向梯度 离散求解 坐标前后灰度值作差/2) 后者按上式 得到



* 对于N个点的问题 可以使用优化问题的 雅克比 使用 高斯牛顿 GN或者 LM计算更新增量 迭代求解



* 根据 空间点 P的来源 可以把直接法进行分类

* 【1】稀疏直接法 P来自 稀疏关键点 或者光流法跟踪的 关键点

* 【2】半稠密直接法 J = I2对u偏导 * u对 ∇f 偏导 如果像素梯度为0 则J=0 没有左右 ,使用像素梯度不为0的点

* 【3】稠密直接法 P为 所有像素点 需要GPU加速


github连接 点击打开链接

#include //输入输出流
#include //文件流
#include //列表
#include //容器
#include //计时
#include //时间
#include //限制
//opencv
#include
#include //图像处理
#include
#include //二维特征
//g2o 非线性凸优化
#include //一元边
#include //矩阵快求解器 矩阵分解
#include //LM 数字优化算法
#include //线性方程求解器
#include //
#include //系统定义的顶点类型 6自由度位姿using namespace std;
using namespace g2o;/********************************************* 本节演示了RGBD上的稀疏直接法 ********************************************/// 一次测量的值,包括一个世界坐标系下三维点与一个灰度值
struct Measurement//结构体
{Measurement ( Eigen::Vector3d p, float g ) : pos_world ( p ), grayscale ( g ) {}//直接赋值初始化Eigen::Vector3d pos_world;//世界坐标系下三维点 双整数float grayscale;//点对应的灰度值 浮点型
};// 2D坐标 由 相机内参数 和深度信息 得到 像极坐标系下的三维坐标
// 相机归一化 平面下坐标 x = K逆* px y = K逆* py 相机坐标系下 归一化平面上的点 //相机内参数 K=
// [fx 0 cx
// 0 fy cy
// 0 0 1]
// x= (px -cx)/fx
// y=(py-cy)/fy
// z = 1
// 而深度值 为 dd= d/scale mm 转成m
// 则相机坐标系下的坐标为 xx = x*dd yy = y * dd zz = z * dd
inline Eigen::Vector3d project2Dto3D ( int x, int y, int d, float fx, float fy, float cx, float cy, float scale )
{float zz = float ( d ) /scale;// 深度 mm 转成mfloat xx = zz* ( x-cx ) /fx; //反过来 像素坐标 x = xx/zz * fx + cxfloat yy = zz* ( y-cy ) /fy;// 像素坐标 y = yy/zz * fy + cy return Eigen::Vector3d ( xx, yy, zz );
}// 相机坐标系下 三维坐标 (x, y, z)转化成 像素坐标(u, v)
// 像素坐标 x = xx/zz * fx + cx
// 像素坐标 y = yy/zz * fy + cy
inline Eigen::Vector2d project3Dto2D ( float x, float y, float z, float fx, float fy, float cx, float cy )
{float u = fx*x/z+cx;float v = fy*y/z+cy;return Eigen::Vector2d ( u,v );
}// 直接法估计位姿
// 输入:测量值(空间点的灰度),新的灰度图,相机内参; 输出:相机位姿 4*4 转换矩阵形式
// 返回:true为成功,false失败
bool poseEstimationDirect ( const vector& measurements, cv::Mat* gray, Eigen::Matrix3f& intrinsics, Eigen::Isometry3d& Tcw );//g20图优化
// project a 3d point into an image plane, the error is photometric error
// an unary edge with one vertex SE3Expmap (the pose of camera)
// 边 误差 需要自己定义 直接法 测量值维度(灰度值) 数据类型 连接顶点类型
class EdgeSE3ProjectDirect: public BaseUnaryEdge<1, double, VertexSE3Expmap>//基础一元边 连接相机位姿 顶点
{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 类成员 有Eigen 变量时需要 显示 加此句话 宏定义EdgeSE3ProjectDirect() {}//默认构造函数//自定义构造函数&#xff0c;参数为://一个3d点世界坐标系下坐标//内参矩阵的4个参数//参考图&#xff0c;灰度图EdgeSE3ProjectDirect ( Eigen::Vector3d point, float fx, float fy, float cx, float cy, cv::Mat* image )//成员变量 image 灰度图: x_world_ ( point ), fx_ ( fx ), fy_ ( fy ), cx_ ( cx ), cy_ ( cy ), image_ ( image ) {}//计算误差 覆写 计算误差的 虚函数virtual void computeError(){const VertexSE3Expmap* v &#61;static_cast ( _vertices[0] );//类型强转 相机位姿 Eigen::Vector3d x_local &#61; v->estimate().map ( x_world_ );//第二帧下 相机坐标系下的三维坐标x_local[0]&#xff0c;x_local[1]&#xff0c;x_local[2]// 相机坐标系下 三维坐标 (x, y, z)转化成 像素坐标(u, v)float x &#61; x_local[0]*fx_/x_local[2] &#43; cx_;float y &#61; x_local[1]*fy_/x_local[2] &#43; cy_;// check x,y is in the imageif ( x-4<0 || ( x&#43;4 ) >image_->cols || ( y-4 ) <0 || ( y&#43;4 ) >image_->rows )//像素点坐标超出 有效范围{_error ( 0,0 ) &#61; 0.0;//误差0this->setLevel ( 1 );//边 效果差 不考虑}else{// 这里 误差为e &#61; I(p2) - I2(p1) 原来e &#61; I(p1) - I2(p2) 所以 雅克比 相差一个 负号 // 上面计算出来的 x&#xff0c;y 为浮点数形式 // 需要得到 整数形式 的 坐标值 对应图像的亮度值 需要进行插值运算 这里使用了 双线性插值_error ( 0,0 ) &#61; getPixelValue ( x,y ) - _measurement;//根据 像素坐标 和 灰度图得到 灰度值 - 测量值}}//覆写求雅克比矩阵 的虚函数
// plus in manifoldvirtual void linearizeOplus( ){if ( level() &#61;&#61; 1 ){_jacobianOplusXi &#61; Eigen::Matrix::Zero();return;}VertexSE3Expmap* vtx &#61; static_cast ( _vertices[0] );Eigen::Vector3d xyz_trans &#61; vtx->estimate().map ( x_world_ ); // q in bookdouble x &#61; xyz_trans[0];//Xdouble y &#61; xyz_trans[1];//Ydouble invz &#61; 1.0/xyz_trans[2];// 1/Zdouble invz_2 &#61; invz*invz;// 1/Z^2float u &#61; x*fx_*invz &#43; cx_;float v &#61; y*fy_*invz &#43; cy_;// jacobian from se3 to u,v// NOTE that in g2o the Lie algebra is (\omega, \epsilon), where \omega is so(3) and \epsilon the translation// 旋转在前 平移在后 g2o u对∇f的偏导数 像素坐标 对 变换矩阵李代数增量 的导数
// J1&#61; [ fx *X*Y/Z^2 -fx *(1 &#43; X^2/Z^2) fx*Y/Z -fx/Z 0 fx * X/Z^2 // fy *(1 &#43; Y^2/Z^2) -fy * X*Y/Z^2 -fy*X/Z 0 -fy/Z fy* Y/Z^2 ]
// 上面 误差为e &#61; I(p2) - I2(p1) 原来e &#61; I(p1) - I2(p2) 所以 雅克比 相差一个 负号 Eigen::Matrix jacobian_uv_ksai;jacobian_uv_ksai ( 0,0 ) &#61; - x*y*invz_2 *fx_;jacobian_uv_ksai ( 0,1 ) &#61; ( 1&#43; ( x*x*invz_2 ) ) *fx_;jacobian_uv_ksai ( 0,2 ) &#61; - y*invz *fx_;jacobian_uv_ksai ( 0,3 ) &#61; invz *fx_;jacobian_uv_ksai ( 0,4 ) &#61; 0;jacobian_uv_ksai ( 0,5 ) &#61; -x*invz_2 *fx_;jacobian_uv_ksai ( 1,0 ) &#61; - ( 1&#43;y*y*invz_2 ) *fy_;jacobian_uv_ksai ( 1,1 ) &#61; x*y*invz_2 *fy_;jacobian_uv_ksai ( 1,2 ) &#61; x*invz *fy_;jacobian_uv_ksai ( 1,3 ) &#61; 0;jacobian_uv_ksai ( 1,4 ) &#61; invz *fy_;jacobian_uv_ksai ( 1,5 ) &#61; -y*invz_2 *fy_;// I2对u偏导 J2 图像灰度梯度可以得到(x方向梯度 y方向梯度 离散求解 坐标前后灰度值作差/2) //这里由于各个像素点其实是离散值&#xff0c;其实求的是差分&#xff0c;前一个像素灰度值减后一个像素灰度值&#xff0c;除以2&#xff0c;即认为是这个方向上的梯度Eigen::Matrix jacobian_pixel_uv;jacobian_pixel_uv ( 0,0 ) &#61; ( getPixelValue ( u&#43;1,v )-getPixelValue ( u-1,v ) ) /2;//灰度梯度 x方向 离散形式jacobian_pixel_uv ( 0,1 ) &#61; ( getPixelValue ( u,v&#43;1 )-getPixelValue ( u,v-1 ) ) /2;// 灰度梯度 y方向_jacobianOplusXi &#61; jacobian_pixel_uv*jacobian_uv_ksai;//最后的 雅克比矩阵 }// dummy read and write functions because we don&#39;t care...virtual bool read ( std::istream& in ) {}virtual bool write ( std::ostream& out ) const {}protected://私有函数
// get a gray scale value from reference image (bilinear interpolated)
// x&#xff0c;y 为浮点数形式 需要得到 整数形式 的 坐标值 对应图像的亮度值 需要进行插值运算 这里使用了 双线性插值inline float getPixelValue ( float x, float y ){//这里先说一下各个参数的类型&#xff1a;//image_为Mat*类型&#xff0c;图像指针&#xff0c;所以调用data时用->符号&#xff0c;//data为图像矩阵首地址&#xff0c;支持数组形式访问&#xff0c;data[]就是访问到像素的值了&#xff0c;此处为像素的灰度值&#xff0c;类型为uchar//关于step有点复杂&#xff0c;data[]中括号的式子有点复杂&#xff0c;总的意思就是y行乘上每行内存数&#xff0c;定位到行&#xff0c;然后在加上x&#xff0c;定位到像素//step具体解释在最后面有一些资料//image_->data[int(y)*image_->step &#43; int(x)]这一步读到了x,y处的灰度值&#xff0c;类型为uchar&#xff0c;//但是后面由于线性插值&#xff0c;需要定位这个像素的位置&#xff0c;而不是他的灰度值&#xff0c;所以取其地址&#xff0c;赋值给data_ptr&#xff0c;记住它的位置&#xff0c;后面使用uchar* data_ptr &#61; & image_->data[ int ( y ) * image_->step &#43; int ( x ) ];//对应的 灰度图 的灰度值 的地址 行 * step &#43; 列 对应的 灰度值uchar* data &#61; data_ptr ;//地址//由于x,y这里有可能带小数&#xff0c;但是像素位置肯定是整数&#xff0c;所以&#xff0c;问题来了&#xff0c;(1.2, 4.5)像素坐标处的灰度值为多少呢?OK,线性插值&#xff01;//说一下floor(),std中的cmath函数。向下取整,返回不大于x的整数。例floor(4.9)&#61;4//xx和yy&#xff0c;就是取到小数部分。例&#xff1a;x&#61;4.9的话&#xff0c;xx&#61;x-floor(x)就为0.9。y同理// I(1.2, 4.5) 飞整数的像素值 为周围四点 的 二维线性插值 按距离四点距离大小为权重 // 1-xx xx// 1-yy I(1,4) I(1,5)// yy I(2,4) I(2,5)////float xx &#61; x - floor ( x );// 计算出来的坐标的 小数部分 float yy &#61; y - floor ( y );//return float (( 1-xx ) * ( 1-yy ) * data[0] &#43;xx* ( 1-yy ) * data[1] &#43;( 1-xx ) *yy*data[ image_->step ] &#43;xx*yy*data[image_->step&#43;1]);}public://公开变量Eigen::Vector3d x_world_; // 3D point in world framefloat cx_&#61;0, cy_&#61;0, fx_&#61;0, fy_&#61;0; // Camera intrinsics 相机内参cv::Mat* image_&#61;nullptr; // reference image 图像 image 灰度图
};int main ( int argc, char** argv )
{if ( argc !&#61; 2 ){cout<<"用法&#xff1a;./direct_sparse path_to_dataset"< measurements;// 相机内参float cx &#61; 325.5;float cy &#61; 253.5;float fx &#61; 518.0;float fy &#61; 519.0;float depth_scale &#61; 1000.0;// mm 变成 m Eigen::Matrix3f K;K<>time_rgb>>rgb_file>>time_depth>>depth_file;color &#61; cv::imread ( path_to_dataset&#43;"/"&#43;rgb_file );// rgb 图像depth &#61; cv::imread ( path_to_dataset&#43;"/"&#43;depth_file, -1 );// 深度图if ( color.data&#61;&#61;nullptr || depth.data&#61;&#61;nullptr )continue; cv::cvtColor ( color, gray, cv::COLOR_BGR2GRAY );//彩色图到灰度图if ( index &#61;&#61;0 )//第一帧{// 对第一帧提取FAST特征点vector keypoints;cv::Ptr detector &#61; cv::FastFeatureDetector::create();detector->detect ( color, keypoints );//检测 特征点for ( auto kp:keypoints ){// 去掉邻近图像边缘处的点if ( kp.pt.x <20 || kp.pt.y <20 || ( kp.pt.x&#43;20 ) >color.cols || ( kp.pt.y&#43;20 ) >color.rows )continue;//跳过以下ushort d &#61; depth.ptr ( cvRound ( kp.pt.y ) ) [ cvRound ( kp.pt.x ) ];//对于特征点的深度if ( d&#61;&#61;0 )continue;//跳过Eigen::Vector3d p3d &#61; project2Dto3D ( kp.pt.x, kp.pt.y, d, fx, fy, cx, cy, depth_scale );//2D像素坐标 转换成 相机坐标系下的 三维点 3Dfloat grayscale &#61; float ( gray.ptr ( cvRound ( kp.pt.y ) ) [ cvRound ( kp.pt.x ) ] );//特征点 对应的灰度值 坐标值为整数 需要取整measurements.push_back ( Measurement ( p3d, grayscale ) );//测量值为 三维点 和 对应图像的灰度值}prev_color &#61; color.clone();//赋值 图像continue;//第一幅图 跳过 以下}// 使用直接法计算相机运动chrono::steady_clock::time_point t1 &#61; chrono::steady_clock::now();//计时开始poseEstimationDirect ( measurements, &gray, K, Tcw );//测量值chrono::steady_clock::time_point t2 &#61; chrono::steady_clock::now();//计时结束chrono::duration time_used &#61; chrono::duration_cast> ( t2-t1 );cout<<"直接法耗时 direct method costs time: "< RAND_MAX/5 )continue;Eigen::Vector3d p &#61; m.pos_world;//测量值的 三维点 pEigen::Vector2d pixel_prev &#61; project3Dto2D ( p ( 0,0 ), p ( 1,0 ), p ( 2,0 ), fx, fy, cx, cy );//转换成 2d像素坐标Eigen::Vector3d p2 &#61; Tcw*m.pos_world;//变换到 第二帧图像的坐标系下 Eigen::Vector2d pixel_now &#61; project3Dto2D ( p2 ( 0,0 ), p2 ( 1,0 ), p2 ( 2,0 ), fx, fy, cx, cy );//转化成 2d像素坐标if ( pixel_now(0,0)<0 || pixel_now(0,0)>&#61;color.cols || pixel_now(1,0)<0 || pixel_now(1,0)>&#61;color.rows )// 超出范围的 跳过continue;float b &#61; 255*float ( rand() ) /RAND_MAX;//随机颜色 分量float g &#61; 255*float ( rand() ) /RAND_MAX;float r &#61; 255*float ( rand() ) /RAND_MAX;cv::circle ( img_show, cv::Point2d ( pixel_prev ( 0,0 ), pixel_prev ( 1,0 ) ), 8, cv::Scalar ( b,g,r ), 2 );cv::circle ( img_show, cv::Point2d ( pixel_now ( 0,0 ), pixel_now ( 1,0 ) &#43;color.rows ), 8, cv::Scalar ( b,g,r ), 2 );cv::line ( img_show, cv::Point2d ( pixel_prev ( 0,0 ), pixel_prev ( 1,0 ) ), cv::Point2d ( pixel_now ( 0,0 ), pixel_now ( 1,0 ) &#43;color.rows ), cv::Scalar ( b,g,r ), 1 );}cv::imshow ( "result", img_show );cv::waitKey ( 0 );//等待按键}return 0;
}bool poseEstimationDirect ( const vector& measurements, cv::Mat* gray, Eigen::Matrix3f& K, Eigen::Isometry3d& Tcw )
{// 初始化g2otypedef g2o::BlockSolver> DirectBlock; // 求解的向量 顶点(姿态) 是6&#xff0a;1的DirectBlock::LinearSolverType* linearSolver &#61; new g2o::LinearSolverDense ();DirectBlock* solver_ptr &#61; new DirectBlock ( linearSolver );// g2o::OptimizationAlgorithmGaussNewton* solver &#61; new g2o::OptimizationAlgorithmGaussNewton( solver_ptr ); // G-N 高斯牛顿g2o::OptimizationAlgorithmLevenberg* solver &#61; new g2o::OptimizationAlgorithmLevenberg ( solver_ptr ); // L-M g2o::SparseOptimizer optimizer; optimizer.setAlgorithm ( solver );optimizer.setVerbose( true );// 添加顶点g2o::VertexSE3Expmap* pose &#61; new g2o::VertexSE3Expmap();//位姿pose->setEstimate ( g2o::SE3Quat ( Tcw.rotation(), Tcw.translation() ) );//旋转矩阵 和 平移向量pose->setId ( 0 );//idoptimizer.addVertex ( pose );//添加顶点// 添加边int id&#61;1;for ( Measurement m: measurements ){EdgeSE3ProjectDirect* edge &#61; new EdgeSE3ProjectDirect (m.pos_world,//3D 位置K ( 0,0 ), K ( 1,1 ), K ( 0,2 ), K ( 1,2 ), gray//相机内参数 灰度图);edge->setVertex ( 0, pose );//顶点edge->setMeasurement ( m.grayscale );//测量值为真是灰度值edge->setInformation ( Eigen::Matrix::Identity() );//误差 权重 信息矩阵edge->setId ( id&#43;&#43; );optimizer.addEdge ( edge );}cout<<"边的数量 edges in graph: "<estimate();// 变换矩阵
}






推荐阅读
  • 题目旨在解决树上的路径最优化问题,具体为在给定的树中寻找一条长度介于L到R之间的路径,使该路径上的边权平均值最大化。通过点分治策略,可以有效地处理此类问题。若无长度限制,可采用01分数规划模型,将所有边权减去一个常数m,从而简化计算过程。此外,利用单调队列优化动态规划过程,进一步提高算法效率。 ... [详细]
  • 本文详细解析了 MySQL 5.7.20 版本中二进制日志(binlog)崩溃恢复机制的工作流程。假设使用 InnoDB 存储引擎,并且启用了 `sync_binlog=1` 配置,文章深入探讨了在系统崩溃后如何通过 binlog 进行数据恢复,确保数据的一致性和完整性。 ... [详细]
  • 技术日志:深入探讨Spark Streaming与Spark SQL的融合应用
    技术日志:深入探讨Spark Streaming与Spark SQL的融合应用 ... [详细]
  • 题目《UVa 11978 福岛核爆问题》涉及圆与多边形交集面积的计算及二分法的应用。该问题的核心在于通过精确的几何运算与高效的算法实现来解决复杂图形的面积计算。在实现过程中,特别需要注意的是对多边形顶点的平移处理,确保所有顶点包括最后一个顶点 \( p[n] \) 都经过正确的位移,以避免因细节疏忽导致的错误。此外,使用循环次数为50次的二分法能够有效提高算法的精度和稳定性。 ... [详细]
  • 在幼儿园中,有 \( n \) 个小朋友需要通过投票来决定是否午睡。尽管这个问题对每个孩子来说并不是特别重要,但他们仍然希望通过谦让的方式达成一致。每个人都有自己的偏好,但为了集体和谐,他们决定采用一种最小割的方法来解决这一问题。这种方法不仅能够确保每个人的意愿得到尽可能多的尊重,还能找到一个最优的解决方案,使整体满意度最大化。 ... [详细]
  • 本文提供了 RabbitMQ 3.7 的快速上手指南,详细介绍了环境搭建、生产者和消费者的配置与使用。通过官方教程的指引,读者可以轻松完成初步测试和实践,快速掌握 RabbitMQ 的核心功能和基本操作。 ... [详细]
  • 在Android平台上利用FFmpeg的Swscale组件实现YUV与RGB格式互转
    本文探讨了在Android平台上利用FFmpeg的Swscale组件实现YUV与RGB格式互转的技术细节。通过详细分析Swscale的工作原理和实际应用,展示了如何在Android环境中高效地进行图像格式转换。此外,还介绍了FFmpeg的全平台编译过程,包括x264和fdk-aac的集成,并在Ubuntu系统中配置Nginx和Nginx-RTMP-Module以支持直播推流服务。这些技术的结合为音视频处理提供了强大的支持。 ... [详细]
  • 本研究基于状态空间方法,通过动态可视化技术实现了汉诺塔问题的求解过程,即将n个盘子从A柱移动到C柱。本文提供了一个使用C语言在控制台进行动画绘制的示例,并详细注释了程序逻辑,以帮助读者更好地理解和学习该算法。 ... [详细]
  • 在MFC开发过程中,利用Windows内置的文件对话框可以显著提高文件操作的效率。本文总结了使用文件对话框进行文件选择和处理的经验,详细介绍了相关API的调用方法和参数设置,如`CFileDialog`类的使用、结构体`OPENFILENAME`的配置以及如何获取选中的文件路径。通过这些技巧,开发者可以快速实现文件的打开、保存等功能,提升应用程序的用户体验。 ... [详细]
  • 本文介绍了在 Android 平台上的图片上传工具类优化方案,重点讨论了如何通过设置 `MultipartEntity` 来实现图片的高效上传。具体实现中,通过自定义 `UserUploadServiceImpl` 类,详细展示了如何构建和发送包含图片数据的 HTTP 请求。此外,还探讨了如何处理上传过程中的常见问题,如网络异常和文件格式验证,以确保上传的稳定性和可靠性。 ... [详细]
  • 在2015年世界总决赛的Tours问题中,参赛者面临一个包含n个节点和m条边的无向图挑战。任务是选定一种颜色数量k,并使用这些颜色为每条边着色。核心要求是在图中的任何简单循环中,每种颜色的边数必须相等。该研究对竞赛路线进行了全面分析,探讨了满足条件的所有可能的着色方案。 ... [详细]
  • 本文详细介绍了如何使用C++实现邻接表数据结构。邻接表是一种用于表示图的数据结构,特别适用于稀疏图的存储。通过定义最大顶点数和顶点类型,本文展示了如何利用标准模板库(STL)中的容器来构建和操作邻接表,从而高效地管理和查询图中的节点及其连接关系。此外,还提供了具体的代码示例,帮助读者更好地理解和应用这一数据结构。 ... [详细]
  • 精通jQuery:深入解析事件处理机制与应用技巧
    本文详细探讨了jQuery的事件处理机制及其应用技巧,通过具体的代码示例,逐一解析了每个jQuery代码片段与其对应的HTML结构。文章以标记为基准,CSS作为通用样式,确保每段代码都能独立运行。HTML和CSS代码统一放置在文章末尾,方便读者参考和实践。 ... [详细]
  • 本文将详细介绍如何在SSM框架中无缝集成ShardingSphere 4.10,以实现高效的数据分片和读写分离。通过实例演示和代码解析,帮助开发者快速掌握这一复杂但实用的技术。文章从基础概念入手,逐步深入到具体配置和应用实践,旨在为读者提供一个全面、易懂的整合指南。 ... [详细]
  • 如何利用C语言进行高效的商品管理程序设计与开发
    本文详细探讨了使用C语言高效开发商品管理系统的技巧与方法。通过简洁明了的代码示例,文章逐步引导读者掌握商品管理程序的设计与实现,适合初学者及有一定基础的开发者参考学习。 ... [详细]
author-avatar
蟹子萝卜控
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有