imclearboder matlab,Lucas

Lucas-Kanade跟踪算法是视觉跟踪中一个很经典的基于点的逐帧跟踪算法。起初这个算法是用来求解stero matching1的,后来经过Carlo Tomasi2和Jianbo Shi3等人的发展渐趋成熟。Jianbo Shi提出了一种筛选跟踪点特征的方法,使得特征的跟踪更可靠。Jean-Yves Bouguet4详细阐述了如何采用金字塔方式实现LK算法以处理两帧之间特征点位移较大的情况。

问题阐述

首先我们来看一下我们要解决的问题是什么?LK算法是基于特征点的跟踪,而这里的特征点就是每个点对应的一个小窗口图像块,LK所要解决的是求解连续两帧图像相同特征点的位移问题。这里我们假设I和J为连续两帧图像,其(x,y)点的灰度值分别对应I(x,y),J(x,y)。设u=[ux,uy]T是图像I上一点,LK算法的目标是在图像J找到一点v=u+d=[ux+dx,uy+dy]T使得点I(u)和点J(v)是同一个位置。为了求解这样的点,LK求解这两个点对应的小窗口内像素的相似度。设ωx和ωy分别是点左右扩展的窗口范围,这样我们可以定义如下residual function为

(d)=(dx,dy)=∑x=uxωxux+ωx∑y=uyωyuy+ωy(I(x,y)J(x+dx,y+dy))2

窗口大小为(2ωx+1)×(2ωy+1),通常情况下ωx和ωy的值为2,3,4,5,6,7。

标准LK算法

针对上述最优化问题,求解方法是求解(d)关于向量d的偏导使其等于0,即

(d)d|d=dopt=[00]

这样可以推到出其偏导结果:

(d)d=2∑x=uxωxux+ωx∑y=uyωyuy+ωy(I(x,y)J(x+dx,y+dy))J(x+dx,y+dy)d=2∑x=uxωxux+ωx∑y=uyωyuy+ωy(I(x,y)J(x+dx,y+dy))[JxJy]

利用泰勒级数展开J(x+dx,y+dy)得,

J(x+dx,y+dy)=J(x,y)+(dxx+dyy)J(x,y)+12!(dxx+dyy)2J(x,y)+...≈J(x,y)+(dxx+dyy)J(x,y)≈J(x,y)+[JxJy]d

这样得出residual function为,

(d)d≈2∑x=uxωxux+ωx∑y=uyωyuy+ωy(I(x,y)J(x,y)[JxJy]d)[JxJy]

这里关于图像J(x,y)的偏导可以通过求解I(x,y)的偏导近似计算。设

▽I=[IxIy]=[JxJy]T,

δI=I(x,y)J(x,y) 这样residual function变为

12(d)d≈∑x=uxωxux+ωx∑y=uyωyuy+ωy(▽ITdδI)▽IT≈∑x=uxωxux+ωx∑y=uyωyuy+ωy(▽ITd▽ITδI▽IT)

等式两边取倒置

12[(d)d]T≈∑x=uxωxux+ωx∑y=uyωyuy+ωy(▽IdT▽IδI▽I)≈∑x=uxωxux+ωx∑y=uyωyuy+ωy(▽I▽ITdδI▽I)≈∑x=uxωxux+ωx∑y=uyωyuy+ωy([I2xIxIyIxIyI2y]d[δIIxδIIy])

我们用简单符号替代其中的两个部分,分别设

Gb=∑x=uxωxux+ωx∑y=uyωyuy+ωy[I2xIxIyIxIyI2y]=∑x=uxωxux+ωx∑y=uyωyuy+ωy[δIIxδIIy]

现在residual function变成了,

12[(d)d]T≈Gdb

使上式等于0,得出位移d为,

d=G1b

这里必须保证G是可逆的,也就是保证图像I(x,y)在x和y方向上的梯度值必须不是0。

以上便是基本的LK算法的推导过程,具体实现的时候需要多次的迭代才能得到一个较准确的点的位移矢量,类似牛顿-拉弗森方法(Newton-Raphson method)的迭代过程,这是一个逐渐趋近最优值的过程。下面详细介绍迭代的过程,针对第k(k1)次迭代:

设第k1次迭代的位移dk1=[dk1x,dk1y],则我们利用第k1次迭代的位移作为第k次迭代位移的初始化值,即当前次迭代的J(x,y)变为

J(x,y)=J(x+dk1x,y+dk1y)

residual function变为

(d)=(dx,dy)=∑x=uxωxux+ωx∑y=uyωyuy+ωy(I(x,y)J(x+dkx,y+dky))2

通过一次标准的LK算法,得出第k次的位移

dk=G1bk

这里我们发现每次迭代中,G是不变的,通过I(x,y)计算,唯一变化的是b,每次迭代图像J(x,y)对应的窗口都会向所要求的位置点靠近一点点(即上一次迭代的位移作为初始化),而b的计算与J(x,y)有关,所以每次迭代都会发生变化,这样每次迭代需要计算的就只有b。

假设进行了K次迭代后收敛,最终位移的结果为

d=∑k=1Kdk

对于第一次迭代其对应的初始化位移为:

d0=[00]T

但是上述推导的一个基本假设是点特征的位移是很小的,这样才能满足泰勒展开式中只保留前两项的近似操作。而为了能处理较大的位移情况,则需要基于图像金字塔在不同分辨率的图层下进行跟踪。

图像金字塔跟踪

首先举一个简单的例子,比如知道一个点前后两幅图像的位移为16个像素,这么大的位移直接使用标准LK算法是很难计算出来结果的,而如果在图像分辨率降低到原来一半后,其位移就变为8个像素,再降低一半,则为4个像素,如果金字塔的层数是3,则在最底层,点的位移只有两个像素,这样就满足了小位移的假设。这样首先在最底层进行标准LK算法,得出一个位移后乘以2作为上一层的初始位移,再进行标准LK算法,以此类推,最终得到点的位移。

设图像金字塔层数为L=0,1,2...Lm,跟踪是从图像金字塔的最底层Lm开始的,对于图像从第L+1层到L层的跟踪流程,和标准LK算法的迭代有点类似,第L层的初始化位置是基于第L+1层计算出来的。

设gL=[gLxgLy]T是第L层的初始化位移,它是通过第L+1层的位移计算得到的。这样第L层的residual function就变成了

L(dL)=(dLx,dLy)=∑x=uLxωxuLx+ωx∑x=uLyωyuLy+ωy(IL(x,y)JL(x+gLx+dLx,y+gLy+dLy))2

从上式可以看出,第L层的JL(x,y)由于有了gL作为初始化位置使得要求解的位移dL变得很小,也就很适合用标准的LK算法计算了。

gL的计算是通过第L+1层的位移和初始化位置计算的,

gL=2(gL+1+dL+1)

对于最底层的初始化位置设为,

gLm=[00]T

最后得出点的位移为,

d0=g0+d0

图像金字塔的构建是通过首先对上一层图像进行去边缘滤波,然后下采样得到的,具体的实现参考下面章节。

OpenCV代码实现分析

这个算法的实现主要分为三个重要的部分:图像金字塔的构建,图像梯度图的计算,标准LK算法的迭代。

图像金字塔的构建

OpenCV是通过下采样和去边缘滤波器两个流程生成的多分辨率的图像金字塔,当然为了程序的优化,这两个流程是同时进行的。滤波器采用的kernel是

12561464141624164624362464162416414641

程序实现的核心函数是

1void pyrDown(InputArray src, OutputArray dst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT )

其OpenCV的源代码如下(OpenCV-2.4.8/video/pyramids.cpp/line:187)。这个函数实现的大体思路是以目标图像的长宽为基准同时实现对源图像的去边缘滤波以及下采样操作。因为图像在滤波后还要做下采样,如果这两步骤是分开做的话,前面滤波到的像素就会额外计算了一半下采样后根本不需要的像素,浪费了计算,所以这里仅仅是滤波了下采样中保留下来的像素。

滤波器的实现最直接的想法就是每次计算所有窗口里面的像素值然后求均值,但这样做会重复计算前后两行重叠的部分像素和,代码效率并不高,实际上代码中的实现是首先计算每一个像素对应的前后两行的行和,然后存储下这5个行和,每行所有像素计算完所有5个行和并存储好后,再用一个for循环求和每个像素的5个行和,这样就可以避免重复计算前后两行重复的行和而提高了效率。如下图所示,中间红色像素对应窗口为1-5行,蓝色像素对应窗口为2-6行,其中其中2-5行的和都是重复的,不需要重复计算。

61147728_1.pngpyrDown实现示意图

这里有几个需要解释的地方:

第一,代码第54行的for循环实现的是求解每个元素对应的各个行和。这里它采用了一种循环存取机制。例如,当计算目标图像第k行像素(即源图像第2k行像素)时,其需要求解的行和分别是对应源图像上的2k-2,2k-1,2k,2k+1,2k+2行,这时假设内存中是按照顺序方式存储的,当计算目标图像k+1行像素(即源图像第2k+2行像素)时,我们只需要再计算2k+3和2k+4的行和并且存储在本来存储2k-2和2k-1行的行和的内存中,这样的计算和存储开销是最小的。这样说可能有点抽象,如下图所示,左边是计算目标图像第k行像素时,数组中5个元素存储的内容,右边是计算目标图像k+1行像素存储的内容,仅仅是把原来存储2k-2和2k-1行的元素替换成新计算出来的2k+3和2k+4行的行和,这样在访问这些行和时,顺序就会发生一定的变化,由左边的1,2,3,4,5变成右边的4,5,1,2,3。这样在计算k+2行像素时,只需把原来存储2k-2和2k-1的内存替换为2k+3和2k+4的行和即可,然后依次类推。查看程序第57行WT* row = buf + ((sy - sy0) % PD_SZ)*bufstep;,采用的就是这种循环存储方法。那这样在取这些存在数组中的行和是,顺序也是对应的顺序,因为每个行和要乘以的权重不一,自然顺序不能错。顺序的计算方法查看程序第122行rows[k] = buf + ((y*2 - PD_SZ/2 + k - sy0) % PD_SZ)*bufstep;

61147728_2.png存储示意图

第二,程序在处理边缘问题是采用了borderInterpolate这个函数,主要涉及对称边缘图像或直接复制边缘图像等方式添加虚拟边缘。当boderType = BORDER_REPLICATE时,是简单的复制边缘图像,当boderType = BORDER_REFLECT时,就是以边缘为中心对称复制边缘里层的图像。而pyrDown采用的就是这种边缘处理方式。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130template void

pyrDown_( const Mat& _src, Mat& _dst, int borderType )

{

//滤波器的窗口大小

const int PD_SZ = 5;

typedef typename CastOp::type1 WT;

typedef typename CastOp::rtype T;

CV_Assert( !_src.empty() );

Size ssize = _src.size(), dsize = _dst.size();

int cn = _src.channels();

int bufstep = (int)alignSize(dsize.width*cn, 16);

AutoBuffer _buf(bufstep*PD_SZ + 16);

WT* buf = alignPtr((WT*)_buf, 16);

//处理左右边界

int tabL[CV_CN_MAX*(PD_SZ+2)], tabR[CV_CN_MAX*(PD_SZ+2)];

AutoBuffer _tabM(dsize.width*cn);

int* tabM = _tabM;

//存储PD_SZ行的和,用于第二次循环列的求和

WT* rows[PD_SZ];

CastOp castOp;

VecOp vecOp;

CV_Assert( std::abs(dsize.width*2 - ssize.width) <= 2 &&

std::abs(dsize.height*2 - ssize.height) <= 2 );

int k, x, sy0 = -PD_SZ/2, sy = sy0, width0 = std::min((ssize.width-PD_SZ/2-1)/2 + 1, dsize.width);

for( x = 0; x <= PD_SZ+1; x++ )

{

int sx0 = borderInterpolate(x - PD_SZ/2, ssize.width, borderType)*cn;

int sx1 = borderInterpolate(x + width0*2 - PD_SZ/2, ssize.width, borderType)*cn;

for( k = 0; k < cn; k++ )

{

tabL[x*cn + k] = sx0 + k;

tabR[x*cn + k] = sx1 + k;

}

}

ssize.width *= cn;

dsize.width *= cn;

width0 *= cn;

for( x = 0; x < dsize.width; x++ )

tabM[x] = (x/cn)*2*cn + x % cn;

//基于目标图像的高度

for( int y = 0; y < dsize.height; y++ )

{

T* dst = (T*)(_dst.data + _dst.step*y);

WT *row0, *row1, *row2, *row3, *row4;

// fill the ring buffer (horizontal convolution and decimation)

//水平方向求解各个行和

for( ; sy <= y*2 + 2; sy++ )

{

//循环存储行和

WT* row = buf + ((sy - sy0) % PD_SZ)*bufstep;

int _sy = borderInterpolate(sy, ssize.height, borderType);

const T* src = (const T*)(_src.data + _src.step*_sy);

int limit = cn;

const int* tab = tabL;

for( x = 0;;)

{

for( ; x < limit; x++ )

{

row[x] = src[tab[x+cn*2]]*6 + (src[tab[x+cn]] + src[tab[x+cn*3]])*4 +

src[tab[x]] + src[tab[x+cn*4]];

}

if( x == dsize.width )

break;

if( cn == 1 )

{

for( ; x < width0; x++ )

row[x] = src[x*2]*6 + (src[x*2 - 1] + src[x*2 + 1])*4 +

src[x*2 - 2] + src[x*2 + 2];

}

else if( cn == 3 )

{

for( ; x < width0; x += 3 )

{

const T* s = src + x*2;

WT t0 = s[0]*6 + (s[-3] + s[3])*4 + s[-6] + s[6];

WT t1 = s[1]*6 + (s[-2] + s[4])*4 + s[-5] + s[7];

WT t2 = s[2]*6 + (s[-1] + s[5])*4 + s[-4] + s[8];

row[x] = t0; row[x+1] = t1; row[x+2] = t2;

}

}

else if( cn == 4 )

{

for( ; x < width0; x += 4 )

{

const T* s = src + x*2;

WT t0 = s[0]*6 + (s[-4] + s[4])*4 + s[-8] + s[8];

WT t1 = s[1]*6 + (s[-3] + s[5])*4 + s[-7] + s[9];

row[x] = t0; row[x+1] = t1;

t0 = s[2]*6 + (s[-2] + s[6])*4 + s[-6] + s[10];

t1 = s[3]*6 + (s[-1] + s[7])*4 + s[-5] + s[11];

row[x+2] = t0; row[x+3] = t1;

}

}

else

{

for( ; x < width0; x++ )

{

int sx = tabM[x];

row[x] = src[sx]*6 + (src[sx - cn] + src[sx + cn])*4 +

src[sx - cn*2] + src[sx + cn*2];

}

}

limit = dsize.width;

tab = tabR - x;

}

}

// do vertical convolution and decimation and write the result to the destination image

//循环取得各个行和的指针

for( k = 0; k < PD_SZ; k++ )

rows[k] = buf + ((y*2 - PD_SZ/2 + k - sy0) % PD_SZ)*bufstep;

row0 = rows[0]; row1 = rows[1]; row2 = rows[2]; row3 = rows[3]; row4 = rows[4];

x = vecOp(rows, dst, (int)_dst.step, dsize.width);

//每一行结束的位置,把该行所有像素点的滤波结果求解出来

for( ; x < dsize.width; x++ )

dst[x] = castOp(row2[x]*6 + (row1[x] + row3[x])*4 + row0[x] + row4[x]);

}

}

图像梯度图的计算

图像梯度的实现也要设计到窗口计算问题,所以和上面提到的方法有类似的地方,也是先计算行和,再计算列和。程序采用了Sharr算子进行梯度的计算,x方向的梯度图算子是

31030003103 y方向的梯度算子是

30310010303

OpenCV详细代码如下,注意这里程序计算出的x和y方向的梯度值分别存放在了一个双通道的目标图像中,每个通道占用一个方向的梯度值。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86static void calcSharrDeriv(const cv::Mat& src, cv::Mat& dst)

{

using namespace cv;

using cv::detail::deriv_type;

int rows = src.rows, cols = src.cols, cn = src.channels(), colsn = cols*cn, depth = src.depth();

CV_Assert(depth == CV_8U);

dst.create(rows, cols, CV_MAKETYPE(DataType::depth, cn*2));

#ifdef HAVE_TEGRA_OPTIMIZATION

if (tegra::calcSharrDeriv(src, dst))

return;

#endif

int x, y, delta = (int)alignSize((cols + 2)*cn, 16);

AutoBuffer _tempBuf(delta*2 + 64);

deriv_type *trow0 = alignPtr(_tempBuf + cn, 16), *trow1 = alignPtr(trow0 + delta, 16);

#if CV_SSE2

__m128i z = _mm_setzero_si128(), c3 = _mm_set1_epi16(3), c10 = _mm_set1_epi16(10);

#endif

for( y = 0; y < rows; y++ )

{

const uchar* srow0 = src.ptr(y > 0 ? y-1 : rows > 1 ? 1 : 0);

const uchar* srow1 = src.ptr(y);

const uchar* srow2 = src.ptr(y < rows-1 ? y+1 : rows > 1 ? rows-2 : 0);

deriv_type* drow = dst.ptr(y);

// do vertical convolution

x = 0;

#if CV_SSE2

for( ; x <= colsn - 8; x += 8 )

{

__m128i s0 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i*)(srow0 + x)), z);

__m128i s1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i*)(srow1 + x)), z);

__m128i s2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i*)(srow2 + x)), z);

__m128i t0 = _mm_add_epi16(_mm_mullo_epi16(_mm_add_epi16(s0, s2), c3), _mm_mullo_epi16(s1, c10));

__m128i t1 = _mm_sub_epi16(s2, s0);

_mm_store_si128((__m128i*)(trow0 + x), t0);

_mm_store_si128((__m128i*)(trow1 + x), t1);

}

#endif

for( ; x < colsn; x++ )

{

int t0 = (srow0[x] + srow2[x])*3 + srow1[x]*10;

int t1 = srow2[x] - srow0[x];

trow0[x] = (deriv_type)t0;

trow1[x] = (deriv_type)t1;

}

// make border

int x0 = (cols > 1 ? 1 : 0)*cn, x1 = (cols > 1 ? cols-2 : 0)*cn;

for( int k = 0; k < cn; k++ )

{

trow0[-cn + k] = trow0[x0 + k]; trow0[colsn + k] = trow0[x1 + k];

trow1[-cn + k] = trow1[x0 + k]; trow1[colsn + k] = trow1[x1 + k];

}

// do horizontal convolution, interleave the results and store them to dst

x = 0;

#if CV_SSE2

for( ; x <= colsn - 8; x += 8 )

{

__m128i s0 = _mm_loadu_si128((const __m128i*)(trow0 + x - cn));

__m128i s1 = _mm_loadu_si128((const __m128i*)(trow0 + x + cn));

__m128i s2 = _mm_loadu_si128((const __m128i*)(trow1 + x - cn));

__m128i s3 = _mm_load_si128((const __m128i*)(trow1 + x));

__m128i s4 = _mm_loadu_si128((const __m128i*)(trow1 + x + cn));

__m128i t0 = _mm_sub_epi16(s1, s0);

__m128i t1 = _mm_add_epi16(_mm_mullo_epi16(_mm_add_epi16(s2, s4), c3), _mm_mullo_epi16(s3, c10));

__m128i t2 = _mm_unpacklo_epi16(t0, t1);

t0 = _mm_unpackhi_epi16(t0, t1);

// this can probably be replaced with aligned stores if we aligned dst properly.

_mm_storeu_si128((__m128i*)(drow + x*2), t2);

_mm_storeu_si128((__m128i*)(drow + x*2 + 8), t0);

}

#endif

for( ; x < colsn; x++ )

{

deriv_type t0 = (deriv_type)(trow0[x+cn] - trow0[x-cn]);

deriv_type t1 = (deriv_type)((trow1[x+cn] + trow1[x-cn])*3 + trow1[x]*10);

drow[x*2] = t0; drow[x*2+1] = t1;

}

}

}

标准LK算法的迭代

这个部分的实现需要注意的地方主要是subpixel的计算,因为每次计算出的位移都很小,考虑到计算的精度,必须的精确到小数位,所以需要注意如何计算一个小数位置的像素值,这个就和线性插值是类似的。如下图所示,小数位置的像素值是有四个相邻像素拟合出来的。设中间蓝色像素点的坐标为(xsub,ysub),四周四个整数位置的像素点自分别为(x0,y0),(x0,y1),(x1,y0),(x1,y1),中间蓝色像素离其他四个像素水平和垂直方向上的像素距离分别为w00,w01,w10,w11,如图中所标。

61147728_3.pngsubpxiel的计算

subpxiel的计算公式为

I(xsub,ysub)=w11w01I(x0,y0)+w10w01I(x0,y1)+w11w00I(x1,y0)+w10w00I(x1,y1)

在计算权重wi,j的时候,为了避免浮点计算,会对wi,j乘以一定的倍数使用整数运算。用的subpixel的地方主要有两个地方,一个是在计算矩阵G的时候,需要取梯度图的值,但是点的位置不一定是整数,所以一定要使用subpixel取值;还有一个地方是计算b的时候,因为要取前后两幅图像的像素值,而这两个点的位置也不一定是整数,所以也要用到subpixel。考虑到这部分的OpenCV代码比较长,而理解相对没有那么困难,这里就不再贴出,仅列出上面需要注意的地方,需要的可以参考oepncv2.4.8/video/lkpramid.cpp/line:159-483

尾声

LK算法的实现除了以上所讲的OpenCV的实现外,还有几个其他的版本,分别是由Stan Birchfield实现的版本KLT,速率相比OpenCV慢一些;一个GPU加速实现的版本GPU KLT;一个Matlab实现的版本Matlab KLT以及一个Java实现的版本Java KLT。

Bruce D. Lucas and Takeo Kanade. An Iterative Image Registration Technique with an Application to Stereo Vision. International Joint Conference on Artificial Intelligence, pages 674-679, 1981.

Carlo Tomasi and Takeo Kanade. Detection and Tracking of Point Features. Carnegie Mellon University Technical Report CMU-CS-91-132, April 1991.

Jianbo Shi and Carlo Tomasi. Good Features to Track. IEEE Conference on Computer Vision and Pattern Recognition, pages 593-600, 1994.

Jean-Yves Bouguet. Pyramidal Implementation of the Lucas Kanade Feature Tracker.

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

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

相关文章

matlab求勒让德多项式零点,有没有勒让德多项式导数 零点程序

求N1次勒让德多项式的m(m0,1,...)阶导数零点Matlab程序子程序&#xff1a;function xjp(N,alpha,beta)n1:N;a(1)(alphabeta2)/2;b(1)(beta-alpha)/2;a([2:N1])(2*nalphabeta1).*(2*nalphabeta2)./(2*(n1).*(nalphabeta1));b([2:N1])(alpha*alpha-beta*beta)*(2*nalphabeta1)./(…

js_long.php,protobuf.js 与 Long.js的使用详解

这次给大家带来protobuf.js 与 Long.js的使用详解&#xff0c;是急用protobuf.js 与 Long.js的注意事项有哪些&#xff0c;下面就是实战案例&#xff0c;一起来看一下。protobuf.js的结构和webpack的加载之后的结构很相似。这样的模块化组合是个不错的结构方式。1个是适应了不同…

oracle 存储过程设置回滚点,(转)oracle 存储过程事宜使用断点回滚 -savepoint

学习存储过程中使用断点回滚事务时&#xff0c;发现目前网络上存在一个问题&#xff0c;那就是使用断点回滚后&#xff0c;都忘记了一个很重要的事情&#xff0c;提交事务。虽然使用了断点回滚&#xff0c;但是断点回滚不像rollBack或commit一样结束当前事务&#xff0c;而使用…

oracle每季度补丁,Oracle 2020 年第四季度补丁发布

半个月前&#xff0c;也就是 10 月 20 日&#xff0c; Oracle 发布了今年最后一次补丁更新&#xff0c;那么很多人都想打最新的 PSU&#xff0c;理由是有被扫到各种漏洞&#xff0c;有的扫描工具着实太坑&#xff0c;这里就不用说了&#xff0c;前几天看到盖总发布的文章2020年…

路由器 刷 linux系统版本,在Linux下用tftp刷写路由器固件

(以Buffalo WHR-G300N V2路由器为例)以发行版Ubuntu为例(如果你在Windows下&#xff0c;可下载Ubuntu的ISO文件&#xff0c;再用wubi安装Ubuntu&#xff0c;可免去重新分区的麻烦)&#xff0c;下载Buffalo WHR-G300N V2路由器的FTP版固件文件&#xff0c;放到Ubuntu的/home目录…

eval函数linux,Python中的eval()、exec()及其相关函数

刚好前些天有人提到eval()与exec()这两个函数&#xff0c;所以就翻了下Python的文档。这里就来简单说一下这两个函数以及与它们相关的几个函数&#xff0c;如globals()、locals()和compile()&#xff1a;1. eval函数函数的作用&#xff1a;计算指定表达式的值。也就是说它要执行…

linux更改语言脚本,Linux shell脚本入门——shell语言脚本【CentOS】

认识脚本是使用一种特定的描述性语言&#xff0c;依据一定的格式编写的可执行文件。脚本语言又被称为扩建的语言, 或者动态语言, 是一种编程语言, 用来控制软件应用程序, 脚本通常是以文本 (ASCⅡ) 保存, 只是在被调用时进行解释或者编译。学习shell脚本的用途对于一个合格的系…

linux视图版怎么输入命令,分享在Linux命令下操作MySQL视图实例代码

视图VIEW命令简介&#xff1a;VIEW视图就是存储下来的SELECT语句数据1。创建视图命令格式&#xff1a;创建[或更换][ALGORITHM {UNDEFINED | MERGE | 不是Temptable}][DEFINER {user | 当前用户 }][SQL SECURITY {DEFINER | INVOKER}]VIEW view_name [(column_list)]AS selec…

宝塔linux 做负载均衡,利用BT宝塔面板做网站多服务器负载均衡图文教程

对于我们做网站的来说&#xff0c;如果流量大了&#xff0c;一台服务器肯定是不够的&#xff0c;接下来要考虑很多方面&#xff0c;比如动静分离、比如数据库异步&#xff0c;比如负载均衡等等。因为我们很多新手站长都用的是BT宝塔面板&#xff0c;下面虾皮路就介绍一下利用BT…

血型遗传关系c语言编程,根据血型遗传关系,编程实现:○1.输入

满意答案df4dfw5e562017.05.28采纳率&#xff1a;51% 等级&#xff1a;6已帮助&#xff1a;106人//仅作参考#include "stdio.h"#include "conio.h"#include "stdlib.h"#include "string.h"int studentNum 0;#define MAXSTUDENT (1…

android 通话结束广播,在Android中以编程方式结束通话

侃侃尔雅您无需成为系统应用程序。首先&#xff0c;com.android.internal.telephony在您的项目中创建包&#xff0c;并将其放入名为“ ITelephony.aidl” 的文件中&#xff1a;package com.android.internal.telephony; interface ITelephony { boolean endCall(); vo…

带nfc 的 android 华为,华为手机NFC功能,原来这么好用,不会用真可惜几千块钱了...

华为手机NFC功能&#xff0c;原来这么好用&#xff0c;不会用真可惜几千块钱了2019-05-06 17:40:259点赞12收藏6评论华为手机在国内也是数一数二的了&#xff0c;那么用华为手机的小伙伴&#xff0c;怎么能错过华为手机中NFC的功能呢&#xff1f;下面就随小编一起来了解一下吧。…

小米3升级android 6.0,可升级Android 6.0机型一览 小米手机亮了

前不久&#xff0c;Android M被谷歌正式确定为6.0系统&#xff0c;代号Marshmallow&#xff0c;预计年底前发布。而对于普通用户来说&#xff0c;最关心的就是自己的手机能不能升级。不过别着急&#xff0c;在此之前&#xff0c;我们不妨看看Android 6.0有哪些新功能。一、Andr…

浏览器兼容性怎么设置HTML,ie10浏览器中兼容性设置在哪里

之前ie浏览器一直被诟病的因素之一就是兼容性&#xff0c;不过ie10浏览器可以对兼容性进行设置&#xff0c;使得在兼容性视图中显示所有网站&#xff0c;具体怎么设置呢。下面由学习啦小编为你整理了ie10浏览器兼容性设置在哪里的解决方法&#xff0c;希望对你有帮助!ie10兼容性…

ksu7对讲机调频软件_科普 | 数字对讲机的群呼功能原理是什么?你了解多少?...

点击上方蓝字关注我们&#xff01;无线对讲机群呼&#xff0c;是为了更好地达到1个数字对讲机能够同一时间跟多个数字对讲机实现语音通话。群呼跟单呼有很多共同之处&#xff0c;下面我们和大家来说说数字对讲机群呼与单呼不同点。当1个数字对讲机处在待机状态的情况下机要发动…

计算机学不学工程制图,大一,马上要考试了,不想再学工程制图了?

买买提烤串累计帮助了195人工科和文科思维方式是不一样的。本人刚好高中理科&#xff0c;大学学文的&#xff0c;而且一专业是中文&#xff0c;二专业管理。大多数工科的特点是靠努力为主的&#xff0c;除了特别难的那种专业&#xff0c;大多数工科的特点是这样的&#xff1a;除…

辅助驾驶等级_双AMR电机位置传感器,助力自动驾驶安全出行

好文章当然要分享啦~如果您喜欢这篇文章&#xff0c;请联系后台添加白名单&#xff0c;欢迎转载哟~在自动驾驶汽车快速发展的今天&#xff0c;汽车电气化趋势逐渐朝着半自动驾驶和全自动驾驶发展&#xff0c;尤其是&#xff0c;为了让电子转向助力(EPS)和电子制动系统满足必要的…

计算机怎么建立共享网络打印机共享,电脑如何连接局域网中的共享打印机—两种方法...

多台电脑而只有一台打印机的时候可以通过连接局域网的方式共享打印机&#xff0c;这样就不需要给每一台电脑都配上打印机了。下面是学习啦小编收集整理的电脑如何连接局域网中的共享打印机—两种方法&#xff0c;希望对大家有帮助~~电脑连接局域网中的共享打印机—两种方法操作…

html5 密码框明文,elementUI的密码框的密文和明文

基于elementui 框架的登录时密码框的明文和密文登录1、templatev-model.trim"ruleForm.password"placeholder"请输入密码":type"passw"clearableblur"onBlur">2、scriptdata(){return{icon: "el-input__icon el-icon-view&quo…

1个显示器分割2画面_我家房子100㎡,原始设计有2个卫生间,纠结保留1个还是2个...

100平米左右的新房装修&#xff0c;设计布局最纠结。尤其是&#xff0c;面积本来就是中等、不大的户型&#xff0c;开发商还给配了2个卫生间。一个主卫&#xff0c;一个客卫。那么问题来了&#xff1a;对于主卧卫生间&#xff0c;我到底是保留还是改成其他的用途&#xff1f;只…