diff --git a/.gitignore b/.gitignore index 47000fd..46cb50d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ ext/opencv/test.txt pkg/ log.txt *.avi -examples/rotated-boxes-with-detected-bounding-rectangles.jpg +examples/contours/rotated-boxes-with-detected-bounding-rectangles.jpg diff --git a/examples/contours/bitmap-contours-with-labels.png b/examples/contours/bitmap-contours-with-labels.png new file mode 100644 index 0000000..674050a Binary files /dev/null and b/examples/contours/bitmap-contours-with-labels.png differ diff --git a/examples/contours/bitmap-contours.png b/examples/contours/bitmap-contours.png new file mode 100644 index 0000000..4890a4e Binary files /dev/null and b/examples/contours/bitmap-contours.png differ diff --git a/examples/bounding-box-detect-canny-no-gui.rb b/examples/contours/bounding-box-detect-canny.rb similarity index 56% rename from examples/bounding-box-detect-canny-no-gui.rb rename to examples/contours/bounding-box-detect-canny.rb index b61158b..a2a6c33 100755 --- a/examples/bounding-box-detect-canny-no-gui.rb +++ b/examples/contours/bounding-box-detect-canny.rb @@ -30,15 +30,35 @@ contour = canny.find_contours(:mode => OpenCV::CV_RETR_LIST, :method => OpenCV:: while contour # No "holes" please (aka. internal contours) unless contour.hole? + + puts '-' * 80 + puts "BOUNDING RECT FOUND" + puts '-' * 80 + + # You can detect the "bounding rectangle" which is always oriented horizontally and vertically box = contour.bounding_rect - puts "found external contour from #{box.top_left.x},#{box.top_left.y} to #{box.bottom_right.x},#{box.bottom_right.y}" + puts "found external contour with bounding rectangle from #{box.top_left.x},#{box.top_left.y} to #{box.bottom_right.x},#{box.bottom_right.y}" + + # The contour area can be computed: + puts "that contour encloses an area of #{contour.contour_area} square pixels" + + # .. as can be the length of the contour + puts "that contour is #{contour.arc_length} pixels long " # Draw that bounding rectangle cvmat.rectangle! box.top_left, box.bottom_right, :color => OpenCV::CvColor::Black + + # You can also detect the "minimal rectangle" which has an angle, width, height and center coordinates + # and is not neccessarily horizonally or vertically aligned. + # The corner of the rectangle with the lowest y and x position is the anchor (see image here: http://bit.ly/lT1XvB) + # The zero angle position is always straight up. + # Positive angle values are clockwise and negative values counter clockwise (so -60 means 60 degree counter clockwise) + box = contour.min_area_rect + puts "found minimal rectangle with its center at (#{box.center.x.round},#{box.center.y.round}), width of #{box.size.width.round}px, height of #{box.size.height.round} and an angle of #{box.angle.round} degree" end contour = contour.h_next end # And save the image -puts "Saving image with bounding rectangles" +puts "\nSaving image with bounding rectangles" cvmat.save_image("rotated-boxes-with-detected-bounding-rectangles.jpg") diff --git a/examples/contours/contour_retrieval_modes.rb b/examples/contours/contour_retrieval_modes.rb new file mode 100755 index 0000000..929b42c --- /dev/null +++ b/examples/contours/contour_retrieval_modes.rb @@ -0,0 +1,139 @@ +#!/usr/bin/env ruby +# +# This file shows the different retrieval modes for contour detection +# +require "opencv" + +# Load image +# The structure of the image is "explained" in bitmap-contours-with-labels.png +cvmat = OpenCV::CvMat.load("bitmap-contours.png") + +# "find_contours" does only operate on bitmap images (black/white) +mat = OpenCV::CvMat.new(cvmat.rows, cvmat.cols, :cv8u, 1) +(cvmat.rows * cvmat.cols).times do |i| + mat[i] = (cvmat[i][0] <= 128) ? OpenCV::CvScalar.new(0) : OpenCV::CvScalar.new(255) +end + +# find_contours takes two parameters: +# 1. Retrieval mode (:mode, defines the structure of the contour sequence returned) +# - CV_RETR_LIST (default) +# - CV_RETR_EXTERNAL +# - CV_RETR_CCOMP +# - CV_RETR_TREE +# 2. Retrieval Method (:method, how the contours are approximated) +# - CV_CHAIN_CODE +# - CV_CHAIN_APPROX_NONE +# - CV_CHAIN_APPROX_SIMPLE (default) +# - CV_CHAIN_APPROX_TC89_L1 +# - CV_CHAIN_APPROX_T89_KCOS +# - CV_LINK_RUNS + +# +# The default: CV_RETR_LIST and CV_CHAIN_APPROX_SIMPLE +# This produces a flat list of contours that can be traversed with .h_next and .h_prev +# +puts "Detecting using CV_RETR_LIST and CV_CHAIN_APPROX_SIMPLE" +contour = mat.find_contours(:mode => OpenCV::CV_RETR_LIST, :method => OpenCV::CV_CHAIN_APPROX_SIMPLE) +cindex=1 + +while contour + puts "Contour ##{cindex} is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" + contour = contour.h_next + cindex+=1 +end + +# +# CV_RETR_EXTERNAL retrieves only the outer most non "hole" contour +# +puts '-'*80 +puts "Detecting using CV_RETR_EXTERNAL and CV_CHAIN_APPROX_SIMPLE" +contour = mat.find_contours(:mode => OpenCV::CV_RETR_EXTERNAL, :method => OpenCV::CV_CHAIN_APPROX_SIMPLE) +cindex=1 + +while contour + puts "Contour ##{cindex} is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" + contour = contour.h_next + cindex+=1 +end + +# +# CV_RETR_CCOMP organizes the contours in a two level deep stack +# The first level holds the contours +# The second level contains the holes of the contours in level 1 +# +# C00001 <-> C00000 <-> C000 <-> C0 +# | | +# V V +# H0000 H00 +# +puts '-'*80 +puts "Detecting using CV_RETR_CCOMP and CV_CHAIN_APPROX_SIMPLE" +contour = mat.find_contours(:mode => OpenCV::CV_RETR_CCOMP, :method => OpenCV::CV_CHAIN_APPROX_SIMPLE) + +# C00001 +puts "Contour #1 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour = contour.h_next + +# C00000 +puts "Contour #2 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour = contour.h_next + +# C000 +puts "Contour #3 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour_down = contour.v_next + +# H0000 +puts "Contour #4 is #{contour_down.contour_area} px^2 (width: #{contour_down.bounding_rect.width}, height: #{contour_down.bounding_rect.height}, type: #{(contour_down.hole?)?"hole":"contour"})" +contour = contour.h_next + +# C0 +puts "Contour #5 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour_down = contour.v_next + +# H00 +puts "Contour #6 is #{contour_down.contour_area} px^2 (width: #{contour_down.bounding_rect.width}, height: #{contour_down.bounding_rect.height}, type: #{(contour_down.hole?)?"hole":"contour"})" + +# +# CV_RETR_TREE manages the contours in a tree structure +# This reconstructs the complete hierarchy of contours and holes that the image displayed +# +# C0 +# | +# V +# H00 +# | +# V +# C000 +# | +# V +# H0000-------+ +# | | +# V V +# C00000 C00001 +# +puts '-'*80 +puts "Detecting using CV_RETR_TREE and CV_CHAIN_APPROX_SIMPLE" +contour = mat.find_contours(:mode => OpenCV::CV_RETR_TREE, :method => OpenCV::CV_CHAIN_APPROX_SIMPLE) + +# C0 +puts "Contour #1 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour = contour.v_next + +# H00 +puts "Contour #2 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour = contour.v_next + +# C000 +puts "Contour #3 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour = contour.v_next + +# H0000 +puts "Contour #4 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour = contour.v_next + +# C00000 +puts "Contour #5 is #{contour.contour_area} px^2 (width: #{contour.bounding_rect.width}, height: #{contour.bounding_rect.height}, type: #{(contour.hole?)?"hole":"contour"})" +contour_right = contour.h_next + +# C00001 +puts "Contour #6 is #{contour_right.contour_area} px^2 (width: #{contour_right.bounding_rect.width}, height: #{contour_right.bounding_rect.height}, type: #{(contour_right.hole?)?"hole":"contour"})" diff --git a/examples/contours/rotated-boxes.jpg b/examples/contours/rotated-boxes.jpg new file mode 100644 index 0000000..cf3a2e6 Binary files /dev/null and b/examples/contours/rotated-boxes.jpg differ diff --git a/examples/rotated-boxes.jpg b/examples/rotated-boxes.jpg deleted file mode 100644 index 344210b..0000000 Binary files a/examples/rotated-boxes.jpg and /dev/null differ