测距仪






4.95/5 (92投票s)
一个简单的程序,用于测量数码相机照片中物体的实际尺寸或距离


目录
引言
旅行结束后,当你查看旅行照片时,有时你会想知道照片中物体的距离或大小。这在某种程度上是可行的。
如果你能在照片中找到一些你知道大小的物体,你就可以测量它们的距离;如果你知道一个物体的距离,你就可以找到它的大小。例如,如果你知道一座特殊建筑(例如法罗斯灯塔或一座塔)的高度,并且你离它有几公里远,你可以用一张简单的照片测量你的距离。有时这很有趣!
它是如何工作的?
光线穿过镜头时会发生折射并聚焦在表面。这种效应被用于我们的眼睛和类似的设备,如相机、显微镜、望远镜等。

在相机中,有一组镜头(而不是一个)协同工作,以更好地控制光线并获得更好的质量。
|
|
但对于单个镜头或一组镜头,规则是相同的。我们的计算中有四个变量很重要。
- 物体的大小 (Y1)
- 物体到镜头的距离 (X1)
- 传感器或胶片上图像的大小 (Y2)
- 传感器与镜头之间的距离 (X2)
我们的变量有一个方程:X1/Y1 = X2/Y2

我们是如何找到这个方程的?
如果我们看前面的图,我们可以找到两个直角三角形。关于三角形有一些规则,我们在这里使用它们。

a1 = a2 ==> tan a1 = tan a2
正切 = 对边 / 邻边
tan a1 = Y1/X1
tan a2 = Y2/X2
tan a1 = tan a2 ==> Y1/X1 = Y2/X2 ==> 我们可以将方程两边同时取倒数 ==> X1/Y1 = X2/Y2
现在我们找到了公式,如果我们知道三个变量,第四个就在我们手中了。
好消息是我们总是知道 X2 的大小。X2 的通常名称是焦距,镜头制造商会将其印在镜头主体上(以毫米为单位测量)。下面的镜头焦距为 50 毫米。

有些镜头具有可变焦距。它们被称为变焦镜头。下图显示了一个 18 到 55 毫米的变焦镜头。

这张图是一个傻瓜相机,焦距范围从 7.4 到 44.4 毫米(变焦)。

但记住我们拍摄的焦距很困难,尤其是在我们用不同的变焦值拍摄多张照片时。好消息是,每台数码相机都会在图像文件中保存一些有用的数据,焦距就是其中之一。所以我们不需要记住任何东西。如果你想查看图像中保存了哪些数据,只需按以下步骤操作
- 右键单击图像
- 选择“属性”
- 在新窗口中,选择摘要选项卡并单击“高级”按钮 (Windows XP) 或选择详细信息选项卡 (Windows Vista)
这些数据被称为“EXIF”信息。

我们需要测量的第二个变量是传感器上的图像大小 (Y2)。我们可以通过一些计算来测量它。这里重要的值是传感器的宽度和高度,以及图像的宽度和高度。同样,有个好消息:我们可以在 EXIF 信息中找到这些值。
请注意,如果您裁剪或调整了图像文件大小,则无法测量正确的值。因此,请使用未经任何更改的原始图像文件。
现在,我们方程的一边已经解决了。另一边还有 2 个变量。这意味着如果你知道物体的大小 (Y1),你可以通过以下方式计算距离:X1 = Y1 * (X2/Y2)。
当你已知距离 (X1) 时,你可以计算大小:Y1 = X1 / (X2/Y2)。
|
|
照片中通常有一些你已知其大小的地标,例如你的朋友、历史遗迹、塔楼、汽车、旗帜,甚至你可以估计其大小的人物。(上图:我的乌克兰朋友)
Using the Code
算法是这样的

变量
最初,我声明了一些变量和一个类(Camera
),用于收集有关相机的所有信息。请注意,我们的 EXIF 信息没有传感器宽度或高度,但它包含相机名称。我添加了一个外部文件(Camera.lst),其中包含流行数码相机的传感器尺寸,并按名称对相机进行排序。
我还将相机型号添加到主菜单中,因此如果您的图像文件不包含 EXIF 信息,您可以手动添加值。
private float myZoom = 1;
private int startX = 0;
private int startY = 0;
private int movedX = 0;
private int movedY = 0;
private double myMeasure = 0;
private double mySize = 0;
private double myDistance = 0;
private double mySizeScale = .01;
private double myDistanceScale = 1;
private int myMeasureStartX = 0;
private int myMeasureStartY = 0;
private bool horiZontal = true;
private Bitmap myBitmap;
class Camera
{
internal string Factory="";
internal string Model = "";
internal string AliasName = "";
internal string SensorType = "";
internal float SensorWidth = 0;
internal float SensorHeight = 0;
}
private List allCameraInfo=new List ();
private void loadCameraInfo()
{
// Open the file
string dataFile=Application.StartupPath +"\\Data\\camera.lst";
if (System.IO.File.Exists(dataFile))
{
using (System.IO.StreamReader sr = System.IO.File.OpenText(dataFile))
{
string myLine = "";
while ((myLine = sr.ReadLine()) != null)
{
if (!myLine.StartsWith("//") && myLine.Trim() != "")
{
string[] myData;
myData = myLine.Split(new string[] { "," }, StringSplitOptions.None);
try
{
//Add Camera information to list collection
Camera myCameraInfo = new Camera();
myCameraInfo.Factory = myData[0].Trim ();
myCameraInfo.Model = myData[1].Trim ();
myCameraInfo.AliasName = myData[2].Trim ();
myCameraInfo.SensorType = myData[3].Trim();
myCameraInfo.SensorWidth = Convert.ToSingle(myData[4]);
myCameraInfo.SensorHeight = Convert.ToSingle(myData[5]);
allCameraInfo.Add(myCameraInfo);
//Add Camera manufacturer to the menu
bool newFactory = true;
foreach (ToolStripMenuItem mi in mnuCamera.DropDownItems )
{
if (mi.Text.Contains(myCameraInfo.Factory))
newFactory = false;
}
if (newFactory)mnuCamera.DropDownItems.Add(myCameraInfo.Factory);
// Add cameral model to the menu
foreach (ToolStripMenuItem mi in mnuCamera.DropDownItems)
{
if (mi.Text.Contains(myCameraInfo.Factory))
mi.DropDownItems.Add(myCameraInfo.Model +
(myCameraInfo.AliasName == "" ? "" : " - " +
myCameraInfo.AliasName ),null, mnuCamera_Click);
}
}
catch (Exception ) { }
}
}
}
}
}
载入新图像
然后程序就准备好了。每次从硬盘加载图片时,以下程序都会检查其传感器宽度、高度和相机图片是否在数据目录中可用。
//Finding camera info and picture
private void cameraSerach()
{
txtSensorHeight.Text = "";
txtSensorWidth.Text = "";
txtSensorType.Text = "";
picCamera.Image = Properties.Resources.noPhoto;
toolTip1.SetToolTip(picCamera, "");
string camControl = "MRK"; // this is for preventing finding
// camera names like D300 instead of D3
string camName = txtCameraFactory.Text.Replace
(" ", "").Replace("-", "").ToLower() + camControl;
bool camFound = false;
byte camLoop = 0;
do
{
foreach (Camera thisCamera in allCameraInfo)
{
if (camName.Contains(thisCamera.Factory.Replace(" ", "").ToLower())
&& !camFound )
{
if (camName.Contains(thisCamera.Model.Replace
(" ", "").Replace("-", "").ToLower() + camControl) ||
(camName.Contains(thisCamera.AliasName.Replace
(" ", "").Replace("-", "").ToLower() + camControl) &&
thisCamera.AliasName != ""))
{
txtSensorType.Text = thisCamera.SensorType;
txtSensorWidth.Text = thisCamera.SensorWidth.ToString();
txtSensorHeight.Text = thisCamera.SensorHeight.ToString();
toolTip1.SetToolTip(picCamera, thisCamera.Factory + " " +
thisCamera.Model);
string picFile = Application.StartupPath + "\\Data\\" +
thisCamera.Factory + "_" +
thisCamera.Model.Replace(" ", "") + ".gif";
if (System.IO.File.Exists(picFile)) picCamera.Image =
Image.FromFile(picFile);
camFound = true;
}
}
}
camControl = "";
camLoop++;
} while (!camFound && camLoop < 2);
}
35 毫米等效焦距
在数码时代之前,我们通常使用 35 毫米胶片相机。胶片尺寸(传感器尺寸)固定为 36 毫米 x 24 毫米。在这种相机中,一个 50 毫米的镜头视角约为 46°(等于我们眼睛的视角),被称为标准镜头。焦距更短的镜头称为广角镜头(例如 28 毫米),焦距更长的镜头称为远摄镜头(例如 200 毫米)。
数码相机由于一些数学原因具有不同的传感器尺寸;当我们将镜头用于不同传感器尺寸的相机时,镜头的焦距是不同的。例如,一个 35 毫米广角镜头在全画幅相机(例如佳能 EOS 5D)上表现为 35 毫米,在 APS-C 传感器尺寸相机(例如尼康 D60)上表现为约 50 毫米标准镜头,在 4/3 英寸传感器尺寸相机(例如奥林巴斯 E-520)上表现为 70 毫米远摄镜头。
为避免焦距混淆,所有制造商在介绍其相机时都使用等效值,这表明如果相机是 35 毫米胶片相机,它将如何工作。例如,以下相机(佳能 A590)具有焦距为 5.8 至 23.2 毫米的变焦镜头,但在目录中,您会看到他们使用 35-140 毫米的等效值。
|
|
为了计算等效值,我们需要将 35 毫米胶片相机传感器(约 43.27 毫米)的对角线尺寸除以我们相机传感器(这里约 7.18 毫米)的对角线尺寸。结果值被称为焦距倍数。最终公式如下:
等效焦距 = 实际焦距 * 焦距倍数。
|
|
在上述相机中,我们的计算如下
焦距倍数 = 43.27 / 7.18 = 6
最小焦距 = 5.8 * 6 = 34.8
最大焦距 = 23.2 * 6 = 139.2
我使用以下代码计算 35 毫米等效焦距
private void txtPicFL_TextChanged(object sender, EventArgs e)
{
string myEquivalent = "";
try
{
double eq =Math.Pow ( (Math.Pow(Convert.ToSingle(txtSensorWidth.Text), 2) +
Math.Pow(Convert.ToSingle(txtSensorHeight.Text), 2)),.5);
eq = Math.Round ((43.2666/eq) * Convert.ToSingle(txtPicFL.Text ));
myEquivalent = eq.ToString();
}
catch (Exception) { }
txtPicEquivalent.Text = myEquivalent;
}
测量传感器上物体的大小
当用户通过鼠标拖动选择图像的一部分时,以下代码测量其在传感器上的尺寸。它考虑了图像的方向(横向或纵向),并在状态栏上显示结果。
// measuring the selected part's size on the sensor
double xDiff = Math.Abs(myMeasureStartX - e.X) * myZoom *
Convert.ToSingle(txtSensorWidth.Text) /
Convert.ToSingle(horiZontal ? txtPicWidth.Text : txtPicHeight.Text);
double yDiff = Math.Abs(myMeasureStartY - e.Y) * myZoom *
Convert.ToSingle(txtSensorHeight.Text) /
Convert.ToSingle(horiZontal ? txtPicHeight.Text : txtPicWidth.Text);
myMeasure = Math.Pow((Math.Pow(xDiff, 2) + Math.Pow(yDiff, 2)), .5);
tssStatusM.Text = "Measure (mm):" + myMeasure.ToString();
// Drawing a red line to show the size of your selection
picMain.Refresh();
Graphics g = picMain.CreateGraphics();
g.ResetClip();
g.DrawLine(new Pen(Color.Red), myMeasureStartX, myMeasureStartY, e.X, e.Y);
最终计算
现在,我们有了焦距 (X2) 和物体在传感器上的大小 (Y2),如果你知道物体的距离 (X1),你可以使用以下代码找到其大小
mySize = myDistance * (myMeasure / Convert.ToDouble(txtPicFL.Text));
tstSize.Text = Convert.ToString(Math.Round ( mySize / mySizeScale,2));
如果你知道大小 (Y1),以下代码将计算物体的距离
myDistance = mySize / (myMeasure / Convert.ToDouble(txtPicFL.Text));
tstDistance.Text = Convert.ToString( Math.Round( myDistance / myDistanceScale,2));
工作坊
现在,我们要试用一下这个应用程序。我从 Imaging-Resource 网站下载了以下图片并保存到我的硬盘。你也可以试试看。
我加载图片。程序从文件中获取所需信息并显示在左侧面板上。

点击左上角的缩略图,您可以快速移动到选定的部分。我在这里找到了一面旗帜。

然后,将缩放条向右移动。您还可以通过在主图像上拖动鼠标来移动图像。

如果你再多尝试一下,你就能找到这个地方。

放大后,你会看到建筑物上有一面旗帜。

现在点击工具栏上的画笔按钮,并在旗帜上画一条线(作为其高度)。程序会测量其在传感器上的大小,并在状态栏中显示结果。

我不知道旗帜的确切尺寸,但我猜它大约是 150 厘米(5 英尺),如果是这样,我在物体尺寸框中输入 150。程序计算出的距离约为 3 公里。

准确性
这种方法的准确性取决于你的相机传感器尺寸、镜头质量、图像噪点等。你可以通过将尺子放置在已知距离并拍照来测试你的相机。然后使用程序进行测量。
平均准确率超过 95%。

关注点
本文的一些部分是关于摄影和相机的。这里有一些有趣的观点。
谈到数码相机,你首先想到的可能是它的百万像素。通常人们认为百万像素越多越好,但在这场百万像素竞赛中,制造商们并未承认一些问题。
在传感器尺寸相同的情况下,更多的百万像素不仅不意味着更好的图像,有时反而会降低质量。为了制造更多的像素,制造商必须在传感器上制造更小的像素,这意味着每个传感器像素收集的光线更少。在这种情况下,处理光线的能力降低,每个像素的噪点增加。结果你得到的图像像素更多,噪点也更多。这个问题在低光照条件下尤为明显。
如果你想花更多的钱购买数码相机,请将其投资于传感器尺寸。一台拥有 800 万像素和 1/1.7 英寸传感器尺寸的数码相机优于一台拥有 1200 万像素和 1/2.5 英寸传感器尺寸的相机。800 万桶水比 1200 万杯水更多。
历史
- 首次发布(2009 年 4 月 6 日)
- 更新 1(2009 年 4 月 22 日)
- 即时计算
- 更新 2(2009 年 4 月 30 日)
- 修正了一些欧洲语言中的小数问题(逗号代替小数点)