wongjan 发表于 2012-2-21 15:53:06

基于摄像头和一字线激光器的3D扫描仪

一直都在打算做个3D扫描仪,只是比较懒,又没有毅力,写下此帖以做鞭策。
这种扫描仪以前有人发过:
[原创+开源]DIY低成本3D激光扫描测距仪(激光雷达)
http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=5247845
按照这种原理咱也DIY一个。
摄像头采用了最普通的,价值25大洋,激光器在淘宝上买的,50元左右。
旋转部分准备用减速电机+计数器,单片机控制,S52。
上位机软件用delphi7来写,用的比较熟悉了。
先上几张图
http://cache.amobbs.com/bbs_upload782111/files_52/ourdev_720874WNZNCQ.JPG
(原文件名:一字线.JPG)

http://cache.amobbs.com/bbs_upload782111/files_52/ourdev_720875WQMQBK.JPG
(原文件名:一字线激光器.JPG)

http://cache.amobbs.com/bbs_upload782111/files_52/ourdev_720876UIYNFI.JPG
(原文件名:整体.JPG)

http://cache.amobbs.com/bbs_upload782111/files_52/ourdev_720877UEUMNH.JPG
(原文件名:整图2.JPG)

http://cache.amobbs.com/bbs_upload782111/files_52/ourdev_720878KL6FRS.JPG
(原文件名:界面.JPG)

wongjan 发表于 2012-2-22 08:29:23

刚取得的一些数据,还没有做标定。
http://cache.amobbs.com/bbs_upload782111/files_52/ourdev_721001WPMAPU.JPG
(原文件名:数据.JPG)

kupica 发表于 2012-3-25 23:30:49

我也想搞一个玩玩,有3D的还原图像么?

fengerye 发表于 2012-3-29 23:20:22

这个有点意思

Rocky_Zou 发表于 2012-3-31 11:43:20

楼主牛逼啊   佩服

wongjan 发表于 2012-4-21 18:51:09

说来惭愧,最近工作挺忙,暂时放下了。保证有进展一定来更新。

zjk 发表于 2012-4-23 11:16:22

效果如何啊,好的话我也试试

Remond 发表于 2012-5-11 13:43:16

跟帖~~ 学习 一字激光器 和摄像头我都买好了~~软件方面 还得从0开始学啊 ~~ {:sweat:}   我用的 VC++

abcfanyuan 发表于 2012-5-17 13:26:31

mark,一直在关注。{:smile:}

wongjan 发表于 2012-6-6 11:18:24

谢谢大家的关注。
拖了这么长时间,终于做出了1.0版,呵呵。
先发个点云图,其他的陆续发。

wongjan 发表于 2012-6-6 11:42:55

硬件组成。

wongjan 发表于 2012-6-6 12:36:10

不用顶,不用问,直接放源代码。不用下载,直接拷贝。
业余水平,欢迎拍砖。
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,videocap, StdCtrls,jpeg, ExtCtrls, ExtDlgs, OleCtrls,
MSCommLib_TLB, Mask;

type
TForm1 = class(TForm)
    Openvideo: TButton;
    Closevideo: TButton;
    Button1: TButton;
    Label1: TLabel;
    Panel1: TPanel;
    Image1: TImage;
    Button2: TButton;
    Label2: TLabel;
    ScrollBar1: TScrollBar;
    Edit2: TEdit;
    Edit3: TEdit;
    Button3: TButton;
    Button4: TButton;
    MSComm1: TMSComm;
    Edit4: TEdit;
    Label3: TLabel;
    Button5: TButton;
    Image2: TImage;
    Memo1: TMemo;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    procedure Button1Click(Sender: TObject);
    procedure OpenvideoClick(Sender: TObject);
    procedure ClosevideoClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure MSComm1Comm(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);





private
    { Private declarations }
public

    { Public declarations }
end;

var
Form1: TForm1;
//定义捕获窗句柄
hWndC : THandle;
jishu:integer;
Z:integer;
positio:string;
type
THDFunction = function(I:integer;s:string):integer; stdcall ;

PVIDEOHDR = ^TVIDEOHDR;
TVIDEOHDR = record
    lpData:pByte;               // pointer to locked data buffer
    dwBufferLength:DWORD;         // Length of data buffer
    dwBytesUsed:DWORD;            // Bytes actually used
    dwTimeCaptured:DWORD;         // Milliseconds from start of stream
    dwUser:DWORD;               // for client's use
    dwFlags:DWORD;                // assorted flags (see defines)
    dwReserved: array of DWORD;    // reserved for driver
end;

TCAPVIDEOSTREAMCALLBACK = function(hWnd:HWND; lpVHdr:PVIDEOHDR):LongInt; stdcall;


const WM_CAP_START = WM_USER;
const WM_CAP_STOP = WM_CAP_START + 68;
const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
const WM_CAP_SAVEDIB = WM_CAP_START + 25;
const WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
const WM_CAP_SEQUENCE = WM_CAP_START + 62;
const WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20;
const WM_CAP_SEQUENCE_NOFILE =WM_CAP_START+ 63;
const WM_CAP_SET_OVERLAY =WM_CAP_START+ 51;
const WM_CAP_SET_PREVIEW =WM_CAP_START+ 50;
const WM_CAP_SET_CALLBACK_VIDEOSTREAM = WM_CAP_START +6;
const WM_CAP_SET_CALLBACK_ERROR=WM_CAP_START +2;
const WM_CAP_SET_CALLBACK_STATUSA= WM_CAP_START +3;
const WM_CAP_SET_CALLBACK_FRAME= WM_CAP_START +5;
const WM_CAP_SET_SCALE=WM_CAP_START+ 53;
const WM_CAP_SET_PREVIEWRATE=WM_CAP_START+ 52;
const WM_CAP_GET_VIDEOFORMAT= WM_CAP_START+44;



implementation

{$R *.dfm}

function capCreateCaptureWindowA(lpszWindowName : PCHAR; dwStyle : longint; x : integer;
y : integer;nWidth : integer;nHeight : integer;ParentWin : HWND;
nId : integer): HWND;STDCALL EXTERNAL 'AVICAP32.DLL';




procedure TForm1.Button1Click(Sender: TObject);
begin
close;
end;

function FrameCallBack(hWnd:HWND;lpVHdr: PVideoHDR):LongInt; stdcall;
var
bit: TBitmap;
info: TBitmapInfo;
p1,p2:pbytearray;
x,y:integer;
rx,ry,ra:real;
Gray:integer;
ltmp,lx,ly:integer;
lianxu:boolean;
shuchu:string;
begin

if (positio=form1.Label3.Caption) or (strtoint(form1.Label3.Caption )<67)   then exit;
positio:=form1.Label3.Caption   ;
SendMessage(hwndc, WM_CAP_GET_VIDEOFORMAT, Wparam(sizeof(info)), LPARAM(@info));
if info.bmiHeader.BiCompression = bi_RGB then
begin
    bit := TBitmap.Create;
    try
      with bit, info.bmiHeader do
      begin
      PixelFormat := pf24bit;
      Width := biWidth;
      Height := biHeight;
      setDiBits(canvas.handle, handle, 0, biheight,
          lpVHdr^.lpData, info, DIB_RGB_COLORS);

      form1.Image1.Width := info.bmiHeader.biwidth;
      form1.Image1.Height := info.bmiHeader.biheight;
      form1.Image1.Picture.Assign(bit);
      end;
    finally
      bit.Free;
    end;
    //以上是捕获到image1中的代码



   //图像处理开始    ××××××核心×××××××××
      ltmp:=0;
      ly:=0;
      lx:=0;
      rx:=0;
      ry:=0;
      ra:=(strtoint(form1.Label3.Caption)-64)*0.72;
      shuchu:='';
      for y:=0 to form1.Image1.Picture.bitmap.Height -1 do
          begin
            jishu:=0;
            p1:=form1.Image1.Picture.bitmap.ScanLine ;
            for x:=1 toform1.Image1.Picture.bitmap.Width -1 do
            begin
                if (p1>(form1.ScrollBar1.Position *2.54)) and (x<form1.Image1.Picture.bitmap.Width -1)   then      //二值化
                  begin
                  lianxu:=true;
                  ltmp:=ltmp+x;
                  jishu:=jishu+1;
                  //rx:=1468.9-206.3*ln(lx);
                  //ry:=(240-ly)/(lx+190)*150;
                  //if rx<400 then form1.Memo1.Lines.Add (formatfloat('0.0',rx*cos(ra/180*pi))+' '+formatfloat('0.0',ry)+' '+formatfloat('0.0',rx*sin(ra/180*pi))) ;
                  //p1:=255;   // 蓝
                  //p1:=255;   //绿
                  //p1:=255;   //红
                  end
                else
                  begin
                  if lianxu then
                      begin
                        lianxu:=false;
                        rx:=1468.9-206.3*ln(ltmp/jishu);
                        ry:=(240-y)/(ltmp/jishu+190)*150;
                        form1.Memo1.Lines.Add (formatfloat('0.0',rx*cos(ra/180*pi))+' '+formatfloat('0.0',ry)+' '+formatfloat('0.0',rx*sin(ra/180*pi))) ;
                        p1:=255;   // 蓝
                        p1:=255;   //绿
                        p1:=255;   //红
                        jishu:=0;
                        ltmp:=0;
                      end
                  else
                      begin
                        p1:=0;
                        p1:=0;
                        p1:=0;
                      end;
                  end;
            end;


          end;
      //if lx=0 then lx:=800;
      //rx:=1468.9-206.3*ln(lx);
      //ry:=(240-ly)/(lx+190)*150;
      //form1.Label1.Caption :=formatfloat('0.0',rx)+' '+formatfloat('0.0',ry)+' '+inttostr(ltmp);


    //图像处理结束


    //图像拷贝至image2
    form1.Image2.Picture.Assign(form1.Image1.Picture.Bitmap );

    form1.mscomm1.Output:=char(65);
end;
end;

procedure TForm1.OpenvideoClick(Sender: TObject);
begin
hWndC := capCreateCaptureWindowA('My Own Capture Window',WS_CHILD or WS_VISIBLE ,Panel1.Left,Panel1.Top,Panel1.Width,Panel1.Height,Form1.Handle,0);
if hWndC <> 0 then
begin
SendMessage(hWndC, WM_CAP_SET_CALLBACK_VIDEOSTREAM, 0, 0);
SendMessage(hWndC, WM_CAP_SET_CALLBACK_ERROR, 0, 0);
SendMessage(hWndC, WM_CAP_SET_CALLBACK_STATUSA, 0, 0);
SendMessage(hWndC, WM_CAP_DRIVER_CONNECT, 0, 0);
SendMessage(hWndC, WM_CAP_SET_SCALE, 1, 0);
SendMessage(hWndC, WM_CAP_SET_PREVIEWRATE, 66, 0);
SendMessage(hWndC, WM_CAP_SET_PREVIEW, 1, 0);
SendMessage(hwndc, WM_CAP_SET_CALLBACK_FRAME, 0,LPARAM( @FrameCallBack)) ;    //回调函数 必须装SG310摄像头驱动


end;

end;

procedure TForm1.ClosevideoClick(Sender: TObject);
begin
if hWndC <> 0 then begin
SendMessage(hWndC, WM_CAP_DRIVER_DISCONNECT, 0, 0);
hWndC := 0;
end;


end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if hWndC <> 0 then
    begin
    SendMessage(hWndC, WM_CAP_DRIVER_DISCONNECT, 0, 0);
    end;
if mscomm1.PortOpenthen
    begin
      mscomm1.PortOpen := false;
      mscomm1.DTREnable :=false;
      mscomm1.RTSEnable :=false;
    end;
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
jishu:=0;
z:=strtoint(label3.Caption);
positio:=form1.Label3.Caption   ;
//初始化串口
edit2.Text :=mscomm1.Settings ;
edit3.Text :=inttostr(mscomm1.CommPort );
mscomm1.InBufferSize := 1024;             //接收缓冲区
mscomm1.OutBufferSize := 1024;             //发送缓冲区
mscomm1.InputMode := 1;       //接收模式    0:文本 1:二进制
mscomm1.InputLen := 0;                     //一次读取所有数据
mscomm1.SThreshold := 0;                   //一次发送所有数据
mscomm1.InBufferCount := 0;               //清空读取缓冲区
mscomm1.OutBufferCount := 0;               //清空发送缓冲区

MSComm1.RThreshold :=1;               //设置接收多少字节开产生oncomm事件太频繁可能会使程序失去响应

end;


procedure TForm1.Button2Click(Sender: TObject);
begin
label3.Caption:=inttostr(strtoint(label3.Caption)+1);
memo1.Clear ;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin

mscomm1.CommPort := strtoint(edit3.Text );                     //指定端口
mscomm1.Settings := edit2.Text;         //其它参数

mscomm1.PortOpen:=true ;
mscomm1.DTREnable :=true;
mscomm1.RTSEnable :=true;
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
if mscomm1.PortOpenthen
    begin
      mscomm1.PortOpen := false;
      mscomm1.DTREnable :=false;
      mscomm1.RTSEnable :=false;
    end;
end;

procedure TForm1.MSComm1Comm(Sender: TObject);
var
filenrc :char;
buffer :olevariant;
s1:string;
c :char;
reData:array of Variant;
restr:string;
i:integer;
begin
//buffer:= form1.MSComm1.commevent;
//form1.memDisplay.Lines.Add (buffer) ;
case form1.mscomm1.commEvent of
    comEvSend:
      begin
      end;
    comEvReceive:
      begin
          reData:= mscomm1.Input;
          mscomm1.InBufferCount :=0;
          restr:='';
          for i:=0 to vararrayhighbound(redata,1) do
            begin
            restr:=restr+ inttostr(redata);
            end;
          label3.Caption :=restr;
          //mscomm1.InBufferCount :=0;   //在此清空缓冲区会丢数据

      end;

   end;

end;

procedure TForm1.Button5Click(Sender: TObject);
var
Tchar:char;
Tbuffer :olevariant;
begin

label1.Caption := inttostr(strtoint(edit4.Text ));
mscomm1.Output:=char(strtoint(edit4.Text )+64);

end;

procedure TForm1.Button7Click(Sender: TObject);
begin
mscomm1.Output:=char(62);

end;

procedure TForm1.Button6Click(Sender: TObject);
begin
mscomm1.Output:=char(66);
end;

procedure TForm1.Button8Click(Sender: TObject);
begin
form1.Memo1.Lines.SaveToFile('d:\haha.xyz');
end;



end.

wongjan 发表于 2012-6-6 12:38:32

还有单片机的源代码,不懂C,只能用汇编写的。
    ;这是一个使用单片机通过NUL2003驱动步进电机程序.
    ;步进电机: 5伏,500MA/STEP,四线四拍.
    ;将步进电机的A B C D四个绕组连接到"步进电机"的A,B,C,D,将公用抽头并接在VCC上.
            BUFF_STEP   EQU   31H
                POSITIONEQU32H
            SW1BIT   P1.5
            SW2BIT         P1.6
            SMG1 EQU P1.0        ;定义数码管阳级控制脚,第一个
          SMG2 EQU P1.1        ;定义数码管阳级控制脚,第二个

            ORG000h
            JMPSTART
               
      ORG 0023H
      AJMP RECEIVE          ;跳转到接收中断入口
;----------------------------------------
;-----------数据存放----------------------
ORG 0100H
TABLE:        DB 0C0h,0F9H,0A4H,0b0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH;表:共阳数码管 0-9 A-F

TAB_CW:DB   11111011B,11101011B,11101111B,11100111B,11110111B,11010111B,11011111B,11011011B   ;正转相序        a橙b黄c黑   d棕   com红
TAB_CCW: DB   11011111B,11010111B,11110111B,11100111B,11101111B,11101011B,11111011B,11011011B    ;反转相序


;-----------------------------------------------
    ORG300H
START:

        MOV   BUFF_STEP,#00H
    MOV   P0,#0fFh          ;初始化MCU端口
    MOV   P1,#0ffh
    MOV   P3,#0ffh
    MOV   P2,#0ffH
    MOV   A,#00H         ;初始化寄存器
    MOV   PSW,#00H
    MOV   IE,#00H
    MOV   SP,#80H
        MOV         POSITION,#40H
        MOV TMOD,#20H         ;T1工作方式2
    MOV TH1,#0FDH         ;波特率9600
    MOV SCON,#50H         ;传口工作方式1,允许中断接受
    SETB EA               ;打开总中断
    SETB ES               ;打开串口中断
    SETB TR1            ;打开定时器1
       

MAIN:

        MOV A,POSITION
        LCALL DISPLAY
        LJMP MAIN
       


RECEIVE:
      CLR RI
      MOV A,SBUF            ;串口接收数据
                MOV R0,A
PANDUAN:MOV A,R0
                CJNE A,#40H,ZHUAN ;判断A是否等于40H,等于则停止转动,程序返回。
                LJMP TING
ZHUAN:        ;转动
                JC ZZHUAN
                ;反转
                LCALL FZ
                INC POSITION
                DEC R0
                LJMP PANDUAN
ZZHUAN:                ;正转
                LCALL ZZ
                DEC POSITION
                INC R0
                LJMP PANDUAN

TING:        MOV A,POSITION;停止转动后返回当前位置
      MOV SBUF,A            ;将数据传送给计算机
      JNB TI,$
      CLR TI
      RETI
;正转8拍子程序=======================      
ZZ:
        MOVDPTR,#TAB_CW
        MOV   A,BUFF_STEP
    MOVCA,@A+DPTR
       
    MOV   P2,A
    MOV   P3,A
        MOV A,POSITION
        LCALL DISPLAY

    INC   BUFF_STEP
    MOV   A,BUFF_STEP
    CJNEA,#08H,ZZ
    MOV   BUFF_STEP,#00H
    RET
;反转8拍子程序===================
FZ:
        MOVDPTR,#TAB_CCW
        MOV   A,BUFF_STEP
    MOVCA,@A+DPTR
       
    MOV   P2,A
    MOV   P3,A
        MOVA,POSITION
        LCALL DISPLAY

    INC   BUFF_STEP
    MOV   A,BUFF_STEP
    CJNEA,#08H,FZ
    MOV   BUFF_STEP,#00H
    RET



;显示A子程序==================
DISPLAY:
        PUSH ACC

        MOV DPTR,#TABLE        ;数码管显示子程序
        MOV B,#16
        DIV AB
        CLR SMG1        ;选择十位数码管       
        MOVC A,@A+DPTR        ;查表输出定义好的数码管段值与P0口,显示相应的0-9
        MOV P0,A
        LCALL DELAY        ;延时,停留一会,让人眼感触到它的亮度
        MOV P0,#0FFH        ;清除数码管显示,因是共阳型,所以不是0
        SETB SMG1        ;关闭十位数码管

        CLR SMG2        ;选择个位数码管
        MOV A,B                ;个位显示
        MOVC A,@A+DPTR        ;查表输出定义好的数码管段值与P0口,显示相应的0-9
        MOV P0,A
        LCALL DELAY        ;延时,停留一会,让人眼感触到它的亮度
        MOV P0,#0FFH        ;清除数码管显示,因是共阳型,所以不是0
        SETB SMG2        ;关闭个位数码管
        POP ACC
        RET

;延时子程序=================================
DELAY:      
                MOV   R6,#4
DEL0:   MOV   R7,#255
      DJNZR7,$
      DJNZR6,DEL0

      RET



         
        END


wongjan 发表于 2012-6-6 12:46:03

待改进的地方还有很多,目前用memo控件显示数据,速度很受影响,如果不显示数据只是扫描的话速度要快N倍。
另外目前步进电机每次转8拍,显得点云间距有些过大,还可以再调整。
支架有些过于简陋,呵呵,是个八宝粥罐子,内含步进电机。
控制板应该搞个盒子装起来。
先这样,做为业余人士的原理验证机,俺已经很满足了,嘿嘿{:lol:}

wongjan 发表于 2012-6-6 14:13:17

再上一张效果图。

lf751117 发表于 2012-6-6 15:12:35

本帖最后由 lf751117 于 2012-6-6 15:31 编辑

你这只是平面扫描巴?并不是三维扫描。

wongjan 发表于 2012-6-6 16:18:54

lf751117 发表于 2012-6-6 15:12 static/image/common/back.gif
你这只是平面扫描巴?并不是三维扫描。

虽然是简单了些,但确实是三维扫描。
因为只是从一个角度去扫的,点云并不完整。完整的点云数据需要多个角度扫描然后拼接。
继续上图:

lf751117 发表于 2012-6-6 16:40:11

能加你QQ不?我最近也在研究这个激光三维扫描,可是有些问题搞不懂,要请教你!我的QQ:17755037

wongjan 发表于 2012-6-6 16:55:56

邮箱吧 wongjan@139.com
QQ不怎么用的。
在这个帖子大家讨论最好吧。

lf751117 发表于 2012-6-6 17:10:59

我知道和被测对象之间距离可以根据成像后像素X坐标值来辨别,而被测对象的实际高度是怎么测得的?你有没有可以算出XYZ各值可直接套用的公式?

wcm_e 发表于 2012-6-6 17:43:27

mark学习了

wongjan 发表于 2012-6-7 09:19:15

lf751117 发表于 2012-6-6 17:10 static/image/common/back.gif
我知道和被测对象之间距离可以根据成像后像素X坐标值来辨别,而被测对象的实际高度是怎么测得的?你有没有 ...

代码里有这么一段,就是计算xyz三个坐标值的。
                        rx:=1468.9-206.3*ln(ltmp/jishu);
                        ry:=(240-y)/(ltmp/jishu+190)*150;
                        form1.Memo1.Lines.Add (formatfloat('0.0',rx*cos(ra/180*pi))+' '+formatfloat('0.0',ry)+' '+formatfloat('0.0',rx*sin(ra/180*pi))) ;
我开始打印了一张坐标纸,放在激光所形成的平面上,摄像头采集到如下图片:

然后依次采集x坐标的点,摄像头像素点和实际x坐标间归纳出一个公式,即
实际x坐标:=1468.9-206.3*ln(像素点x坐标);   
   实际y坐标:=(240-像素y坐标)/(像素点x坐标+190)*150;

有了这两个坐标值,根据扫描仪目前的角度,就可以用三角函数算出实际xyz坐标。

这种算法比较简单,但精度低,只能算是业余算法。

我做这个能简化的就简化了,以后有时间能优化的就优化。


wongjan 发表于 2012-6-7 09:23:05

本帖最后由 wongjan 于 2012-6-7 09:25 编辑

沿一条水平线采集数据,然后导入excel,生成散布图,再添加一条趋势线,找一个拟合程度最好的,显示出公式,就ok了。
下面是我采集的数据:
X像素值        实际x坐标
474        1
452        2
429        3
406        4
386        5
367        6
349        7
331        8
315        9
300        10
287        11
271        12
259        13
248        14
234        15
225        16
215        17
205        18
195        19
187        20
177        21
169        22
161        23
154        24
150        25
142        26
137        27
129        28
123        29
117        30
111        31
105        32
100        33
95        34
90        35
86        36
82        37
77        38
73        39
68        40

lf751117 发表于 2012-6-7 10:59:07

要去翻数学书拉,都忘光拉。。。。。。。。。。对数,好晕!

lf751117 发表于 2012-6-7 14:17:58

本帖最后由 lf751117 于 2012-6-7 14:40 编辑

http://www.ourdev.cn/forum.php?mod=viewthread&tid=5247845
这篇里都根据焦距来算,我觉得是不是应该用像距?凸透镜到感光芯片的距离应该是像距巴?
而且像距不等于焦距阿。

cheaven 发表于 2012-6-7 16:07:00

赞一个{:smile:}

wongjan 发表于 2012-6-8 08:19:44

楼主头像!哈哈!

wongjan 发表于 2012-6-8 08:23:28

lf751117 发表于 2012-6-7 14:17 static/image/common/back.gif
http://www.ourdev.cn/forum.php?mod=viewthread&tid=5247845
这篇里都根据焦距来算,我觉得是不是应该用像 ...

这篇文章我之前也看过,不过说实话,有很多东西没看懂,里面涉及到的很多数学知识理解不了。以前上学时就挺头疼数学的。
我用了比较简单的方法。
核心是找到摄像头的像素点对应的实际空间的xyz坐标。

doujiang 发表于 2012-6-18 21:48:59

牛X,我两个月前也在开始筹备做这个了,准备在这个暑假做下,希望能得到大哥的帮助

abcfanyuan 发表于 2012-11-6 09:38:40

不知道楼主进展怎么样了。最近学校的事弄完了,想学学看。

abcfanyuan 发表于 2012-11-6 10:03:15

求激光头链接,我看了下,大部分光线分布不均匀。

automaticdai 发表于 2012-11-6 11:06:32

不错,希望早日赶上csk陈老哥。

magicer2010 发表于 2012-11-6 12:29:27

mark一下

玉草夕林 发表于 2012-11-8 09:23:15

wongjan 发表于 2012-2-22 08:29 static/image/common/back.gif
刚取得的一些数据,还没有做标定。

(原文件名:数据.JPG)

LZ大神,你的界面是用什么做的??

wongjan 发表于 2012-11-8 12:48:22

上位机程序用delphi开发的。下位机用的keil。
得到数据存成xyz格式文件,然后用solidworks可以编辑。
开发过程中用excel分析。

abcfanyuan 发表于 2012-11-14 22:47:58

SendMessage(hwndc, WM_CAP_GET_VIDEOFORMAT, Wparam(sizeof(info)), LPARAM(@info));
请问上面的这句是不是声明一个回调函数呀?
还有下面的这个判断语句是什么意思啊?if info.bmiHeader.BiCompression = bi_RGB then
应该是这样吧
nfo.bmiHeader.BiCompression := bi_RGB then;
delphi刚学,请指教谢谢啦

wongjan 发表于 2012-11-15 16:59:54

是声名回调函数,捕获视频后处理。
=用在if语句里是判断两边是不是相等。
:=是用来赋值的。
以上发的代码是直接拷贝的,我编译通过了。应该不会有错误。

abcfanyuan 发表于 2012-11-17 15:08:03

wongjan 发表于 2012-11-15 16:59 static/image/common/back.gif
是声名回调函数,捕获视频后处理。
=用在if语句里是判断两边是不是相等。
:=是用来赋值的。


因为没有整个工程,所以有些控件的是指不知道,无法整个工程编译。
研究了几天楼主的东西,info.bmiHeader.BiCompression = bi_RGB你这句话应该是判断位图是否压缩了的标志。bi_RGB 为非压缩
但是看不太明白,不知道从摄像头获取的图片转换为位图后是否压缩了,没有设置应该不会被压缩吧。但是使用了
if info.bmiHeader.BiCompression = bi_RGB then
begin
//info.bmiHeader.BiCompression := bi_RGB;我加的 如果不要if 判断info.bmiHeader.BiCompression = bi_RGB    直接设置为info.bmiHeader.BiCompression := bi_RGB;
    bit := TBitmap.Create;
    try
      with bit, info.bmiHeader do
      begin
效果如下


如果既不判断也不设置info.bmiHeader.BiCompression = bi_RGB
则是


abcfanyuan 发表于 2012-11-17 15:41:25

wongjan 发表于 2012-11-15 16:59 static/image/common/back.gif
是声名回调函数,捕获视频后处理。
=用在if语句里是判断两边是不是相等。
:=是用来赋值的。


如果方便的话能不能把你的工程发一份我,对这部分图像处理比较感兴趣。
顺便上传一本书,很不凑的,在坛子里面淘的





由于pdf太大无法上传,不好意思,
如果要的话留下邮箱。估计是权限不够

下面是我的工程中间的处理部分是引用的您的,其余的是照网上写的,都差不多,我没有加串口,
感觉图像显示不正确,不知道引用您的是不是对的。



过段时间打算用摄像头试试扫描条形码,不知道您对这有什么看法,初步查了下资料,貌似用
这个做的不多,手机上的蛮多。
我的邮箱 abcfanyuan@vip.qq.com 谢谢。

abcfanyuan 发表于 2012-11-18 00:18:48

本帖最后由 abcfanyuan 于 2012-11-18 02:22 编辑

请问你的摄像头用的是什么型号?我的是usb免驱动的,但是查不到输出图片的格式,貌似是YUY2,你的是什么格式的图片?
我用你的处理函数的时候当用了if info.bmiHeader.BiCompression = bi_RGB then时调试时无法进入到if后面。估计是格式不对
我应该要将YUY2 的转换为REB24吧。
其中biBitCount=16.biCompression=844715353

wongjan 发表于 2012-11-22 12:48:47

abcfanyuan 发表于 2012-11-6 10:03 static/image/common/back.gif
求激光头链接,我看了下,大部分光线分布不均匀。

某宝搜一字线激光器。我这个50元左右,也不是很好,不过焦距可以调,也就是线粗细是可以调整的。
我感觉没必要用太好的,因为摄像头采集的图像也不会有太高的分辨率。
我这个3D扫描仪误差有几个mm,只能当玩具用。

wongjan 发表于 2012-11-22 12:57:30

abcfanyuan 发表于 2012-11-17 15:08 static/image/common/back.gif
因为没有整个工程,所以有些控件的是指不知道,无法整个工程编译。
研究了几天楼主的东西,info.bmiHeade ...

关于图像捕获这部分代码都是从网上copy下来的,vfw捕获。

这里强调一点,我发现用免驱动的摄像头,在windows里可以用,但是在程序里vfw捕获不了,需要装驱动。

我是绕了半天才发现的,最后用驱动精灵装了个驱动。也可以拆开看看里面芯片,然后下载相应的驱动。

实话说,我对此视频捕获的原理研究的也不是很透彻,因为捕获完后面的算法才是关键。
捕获到image控件中之后,用scanline读到一个数组,然后当数组处理就可以了。

至于整个工程,我回头整理一下,最近在搞别的项目玩,呵呵

那本Delphi数字图像处理及高级应用.pdf确实不错,几年前就有了,呵呵。我好多代码就是参照上面来的。

wongjan 发表于 2012-11-24 14:55:19

delphi的源代码。
现在电脑上的delphi程序有些问题,这部分代码不敢保证是有效的。仅供参考吧。

abcfanyuan 发表于 2012-11-24 23:41:34

wongjan 发表于 2012-11-24 14:55 static/image/common/back.gif
delphi的源代码。
现在电脑上的delphi程序有些问题,这部分代码不敢保证是有效的。仅供参考吧。 ...

谢谢啦!看看

LONG.214214 发表于 2013-3-23 21:36:56

大神,你的扫描图能应用到精雕机上吗

kneken 发表于 2013-3-23 23:56:14

mark!!!!!!!!!!

chenxp99 发表于 2013-4-11 15:23:47

好东西,慢慢啃,DELPHI早忘光了,研究看看VBA能否搞定

hncjs 发表于 2013-4-12 09:08:07

不错的的激光3d处理

wongjan 发表于 2013-5-17 11:50:47

LONG.214214 发表于 2013-3-23 21:36 static/image/common/back.gif
大神,你的扫描图能应用到精雕机上吗

恐怕是不行,精度太低了,只能当玩具。

winterw 发表于 2013-5-17 12:38:07

mark一下回去研究研究
感谢楼主共享啊

abcfanyuan 发表于 2013-5-18 23:37:46

好久没有弄这个了,没事做又来捣鼓下。之前做得差不多了,可以得到数据。并且对数据进行二值化。但是我发现你仅仅是对RGB中的红色作为分辨
特征,如果遇到背景是白色或者是亮色时当阀值提高时会使激光也消失。不知道是不是我买的激光不太好

abcfanyuan 发表于 2013-5-18 23:39:46

还有个问题,就是你的那个摄像头和激光的角度是个什么关系呢?我计算了下,应该和他们的角度有关吧
是不是你用那个坐标纸求得一个拟合公式就是为了省去计算角度方面的?

zulu 发表于 2013-5-19 02:03:29

mark!!!!!!

wongjan 发表于 2013-5-21 09:46:57

abcfanyuan 发表于 2013-5-18 23:37 static/image/common/back.gif
好久没有弄这个了,没事做又来捣鼓下。之前做得差不多了,可以得到数据。并且对数据进行二值化。但是我发现 ...

周边不应该有强光源干扰,必要的话可以在摄像头加个滤镜(剪张软盘芯就可以)。

wongjan 发表于 2013-5-21 09:49:40

abcfanyuan 发表于 2013-5-18 23:39 static/image/common/back.gif
还有个问题,就是你的那个摄像头和激光的角度是个什么关系呢?我计算了下,应该和他们的角度有关吧
是不是 ...

是的,因为摄像头的焦距、镜头参数都不知道,所以用了张坐标纸,用excel拟合出了个公式。
页: [1]
查看完整版本: 基于摄像头和一字线激光器的3D扫描仪