📄 stereoimg.pas
字号:
unit StereoImg;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, ExtCtrls, Math, Dialogs;
type
TDistribution = (dMove, dUniform, dTriangular, dPiecewise, dNormal);
TStereoImg = class(TComponent)
private
{ Private declarations }
blockW, blockH: Integer; // 图像子块的宽与高(单位:像素)
wN, hM: Integer; // 最后一列的宽度和最后一行的高度
FM: Integer; // 内部存储字段(存储字段)
FN: Integer;
InsPoint: TPoint; // 记录下个图块的插入位置
procedure SetM(const Value: Integer);
procedure SetN(const Value: Integer); // 写方法,Value是常量参数(参见《Delphi4.0实用编程》P200)
procedure ProduceKmn(Distribution: TDistribution); // 产生一个符合要求的随机变量矩阵
function fit(i, j: Integer; Rnd: Single): Boolean;
protected
{ Protected declarations }
public
{ Public declarations }
Kmn: array of array of Single; // 随机变量矩阵Kmn是一个二维动态数组
lImg: TBitmap; // 左眼图像
rImg: TBitmap; // 右眼图像(原始图像)
function Produce_rImg(Distribution: TDistribution): Boolean; // 生成左眼视图
published
{ Published declarations }
property M: Integer read FM write SetM; // read进行直接访问、write进行方法访问(参见《Delphi4.0实用编程》P460)
property N: Integer read FN write SetN;
end;
procedure Register;
implementation
uses Controls;
procedure Register;
begin
RegisterComponents('Samples', [TStereoImg]);
end;
{ TStereoImg }
{
检查是否满足舒适立体融像区的限制条件
这里采用《立体成像系统数学模型和视差控制方法》中的控制方法。
2h是瞳距,一般为6.5cm;η为视锐度,取2.907e-4 rad ;
投影平面与观察者的距离为pd(pd>0),这里取100cm,
E是瞳孔直径,一般为0.4cm,pσ是监视器的像素间隔,这里取3.53e-2 cm;
因此,-13 <= q_j_max <= 13,这个取值范围也满足q_j_max的理论限制。
}
function TStereoImg.fit(i, j: Integer; Rnd: Single): Boolean;
var
k: Integer; // 循环变量
sKst: Single; // sKst = Ks1 + Ks2 + ... + Kst-1
q_j: array of Double; // 用于存储一个子块中各个像素的水平视差的绝对值,
// 像素长度应为0 ~ blockW - 1或0 ~ wN - 1,即为blockW
q_j_max: Integer; // 用于存储数组q_j中的最大值的近似值
begin
Result := False;
// 在这个函数调用之前,随机变量矩阵中已经有数据了,
// 并且blockW、blockH、wN、hM也已经在成员函数Produce_rImg中赋值,
// 水平视差的单位是像素
{ 计算第i行第j列(i、j从0开始计算)的图像子块经变换后得到其像素点的水平坐标 }
if j = N - 1 then // 最后一列的宽度不一定是blockW,而是wN,所以要单独讨论
begin
// 在内存中创建一维动态数组
SetLength(q_j, wN);
// 计算第i行前面j-1列的随机数之和,即计算论文中的Ks1+Ks2+...+Kst-1
sKst := 0;
for k := 0 to j - 1 do
sKst := sKst + Kmn[i, k];
// 计算各个像素的水平视差的绝对值
for k := 0 to wN - 1 do // 像素长度应为0 ~ blockW - 1,元素个数应该是blockW
q_j[k] := Abs(blockW * ((j - sKst) + k * (1 - Rnd) / wN));
// 判断数组q_j中的最大值是否满足舒适立体融像区的限制条件
q_j_max := Round(MaxValue(q_j));
if q_j_max < 13 then
Result := True; // 符合要求
end
else
begin
{ 一般情况 }
// 在内存中创建一维动态数组
SetLength(q_j, blockW);
// 计算第i行前面j-1列的随机数之和,即计算论文中的Ks1+Ks2+...+Kst-1
sKst := 0;
for k := 0 to j - 1 do
sKst := sKst + Kmn[i, k];
// 计算各个像素的水平视差的绝对值
for k := 0 to blockW - 1 do // 像素长度应为0 ~ blockW - 1,元素个数应该是blockW
q_j[k] := Abs(blockW * ((j - sKst) + k * (1 - Rnd) / blockW));
// 判断数组q_j中的最大值是否满足舒适立体融像区的限制条件
q_j_max := Round(MaxValue(q_j));
if q_j_max < 13 then
Result := True; // 符合要求
end;
end;
{ 产生一个符合要求的随机变量矩阵 }
procedure TStereoImg.ProduceKmn(Distribution: TDistribution);
var
i, j: Integer;
r: real;
begin
// 在内存中创建二维动态数组
SetLength(Kmn, M, N);
// 产生随机数,M行,N列
for i := 0 to M - 1 do
begin
// 行与行之间的随机数没有关系,而每一行内部各图像子块之间对应的随机数是有依赖关系的
// 这里采取的方法是,如果某一图像子块对应的随机数不符合要求,这一行对应的随机数就全部重新产生
j := 0;
repeat
// 每产生一个随机数,就验证该随机数是否符合要求
{ 选择随机变量服从哪一种分配 }
case Distribution of
dUniform: Kmn[i, j] := 0.5 + Random; // 随机数范围0.5<=x<1.5,均值a应该等于1
dTriangular:
begin // sin函数
r := Random;
Kmn[i, j] := ArcSin(2 * r - 1) / Pi + 1;
end;
dPiecewise:
begin // 分段均匀分布β=0.82([0.5,1.32], [0.68 ,1.5])相对最优,但也最慢
if j < (N / 2) then
Kmn[i, j] := 0.5 + 0.82 * Random
else
Kmn[i, j] := 0.68 + 0.82 * Random;
end;
dNormal: // 高斯正态分布(1,1/6)
begin
repeat // Kmn[i, j] := RandG(1,1/6);
r := RandG(1, 1 / 6);
until (r >= 0.5) and (r <= 1.5);
Kmn[i, j] := r;
end;
end;
if fit(i, j, Kmn[i, j]) then // 验证产生的随机数是否符合要求
Inc(j)
else
j := 0; // 不符合要求就重新来过
until j = N;
end;
end;
{ 生成左眼视图 }
// TImage和TBitmap的性质
// 当Autosize为True时
// TImage.Picture.Bitmap.Width = TImage.Width
// TImage.Picture.Bitmap.Height = TImage.Height
// TImage.Picture.Bitmap.Canvas = TImage.Canvas
// TBitmap的像素范围: 0 - Width, 0 - Height, 不可以从1开始
// 拼接图的时候每一块不需要间隔一个像素,而是直接从上一块的边开始接
function TStereoImg.Produce_rImg(Distribution: TDistribution): Boolean;
var
i, j: Integer;
tmpImg: TImage;
begin
Result := False;
if (M > 0) and (N > 0) and (not rImg.Empty) then
begin
// 创建左眼视图,保证左、右眼视图大小一致
lImg := nil;
lImg := TBitmap.Create;
lImg.Width := rImg.Width;
lImg.Height := rImg.Height;
lImg.PixelFormat := rImg.PixelFormat;
// 将右眼视图划分成M*N个图像子块,M和N根据最相近的值进行调整。
blockW := Round(rImg.Width / N);
N := Trunc(rImg.Width / blockW);
wN := rImg.Width - blockW * (N - 1);
blockH := Round(rImg.Height / M);
M := Trunc(rImg.Height / blockH);
hM := rImg.Height - blockH * (M - 1);
// 产生一个符合要求的随机变量矩阵
ProduceKmn(Distribution);
InsPoint := Point(0, 0); // 复位
// 处理每一子块
for i := 0 to M - 1 do // 处理每一行
for j := 0 to N - 1 do
if (i <> M - 1) and (j = N - 1) then // 最后一列的宽度不一定是blockW,所以要单独讨论
begin
// 生成一个临时的控件用于缩放
tmpImg := TImage.Create(Self);
with tmpImg do
begin
AutoSize := False;
Stretch := True;
// 将tmpImg拉伸成新的宽度和高度
Width := Round(wN * Kmn[i, j]);
Height := blockH;
// 将图像子块拷贝到tmpImg
Canvas.CopyRect(Rect(0, 0, Width, Height),
rImg.Canvas, Rect(blockW * (N - 1), blockH * i, rImg.Width, blockH
* (i + 1)));
// 将经过拉伸处理的图像拷回左眼视图相应位置
lImg.Canvas.Draw(InsPoint.X, InsPoint.Y, Picture.Bitmap);
Free;
end;
// 确定下一个图像的插入点
InsPoint.X := 0; // 换行
InsPoint.Y := blockH * (i + 1);
end
else
if (i = M - 1) and (j <> N - 1) then // 最后一行的高度不一定是blockH,所以要单独讨论
begin
// 生成一个临时的控件用于缩放,每次重新生成是为了防止上一次的结果影响这一次的结果
tmpImg := TImage.Create(Self);
with tmpImg do
begin
AutoSize := False;
Stretch := True;
// 将tmpImg拉伸成新的宽度和高度
Width := Round(blockW * Kmn[i, j]);
Height := hM;
// 将图像子块拷贝到tmpImg
Canvas.CopyRect(Rect(0, 0, Width, Height),
rImg.Canvas, Rect(blockW * j, blockH * i, blockW * (j + 1),
rImg.Height));
// 将经过拉伸处理的图像拷回左眼视图相应位置
lImg.Canvas.Draw(InsPoint.X, InsPoint.Y, Picture.Bitmap);
Free;
end;
// 确定下一个图像的插入点
InsPoint.X := InsPoint.X + tmpImg.Width;
end
else
if (i = M - 1) and (j = N - 1) then // 最后一个子块要单独讨论
begin
// 生成一个临时的控件用于缩放
tmpImg := TImage.Create(Self);
with tmpImg do
begin
AutoSize := False;
Stretch := True;
// 将tmpImg拉伸成新的宽度和高度
Width := Round(wN * Kmn[i, j]);
Height := hM;
// 将图像子块拷贝到tmpImg
Canvas.CopyRect(Rect(0, 0, Width, Height),
rImg.Canvas, Rect(blockW * j, blockH * i, rImg.Width,
rImg.Height));
// 将经过拉伸处理的图像拷回左眼视图相应位置
lImg.Canvas.Draw(InsPoint.X, InsPoint.Y, Picture.Bitmap);
Free;
end;
end
else
begin
{ 一般情况 }
// 生成一个临时的控件用于缩放
tmpImg := TImage.Create(Self);
with tmpImg do
begin
AutoSize := False;
Stretch := True;
// 将tmpImg拉伸成新的宽度和高度
Width := Round(blockW * Kmn[i, j]);
Height := blockH;
// 将图像子块拷贝到tmpImg
Canvas.CopyRect(Rect(0, 0, Width, Height),
rImg.Canvas, Rect(blockW * j, blockH * i, blockW * (j + 1),
blockH * (i + 1)));
// 将经过拉伸处理的图像拷回左眼视图相应位置
lImg.Canvas.Draw(InsPoint.X, InsPoint.Y, Picture.Bitmap);
// 确定下一个图像的插入点
InsPoint.X := InsPoint.X + Width;
Free;
end;
end;
Result := True;
end;
end;
procedure TStereoImg.SetM(const Value: Integer);
begin
FM := Value;
end;
procedure TStereoImg.SetN(const Value: Integer);
begin
FN := Value;
end;
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -