作为全球某机器视觉头部大厂的资深工程师,深感学校和互联网上主要资料局限于计算机视觉领域,能落地工业领域的算法寥寥无几,且相关算法原理语焉不详、乏善可陈,或示例代码缺少关键实现、谬误百出, 故作【庖丁解牛】系列课程,掰开揉碎了讲解机器视觉领域核心算法,并提供完整的高质量代码,力争为从业者提供严格贴近工业应用场景的、高质量的国际一线算法剖析。
本系列课程围绕工业视觉算法原理及实现,具体场景落地应考虑的工程实践化设计要点及经验,笔者将在其他系列课程中讲解。
庖丁解牛本期公开课的主题是 圆环展开。
术语解释
顾名思义,圆环展开是将指定圆环展开为矩形。
几何基础
如图1所示,圆环内径为r,外径为R,圆心为O。

设圆心O坐标为
(
x
0
,
y
0
)
(x0,y0)
(x0,y0),射线PO到射线OC1的夹角为θ,由几何知识可知:
内圆上点P的坐标
(
x
,
y
)
(x,y)
(x,y)为:
x
=
x
0
+
r
∗
c
o
s
θ
y
=
y
0
−
r
∗
s
i
n
θ
x = x0 + r * cosθ\\ y = y0 - r * sinθ
x=x0+r∗cosθy=y0−r∗sinθ
外圆上点P’的坐标
(
x
′
,
y
′
)
(x',y')
(x′,y′)为:
x
′
=
x
0
+
R
∗
c
o
s
θ
y
′
=
y
0
−
R
∗
s
i
n
θ
x' = x0 + R * cosθ\\ y' = y0 - R * sinθ
x′=x0+R∗cosθy′=y0−R∗sinθ
同理可知,若点P落在以O为圆心,半径为Radius(r≤Radius≤R)的圆周上,则其坐标
(
X
,
Y
)
(X,Y)
(X,Y)为:
X
=
x
0
+
R
a
d
i
u
s
∗
c
o
s
θ
Y
=
y
0
−
R
a
d
i
u
s
∗
s
i
n
θ
X = x0 + Radius * cosθ\\ Y = y0 - Radius * sinθ
X=x0+Radius∗cosθY=y0−Radius∗sinθ
原理推导
首先要确定展开矩形图像的宽和高。在此,我们不假思索地给出以下结论:
D
s
t
W
i
d
t
h
=
(
r
+
R
)
∗
2
π
∗
0.5
D
s
t
H
e
i
g
h
t
=
R
−
r
\begin{align*} & DstWidth = (r + R) * 2π * 0.5 & \\ & DstHeight = R - r & \end{align*}
DstWidth=(r+R)∗2π∗0.5DstHeight=R−r
然后,要确定展开矩形图像中,第i行第j列像素点的灰度值。
假设图1中,点C1到G1连线方向为圆环展开的起始方向,由【原理推导】小节结论可知,展开矩形图像中,第i行第j列像素点对应原图像中的坐标为:
x
=
x
0
+
(
r
+
i
)
∗
c
o
s
(
j
∗
2
π
/
D
s
t
W
i
d
t
h
)
y
=
y
0
−
(
r
+
i
)
∗
s
i
n
(
j
∗
2
π
/
D
s
t
W
i
d
t
h
)
x = x0 + (r + i) * cos(j * 2π / DstWidth)\\ y = y0 - (r + i) * sin(j * 2π / DstWidth)
x=x0+(r+i)∗cos(j∗2π/DstWidth)y=y0−(r+i)∗sin(j∗2π/DstWidth)
显然,展开矩形图像中,第i行第j列像素点对应原图像中的像素坐标不为整数,因此需要采用图像插值算法。最常用的图像插值算法包括最近邻插值和双线性插值,后者插值效果更好,但耗时也相对更长。
- 最近邻插值
最近邻插值示意如图2所示。

如图2所示, ( x 0 , y 0 ) (x0,y0) (x0,y0)、 ( x 0 , y 1 ) (x0,y1) (x0,y1)、 ( x 1 , y 0 ) (x1,y0) (x1,y0)、 ( x 1 , y 1 ) (x1,y1) (x1,y1)都是原图像上的坐标点,灰度值分别对应为Q11、Q12、Q21、Q22。而灰度值未知的插值点 ( x , y ) (x, y) (x,y),根据最近邻插值方法的约束,其与坐标点 ( x 0 , y 0 ) (x0, y0) (x0,y0)位置最接近 (即位于 ( x 0 , y 0 ) (x0,y0) (x0,y0)的邻域内),故插值点 ( x , y ) (x, y) (x,y)的灰度值等于Q11。 - 双线性插值
双线性插值示意如图3所示。

由图3可知,二维图像的双线性插值,需要经过三次一阶线性插值。将其投影至平面,可得图4。

首先,在 x x x方向做一维线性插值:
f ( x , y 0 ) = x 1 − x x 1 − x 0 f ( x 0 , y 0 ) + x − x 0 x 1 − x 0 f ( x 1 , y 0 ) f ( x , y 1 ) = x 1 − x x 1 − x 0 f ( x 0 , y 1 ) + x − x 0 x 1 − x 0 f ( x 1 , y 1 ) f\left( x,y_{0} \right)=\frac{x_{1}-x}{x_{1}-x_{0}}f\left( x_{0},y_{0} \right)+\frac{x-x_{0}}{x_{1}-x_{0}}f\left( x_{1},y_{0} \right)\\ f\left( x,y_{1} \right)=\frac{x_{1}-x}{x_{1}-x_{0}}f\left( x_{0},y_{1} \right)+\frac{x-x_{0}}{x_{1}-x_{0}}f\left( x_{1},y_{1} \right) f(x,y0)=x1−x0x1−xf(x0,y0)+x1−x0x−x0f(x1,y0)f(x,y1)=x1−x0x1−xf(x0,y1)+x1−x0x−x0f(x1,y1)
然后,在 y y y方向做一维线性插值:
f ( x , y ) = y 1 − y y 1 − y 0 f ( x , y 0 ) + y − y 0 y 1 − y 0 f ( x , y 1 ) f\left( x,y \right)=\frac{y_{1}-y}{y_{1}-y_{0}}f\left( x,y_{0} \right)+\frac{y-y_{0}}{y_{1}-y_{0}}f\left( x,y_{1} \right) f(x,y)=y1−y0y1−yf(x,y0)+y1−y0y−y0f(x,y1)
整理上式,可得像素点的灰度值插值计算公式为:
f ( x , y ) = ( y 1 − y ) ( x 1 − x ) ( y 1 − y 0 ) ( x 1 − x 0 ) f ( x 0 , y 0 ) + ( y 1 − y ) ( x − x 0 ) ( y 1 − y 0 ) ( x 1 − x 0 ) f ( x 1 , y 0 ) + ( y − y 0 ) ( x 1 − x ) ( y 1 − y 0 ) ( x 1 − x 0 ) f ( x 0 , y 1 ) + ( y − y 0 ) ( x − x 0 ) ( y 1 − y 0 ) ( x 1 − x 0 ) f ( x 1 , y 1 ) f\left( x,y \right)=\frac{(y_{1}-y)(x_{1}-x)}{(y_{1}-y_{0})(x_{1}-x_{0})}f\left( x_{0},y_{0} \right)+\frac{(y_{1}-y)(x-x_{0})}{(y_{1}-y_{0})(x_{1}-x_{0})}f\left( x_{1},y_{0} \right)+\frac{(y-y_{0})(x_{1}-x)}{(y_{1}-y_{0})(x_{1}-x_{0})}f\left( x_{0},y_{1} \right)+\frac{(y-y_{0})(x-x_{0})}{(y_{1}-y_{0})(x_{1}-x_{0})}f\left( x_{1},y_{1} \right) f(x,y)=(y1−y0)(x1−x0)(y1−y)(x1−x)f(x0,y0)+(y1−y0)(x1−x0)(y1−y)(x−x0)f(x1,y0)+(y1−y0)(x1−x0)(y−y0)(x1−x)f(x0,y1)+(y1−y0)(x1−x0)(y−y0)(x−x0)f(x1,y1)
核心代码
// 双线性插值
unsigned char CAlgorithmModule::BilinearInterpolation(float x, float y, MVDSDK_BASE_MODU_INPUT* modu_input, int width, int height)
{
x = (x < 0) ? 0 : x;
y = (y < 0) ? 0 : y;
x = (x > width) ? width : x;
y = (y > height) ? height : y;
int x1 = floor(x);
int x2 = ceil(x);
int y1 = floor(y);
int y2 = ceil(y);
int pos1 = y1 * width + x1;
int pos2 = y1 * width + x2;
int pos3 = y2 * width + x1;
int pos4 = y2 * width + x2;
unsigned char* pImgData = modu_input->pImageInObj->GetImageData(0)->pData;
unsigned char grayValue = pImgData[pos1] * (x2 - x) * (y2 - y) + pImgData[pos2] * (x - x1) * (y2 - y) + pImgData[pos3] * (x2 - x) * (y - y1) + pImgData[pos4] * (x - x1) * (y - y1);
return grayValue;
}
运行结果对比
分别采用笔者提出的算法、VisionMaster(国内最领先的算法平台,后面统称为VM)对多幅图像进行圆环展开。
VM圆环展开结果:

本文圆环展开结果:

可以看出,笔者提出的圆环展开算法效果足以媲美VM。
本期课程就到此结束了,下期课程我们不见不散。
如觉得本文内容不错,请记得给小编点赞、收藏、分享一键三连,您的支持是笔者更新的不懈动力。
想要算法源码工程的读者朋友可在评论区留言或私信小编。

1834

被折叠的 条评论
为什么被折叠?



