Skip to content

霍夫变换演示图形用户界面

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/hough-transform-demo-gui

10726 对此文章评分:

2.3

霍夫变换演示图形用户界面

匿名 2016年7月28日,星期四

以下代码演示了霍夫变换。它提供了一个交互式显示界面用于在图像域中移动点,以及第二个显示界面用于展示生成的霍夫变换结果。

运行此应用程序:

  1. 将后面的代码保存到名为 hough_demo.pro 的文件中
  2. 在 IDL 开发环境 (IDLDE) 中打开并编译该文件
  3. 在 IDL 命令提示符下执行以下命令

hough_demo

可选关键字 NUM_POINTS 可用于设置图像域显示中的点数。该数字必须在范围 [2,100] 内。

图形用户界面的简要描述(使用命令 hough_demo, NUM_POINTS=10 启动)。

图像域(左侧)中的点可以通过点击并拖拽来移动。霍夫域(右侧)中的图像将随着点的移动而更新。当鼠标在霍夫域上移动时,该点所代表的一条线将出现在图像域中。例如,将鼠标悬停在霍夫域中许多波形相交的点上,将会在图像域中绘制一条穿过多个点的线(抱歉,在图像中你看不到光标)

此外,还显示两个直线方程:

  1. 直线方程 – 图像域中从左到右前两个点(红色)之间连线的方程。
  2. 霍夫直线方程 – 由霍夫域中的一个点定义的直线方程。当鼠标光标在霍夫域窗口中移动时,此方程将更新。

源代码:

;------------------------------------------------------------------------------

;+
; Main event handler
;
; :Params:
;   sEvent: in, required, type="structure"
;     An IDL widget event structure
;
;-
pro hough_demo_event, sEvent
  compile_opt idl2, logical_predicate

  widget_control, sEvent.top, GET_UVALUE=oHoughDemo

  oHoughDemo->Event, sEvent

end

;------------------------------------------------------------------------------

;+
; This method is called when the Hough demo GUI has been realized
;
; :Params:
;   tlb: in, required, type="long"
;     The widget ID of the top-level-base
;-
pro hough_demo_realize, tlb
  compile_opt idl2, logical_predicate

  widget_control, tlb, GET_UVALUE=oHoughDemo

  oHoughDemo->NotifyRealize

end

;------------------------------------------------------------------------------

;+
; Lifecycle method called when the object is destroyed vis OBJ_DESTROY.
;
;-
pro hough_demo::Cleanup
  compile_opt idl2, logical_predicate

  self->Destruct

end

;------------------------------------------------------------------------------

;+
; Constructs the GUI for the demo application
;-
pro hough_demo::ConstructGUI
  compile_opt idl2, logical_predicate

  self.tlb = widget_base(EVENT_PRO='hough_demo_event', MAP=0, $
    NOTIFY_REALIZE='hough_demo_realize', /ROW, TITLE='Hough Demo', $
    /TLB_KILL_REQUEST_EVENTS)

  wBase = widget_base(self.tlb, /COLUMN)

  wDraw = widget_draw(wBase, /BUTTON_EVENTS, GRAPHICS_LEVEL=2, /MOTION_EVENTS, $
    UNAME='draw_points', XSIZE=300, YSIZE=300)

  wLabel = widget_label(wBase, VALUE='Line Equation')

  wLabel = widget_label(wBase, /DYNAMIC_RESIZE, UNAME='label_line', VALUE='')

  wLabel = widget_label(wBase, VALUE='Hough Line Equation')

  wLabel = widget_label(wBase, /DYNAMIC_RESIZE, UNAME='label_hough', VALUE='')

  wBase = widget_base(self.tlb, /COLUMN)

  wDraw = widget_draw(wBase, GRAPHICS_LEVEL=2, /MOTION_EVENTS, $
    /TRACKING_EVENTS, UNAME='draw_hough', XSIZE=500, YSIZE=400)

  widget_control, self.tlb, /REALIZE, SET_UVALUE=self

end

;------------------------------------------------------------------------------

;+
; Cleans up any heap member variables
;-
pro hough_demo::Destruct
  compile_opt idl2, logical_predicate

  if widget_info(self.tlb, /VALID_ID) then begin

    void = self->GetObject('model/points', WINDOW=oWindow)
    obj_destroy, oWindow

    void = self->GetObject('model/image', /HOUGH, WINDOW=oWindow)
    obj_destroy, oWindow

    widget_control, self.tlb, /DESTROY

  endif

  ptr_free, [self.pRho, self.pTheta]

end

;------------------------------------------------------------------------------

;+
; Main entry point for widget events
;
; :Params:
;   sEvent: in, required, type="structure"
;     An IDL widget event structure
;-
pro hough_demo::Event, sEvent
  compile_opt idl2, logical_predicate

  case tag_names(sEvent, /STRUCTURE_NAME) of
    'WIDGET_DRAW': self->EventDraw, sEvent
    'WIDGET_KILL_REQUEST': self->Destruct
    'WIDGET_SLIDER': self->EventSlider, sEvent
    'WIDGET_TRACKING': self->EventTracking, sEvent
    else: help, sEvent
  endcase

end

;------------------------------------------------------------------------------

;+
; This method handles events from draw widgets.  It simply passes the event
; to the event handler for the specific draw widget.
;
; :Params:
;   sEvent: in, required, type="structure"
;     An IDL {WIDGET_DRAW} structure
;-
pro hough_demo::EventDraw, sEvent
  compile_opt idl2, logical_predicate

  case widget_info(sEvent.id, /UNAME) of
    'draw_hough': self->EventDrawHough, sEvent
    'draw_points': self->EventDrawPoints, sEvent
    else:
  endcase

end

;------------------------------------------------------------------------------

;+
; This method handles events from the draw widget displaying the Hough domain.
;
; :Params:
;   sEvent: in, required, type="structure"
;     An IDL {WIDGET_DRAW} structure
;-
pro hough_demo::EventDrawHough, sEvent
  compile_opt idl2, logical_predicate

  case sEvent.type of
    2: self->UpdateLinePlot, [sEvent.x,sEvent.y]
    else:
  endcase

end

;------------------------------------------------------------------------------

;+
; This method handles events from the draw widget displaying the image-domain
; points.
;
; :Params:
;   sEvent: in, required, type="structure"
;     An IDL {WIDGET_DRAW} structure
;-
pro hough_demo::EventDrawPoints, sEvent
  compile_opt idl2, logical_predicate

  widget_control, sEvent.id, GET_VALUE=oWindow

  case sEvent.type of
    0: begin
    ; Press event
      oWindow->GetProperty, GRAPHICS_TREE=oView
      oSelect = oWindow->Select(oVIew, [sEvent.x,sEvent.y])
      if obj_valid(oSelect[0]) then begin
        self.oSelect = oSelect[0]
        self.oSelect->GetProperty, DATA=pts
        void = min(abs(sEvent.x-pts[0,*]) + abs(sEvent.y-pts[1,*]), index)
        self.oSelect->SetProperty, UVALUE=index
      endif

    end
    1: begin
    ; Release event
      self.oSelect = obj_new()
      return
    end
    2: begin
    ; Motion event
      if obj_valid(self.oSelect) then begin
        widget_control, sEvent.id, GET_VALUE=oWindow
        oWindow->GetProperty, DIMENSIONS=dims

        if (sEvent.x LT 0) || (sEvent.y LT 0) $
        || (sEvent.x GT dims[0]) || (sEvent.y GT dims[1]) then begin
          return
        endif

        self.oSelect->GetProperty, DATA=pts, UVALUE=index
        pts[*,index] = [sEvent.x,sEvent.y]
        self.oSelect->SetProperty, DATA=pts

        self->UpdateHough

      endif

    end
    else: return
  endcase

  oWindow->Draw

end

;------------------------------------------------------------------------------

;+
; This method handles traking events
;
; :Params:
;   sEvent: in, required, type="structure"
;     An IDL {WIDGET_TRACKING} structure
;-
pro hough_demo::EventTracking, sEvent
  compile_opt idl2, logical_predicate

  case widget_info(sEvent.id, /UNAME) of
    'draw_hough': begin
      if ~sEvent.enter then begin
        oPlot = self->GetObject('model/plot', WINDOW=oWindow)
        oPlot->SetProperty, HIDE=1
        oWindow->Draw

        widget_control, self->GetWID('label_hough'), SET_VALUE=''
      endif

    end
    else:
  endcase

end

;------------------------------------------------------------------------------

;+
; Calculates the default MINX and MINY values used by the HOUGH algorithm
;
; :Keywords:
;   X: out, optional, type="float"
;     Set this keyword to a named variable to retrieve the default MINX value
;     used by HOUGH
;   Y: out, optional, type="float"
;     Set this keyword to a named variable to retrieve the default MINY value
;     used by HOUGH
;-
pro hough_demo::GetMinXY, $
  X=minX, Y=minY
  compile_opt idl2, logical_predicate

  void = self->GetObject('model/points', WINDOW=oWindow)
  oWindow->GetProperty, DIMENSIONS=dims

  minX = -(dims[0]-1)/2
  minY = -(dims[1]-1)/2

end

;------------------------------------------------------------------------------

;+
; This method is for accessing objects in the object-graphics tree
;
; :Returns:
;   A reference to the object with the spacified name.  If no object contains
;   a match a null object will be returned.
;
; :Params:
;   name: in, required, type="string"
;     The name of the object to be retrieved.
;    
; :Keywords:
;   HOUGH: in, optional, type="boolean"
;     Set this keyword to have the object retrieved from the Hough-domain
;     graphics window.  By default, the object will be retrieved from the
;     image-domian graphics window.
;   VIEW: out, optional, type="objref"
;     Set this keyword to a named variable to retrieve a reference to the
;     IDLgrVIEW object from the graphics window.
;   WINDOW: out, optional, type="objref"
;     Set this keyword to a named variable to retrieve a reference to the
;     IDLgrWINDOW object
;-
function hough_demo::GetObject, name, $
  HOUGH=hough, $
  VIEW=oView, $
  WINDOW=oWindow

  uName = keyword_set(hough) ? 'draw_hough' : 'draw_points'

  widget_control, self->GetWID(uName), GET_VALUE=oWindow

  oWindow->GetProperty, GRAPHICS_TREE=oView

  return, oView->GetByName(name)

end

;------------------------------------------------------------------------------

;+
; Calculates the slope and y-intercept of a line in either of the graphics
; windows
;
; :Returns:
;   A tow element, floating-point vector containing, in order, the slope and
;   y-intercept of the line
;  
; :Params:
;   xy: in, optional, type="float"
;     Set this to the (x,y)-postition of the cursor in the HOUGH window.  This
;     information is only used when the HOUGH keyword is set.
;    
; :Keywords:
;   HOUGH: in, optional, type="boolean"
;     Set this keyword to have the slope and intercept calculated from a point
;     in the Hough domain.  By default, the first two points in the image-
;     domain are used.
;-
function hough_demo::GetSlopeIntercept, xy, $
  HOUGH=hough

  mb = fltarr(2)

  if keyword_set(hough) then begin
    rho = (*self.pRho)[xy[1]]
    theta = (*self.pTheta)[xy[0]]

    self->GetMinXY, X=xMin, Y=yMin

    mb[0] = -1.0/tan(theta)
    mb[1] = (rho - xMin*cos(theta) - yMin*sin(theta)) / sin(theta)

  endif else begin
    oPoints = self->GetObject('model/points')
    oPoints->GetProperty, DATA=pts

    mb[0] = (pts[1,1]-pts[1,0])/(pts[0,1]-pts[0,0])
    mb[1] = pts[1,0] - mb[0]*pts[0,0]
  endelse

  return, mb

end

;------------------------------------------------------------------------------

;+
; This method the ID of the widget that uses the specified name as its UNAME.
;
; :Returns:
;   The ID of the widget using the specified UNAME.  If no widget is using the
;   UNAME then 0 is returned
;
; :Params:
;   name: in, required, type="string"
;       The UNAME of the widget whose ID is to be returned
;
; :Keywords:
;   PARENT: in, optional, type="integer"
;       The widget ID of the parent widget.  If not set, self.tlb will be used.
;-
function hough_demo::GetWID, name, $
  PARENT=wParent
  compile_opt idl2, logical_predicate

  if ~n_elements(wParent) then wParent = self.tlb

  if ~widget_info(wParent, /VALID_ID) then return, 0

  return, widget_info(wParent, FIND_BY_UNAME=name)

end

;------------------------------------------------------------------------------

;+
; Lifecycle method for initializing an instance of the object via OBJ_NEW()
;
; :Returns:
;   1 if the object is successfully initialized
;  
; :Params:
;   NUM_POINTS: in, optional, type="integer"
;     Sets the numner of points to be displayed in the image-domain display.
;     The default is 2.  If more than 2 points are displayed the first two will
;     be red and the rest will be blue.  This is because the slope/intercept
;     calculation for this window is done using the first two points.
;-
function hough_demo::Init, $
  NUM_POINTS=nPoints
  compile_opt idl2, logical_predicate

  self.pRho = ptr_new(/ALLOCATE_HEAP)
  self.pTheta = ptr_new(/ALLOCATE_HEAP)

  self.nPoints = n_elements(nPoints) ? nPoints : 2

  return, 1

end

;------------------------------------------------------------------------------

;+
; This method initializes the object graphics used by the Hough demo
;-
pro hough_demo::InitializeGraphics
  compile_opt idl2, logical_predicate

; Image domain (Points)
  widget_control, self->GetWID('draw_points'), GET_VALUE=oWindowPoint
  oWindowPoint->GetProperty, DIMENSIONS=dims

  oSymbol = obj_new('IDLgrSymbol', 1, THICK=2, SIZE=5)

  x = fix(randomu(s,self.nPoints)*dims[0])
  y = fix(randomu(s,self.nPoints)*dims[1])

  colors = bytarr(3,self.nPoints)
  colors[0,0:1] = 255
  if (self.nPoints GT 2) then colors[2,2:self.nPoints-1] = 255

  oPoints = obj_new('IDLgrPolyline', x, y, LINESTYLE=6, NAME='points', $
    SYMBOL=oSymbol, VERT_COLORS=colors)

  oPlot = obj_new('IDLgrPlot', HIDE=1, NAME='plot')

  oModelPoints = obj_new('IDLgrModel', NAME='model')
  oModelPoints->Add, [oPoints, oPlot]

  oViewPoints = obj_new('IDLgrView', COLOR=[255,255,255], VIEWPLANE_RECT=[0,0,dims])
  oViewPoints->Add, oModelPoints

  oWindowPoint->Setproperty, GRAPHICS_TREE=oViewPoints
  oWindowPoint->Draw

; Hough domain
  widget_control, self->GetWID('draw_hough'), GET_VALUE=oWindowHough

  oImage = obj_new('IDLgrImage', NAME='image')

  oModelHough = obj_new('IDLgrModel', NAME='model')
  oModelHough->Add, oImage

  oViewHough = obj_new('IDLgrView', COLOR=[255,255,255])
  oViewHough->Add, oModelHough

  oWindowHough->SetProperty, GRAPHICS_TREE=oViewHough
  oWindowHough->Draw

end

;------------------------------------------------------------------------------

;+
; This method is called when the GUI is realized.  It finishes up some
; initialization and starts XMANAGER.
;-
pro hough_demo::NotifyRealize
  compile_opt idl2, logical_predicate

  ss = get_screen_size()

  self->InitializeGraphics
  self->UpdateHough, /RESIZE_WINDOW

  wGeom = widget_info(self.tlb, /GEOMETRY)
  widget_control, self.tlb, XOFFSET=(ss[0]-wGeom.scr_xSize)/2, YOFFSET=(ss[1]-wGeom.scr_ySize)/2

  widget_control, self.tlb, MAP=1

  xmanager, 'hough_demo', self.tlb, /NO_BLOCK

end

;------------------------------------------------------------------------------

;+
; This method updates the image in the hough domain according to the points
; in the image doimain.
;
; :Keywords:
;   RESIZE_WINDOW: in, optional, type="boolean"
;     Set this keyword to have the Hough-domain display resized to the size
;     of the output from HOUGH.  This is only called the first time a Hough
;     image is calculated.
;-
pro hough_demo::UpdateHough, $
  RESIZE_WINDOW=resize
  compile_opt idl2, logical_predicate

  oPoints = self->GetObject('model/points', WINDOW=oWindowPoints)
  oWindowPoints->GetProperty, DIMENSIONS=dims

  oPoints->GetProperty, DATA=pts

  temp = make_array(dims, /FLOAT, VALUE=0.0)

  pts = transpose(pts)
  temp[pts[*,0],pts[*,1]] = 1.0

  self->GetMinXY, X=xMin, Y=yMin

  imgHough = hough(temporary(temp), RHO=rho, THETA=theta)

  *self.pRho = rho
  *self.pTheta = theta

  dimHough = size(imgHough, /DIMENSIONS)

  oImage = self->GetObject('model/image', /HOUGH, VIEW=oViewHough, WINDOW=oWindowHough)

  if keyword_set(resize) then begin
    widget_control, self->GetWID('draw_hough'), XSIZE=dimHough[0], YSIZE=dimHough[1]

    oViewHough->SetProperty, VIEWPLANE_RECT=[0,0,dimHough]
  endif

  oImage->SetProperty, DATA=255-bytscl(imgHough)

  oWindowHough->Draw

  self->UpdateLineEquation

end

;------------------------------------------------------------------------------

;+
; This method updates the Hough line-equation on the GUI.
;
; :Params:
;   xy: in, required, type="float"
;     A 2-element vector containing the xy-location in the Hough domain for
;     which the line will be calculated.
;-
pro hough_demo::UpdateHoughEquation, xy
  compile_opt idl2, logical_predicate

  mb = self->GetSlopeIntercept(xy, /HOUGH)

  str = 'y = '+string(mb[0], FORMAT='(f0.2)')+' * x + '+string(mb[1], FORMAT='(f0.2)')

  widget_control, self->GetWID('label_hough'), SET_VALUE=str

end

;------------------------------------------------------------------------------

;+
; This method updates the image-domain line equation on the GUI according to
; the first two points.
;-
pro hough_demo::UpdateLineEquation
  compile_opt idl2, logical_predicate

  mb = self->GetSlopeIntercept()

  str = 'y = '+string(mb[0], FORMAT='(f0.2)')+' * x + '+string(mb[1], FORMAT='(f0.2)')

  widget_control, self->GetWID('label_line'), SET_VALUE=str

end

;------------------------------------------------------------------------------

;+
; This method updates the line plot in the image-domain display using a point
; in the Hough domain.
;
; :Params:
;   xy: in, required, type="long"
;     A two-element vector containing the xy-location in the Hough domain for
;     which the line will be drawn in the image-domain display
;-
pro hough_demo::UpdateLinePlot, xy
  compile_opt idl2, logical_predicate

  oPlot = self->GetObject('model/plot', WINDOW=oWindow)

  oWindow->GetProperty, DIMENSIONS=dims

  rho = (*self.pRho)[xy[1]]
  theta = (*self.pTheta)[xy[0]]

  self->GetMinXY, X=xMin, Y=yMin

  m = -1.0/tan(theta)
  b = (rho - xMin*cos(theta) - yMin*sin(theta)) / sin(theta)

  x = findgen(dims[0])
  y = m*x+b

  oPlot->SetProperty, DATAX=x, DATAY=y, HIDE=0
  oWindow->Draw

  self->UpdateHoughEquation, xy

end
;------------------------------------------------------------------------------

;+
; Class structure definition
;
; :Fields:
;   nPoints: The number of points in the image-domain display
;   oSelect: A reference to the selected graphics object.  This is used for
;     moving points in the image-domain display
;   pRho: Holds the rho values for the hough transform
;   pTheta: Holds the theta values for the hough transform
;   tlb: The widget ID of the top-level-base of the GUI
;-
pro hough_demo__define
  compile_opt idl2, logical_predicate

  void = {hough_demo     $

    ,nPoints : 0         $
    ,oSelect : obj_new() $
    ,pRho    : ptr_new() $
    ,pTheta  : ptr_new() $   
    ,tlb     : 0L        $

    }

end

;------------------------------------------------------------------------------

;+
; This routine starts the Hough demo
;
; :Keywords:
;   NUM_POINTS: in, optional, type="integer"
;     Sets the numner of points to be displayed in the image-domain display.
;     The allowed range is [2,100].  The default is 2.  If more than 2 points
;     are displayed the first two will be red and the rest will be blue.  This
;     is because the slope/intercept calculation for this window is done using
;     the first two points.
;-
pro hough_demo, NUM_POINTS=nPoints
  compile_opt idl2, logical_predicate

  if n_elements(nPoints) && ((nPoints LT 2) || (nPoints GT 100)) then begin
    void = dialog_message('The number ofpoints must be in [2,100]', /ERROR)
    return
  endif

  oHoughDemo = obj_new('hough_demo', NUM_POINTS=nPoints)

  oHoughDemo->ConstructGUI

end

在 ENVI 中创建图像瓦片 将分析带到数据所在之处