定义原始图像(干净图像)I和噪声图像K,则有
放在前面的知识

而机器人通电启动时候放置的方向将作为0度,如下图所示:

随着时间的推移,机器人将会获得一个实际的偏移角度,对这种漂移影响最大的因素是陀螺仪的偏置稳定度规格,这是衡量陀螺仪在长时间段内测量结果稳定性的一个指标。如果将机器人开启一个小时并且不移动它,陀螺仪在一个小时结束时读取的数值就不是零。也就是说如果开电一个小时,机器人放置静止,一个小时后可能机器人会产生20-30度的偏转角度,而开电十分钟之内可能会产生3度左右的偏转,这些偏移角度都将是机器人前进方向相比较之前产生的偏转。这就是陀螺仪的漂移。
陀螺仪数据校准
零点偏移量,这个概念主要是飞控中对陀螺仪进行数据的校准必须掌握的概念。什么叫陀螺仪的数据校准:飞控所获得的数据减去零点偏移量集可,而零点偏移量就是采集到的数据的平均值。
陀螺仪数据校准的目的
根据陀螺仪计算得到的零点偏差对于水平稳定有着重要的作用,例如X轴产生0.2度/秒的零偏,那么通过X轴计算得到的角度也不会从0度开始,直接会导致姿态角产生一定的偏差,飞行过程中会很难控制水平。
校准方法
上电会自动校准,产生零偏,每次上电得到的零偏量是不同的。
注意 陀螺仪上电校准需要静止一段时间,否则是一段错误的数值,这是因为陀螺仪的校准需要识别静止状态(两次采集到的数据差的和是否超过阈值,超过阈值则说明处于运动状态),否则代码将一直处于待机循环状态直至静止状态符合。
陀螺仪的误差分析
误差主要是两类:
系统性误差
本质上就是有规律的误差,可以实时补偿,常值偏移、轴安装误差、比例因子等
随机误差
本质上就是随机产生的噪声,一般情况下比较难拟合,没有固定的函数去拟合产生的噪声项,一般采用时间分析法对误差进行建模分析,再通过卡尔曼滤波等算法去减小噪声项的影响。(注卡尔曼滤波,也就是通过前一时刻以及以往产生的误差来预计下一时刻物体的最佳位置或者可能出现位置)
从物理意义以及误差来源可以将误差分为如下几种陀螺仪的误差:




本质上是
图像对齐
\textcolor{yellow}{\textbf{图像对齐}}
图像对齐的任务。视频稳定通过两种方式,离线和在线,离线方式就是诸如premire类似的后处理工具;在线视频稳定通过两种方式:单一运动参数模型(仿射、单适应性等)或者陀螺仪传感器。meshflow是一个空间平滑稀疏运动场,空间矢量只存在于空间网格的顶点,特别是,匹配特征点上的运动矢量被转移到其相应的附近网格顶点。网格流是通过两个中值滤波器为每个顶点指定一个唯一的运动矢量来生成的。
MeshFlow方法可以概括如下:在视频帧上放置一个规则的2D网格。我们会跟踪连续帧之间的图像特征角点,在每个特征角点位置处会生成一个运动矢量。接下来,将这些运动矢量转移到其相应的附近网格顶点,以便每个顶点从其周围特征累积若干运动。meshflow是由所有网格顶点运动组成的一个稀疏2D阵列
(1) 网格 \textcolor{red}{网格} 网格。首先在每一帧的图像上形成16x16的网格
(2) 获取 m o t i o n v e c t o r ( M V ) \textcolor{red}{获取motion vector(MV)} 获取motionvector(MV)。特征提取图像,一般是获取到图像的特征角点集合(通过具体的方法函数获得FAST或者SIFT或者ORB特征角点集合),相邻帧匹配到的特征位置的差作为MV,特征差也就是运动矢量,反映后面的帧在前一帧上面的运动趋势。针对一对特征匹配点 P P P和 P ^ \hat{P} P^,有运动矢量 V P = P − P ^ V_P=P-\hat{P} VP=P−P^
(3) 得到 M e s h F l o w \textcolor{red}{得到MeshFlow} 得到MeshFlow,将MV关联到相邻的网格顶点,可以在下图中看出, p p p点以及 p ′ p^\prime p′分别表示当前帧以及上一帧上的某一个图像特征点位置,然后在当前帧上形成了16 × \times × 16的网格,进一步在当前特征点所在的位置上形成一个椭圆区域,该区域包含了n个网格区域,将每一个网格区域的顶点均赋予特征点所在的运动矢量。
下述的流程表示了上述的三个步骤:

(4)
第一个中值滤波器
F
1
进行过滤
\textcolor{red}{第一个中值滤波器F1进行过滤}
第一个中值滤波器F1进行过滤。一个网格顶点会关联到多个特征匹配的MV,用F1进行平滑处理得到规则化的meshflow。如图所示:

(5)
第二个中值滤波器
F
2
进行过滤
\textcolor{red}{第二个中值滤波器F2进行过滤}
第二个中值滤波器F2进行过滤。在3
×
\times
× 3的中值滤波器处理之后,去除离群点噪声,产生空间稀疏运动场。
黄色箭头
\textcolor{yellow}{黄色箭头}
黄色箭头就是噪声流,而
橙色箭头
\textcolor{orange}{橙色箭头}
橙色箭头则是我们想要的网格顶点流,如图所示:

两个中值滤波器提供了必要的空间平滑,对于噪声流也就是不一致流的处理至关重要,噪声流上的运动补偿一般会造成空间视觉中不连续边界的渲染虚伪影,
(6) 顶点 p r o f i l e \textcolor{red}{顶点profile} 顶点profile。每一个顶点MV路径内数据。
每一个网格产生多个MV,而角点响应了全局阈值的大小,阈值容易受到高纹理区域的偏移,因此,全局角点响应阈值->划分为小区域的局部响应阈值,旨在避免在语义信息少的区域(纹理较差的区域)采集不到MV,例如天空地面就很容易采集不到MV。根据网格划分,设置局部阈值,避免部分网格内检测不到合适数量的特征点的问题。
原图划分为4x4的子图,使用局部RANSAC排除离群点。由误匹配和动态目标产生的运动背离可以被成功去除,由深度变化和镜头rolling影响产生的小变化点可以被保留。中值滤波和RANSAC都对鲁棒的去处离群点很重要,前者局部作用,后者在更全局的范围作用。
在MehFlow之前,用全部匹配后的特征点计算出一个全局的单应性矩阵 F t F_{t} Ft用以表征全图的运动向量 V t V_{t} Vt,并用 F t F_t Ft对 P ^ \hat{P} P^的坐标进行预矫正,即该点的局部运动向量为 V P ^ = P − F t P ^ \hat{V_P}=P-F_t\hat{P} VP^=P−FtP^,该点的全部运动向量为 V P = V t + V P ^ V_P=V_t+\hat{V_P} VP=Vt+VP^。warping 操作得到最后组合的全局和局部的组合图像帧。
计算一个基准速度
O ( P ( t ) ) = ∑ t ( ∥ P ( t ) − C ( t ) ∥ 2 + λ t ∑ r ∈ Ω t w t , r ∥ P ( t ) − P ( r ) ∥ 2 ) \mathcal{O}(\mathbf{P}(t))=\sum_{t}\left(\|\mathbf{P}(t)-\mathbf{C}(t)\|^{2}+\lambda_{t} \sum_{r \in \Omega_{t}} w_{t, r}\|\mathbf{P}(t)-\mathbf{P}(r)\|^{2}\right) O(P(t))=∑t(∥P(t)−C(t)∥2+λt∑r∈Ωtwt,r∥P(t)−P(r)∥2)
上述的等式中, P ( t ) \mathbf{P}(t) P(t)是优化路径,而 C ( t ) \mathbf{C}(t) C(t)则是原始路径, λ t \lambda_{t} λt是平衡参数也是最重要的参数,它如果值为0,表示视频帧没有发生任何的裁剪和抖动,因此设置它的值越小表示视频帧的裁剪和抖动程度将会越少。 ω t \omega_{t} ωt是时序半径, w t , r w_{t,r} wt,r是高斯分布权重。前项约束优化path与原始path一致,后项约束序列窗口之间每一帧的平滑,离线场景依赖前后帧,每一帧求解一个最优解 λ t \lambda_{t} λt。
使用离线视频,预测出场景下适用的λt。使用两个相关标志元素,转移元素Tv(适合快速镜头移动),仿射组成元素Fa(适合复杂景深变换),两个都从相邻帧的全局homography中提取。其中,Tv转移元素的计算方式是计算前后帧之间的移,计算方式如下:
T v = ( v x 2 + v y 2 ) T_{v}=\sqrt{\left(v_{x}^{2}+v_{y}^{2}\right)} Tv=(vx2+vy2)
Fa由homography中两个最大的特征值比值计算。视频数据集离线优化,拟合与λt线性关系:
λ t ′ = − 1.93 ∗ T v + 0.95 \lambda_{t}^{\prime}=-1.93 * T_{v}+0.95 λt′=−1.93∗Tv+0.95
λ t ′ ′ = 5.83 ∗ F a + 4.88 \lambda_{t}^{\prime \prime}=5.83 * F_{a}+4.88 λt′′=5.83∗Fa+4.88
最终的 λ t \lambda_t λt通过 m a x ( m i n ( λ t ′ , λ t ′ ′ ) , 0 ) max(min(\lambda_t\prime,\lambda_t\prime\prime),0) max(min(λt′,λt′′),0)取得,越小的 λ t \lambda_t λt越可以拟合两者,
下图展示的是我们可视化在线视频稳定的一个流程直观图:

其中,红色的是未来帧,会在初始的时候延迟一帧的间隔,而绿色是展示的我们所看到的视频帧,白色的是过去帧。
采用一个移动的缓存窗口用来缓存过去帧,丢弃最旧的视频帧保留较新的视频帧,这个buffer初始设置很小并逐步增加大小。
40帧前向缓存buffer,增加第三项约束
O
(
P
(
ξ
)
(
t
)
)
=
∑
t
∈
Φ
(
∥
P
(
ξ
)
(
t
)
−
C
(
t
)
∥
2
+
λ
t
∑
t
∈
Φ
,
r
∈
Ω
t
w
t
,
r
∥
P
(
ξ
)
(
t
)
−
P
(
ξ
)
(
r
)
∥
2
)
+
β
∑
t
∈
Φ
∥
P
(
ξ
)
(
t
−
1
)
−
P
(
ξ
−
1
)
(
t
−
1
)
∥
2
\begin{aligned} \mathcal{O}(\mathbf{P}^{(\xi)}(t)) &=\sum_{t \in \Phi}(\|\mathbf{P}^{(\xi)}(t)-\mathbf{C}(t)\|^{2}+\lambda_{t} \sum_{t \in \Phi, r \in \Omega_{t}} w_{t, r}\|\mathbf{P}^{(\xi)}(t)-\mathbf{P}^{(\xi)}(r)\|^{2}) \\ &+\beta \sum_{t \in \Phi}\|\mathbf{P}^{(\xi)}(t-1)-\mathbf{P}^{(\xi-1)}(t-1)\|^{2} \end{aligned}
O(P(ξ)(t))=t∈Φ∑(∥P(ξ)(t)−C(t)∥2+λtt∈Φ,r∈Ωt∑wt,r∥P(ξ)(t)−P(ξ)(r)∥2)+βt∈Φ∑∥P(ξ)(t−1)−P(ξ−1)(t−1)∥2
U = P-C
优化网格顶点运动P减去原始网格顶点运动C,得到更新运动向量U,也就是更新运动网格。依据更新运动网格对原图warp
三项评估指标越接近于1说明效果越好
去掉黑色边界区域之后的视频图像比例,比例越大说明裁剪越少,视频的质量效果越好。在输入和输出视频之间的每一帧处都安装了单应性Bt。每个帧的裁剪比可以从单应性的比例分量中提取。我们对所有帧的所有比率进行平均,以得出裁剪比率。
该项指标同样由Bt产生,Bt中的两项最大的特征值比值作为失真得分的估计,该比值同样作为了求平衡参数时候的Fa的计算方式。每一个帧都会产生一个失真得分,选择最差的作为最终的失真得分
我们使用从稳定视频中提取的顶点轮廓进行评估。我们在频域中分析每个顶点轮廓。我们取几个最低频率的结果并计算全频率上的能量百分比(直流分量除外)。从所有配置文件中取平均值得出最终分数
对于两个图像A和B,首先提起各自的图像特征,然后匹配A与B图像中的特征,然后求解A与B的对齐矩阵,这个对齐矩阵就是求变换矩阵的过程,之前是最小二乘法现在是随机数一致方法,其基本思想如图:

即————随机选取S个样本点,生成一个模型,这个模型会对对这些样本点的局内点进行计数,重复N次之后选取样本点最多的模型
homography也被称为H矩阵 它就是一个3
×
\times
× 3的矩阵,一般使用单应性矩阵关联同一个场景下的两个图片,从而实现全景拼接。如图

具体有如下不同2D仿射变换类型:
投影变换8dof —将图像转换为不同的投影。
仿射变换6dof —转换将保留原图像平行线的图像。
欧几里得变换3dof —旋转原图像。
相似变换4dof —旋转和缩放原图像
我们用下图中的公式表示仿射变换,

也就是 X ′ = H ( X ) X^\prime=H(X) X′=H(X),而原始图像就是 X X X,变换后的图像就是 X ′ X^\prime X′
注意 \textbf{注意} 注意
利用单应性矩阵做图像拼接的时候,如果只在二维的空间中处理,要求相机的中心不能有
平移
\textcolor{lime}{平移}
平移,因为这样可以在不知道场景深度信息的条件下就可以完成图像的拼接;有一种情况,就算相机中心有移动,也可以利用单应性矩阵对图像拼接。就是拍摄的场景是平面的时候。
下面先尝试着理解下面的式子,这个是单应性矩阵的完整定义:
H
⟹
{
R
,
t
,
n
}
\mathbf{H} \Longrightarrow\{\mathbf{R}, \mathbf{t}, \mathbf{n}\}
H⟹{R,t,n}
,其中R是旋转矩阵,t是平移矩阵,n是法向量
H = s [ f x γ u 0 0 f y v 0 0 0 1 ] [ r 1 r 2 t ] = s M [ r 1 r 2 t ] H=s\left[\begin{array}{ccc}f_{x} & \gamma & u_{0} \\ 0 & f_{y} & v_{0} \\ 0 & 0 & 1\end{array}\right]\left[\begin{array}{lll}r_{1} & r_{2} & t\end{array}\right]=s M\left[\begin{array}{lll}r_{1} & r_{2} & t\end{array}\right] H=s⎣ ⎡fx00γfy0u0v01⎦ ⎤[r1r2t]=sM[r1r2t]
即
[ u v 1 ] = s [ f x γ u 0 0 f y v 0 0 0 1 ] [ r 1 r 2 t ] [ x W y W 1 ] \left[\begin{array}{c}u \\ v \\ 1\end{array}\right]=s\left[\begin{array}{ccc}f_{x} & \gamma & u_{0} \\ 0 & f_{y} & v_{0} \\ 0 & 0 & 1\end{array}\right]\left[\begin{array}{lll}r_{1} & r_{2} & t\end{array}\right]\left[\begin{array}{c}x_{W} \\ y_{W} \\ 1\end{array}\right] ⎣ ⎡uv1⎦ ⎤=s⎣ ⎡fx00γfy0u0v01⎦ ⎤[r1r2t]⎣ ⎡xWyW1⎦ ⎤,其中, M = [ f x γ u 0 0 f y v 0 0 0 1 ] M=\left[\begin{array}{ccc}f_{x} & \gamma & u_{0} \\ 0 & f_{y} & v_{0} \\ 0 & 0 & 1\end{array}\right] M=⎣ ⎡fx00γfy0u0v01⎦ ⎤ 表示内参矩阵
我们定义如下:u和v分别是转换之后的坐标,可以表示为(u,v),而s是尺度因子,M中的五个量是相机生产过程中产生的误差,相当于相机内参,通常影响很小,{R,t}则是相机外参,xw和yw分别代表原始图像中的坐标。
下面我们看两个案例巩固H矩阵:H矩阵默认最少是4对匹配点,多则更加准确。
如下是两个图片的放射变换,主要是变换的是球场位置:

具体的代码实现如下:
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imread, imshow
from skimage import transform
still1 = imread('still1.png')
src = np.array([(608, 641),
(683, 553),
(1841, 678),
(1750, 579)])
dst = np.array([(800, 339),
(1273, 339),
(800, 1000),
(1310, 1000)])
tform = transform.estimate_transform('projective', src, dst)
tf_still1 = transform.warp(still1, tform.inverse)
fig, ax = plt.subplots(figsize=(20, 6))
ax.imshow(tf_still1)
ax.set_title('projective transformation')
下面是另一个案例转正比萨斜塔:

tower = imread('tower_pisa.jpeg')
src = np.array([(291, 329),
(537, 344),
(230, 868),
(507, 891)])
dst = np.array([(220, 320),
(462, 320),
(220, 870),
(462, 870)])
tform = transform.estimate_transform('euclidean', src, dst)
tf_still1 = transform.warp(tower, tform.inverse)
fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 8))
ax0.imshow(tower)
ax0.set_title('Leaning tower of Pisa')
ax0.set_axis_off()
ax1.imshow(tf_still1)
ax1.set_title('Euclidean transformation')
ax1.set_axis_off()
plt.tight_layout()
理论上只要获知大于等于四对匹配的特征点就可以使用opencv中的homography方法——findhomography(),找到图片中的对应关键点或者称之为角点特征可以使用opencv中的sift、surf或者orb,一般sift和surf都是收费的,而orb是免费的
ORB本质是带方向的FAST特征点和BRIEF描述子,其中FAST是定位器,找寻图片中具有图片特征的x和y的坐标;而BRIEF是获取到不同图片同一个位置相同的外观编码
以下是一个具体的案例:
title: 基于特征的图像对齐
content:
from __future__ import print_function
import cv2
import numpy as np
MAX_FEATURES = 500 #定义最大匹配的特征检测点的数量,这里定义500
GOOD_MATCH_PERCENT = 0.15 #定义保留15%的存好率
#返回图像和h矩阵
def alignImages(im1, im2):
# 转换图像为灰度图像
im1Gray = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY)
im2Gray = cv2.cvtColor(im2, cv2.COLOR_BGR2GRAY)
# ORB计算FAST和BRIEF
orb = cv2.ORB_create(MAX_FEATURES)
# 得到两幅图像的两组FAST和BRIEF
keypoints1, descriptors1 = orb.detectAndCompute(im1Gray, None)
keypoints2, descriptors2 = orb.detectAndCompute(im2Gray, None)
# 匹配特征,方法通过汉明距离进行检测匹配,首先定义一个检测器,然后使用这个检测器对具体的两幅图像进行匹配
matcher = cv2.DescriptorMatcher_create(cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING)
matches = matcher.match(descriptors1, descriptors2, None)
# 按照匹配度排序
matches.sort(key=lambda x: x.distance, reverse=False)
# 去除匹配度不好的组合,保留较好的组合
numGoodMatches = int(len(matches) * GOOD_MATCH_PERCENT)
matches = matches[:numGoodMatches]
# 画包含有匹配线条的图像,这一步可有可无
imMatches = cv2.drawMatches(im1, keypoints1, im2, keypoints2, matches, None)
cv2.imwrite("matches.jpg", imMatches)
# 提取匹配组合的位置,这里相当于定于了n行2列的坐标
points1 = np.zeros((len(matches), 2), dtype=np.float32)
points2 = np.zeros((len(matches), 2), dtype=np.float32)
#查找单应性矩阵并给予坐标的匹配
for i, match in enumerate(matches):
points1[i, :] = keypoints1[match.queryIdx].pt
points2[i, :] = keypoints2[match.trainIdx].pt
# 使用homography方法得到h矩阵
h, mask = cv2.findHomography(points1, points2, cv2.RANSAC)
# Use homography
height, width, channels = im2.shape
im1Reg = cv2.warpPerspective(im1, h, (width, height))
return im1Reg, h
if __name__ == '__main__':
# Read reference image
refFilename = "form.jpg"
print("Reading reference image : ", refFilename)
imReference = cv2.imread(refFilename, cv2.IMREAD_COLOR)
# Read image to be aligned
imFilename = "scanned-form.jpg"
print("Reading image to align : ", imFilename);
im = cv2.imread(imFilename, cv2.IMREAD_COLOR)
print("Aligning images ...")
# Registered image will be resotred in imReg.
# The estimated homography will be stored in h.
imReg, h = alignImages(im, imReference)
# Write aligned image to disk.
outFilename = "aligned.jpg"
print("Saving aligned image : ", outFilename);
cv2.imwrite(outFilename, imReg)
# Print estimated homography
print("Estimated homography : \n", h)
import os
import cv2
import numpy as np
#读取图片和缩放图片
img1=cv2.imread('images/img1.jpg')
img1=cv2.resize(src=img1,dsize=(450,450))
gray1=cv2.cvtColor(src=img1,code=cv2.COLOR_BGR2GRAY)
img2=cv2.imread('images/img2.jpg')
img2=cv2.resize(src=img2,dsize=(450,450))
gray2=cv2.cvtColor(src=img2,code=cv2.COLOR_BGR2GRAY)
#创建SIFT(老版本中的方法是这么弄,新版本中不是这么弄的)
#sift=cv2.xfeatures2d.SIFT_create()
sift = cv2.SIFT_create()
#计算特征点和描述点
kp1,des1=sift.detectAndCompute(gray1,None)
kp2,des2=sift.detectAndCompute(gray2,None)
#使用KDTREE算法,树的层级使用5
index_params=dict(algorithm=1,trees=5)
search_params=dict(checks=50)
#创建匹配器
flann=cv2.FlannBasedMatcher(index_params,search_params)
#特征点匹配
match=flann.knnMatch(des1,des2,k=2)
print('mathc: {}'.format(match))
#绘制匹配特征点
good=[]
for i ,(m,n) in enumerate(match):
if m.distance<0.7*n.distance:
good.append(m)
#当匹配项大于4时
if len(good)>=4:
#查找单应性矩阵
srcPoints=np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)#转换为n行的元素,每一行一个元素,并且这个元素由两个值组成
dstPoints=np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
#获取单应性矩阵
H,_=cv2.findHomography(srcPoints=srcPoints,dstPoints=dstPoints,method=cv2.RANSAC,ransacReprojThreshold=5.0)
#要搜索的图的四个角点
h,w=np.shape(img1)[0],np.shape(img1)[1]
pts=np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2)
dst=cv2.perspectiveTransform(src=pts,m=H)
#绘制多边形
cv2.polylines(img=img2,pts=[np.int32(dst)],isClosed=True,color=(0,255,0))
dest=cv2.drawMatchesKnn(img1=img1,keypoints1=kp1,img2=img2,keypoints2=kp2,matches1to2=[good],outImg=None,matchColor=(0,255,0))
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
print('Pycharm')
光流场反映前一帧和当前帧之间的运动关系,具体就是反映每一个像素点的运动矢量。光流可以分为两种:稠密光流和稀疏光流。
稠密光流需要使用某种插值方法在比较容易跟踪的像素之间进行插值,从而解决那些运动不明确的像素,所以它的计算开销是相当大的。而对于稀疏光流来说,计算时需要在被跟踪之前指定一组点(容易跟踪的点,例如角点),因此在使用LK方法之前我们需要配合使用cvGoodFeatureToTrack()来寻找角点,然后利用金字塔LK光流算法,对运动进行跟踪。但个人感觉,对于少纹理的目标,例如人手,LK稀疏光流就比较容易跟丢
光流的案例A:
import numpy as np
import cv2
import argparse
parser = argparse.ArgumentParser(description='传入的一段视频的名字为slow_traffic_small.mp4')
parser.add_argument('xxxvideo', type=str, help='path to image file')
args = parser.parse_args()
#opencv中获取视频流的写法
cap = cv2.VideoCapture(args.xxxvideo)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) #获取到视频的宽
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) #获取到视频的高
writer = cv2.VideoWriter('demo.mp4', cv2.VideoWriter_fourcc(*'mp4v'),
30, (w, h))
# params for ShiTomasi corner detection
# 定义参数字典
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
# lucas kanade 光流的参数设置
lk_params = dict( winSize = (15,15),
maxLevel = 2,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# Create some random colors
color = np.random.randint(0,255,(100,3))
# Take first frame and find corners in it
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
#goodfeaturetotrack()函数是用来找到光流估计的角点,参数说明:old_gray表示输入图片,mask表示掩模,feature_params:maxCorners=100角点的最大个数,qualityLevel=0.3角点品质,minDistance=7即在这个范围内只存在一个品质最好的角点
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
# Create a mask image for drawing purposes
mask = np.zeros_like(old_frame)
while ret:
ret, frame = cap.read()
if not ret:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# calculate optical flow
#参数说明:pl表示光流检测后的角点位置,st表示是否是运动的角点,err表示是否出错,old_gray表示输入前一帧图片,frame_gray表示后一帧图片,p0表示需要检测的角点,lk_params:winSize表示选择多少个点进行u和v的求解,maxLevel表示空间金字塔的层数
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# Select good points
if p1 is not None:
good_new = p1[st==1]
good_old = p0[st==1]
# draw the tracks
for i,(new,old) in enumerate(zip(good_new, good_old)):
a,b = new.ravel()
c,d = old.ravel()
mask = cv2.line(mask, (int(a),int(b)),(int(c),int(d)), color[i].tolist(), 2)
frame = cv2.circle(frame,(int(a),int(b)),5,color[i].tolist(),-1)
img = cv2.add(frame, mask)
# cv.imshow('frame',img)
writer.write(img)
# Now update the previous frame and previous points
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
光流案例B:
import numpy as np
import cv2
# 第一步:视频的读入
cap = cv2.VideoCapture('test.avi')
# 第二步:构建角点检测所需参数
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7)
# lucas kanade参数
lk_params = dict(winSize=(15, 15),
maxLevel=2)
# 随机颜色条
color = np.random.randint(0, 255, (100, 3))
# 第三步:拿到第一帧图像并灰度化作为前一帧图片
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 第四步:返回所有检测特征点,需要输入图片,角点的最大数量,品质因子,minDistance=7如果这个角点里有比这个强的就不要这个弱的
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 第五步:创建一个mask, 用于进行横线的绘制
mask = np.zeros_like(old_frame)
while(True):
# 第六步:读取图片灰度化作为后一张图片的输入
ret, frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 第七步:进行光流检测需要输入前一帧和当前图像及前一帧检测到的角点
pl, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 第八步:读取运动了的角点st == 1表示检测到的运动物体,即v和u表示为0
good_new = pl[st==1]
good_old = p0[st==1]
# # 第九步:绘制轨迹
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel()
c, d = old.ravel()
mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
frame = cv2.circle(frame, (a, b), 5, color[i].tolist(), -1)
# 第十步:将两个图片进行结合,并进行图片展示
img = cv2.add(frame, mask)
cv2.imshow('frame', img)
k = cv2.waitKey(150) & 0xff
if k == 27:
break
# 第十一步:更新前一帧图片和角点的位置
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
# p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
cv2.destroyAllWindows()
cap.release()
核心成分就是两个:一个初始的视频帧间的homography(between-camera,BC),每一帧中都存在的相机路径homography(CP)用于估计BC。BC homography通过使用块匹配来调整估计CP的误差来细化(单应性细化)。为了快速处理,我们使用强度差提取特征,并使用光流估计摄像机运动(CM)homography,将其与之前的CMs相乘以计算CP homography。
视频拼接是缝合在移动摄像机上捕捉到的多帧视频技术。
经典的工作方法是采用相机路径(CP),它是连续帧中的homograpgy的乘积,但是不依赖于homography的直接计算。这种方法性能还是可观的,主要有两项不足:
1)需要过多的处理时间,不适合用于实时系统的处理,传统电脑需要3-4秒的时间去缝合分辨率为1280x720的视频的两帧。 2)连续的复合误差将会导致不对齐的现象,因此在出现cp误差的情况下会出现空间伪影
主要为了解决上述两个大问题,本文提出了相机路径估计(CP)和单应性矩阵细化,
核心问题是如何消除视差产生的错位,解决方式主要是两种:1)接缝 2)warping
核心问题是如何将错位进行消除以及尽可能的缩短处理时间
运动模型:空间运动模型和时间运动模型,单应性矩阵用于获取运动模型。空间运动模型是相机间的单应性矩阵,表示在同一帧中多个输入之间的单应性矩阵, S n m ( t ) S_{nm}(t) Snm(t)是帧t中相机n到相机m的相机间homography(BC);时间运动模型是由连续的CMs和CPs组成,CMs是相同相机在相邻帧之间的单应性矩阵, T n ( t ) T_{n}(t) Tn(t)是帧t-1到帧t之间的相机n的相机运动(CM),即 T n ( t ) = C M T_{n}(t)=CM Tn(t)=CM;相机路径CP是由多个相机运动CM累积得到,即 C P = ∏ C M s = ∏ t = 1 t T n ( t ) CP=\prod CM_{s}=\prod_{t=1}^{t} T_{n}(t) CP=∏CMs=∏t=1tTn(t)其中,相机路径CPs对于精准视频拼接来说是重要的组件成分,CP可以写为 C n ( t ) C_{n}(t) Cn(t):帧t中的相机n,表达式如下:
C P = C n ( t ) = T n ( t ) C n ( t − 1 ) = T n ( t ) ⋯ T n ( 2 ) T n ( 1 ) = C M t ⋯ C M 2 C M 1 CP=C_{n}(t)=T_{n}(t) C_{n}(t-1)\\=T_{n}(t) \cdots T_{n}(2) T_{n}(1)=CM_{t}\cdots CM_{2}CM_{1} CP=Cn(t)=Tn(t)Cn(t−1)=Tn(t)⋯Tn(2)Tn(1)=CMt⋯CM2CM1
如下是各种运动模型的示意图:

如下探究CP和BC之间的联系,下面等式展示了对应的描述:
S 12 ( t ) = C 1 ( t ) S 12 ( 0 ) C 2 − 1 ( t ) S_{12}(t)=C_{1}(t) S_{12}(0) C_{2}^{-1}(t) S12(t)=C1(t)S12(0)C2−1(t)
其中,
C
1
(
t
)
C_{1}(t)
C1(t)和
C
2
(
t
)
C_{2}(t)
C2(t)是相机1和相机2的CP;
S
12
(
0
)
S_{12}(0)
S12(0)是初始帧的BC homography,
S
12
(
t
)
S_{12}(t)
S12(t)可以将摄像机1捕获到的图像坐标转换到摄像机2的坐标。
下图展示了几者之间的联系:

然而,BC
S
12
(
t
)
S_{12}(t)
S12(t)不用于帧之间的坐标变换。由于参考帧发生了显著变化,因此变换帧的运动可能会变得更剧烈。因此,将参考帧设置为第一帧,以获得使用固定摄像机拍摄的稳定结果。对于这项工作,我们需要扭曲运动warping motions(
W
M
s
WMs
WMs),将矩阵从第一帧的参考帧映射到当前帧。我们将
W
M
WM
WM表示为
W
n
(
t
)
W_{n}(t)
Wn(t),其中的摄像机n位于帧t处。
W
M
s
WMs
WMs是由初始BC homography和相机路径CP组成,下图是上述几种运动模型之间的关系,其中,每个输入由
W
M
WM
WM在摄像机1的第一帧的基础上进行变换。

表示WM的方程可以写成:
W 1 ( t ) = C 1 − 1 ( t ) W_{1}(t)=C_{1}^{-1}(t) W1(t)=C1−1(t)
W 2 ( t ) = S 12 ( 0 ) C 2 − 1 ( t ) W_{2}(t)=S_{12}(0) C_{2}^{-1}(t) W2(t)=S12(0)C2−1(t)
其中 W 1 ( t ) W_1(t) W1(t)和 W 2 ( t ) W_2(t) W2(t)分别是摄像机1和2的 W M s WM_s WMs, C 1 ( t ) C_1(t) C1(t)和 C 2 ( t ) C_2(t) C2(t)是CP, S 12 ( 0 ) S_{12}(0) S12(0)是摄像机1和2在第一帧处的初始BC。

获取多个摄像机的输入,进行粗略的同步
估计相机路径CP,应用网格上的帧的强度提取特征,利用光流法跟踪特征生成匹配点
第三步是细化BCs。由于CP不准确,有时会在结果中观察到错位。为了消除错位,我们使用块匹配重复估计误差运动。然后,将误差运动的倒数乘以当前BC。
最后一步执行warping和混合。对于warping阶段,我们使用 C P CP CP和 B C s BC_s BCs估计 W M s WM_s WMs,并通过将 W M s WM_s WMs乘以帧来生成warping图像。在混合阶段,我们去除了由两幅图像之间的强度差异产生的视觉接缝。在我们的框架中,采用多波段混合作为混合方法。
提出的CP估计方法对该领域的贡献主要有两个原因:(1)均匀特征提取,(2)快速特征匹配。第一种方法是基于网格的均匀特征提取。CP估计受特征分布的敏感影响。例如,假设特征在某个区域上密集聚集;创建局部单应性,而不是全局单应性。该局部单应性产生的结果包括错位伪影。因此,特征应均匀分布。对于均匀分布,使用网格提取特征。该方法的第二个贡献是使用光流进行快速特征匹配。传统上,在匹配特征时,计算描述符需要很长的处理时间。因此,我们需要定义一种没有描述符的新特征匹配方法。在该方法中,通过使用光流跟踪特征来获得匹配点。下图显示了CP估计方法的流程图。

基于网格的特征提取过程由两个步骤组成:(1)在帧上面分割以实现特征的均匀分布;(2)使用相邻帧之间的差异强度进行特征提取。具体体现在:首先,我们将输入帧划分为网格。下图显示了划分为网格的输入。

其中绿线是网格线。根据经验,在高清分辨率(1280×720像素)下,网格大小设置为70。网格的顶点成为提取特征的潜在特征。将图像分割成网格后,我们在网格顶点上选择候选特征。其次,提取方法使用当前帧和前一帧之间的强度差:
∣ I t ( x , y ) − I t − 1 ( x , y ) ∣ > τ i \left|I_{t}(x, y)-I_{t-1}(x, y)\right|>\tau_{i} ∣It(x,y)−It−1(x,y)∣>τi
其中x和y是网格顶点上的坐标,
I
t
(
x
,
y
)
I_{t}(x, y)
It(x,y)和
I
t
−
1
(
x
,
y
)
I_{t-1}(x, y)
It−1(x,y)是当前时间t和前一时间t的强度;而
t
a
u
i
tau_{i}
taui是阈值(我们使用
t
a
u
i
tau_{i}
taui=5)。下图(a)显示了特征提取。
基于网格的特征提取不仅快速,而且稳定。我们的提取方法比其他方法产生更稳定的CM。例如,当将一般方法应用到帧中时,例如使用哈里斯角点检测,我们经常确认特征密集分布在某些区域,上面图(b)所示,因为该方法在高纹理区域(例如建筑物、树木)上产生的特征比纹理较差的区域(例如天空、道路)更多。有偏特征分布基于特征数最多的平面估计局部单应性。使用局部单应性估计CM,我们得到的结果很差,集中在具有许多特征的平面上。相反,我们提出的方法提取均匀特征。该方法估计全局单应性,其产生的结果优于任何单个局部单应性。
在传统的拼接方法中,匹配方法使用包括高维描述子(descriptor)在内的特征。为了生成描述子,匹配算法执行了一个复杂的过程。这种方法需要更多的时间才能获得结果。因此,我们需要新的特征匹配方法,而不使用描述子。对于没有描述子的快速匹配方法,我们使用光流方法。光流法是一种跟踪算法,旨在找到相邻帧之间的目标。光流法的目的是跟踪物体。然而,我们使用光流来跟踪特征,而不是对象。我们使用特征的位置作为光流的种子,并跟踪特征。由于当前帧和前一帧之间区域的模式相似,可能会观察到跟踪误差。为了消除跟踪误差,我们使用以下等式:
d
1
(
p
,
p
track
)
/
N
(
w
)
<
τ
e
d_{1}\left(p, p_{\text {track }}\right) / N(w)<\tau_{e}
d1(p,ptrack )/N(w)<τe
其中 d 1 ( p , p t r a c k ) d_1(p,p_{track}) d1(p,ptrack)是初始点 p p p和跟踪点 p t r a c k p_{track} ptrack之间的 L 1 L1 L1距离, N ( w ) N(w) N(w)是搜索窗口中的像素数, τ e \tau_{e} τe是确定点是否正确的阈值(我们使用 τ e \tau_{e} τe=5)。通过使用此约束,消除了跟踪误差。
通常,帧的特征分布是相似的,因为帧之间的时间差非常小。然而,由于照明的变化或摄像机的剧烈运动,往往会出现不同的特征分布。这一事实往往导致结果不稳定。为了防止这些情况,我们在估计CM之前实施了特征选择过程。特征选择可以防止两种不需要的情况:(1)与前一帧比较不同的特征分布,(2)没有识别特征点。首先,特征的分布有时不同于前一帧的分布。这种情况会产生不连续的结果。为了解决这个问题,假设当前和以前的 C M s CM_s CMs之间几乎没有差异。利用这个假设,我们使用前一个 C M CM CM变换当前特征。然后,我们计算由先前 C M CM CM转换的特征与使用光流方法跟踪的特征之间的欧几里德距离。该距离将与阈值做比较,得到inliners,最后,使用RANSAC由inliners估计 C M s CM_s CMs,并与之前的CP相乘以估计新的CP,
d 2 ( p pre , p track ) < τ S d_{2}\left(p_{\text {pre }}, p_{\text {track }}\right)<\tau_{S} d2(ppre ,ptrack )<τS
其中 d 2 ( p p r e , p t r a c k ) d_{2}(ppre,ptrack) d2(ppre,ptrack)是由先前CM转换的特征ppre和使用光流的跟踪特征ptrack之间的 L 2 L_2 L2距离。 τ s \tau_s τs是阈值。我们将该阈值设置为 τ s \tau_s τs=30。然后,使用RANSAC由估计CMs,并与先前CP相乘以估计新CP。其次,我们防止了在选择特征后提取少量inliners的情况。如果使用少量特征估计CM,则可能估计错误的CM。为了防止这种不良情况,我们采用了一种简单的方法,用以前的配置管理替换当前的配置管理。由于相邻 C M s CM_s CMs之间几乎没有差异,因此可以利用此方法。
我们将介绍如何完善BCs。在视频拼接过程中,使用下面的等式在公共空间上表示不同摄像机的坐标。
S 12 ( t ) = C 1 ( t ) S 12 ( 0 ) C 2 − 1 ( t ) S_{12}(t)=C_{1}(t) S_{12}(0) C_{2}^{-1}(t) S12(t)=C1(t)S12(0)C2−1(t)
W 1 ( t ) = C 1 − 1 ( t ) W_{1}(t)=C_{1}^{-1}(t) W1(t)=C1−1(t)
W 2 ( t ) = S 12 ( 0 ) C 2 − 1 ( t ) W_{2}(t)=S_{12}(0) C_{2}^{-1}(t) W2(t)=S12(0)C2−1(t)
然而,BC可能包括由不准确的
C
P
s
CP_s
CPs引起的误差,这些误差是由于剧烈的摄像机移动或强度变化而错误估计的。因此,我们建议使用块匹配来实现BC细化。
B
C
S
ˉ
12
(
t
)
\mathrm{BC} \bar{S}_{12}(t)
BCSˉ12(t)定义如下:
S
ˉ
12
(
t
)
=
C
ˉ
1
(
t
)
S
12
(
0
)
C
ˉ
2
−
1
(
t
)
\bar{S}_{12}(t)=\bar{C}_{1}(t) S_{12}(0) \bar{C}_{2}^{-1}(t)
Sˉ12(t)=Cˉ1(t)S12(0)Cˉ2−1(t),
S ˉ 12 ( t ) = E 12 ( t ) C 1 ( t ) S 12 ( 0 ) C 2 − 1 ( t ) \bar{S}_{12}(t)=E_{12}(t) C_{1}(t) S_{12}(0) C_{2}^{-1}(t) Sˉ12(t)=E12(t)C1(t)S12(0)C2−1(t),
其中 C ˉ 1 ( t ) \bar{C}_1(t) Cˉ1(t)和 C ˉ 2 ( t ) \bar{C}_2(t) Cˉ2(t)是包括误差运动 E 12 ( t ) E_{12}(t) E12(t)的CP的观测值,BC观测值和理想BC之间的关系方程估计为:
S ˉ 12 ( t ) = E 1 , 2 ( t ) S 12 ( t ) \bar{S}_{12}(t)=E_{1,2}(t) S_{12}(t) Sˉ12(t)=E1,2(t)S12(t)
实际上,相邻帧之间的误差运动对错位影响很小。然而,误差是随着时间积累的。在最后一帧,结果包括明显错位的伪影。因此,BC的观察必须通过与 E 12 ( t ) E_{12}(t) E12(t)的逆相乘来更新,如下所示:
S ^ 12 ( t ) = E 12 − 1 ( t ) S ˉ 12 ( t ) \hat{S}_{12}(t)=E_{12}^{-1}(t) \bar{S}_{12}(t) S^12(t)=E12−1(t)Sˉ12(t)
其中 S ^ 12 ( t ) \hat{S}_{12}(t) S^12(t)是精炼BC。实际上,使用 S ^ 12 ( t ) = E 12 − 1 ( t ) S ˉ 12 ( t ) \hat{S}_{12}(t)=E_{12}^{-1}(t) \bar{S}_{12}(t) S^12(t)=E12−1(t)Sˉ12(t)来扭曲图像,估计精细 W M s W ^ 1 ( t ) WM_s\hat{W}_1(t) WMsW^1(t)和 W ^ 2 ( t ) \hat{W}_2(t) W^2(t):
W ^ 1 ( t ) = C ˉ 1 − 1 ( t ) \hat{W}_{1}(t)=\bar{C}_{1}^{-1}(t) W^1(t)=Cˉ1−1(t)
W ^ 2 ( t ) = S ^ 12 ( t ) C ˉ 2 − 1 ( t ) = E 12 − 1 ( t ) S ˉ 12 ( t ) C ˉ 2 − 1 ( t ) \hat{W}_{2}(t)=\hat{S}_{12}(t) \bar{C}_{2}^{-1}(t)=E_{12}^{-1}(t) \bar{S}_{12}(t) \bar{C}_{2}^{-1}(t) W^2(t)=S^12(t)Cˉ2−1(t)=E12−1(t)Sˉ12(t)Cˉ2−1(t)
为了更新细化BCs,应使用特征和细化特征估计误差运动
E
12
(
t
)
E_{12}(t)
E12(t),这是理想的缝合特征,如下图所示。

换句话说,找到了细化特征的位置。寻找精细特征的最简单方法是使用SIFT和FLANN的方法。然而,由于处理时间长,这种方法不适用于实时系统。因此,我们使用块匹配来估计误差运动。块匹配是一种查找模板位置的匹配算法。为了找到精确的特征,围绕右帧的特征和搜索ROI构建模板,这是块匹配的搜索范围,并构建在左帧上,如上图(b)所示。然后,通过实现块匹配算法来估计误差运动。
下图所示了单应性细化的流程图。

在第一步中,我们在第一帧使用SIFT和FLANN计算初始BC。虽然该初始步骤具有较长的处理时间,但准确估计初始业务连续性很重要,该初始业务连续性可作为精细业务连续性的参考。在第二步中,我们重用在CP估计中获得的来自右帧的特征来构造模板和搜索ROI。在第三步中,我们分别在扭曲的右帧和左帧中的扭曲特征周围形成大小(n×n)的模板和大小(m×m)的搜索ROI。我们分别将n、m定义为9、21。在第四步中,我们执行块匹配以估计误差运动。块匹配使用归一化互相关(NCC)来测量模板和搜索ROI之间的匹配率:
r ( x , y ) = ∑ x ′ , y ′ ( i t ( x ′ , y ′ ) i ( x + x ′ , y + y ′ ) ) ∑ x ′ , y ′ i t ( x ′ , y ′ ) 2 ∑ x ′ , y ′ i ( x + x ′ , y + y ′ ) 2 r(x, y)=\frac{\sum_{x^{\prime}, y^{\prime}}\left(i_{t}\left(x^{\prime}, y^{\prime}\right) i\left(x+x^{\prime}, y+y^{\prime}\right)\right)}{\sqrt{\sum_{x^{\prime}, y^{\prime}} i_{t}\left(x^{\prime}, y^{\prime}\right)^{2} \sum_{x^{\prime}, y^{\prime}} i\left(x+x^{\prime}, y+y^{\prime}\right)^{2}}} r(x,y)=∑x′,y′it(x′,y′)2∑x′,y′i(x+x′,y+y′)2∑x′,y′(it(x′,y′)i(x+x′,y+y′))
其中
x
x
x和
y
y
y是扭曲帧中的像素坐标,x0和y0是模板中的像素坐标,r(x,y)是NCC的值,i(x,y)和it(x,y)分别是扭曲帧和模板中的强度。我们选择r(x,y)最大的特征作为细化特征。最后,通过误差运动更新当前BCs,误差运动由使用RANSAC算法和(11)的精细匹配点估计。
该初始业务连续性可作为精细业务连续性的参考。在第二步中,我们重用在CP估计中获得的来自右帧的特征来构造模板和搜索ROI。在第三步中,我们分别在扭曲的右帧和左帧中的扭曲特征周围形成大小(n×n)的模板和大小(m×m)的搜索ROI。我们分别将n、m定义为9、21。在第四步中,我们执行块匹配以估计误差运动。块匹配使用归一化互相关(NCC)来测量模板和搜索ROI之间的匹配率:
r ( x , y ) = ∑ x ′ , y ′ ( i t ( x ′ , y ′ ) i ( x + x ′ , y + y ′ ) ) ∑ x ′ , y ′ i t ( x ′ , y ′ ) 2 ∑ x ′ , y ′ i ( x + x ′ , y + y ′ ) 2 r(x, y)=\frac{\sum_{x^{\prime}, y^{\prime}}\left(i_{t}\left(x^{\prime}, y^{\prime}\right) i\left(x+x^{\prime}, y+y^{\prime}\right)\right)}{\sqrt{\sum_{x^{\prime}, y^{\prime}} i_{t}\left(x^{\prime}, y^{\prime}\right)^{2} \sum_{x^{\prime}, y^{\prime}} i\left(x+x^{\prime}, y+y^{\prime}\right)^{2}}} r(x,y)=∑x′,y′it(x′,y′)2∑x′,y′i(x+x′,y+y′)2∑x′,y′(it(x′,y′)i(x+x′,y+y′))
其中 x x x和 y y y是扭曲帧中的像素坐标,x0和y0是模板中的像素坐标,r(x,y)是NCC的值,i(x,y)和it(x,y)分别是扭曲帧和模板中的强度。我们选择r(x,y)最大的特征作为细化特征。最后,通过误差运动更新当前BCs,误差运动由使用RANSAC算法和(11)的精细匹配点估计。
#注:以上内容均为本人阅读大神论文摘选各自内容产生的笔记,没有进行任何出版或者其他行为,本博客仅供有需要的人学习成长浏览!!!
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p