转载请注明出处。
文章链接:https://blog.csdn.net/duiwangxiaomi/article/details/126406184

博文目录

  • 一. 前言
  • 二. cvHoughLines2函数定义
    • (一) 函数说明
    • (二) 函数使用
  • 三. 源码解析
    • (一) HoughLines、HoughLinesP源码
    • (二) cvHoughLines2源码解析
  • 四. 总结

一. 前言

  霍夫变换是一种在图像中寻找直线、圆及其他简单形状的方法,利用Hough变换在二值图像中找到直线。本文主要介绍opencv自带的几种直线检测函数,以及主要检测函数cvHoughLines2()的源码解析。

二. cvHoughLines2函数定义

  目前opencv直线检测方法有如下三种:

1. CV_HOUGH_STANDARD(SHT)传统或标准Hough变换.每一个线段由两个浮点数(ρ,θ)表示,此中ρ是原点(0,0)到直线的距离,θ表示线段与x-轴之间的夹角。是以,矩阵类型必须是 CV_32FC2 type.
2. CV_HOUGH_PROBABILISTIC(PPHT)概率Hough变换(如果图像包含一些长的线性分割,则效率更高). 它返回线段分割而不是整条直线。每个分割用起点和终点来表示,所以矩阵(或创建的序列)类型是 CV_32SC4.
3. CV_HOUGH_MULTI_SCALE(MSHT)传统 Hough 变换的多标准变种。线段的编码体式格式与 CV_HOUGH_STANDARD 的一致。

  opencv自带的几种直线检测函数,如下:

序号 使用方式 函数名称
1 C接口 cvHoughLines2()
2 C++接口-SHT,MSHT HoughLines()
3 C++接口-PPHT HoughLinesP()

(一) 函数说明

!!!注意:参数中的theta为检测直线对应的垂线角度,从后面的源码解析可以看出。
// 1.C接口
CV_IMPL CvSeq*
cvHoughLines2( CvArr* src_image, void* lineStorage, int method,double rho, double theta, int threshold,double param1, double param2 )
函数说明:C接口中的hough检测实现了上述三种检测方法,调用时可通过method设置检测方法。
返 回 值:返回找到的线段序列.
参数说明:src_image   输入 8-比特、单通道 (二值) 图像lineStorage  指向保存结果位置的指针,既可以是内存块cvMemoryStorage,也可以是N*1的矩阵数列(行数N将有助于限制直线的最大数量)method     采用的检测方法,可以是CV_HOUGH_STANDARD(SHT)CV_HOUGH_PROBABILISTIC(PPHT)CV_HOUGH_MULTI_SCALE(MSHT)rho           以像素为单位的距离精度。另一种形容方式是直线搜索时的进步尺寸的单位半径。一般设置为1theta      以弧度为单位的角度精度.另一种形容方式是直线搜索时的进步尺寸的单位角度.一般设置为CV_PI/180.threshold    累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值.阈值>threshold的线段才可以被检测通过并返回到结果中.param1      1)对传统 Hough 变换,不使用(0).2)对概率 Hough 变换,它是最小线段长度.x方向或y向有一者距离满足要求即可.3)对多尺度 Hough 变换,它是距离精度rho的分母(大致的距离精度是rho而精确的应该是rho/param1 ).param2     1)对传统 Hough 变换,不使用 (0).2)对概率 Hough 变换,这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直线上的两条碎线段之间的间隔小于param2时,将其合二为一。3)对多尺度 Hough 变换,它是角度精度 theta 的分母 (大致的角度精度是 theta 而精确的角度应该是 theta/param2).
// 2.C++接口
// 标准和多尺度霍夫变换函数
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
函数说明:此函数实现了标准霍夫变换SHT和多尺度霍夫变换MSHT进行直线检测。调用时可通过method设置检测方法。
参数说明:image       InputArray类型的image,输入图像,即源图像,需为8位的单通道二进制,可将任意的源图载入进来由函数修改成此格式后,填在此处。lines        OutputArray类型的lines,储存检测到线条的输出矢量.每一条线由(ρ,θ),其中,ρ是离坐标原点((0,0)(也就是图像的左上角)的距离.θ是弧度线条旋转角度(0~垂直线,π/2~水平线).rho         同cvHoughLines2中参数说明theta        同cvHoughLines2中参数说明threshold    同cvHoughLines2中参数说明srn          默认值0对于多尺度霍夫变换,是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,而精确的累加器进步尺寸为rho/srn。stn            默认值0对于多尺度霍夫变换,srn表示第四个参数进步尺寸的单位角度theta的除数距离。且如果srn和stn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数。// 概率霍夫变换
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
函数说明:C++接口将概率霍夫变换单独出来的函数。
参数说明:image           同HoughLines中参数说明lines           同HoughLines中参数说明rho             同cvHoughLines2中参数说明theta            同cvHoughLines2中参数说明threshold        同cvHoughLines2中参数说明minLineLength    同cvHoughLines2中参数param1-2)说明maxLineGap      同cvHoughLines2中参数param2-2)说明

(二) 函数使用

  1. cvHoughLines2示例
#include <highgui.h>
#include <cv.h>
#include <math.h>int main(int argc, char** argv)
{IplImage* src;src = cvLoadImage( “./001.jpg”, 0 ); //加载灰度图IplImage* dst = cvCreateImage( cvGetSize( src ), IPL_DEPTH_8U, 1 );IplImage* color_dst = cvCreateImage( cvGetSize( src ), IPL_DEPTH_8U, 3 );  //创建三通道图像,用于直线显示CvMemStorage* storage = cvCreateMemStorage(0);CvSeq* lines = 0;cvCanny( src, dst, 50, 100, 3 );  //首先运行边缘检测,得到只有边缘的二值图像lines = cvHoughLines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 80, 30, 10 ); //提取直线并显示cvCvtColor( dst, color_dst, CV_GRAY2BGR ); for( int i = 0; i < lines ->total; i++ )  //lines存储的是直线{CvPoint* line = ( CvPoint* )cvGetSeqElem( lines, i );  //lines序列里面存储的是像素点坐标cvLine( color_dst, line[0], line[1], CV_RGB( 0, 255, 0 ) );  //将找到的直线标记为绿色}cvNamedWindow( "src", 1 );cvShowImage( "src", src );cvNamedWindow( "Hough", 1 );cvShowImage( "Hough", color_dst );cvWaitKey(0);return 0;
}
  1. 对于HoughLines、HoughLinesP使用,可直接参照毛星云博客,大佬写的非常详细,这里贴上链接。
    毛星云hough直线检测

三. 源码解析

(一) HoughLines、HoughLinesP源码

  先贴出HoughLines、HoughLinesP函数源码,可以看出,二者最终都调用了cvHoughLines2函数,因此,我们直接对cvHoughLines2源码进行解析。

void cv::HoughLines( InputArray _image, OutputArray _lines,double rho, double theta, int threshold,double srn, double stn )
{Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);Mat image = _image.getMat();CvMat c_image = image;CvSeq* seq = cvHoughLines2( &c_image, storage, srn == 0 && stn == 0 ?CV_HOUGH_STANDARD : CV_HOUGH_MULTI_SCALE,rho, theta, threshold, srn, stn );seqToMat(seq, _lines);
}void cv::HoughLinesP( InputArray _image, OutputArray _lines,double rho, double theta, int threshold,double minLineLength, double maxGap )
{Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);Mat image = _image.getMat();CvMat c_image = image;CvSeq* seq = cvHoughLines2( &c_image, storage, CV_HOUGH_PROBABILISTIC,rho, theta, threshold, minLineLength, maxGap );seqToMat(seq, _lines);
}

(二) cvHoughLines2源码解析

  这里主要对概率霍夫变换和标准变换进行分析,直接上源码,注释在代码中~

  1. cvHoughLines2,method处是代码核心,分别调用了三种检测方法的函数,2、3直接进行标准变换和概率霍夫变换函数的源码解析。
// cvHoughLines2
CV_IMPL CvSeq*
cvHoughLines2( CvArr* src_image, void* lineStorage, int method,double rho, double theta, int threshold,double param1, double param2 )
{CvSeq* result = 0;CvMat stub, *img = (CvMat*)src_image;CvMat* mat = 0;CvSeq* lines = 0;CvSeq lines_header;CvSeqBlock lines_block;int lineType, elemSize;int linesMax = INT_MAX;int iparam1, iparam2;img = cvGetMat( img, &stub );if( !CV_IS_MASK_ARR(img))CV_Error( CV_StsBadArg, "The source image must be 8-bit, single-channel" );if( !lineStorage )CV_Error( CV_StsNullPtr, "NULL destination" );if( rho <= 0 || theta <= 0 || threshold <= 0 )CV_Error( CV_StsOutOfRange, "rho, theta and threshold must be positive" );if( method != CV_HOUGH_PROBABILISTIC ){lineType = CV_32FC2;elemSize = sizeof(float)*2;}else{lineType = CV_32SC4;elemSize = sizeof(int)*4;}if( CV_IS_STORAGE( lineStorage )){lines = cvCreateSeq( lineType, sizeof(CvSeq), elemSize, (CvMemStorage*)lineStorage );}else if( CV_IS_MAT( lineStorage )){mat = (CvMat*)lineStorage;if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 && mat->cols != 1) )CV_Error( CV_StsBadArg,"The destination matrix should be continuous and have a single row or a single column" );if( CV_MAT_TYPE( mat->type ) != lineType )CV_Error( CV_StsBadArg,"The destination matrix data type is inappropriate, see the manual" );lines = cvMakeSeqHeaderForArray( lineType, sizeof(CvSeq), elemSize, mat->data.ptr,mat->rows + mat->cols - 1, &lines_header, &lines_block );linesMax = lines->total;cvClearSeq( lines );}elseCV_Error( CV_StsBadArg, "Destination is not CvMemStorage* nor CvMat*" );iparam1 = cvRound(param1);iparam2 = cvRound(param2);switch( method ){case CV_HOUGH_STANDARD:icvHoughLinesStandard( img, (float)rho,(float)theta, threshold, lines, linesMax );break;case CV_HOUGH_MULTI_SCALE:icvHoughLinesSDiv( img, (float)rho, (float)theta,threshold, iparam1, iparam2, lines, linesMax );break;case CV_HOUGH_PROBABILISTIC:icvHoughLinesProbabilistic( img, (float)rho, (float)theta,threshold, iparam1, iparam2, lines, linesMax );break;default:CV_Error( CV_StsBadArg, "Unrecognized method id" );}if( mat ){if( mat->cols > mat->rows )mat->cols = lines->total;elsemat->rows = lines->total;}elseresult = lines;return result;
}
  1. 标准霍夫变换
static void
icvHoughLinesStandard( const CvMat* img, float rho, float theta,int threshold, CvSeq *lines, int linesMax )
{const uchar* image;int step, width, height;int numangle, numrho;int total = 0;int i, j;float irho = 1 / rho;double scale;CV_Assert( CV_IS_MAT(img) && CV_MAT_TYPE(img->type) == CV_8UC1 );image = img->data.ptr;step = img->step;width = img->cols;height = img->rows;numangle = cvRound(CV_PI / theta); //极坐标空间theta轴细分程度numrho = cvRound(((width + height) * 2 + 1) / rho); //极坐标空间rho轴细分程度//实质最小可以取图像两个对角之间的最大距离,eg: M*N的图片最大距离为sqrt(M^2+N^2)//如上计算,显然>sqrt(M^2+N^2), 使得计算分辨率更高_accum.allocate((numangle+2) * (numrho+2));  //多分配一行一列,主要是方便stage    //2中4邻域的比较,否则比较时会溢出_sort_buf.allocate(numangle * numrho);_tabSin.allocate(numangle);_tabCos.allocate(numangle);int *accum = _accum, *sort_buf = _sort_buf;float *tabSin = _tabSin, *tabCos = _tabCos;memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );float ang = 0;for(int n = 0; n < numangle; ang += theta, n++ ){tabSin[n] = (float)(sin((double)ang) * irho);    //做好tabSin数组,后面备查tabCos[n] = (float)(cos((double)ang) * irho);    //做好tabCos数组,后面备查}// stage 1. fill accumulatorfor( i = 0; i < height; i++ )for( j = 0; j < width; j++ ){if( image[i * step + j] != 0 )                //二值图像非零点for(int n = 0; n < numangle; n++ ){int r = cvRound( j * tabCos[n] + i * tabSin[n] );   //ρ = x cos θ + y sin θr += (numrho - 1) / 2;               //距离偏移一半,r有负值,使r取值在[0,numrho - 1]区间accum[(n+1) * (numrho+2) + r+1]++;   //累加器相应单元+1,//n+1是为了第一行空出来//numrho+2 是总共的列数//r+1把第一列空出来,stage 2需要比较4邻域累加器中值的大小}}// stage 2. find local maximumsfor(int r = 0; r < numrho; r++ )for(int n = 0; n < numangle; n++ ){int base = (n+1) * (numrho+2) + r+1;         //累加器空间的索引,与stage 1中相同if( accum[base] > threshold &&accum[base] > accum[base - 1] && accum[base] >= accum[base + 1] &&accum[base] > accum[base - numrho - 2] && accum[base] >= accum[base + numrho + 2] )sort_buf[total++] = base;}// stage 3. sort the detected lines by accumulator valueicvHoughSortDescent32s( sort_buf, total, accum );    //opencv自带排序函数,//降序排列,降序排列后的数据在accum中的序号赋给sort_buf// stage 4. store the first min(total,linesMax) lines to the output bufferlinesMax = MIN(linesMax, total);scale = 1./(numrho+2);for( i = 0; i < linesMax; i++ ){CvLinePolar line;int idx = sort_buf[i];   //累加器空间accum的序号int n = cvFloor(idx*scale) - 1;   //cvFloor()将浮点数转换为不大于该参数的整数//除以(numrho + 2)并减1→获得行数int r = idx - (n+1)*(numrho+2) - 1;  //获得列数line.rho = (r - (numrho - 1)*0.5f) * rho;   //0.5=1/2,距离大小,与之前偏移相对应line.angle = n * theta;        //角度大小cvSeqPush( lines, &line );     //直线以(ρ,r)装到lines中}}
  1. 概率霍夫变换
// icvHoughLinesProbabilistic
static void
icvHoughLinesProbabalistic( CvMat* image,float rho, float theta, int threshold,int lineLength, int lineGap,CvSeq *lines, int linesMax )
{CvMat* accum = 0;//累加器CvMat* mask = 0;//保存0,1图像CvMat* trigtab = 0;//保存cos、sin与距离精度(irho)的乘积CvMemStorage* storage = 0;CV_FUNCNAME( "icvHoughLinesProbalistic" );__BEGIN__;CvSeq* seq;CvSeqWriter writer;int width, height;int numangle, numrho;float ang;int r, n, count;CvPoint pt;float irho = 1 / rho;CvRNG rng = cvRNG(-1);//产生随机数const float* ttab;uchar* mdata0;CV_ASSERT( CV_IS_MAT(image) && CV_MAT_TYPE(image->type) == CV_8UC1 );width = image->cols;height = image->rows;numangle = cvRound(CV_PI / theta);numrho = cvRound(((width + height) * 2 + 1) / rho);CV_CALL( accum = cvCreateMat( numangle, numrho, CV_32SC1 ));CV_CALL( mask = cvCreateMat( height, width, CV_8UC1 ));CV_CALL( trigtab = cvCreateMat( 1, numangle, CV_32FC2 ));cvZero( accum );CV_CALL( storage = cvCreateMemStorage(0) );for( ang = 0, n = 0; n < numangle; ang += theta, n++ ){trigtab->data.fl[n*2] = (float)(cos(ang) * irho);trigtab->data.fl[n*2+1] = (float)(sin(ang) * irho);}ttab = trigtab->data.fl;mdata0 = mask->data.ptr;CV_CALL( cvStartWriteSeq( CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage, &writer )); //第一步生成0,1图像,即:选择非零的点// stage 1. collect non-zero image pointsfor( pt.y = 0, count = 0; pt.y < height; pt.y++ ){const uchar* data = image->data.ptr + pt.y*image->step;uchar* mdata = mdata0 + pt.y*width;for( pt.x = 0; pt.x < width; pt.x++ ){if( data[pt.x] ){mdata[pt.x] = (uchar)1;CV_WRITE_SEQ_ELEM( pt, writer );//存入链表}elsemdata[pt.x] = 0;}}seq = cvEndWriteSeq( &writer );count = seq->total;//随机处理// stage 2. process all the points in random orderfor( ; count > 0; count-- ){// choose random point out of the remaining onesint idx = cvRandInt(&rng) % count;//生成随机数int max_val = threshold-1, max_n = 0;CvPoint* pt = (CvPoint*)cvGetSeqElem( seq, idx );CvPoint line_end[2] = {{0,0}, {0,0}};float a, b;int* adata = accum->data.i;int i, j, k, x0, y0, dx0, dy0, xflag;int good_line;const int shift = 16;i = pt->y;j = pt->x;//注意这行代码是为了覆盖pt指向的内容,也就是说pt指向的链表seq的内容被count-1位置上的内容覆盖了// "remove" it by overriding it with the last element*pt = *(CvPoint*)cvGetSeqElem( seq, count-1 );// check if it has been excluded already (i.e. belongs to some other line)if( !mdata0[i*width + j] )continue;//更新 累加器,查找最大概率的线// update accumulator, find the most probable linefor( n = 0; n < numangle; n++, adata += numrho ){r = cvRound( j * ttab[n*2] + i * ttab[n*2+1] );r += (numrho - 1) / 2;//r有负值,使r取值在[0,numrho - 1]区间int val = ++adata[r];if( max_val < val ){max_val = val;max_n = n;}}//如果点的个数max_val < threshold 就被认为是不符合条件的候选点(i,j)// if it is too "weak" candidate, continue with another pointif( max_val < threshold )continue;//如果点的个数max_val >= threshold 就被认为是符合条件的候选点(i,j)// from the current point walk in each direction// along the found line and extract the line segment//极坐标中的方向角是直线的垂线与极轴正向的夹角,在图像中夹角是第四象限的角//(极轴正向逆时针旋转,极轴就是在平面直角坐标系中的x轴正方向,对于图像来说,y轴正向是向下的)//所以sin取负值,cos不变a = -ttab[max_n*2+1];b = ttab[max_n*2];x0 = j;y0 = i;//计算步长dx0,dy0if( fabs(a) > fabs(b) ){xflag = 1;dx0 = a > 0 ? 1 : -1;dy0 = cvRound( b*(1 << shift)/fabs(a) );y0 = (y0 << shift) + (1 << (shift-1));//1 << shift这是为了把浮点数计算转化为整数计算}else{xflag = 0;dy0 = b > 0 ? 1 : -1;dx0 = cvRound( a*(1 << shift)/fabs(b) );x0 = (x0 << shift) + (1 << (shift-1));}//当点的位置和cos、sin确定后,每条直线都有两个方向for( k = 0; k < 2; k++ ){int gap = 0, x = x0, y = y0, dx = dx0, dy = dy0;if( k > 0 ) //控制两个方向(正好相反)dx = -dx, dy = -dy;// walk along the line using fixed-point arithmetics,// stop at the image border or in case of too big gapfor( ;; x += dx, y += dy ){uchar* mdata;int i1, j1;if( xflag ){j1 = x;i1 = y >> shift;}else{j1 = x >> shift;i1 = y;}if( j1 < 0 || j1 >= width || i1 < 0 || i1 >= height )break;mdata = mdata0 + i1*width + j1;// for each non-zero point://    update line end,//    clear the mask element//    reset the gapif( *mdata ){gap = 0;line_end[k].y = i1;line_end[k].x = j1;}else if( ++gap > lineGap )//像素间隙大于lineGap 则退出break;}}//分别计算X、Y方向距离good_line = abs(line_end[1].x - line_end[0].x) >= lineLength ||abs(line_end[1].y - line_end[0].y) >= lineLength;for( k = 0; k < 2; k++ ){int x = x0, y = y0, dx = dx0, dy = dy0;if( k > 0 )dx = -dx, dy = -dy;// walk along the line using fixed-point arithmetics,// stop at the image border or in case of too big gapfor( ;; x += dx, y += dy ){uchar* mdata;int i1, j1;if( xflag ){j1 = x;i1 = y >> shift;}else{j1 = x >> shift;i1 = y;}mdata = mdata0 + i1*width + j1;// for each non-zero point://    update line end,//    clear the mask element//    reset the gap//如果*mdata == 1则设置为0,去除已经检测过的点if( *mdata ){//如果是直线,则去除累加器里面的值if( good_line ){adata = accum->data.i;for( n = 0; n < numangle; n++, adata += numrho ){r = cvRound( j1 * ttab[n*2] + i1 * ttab[n*2+1] );r += (numrho - 1) / 2;//r有负值,使r取值在[0,numrho - 1]区间adata[r]--;}}*mdata = 0;}if( i1 == line_end[k].y && j1 == line_end[k].x )break;}}if( good_line ){CvRect lr = { line_end[0].x, line_end[0].y, line_end[1].x, line_end[1].y };cvSeqPush( lines, &lr );if( lines->total >= linesMax )EXIT;}}__END__;cvReleaseMat( &accum );cvReleaseMat( &mask );cvReleaseMat( &trigtab );cvReleaseMemStorage( &storage );
}

四. 总结

  今天的博客就到这里啦,欢迎大家在评论区互相学习讨论,我们下期见,三连哦~

参考链接:
1.【OpenCV入门教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑
2. openCV cvHoughLines2 函数源码解析(CV_HOUGH_PROBABILISTIC 基于概率的霍夫变换)
3. 第六章 - 图像变换 - 霍夫线变换(cvHoughLines2)

cvHoughLines2霍夫直线检测函数详解及源码解析相关推荐

  1. 霍夫直线检测原理详解

  2. RN FlatList使用详解及源码解析

    FlatList使用详解及源码解析 前言 长列表或者无限下拉列表是最常见的应用场景之一.RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧.而在最新的 0.43 版本中,提供 ...

  3. FreeRTOS之Tracealyzer for FreeRTOS(FreeRTOS+Trace) 详解(源码解析+移植)

    源:FreeRTOS之Tracealyzer for FreeRTOS(FreeRTOS+Trace) 详解(源码解析+移植)

  4. okhttp的应用详解与源码解析--http的发展史

    乘5G之势,借物联网之风,Android未来亦可期,Android优势在于开放,手机.平板.车载设备.智能家居等都是Android的舞台,Google不倒,Android不灭,本专栏的同步视频教程已经 ...

  5. Diffusion Model原理详解及源码解析

    作者:秃头小苏@CSDN 编辑:3D视觉开发者社区 文章目录 Diffusion Model原理详解及源码解析 写在前面 Diffusion Model原理详解✨✨✨ 整体思路 实施细节 正向过程 逆 ...

  6. Android应用Context详解及源码解析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 今天突然想起之前在上家公司(做TV与BOX盒子)时有好几个人问过我关于Android ...

  7. 协方差矩阵数学原理,numpy计算协方差矩阵(np.cov)函数详解与源码剖析

    协方差矩阵详解以及numpy计算协方差矩阵(np.cov) 协方差矩阵详解 均值,标准差与方差 由简单的统计学基础知识,我们有如下公式: X ˉ = ∑ i = 1 n X i n \bar X{\r ...

  8. 【OS xv6】1 万字详解shell源码解析命令(内含wsl+vscode调试xv6教程 文档第一章助读)

    现在前面的 嘻嘻几百年没写文了确实没时间,等搞完毕设可以一起重温重温.最近学os,读源码发现还挺多东西得整理的,尤其途中有必要找资料整理的时候,内容有点多有点乱,写在源码已经显得不现实了.用的vsco ...

  9. FreeRTOS 之二 Tracealyzer for FreeRTOS(FreeRTOS+Trace) 详解(源码解析+移植)

    2020/5/19 更新了在使用 4.3.8 时遇到的一些问题说明 2018/5/16 大约一个月之前,Tracealyzer for FreeRTOS目前更新到了4.x,新版本不在区分针对哪个系统, ...

最新文章

  1. python贴吧回帖-python控制浏览器爬取百度贴吧回复并写入Excel
  2. 分析mysql日志文件_MySQL日志文件与分析
  3. DataFrame不同风格比较
  4. 为什么说Serverless是云的未来?
  5. SpringBoot中常见注解
  6. 2015-2016前端知识体系(转)
  7. 把iPad上的视频推送到大麦盒子去
  8. 下载文件HTTP请求及处理过程
  9. db2 导出 oracle,db2导出数据库数据库
  10. Vscode工作区调试(虚拟环境)配置指北
  11. Adobe Photoshop 7.0.1 简体中文版注册码
  12. python 正则表达式 compile_使用compile()函数编译正则表达式【Python技术文章】
  13. canvas画正六边形
  14. 腾讯招聘信息 爬取案例
  15. python 网络通讯 plc_Python TCP通信网络编程
  16. 股票交易接口的应用场景-在miajs中导入apjs
  17. Linux 有/无设备树下 platform_driver 驱动框架
  18. 2020年最新前端框架大全,Web工程师人手一份
  19. 触摸传感器PCB布局设计指南(二)
  20. 关于屏蔽网页广告的方法

热门文章

  1. python中用len函数获取列表的长度
  2. OpenCT—北师大教育学博士罗海风构建大型网络测评社区
  3. sqlserver 分组合并列_数学奥赛中美两国并列第一,这场数学巅峰对决竟是中国天才少年的内战?...
  4. 将小蚂蚁内网穿透bat注册成windows系统服务的方法步骤
  5. RewriteCond指令格式
  6. idea 的奇葩问题
  7. led灯模组是什么东西_现代灯具什么牌子好 节能LED灯新品推荐
  8. Win10 21H1系统远程桌面多用户登录解除限制
  9. 怎么画单极交流放大电路波形图_从吹风机电路中学习二极管的实际应用
  10. VirtualBox从C盘迁移到其它盘方法