From 230fa6da47d2d86159deacecdb8b77cb0b33626f Mon Sep 17 00:00:00 2001 From: ser1zw Date: Sun, 16 Jan 2011 04:29:15 +0900 Subject: [PATCH] modified some image processing functions of CvMat, and added some tests --- ext/cvmat.cpp | 38 +++--- test/helper.rb | 26 +++- test/samples/lena-256x256.jpg | Bin 0 -> 12369 bytes test/samples/lena-32x32.jpg | Bin 0 -> 983 bytes test/test_cvmat_drawing.rb | 52 +++----- test/test_cvmat_imageprocessing.rb | 198 +++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 55 deletions(-) create mode 100644 test/samples/lena-256x256.jpg create mode 100644 test/samples/lena-32x32.jpg create mode 100755 test/test_cvmat_imageprocessing.rb diff --git a/ext/cvmat.cpp b/ext/cvmat.cpp index 8e605f2..2fab6bc 100644 --- a/ext/cvmat.cpp +++ b/ext/cvmat.cpp @@ -1662,7 +1662,7 @@ rb_convert_scale_abs(VALUE self, VALUE hash) scale = rb_hash_aref(hash, ID2SYM(rb_intern("scale"))), shift = rb_hash_aref(hash, ID2SYM(rb_intern("shift"))), dest = new_object(cvGetSize(CVARR(self)), CV_MAKETYPE(CV_8U, CV_MAT_CN(CVMAT(self)->type))); - cvConvertScale(CVARR(self), CVARR(dest), IF_DBL(scale, 1.0), IF_DBL(shift, 0.0)); + cvConvertScaleAbs(CVARR(self), CVARR(dest), IF_DBL(scale, 1.0), IF_DBL(shift, 0.0)); return dest; } @@ -3007,7 +3007,7 @@ rb_put_text_bang(int argc, VALUE *argv, VALUE self) * sobel(xorder,yorder[,aperture_size=3]) -> cvmat * * Calculates first, second, third or mixed image derivatives using extended Sobel operator. - * self should be single-channel 8bit signed/unsigned or 32bit floating-point. + * self should be single-channel 8bit unsigned or 32bit floating-point. * * link:../images/CvMat_sobel.png */ @@ -3019,14 +3019,13 @@ rb_sobel(int argc, VALUE *argv, VALUE self) aperture_size = INT2FIX(3); switch(CV_MAT_DEPTH(CVMAT(self)->type)) { case CV_8U: - case CV_8S: dest = new_object(cvGetSize(CVARR(self)), CV_MAKETYPE(CV_16S, 1)); break; case CV_32F: dest = new_object(cvGetSize(CVARR(self)), CV_MAKETYPE(CV_32F, 1)); break; default: - rb_raise(rb_eNotImpError, "source depth should be CV_8U or CV_8S or CV_32F."); + rb_raise(rb_eRuntimeError, "source depth should be CV_8U or CV_32F."); } cvSobel(CVARR(self), CVARR(dest), NUM2INT(xorder), NUM2INT(yorder), NUM2INT(aperture_size)); return dest; @@ -3037,7 +3036,7 @@ rb_sobel(int argc, VALUE *argv, VALUE self) * laplace([aperture_size = 3]) -> cvmat * * Calculates Laplacian of the image. - * self should be single-channel 8bit signed/unsigned or 32bit floating-point. + * self should be single-channel 8bit unsigned or 32bit floating-point. */ VALUE rb_laplace(int argc, VALUE *argv, VALUE self) @@ -3047,14 +3046,13 @@ rb_laplace(int argc, VALUE *argv, VALUE self) aperture_size = INT2FIX(3); switch(CV_MAT_DEPTH(CVMAT(self)->type)) { case CV_8U: - case CV_8S: dest = new_object(cvGetSize(CVARR(self)), CV_MAKETYPE(CV_16S, 1)); break; case CV_32F: dest = new_object(cvGetSize(CVARR(self)), CV_MAKETYPE(CV_32F, 1)); break; default: - rb_raise(rb_eNotImpError, "source depth should be CV_8U or CV_8S or CV_32F."); + rb_raise(rb_eRuntimeError, "source depth should be CV_8U or CV_32F."); } cvLaplace(CVARR(self), CVARR(dest), NUM2INT(aperture_size)); return dest; @@ -3108,7 +3106,7 @@ VALUE rb_corner_eigenvv(int argc, VALUE *argv, VALUE self) { VALUE block_size, aperture_size, dest; - if (rb_scan_args(argc, argv, "11", &block_size, &aperture_size) < 1) + if (rb_scan_args(argc, argv, "11", &block_size, &aperture_size) < 2) aperture_size = INT2FIX(3); Check_Type(block_size, T_FIXNUM); CvSize size = cvGetSize(CVARR(self)); @@ -3127,7 +3125,8 @@ VALUE rb_corner_min_eigen_val(int argc, VALUE *argv, VALUE self) { VALUE block_size, aperture_size, dest; - rb_scan_args(argc, argv, "11", &block_size, &aperture_size); + if (rb_scan_args(argc, argv, "11", &block_size, &aperture_size) < 2) + aperture_size = INT2FIX(3); dest = new_object(cvGetSize(CVARR(self)), CV_MAKETYPE(CV_32F, 1)); cvCornerMinEigenVal(CVARR(self), CVARR(dest), FIX2INT(block_size), FIX2INT(aperture_size)); return dest; @@ -3178,28 +3177,33 @@ rbi_find_corner_sub_pix(int argc, VALUE *argv, VALUE self) VALUE rb_good_features_to_track(int argc, VALUE *argv, VALUE self) { - VALUE quality_level, min_distance, good_features_to_track_option, eigen, tmp, storage; + VALUE quality_level, min_distance, good_features_to_track_option; + CvMat *eigen, *tmp; + int i; rb_scan_args(argc, argv, "21", &quality_level, &min_distance, &good_features_to_track_option); good_features_to_track_option = GOOD_FEATURES_TO_TRACK_OPTION(good_features_to_track_option); CvMat *src = CVMAT(self); - eigen = cCvMat::new_object(cvGetSize(src), CV_MAKETYPE(CV_32F, 1)); - tmp = cCvMat::new_object(cvGetSize(src), CV_MAKETYPE(CV_32F, 1)); + CvSize size = cvGetSize(src); + eigen = cvCreateMat(size.height, size.width, CV_MAKETYPE(CV_32F, 1)); + tmp = cvCreateMat(size.height, size.width, CV_MAKETYPE(CV_32F, 1)); int np = GF_MAX(good_features_to_track_option); if(!(np > 0)) rb_raise(rb_eArgError, "option :max should be positive value."); CvPoint2D32f *p32 = (CvPoint2D32f*)cvAlloc(sizeof(CvPoint2D32f) * np); if(!p32) rb_raise(rb_eNoMemError, "failed allocate memory."); - cvGoodFeaturesToTrack(src, CVARR(eigen), CVARR(tmp), p32, &np, NUM2DBL(quality_level), NUM2DBL(min_distance), + cvGoodFeaturesToTrack(src, &eigen, &tmp, p32, &np, NUM2DBL(quality_level), NUM2DBL(min_distance), GF_MASK(good_features_to_track_option), GF_BLOCK_SIZE(good_features_to_track_option), GF_USE_HARRIS(good_features_to_track_option), GF_K(good_features_to_track_option)); - storage = cCvMemStorage::new_object(); - CvSeq *pseq = cvCreateSeq(CV_SEQ_POINT_SET, sizeof(CvSeq), sizeof(CvPoint2D32f), CVMEMSTORAGE(storage)); - cvSeqPushMulti(pseq, p32, np); + VALUE corners = rb_ary_new2(np); + for (i = 0; i < np; i++) + rb_ary_store(corners, i, cCvPoint2D32f::new_object(p32[i])); cvFree(&p32); - return cCvSeq::new_sequence(cCvSeq::rb_class(), pseq, cCvPoint2D32f::rb_class(), storage); + cvReleaseMat(&eigen); + cvReleaseMat(&tmp); + return corners; } /* diff --git a/test/helper.rb b/test/helper.rb index 2642e87..c72b631 100755 --- a/test/helper.rb +++ b/test/helper.rb @@ -39,9 +39,29 @@ class OpenCVTestCase < Test::Unit::TestCase end def snap(*images) - win = [] - images.size.times { |i| win << GUI::Window.new("snap-#{i}") } - win.each_with_index { |w, i| w.show images[i] } + n = -1 + images.map! { |val| + n += 1 + if val.is_a? Hash + val + elsif val.is_a? Array + {:title => val[0], :image => val[1] } + else + {:title => "snap-#{n}", :image => val } + end + } + + pos = CvPoint.new(0, 0) + images.each { |img| + w = GUI::Window.new(img[:title]) + w.show(img[:image]) + w.move(pos) + pos.x += img[:image].width + if pos.x > 800 + pos.y += img[:image].height + pos.x = 0 + end + } GUI::wait_key GUI::Window::destroy_all diff --git a/test/samples/lena-256x256.jpg b/test/samples/lena-256x256.jpg new file mode 100644 index 0000000000000000000000000000000000000000..349f8e9d03db4a0e4e57ef4d74b8222796d119e2 GIT binary patch literal 12369 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 OQLAkZtP6Pg``-Y^WAjx2 literal 0 HcmV?d00001 diff --git a/test/samples/lena-32x32.jpg b/test/samples/lena-32x32.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3dffa0c8f555649d5e94f58f6e48f283478358c8 GIT binary patch literal 983 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$#LwE3QasOl^9_@C8YBr|znG@E2J<$2R_2pF4 znT9vnCOpVHP-k24XyVsxo389Ty!80<^U8k~Z!`WUKeaHpd0NM>Rg<1}Z`@v=uGd=h z`Q-1@`Kog~7O&$tp?qyajc0XnWo`cAo5xO9g$tc$RRa{PsJ)Yi(17Ue9x@%=fsP?z(;BmOIyC z(_>!(47UQa9D~-YKi11!oT4q*q%)=n-k3M%-&U3|v0L+Io9@}La+d06g#x<-cMJCW t66@;zGlVsom^Z0TlzVyl%+BNQt}poVWKzA>)7*1f8l}NSg*X1+1OV72fztp0 literal 0 HcmV?d00001 diff --git a/test/test_cvmat_drawing.rb b/test/test_cvmat_drawing.rb index afefd88..3083dd3 100755 --- a/test/test_cvmat_drawing.rb +++ b/test/test_cvmat_drawing.rb @@ -31,10 +31,8 @@ class TestCvMat_drawing < OpenCVTestCase m1.line!(CvPoint.new(1, 0), CvPoint.new(m0.width - 1, m0.height - 1), :color => CvColor::Blue, :thickness => 1, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Line: Blue, thickness = 1').show(m1) - # GUI::Window.new('Line: Red, thickness = 3').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Line: Blue, thickness = 1', m1], ['Line: Red, thickness = 3', m2]) end def test_rectangle @@ -45,10 +43,8 @@ class TestCvMat_drawing < OpenCVTestCase m1.rectangle!(CvPoint.new(20, 20), CvPoint.new(m0.width - 20, m0.height - 20), :color => CvColor::Blue, :thickness => 1, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Rectangle: Blue, thickness = 1').show(m1) - # GUI::Window.new('Rectangle: Red, thickness = 3').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Rectangle: Blue, thickness = 1', m1], ['Rectangle: Red, thickness = 3', m2]) end def test_circle @@ -58,10 +54,8 @@ class TestCvMat_drawing < OpenCVTestCase :color => CvColor::Red, :thickness => 3, :line_type => :aa) m1.circle!(CvPoint.new(m0.width / 2, m0.height / 2), 80, :color => CvColor::Blue, :thickness => 1, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Circle: Blue, thickness = 1').show(m1) - # GUI::Window.new('Circle: Red, thickness = 3').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Circle: Blue, thickness = 1', m1], ['Circle: Red, thickness = 3', m2]) end def test_ellipse @@ -72,10 +66,8 @@ class TestCvMat_drawing < OpenCVTestCase m1.ellipse!(CvPoint.new(m0.width / 2, m0.height / 2), CvSize.new(100, 60), 30, 0, 360, :color => CvColor::Blue, :thickness => 1, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Ellipse: Blue, thickness = 1').show(m1) - # GUI::Window.new('Ellipse: Red, thickness = 3').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Ellipse: Blue, thickness = 1', m1], ['Ellipse: Red, thickness = 3', m2]) end def test_ellipse_box @@ -85,10 +77,8 @@ class TestCvMat_drawing < OpenCVTestCase m2 = m0.ellipse_box(box, :color => CvColor::Red, :thickness => 3, :line_type => :aa) m1.ellipse_box!(box, :color => CvColor::Blue, :thickness => 1, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Ellipse box: Blue, thickness = 1').show(m1) - # GUI::Window.new('Ellipse box: Red, thickness = 3').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Ellipse box: Blue, thickness = 1', m1], ['Ellipse box: Red, thickness = 3', m2]) end def test_fill_poly @@ -101,10 +91,8 @@ class TestCvMat_drawing < OpenCVTestCase m2 = m0.fill_poly(pt, :color => CvColor::Red, :line_type => :aa) m1.fill_poly!(pt, :color => CvColor::Blue, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Fill poly: Blue').show(m1) - # GUI::Window.new('Fill poly: Red').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Fill poly: Blue', m1], ['Fill poly: Red', m2]) end def test_fill_convex_poly @@ -115,10 +103,8 @@ class TestCvMat_drawing < OpenCVTestCase m2 = m0.fill_convex_poly(pt, :color => CvColor::Red, :line_type => :aa) m1.fill_convex_poly!(pt, :color => CvColor::Blue, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Fill convex poly: Blue').show(m1) - # GUI::Window.new('Fill convex poly: Red').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Fill convex poly: Blue', m1], ['Fill convex poly: Red', m2]) end def test_poly_line @@ -130,10 +116,8 @@ class TestCvMat_drawing < OpenCVTestCase m2 = m0.poly_line(pt, :color => CvColor::Red, :thickness => 3, :line_type => :aa) m1.poly_line!(pt, :color => CvColor::Blue, :thickness => 1, :line_type => :aa) - # Uncomment the following lines to view the image - # GUI::Window.new('Poly line: Blue, thickness = 1').show(m1) - # GUI::Window.new('Poly line: Red, thickness = 3').show(m2) - # GUI::wait_key + # Uncomment the following line to view the image + # snap(['Fill poly line: Blue, thickness = 1', m1], ['Fill poly line: Red, thickness = 3', m2]) end def test_put_text @@ -145,9 +129,7 @@ class TestCvMat_drawing < OpenCVTestCase m2 = m0.put_text('test 2', CvPoint.new(30, 80), font, CvColor::Red) # Uncomment the following lines to view the image - # GUI::Window.new('Put text: Blue, thickness = 1').show(m1) - # GUI::Window.new('Put text: Red, thickness = 3').show(m2) - # GUI::wait_key + # snap(['Put text: Blue, thickness = 1', m1], ['Put text: Red, thickness = 3', m2]) end end diff --git a/test/test_cvmat_imageprocessing.rb b/test/test_cvmat_imageprocessing.rb new file mode 100755 index 0000000..20540a5 --- /dev/null +++ b/test/test_cvmat_imageprocessing.rb @@ -0,0 +1,198 @@ +#!/usr/bin/env ruby +# -*- mode: ruby; coding: utf-8-unix -*- +require 'test/unit' +require 'opencv' +require File.expand_path(File.dirname(__FILE__)) + '/helper' + +include OpenCV + +# Tests for image processing functions of OpenCV::CvMat +class TestCvMat_imageprocessing < OpenCVTestCase + FILENAME_LENA256x256 = File.expand_path(File.dirname(__FILE__)) + '/samples/lena-256x256.jpg' + FILENAME_LENA32x32 = File.expand_path(File.dirname(__FILE__)) + '/samples/lena-32x32.jpg' + + def test_sobel + mat0 = CvMat.load(FILENAME_LENA256x256, CV_LOAD_IMAGE_GRAYSCALE) + + mat1 = mat0.sobel(1, 0).convert_scale_abs(:scale => 1, :shift => 0) + mat2 = mat0.sobel(0, 1).convert_scale_abs(:scale => 1, :shift => 0) + mat3 = mat0.sobel(1, 1).convert_scale_abs(:scale => 1, :shift => 0) + mat4 = mat0.sobel(1, 1, 3).convert_scale_abs(:scale => 1, :shift => 0) + mat5 = mat0.sobel(1, 1, 5).convert_scale_abs(:scale => 1, :shift => 0) + + assert_equal('30a26b7287fac75bb697bc7eef6bb53a', hash_img(mat1)) + assert_equal('b740afb13b556d55280fa785190ac902', hash_img(mat2)) + assert_equal('36c29ca64a599e0f5633f4f3948ed858', hash_img(mat3)) + assert_equal('36c29ca64a599e0f5633f4f3948ed858', hash_img(mat4)) + assert_equal('30b9e8fd64e7f86c50fb67d8703628e3', hash_img(mat5)) + + assert_equal(:cv16s, CvMat.new(16, 16, :cv8u, 1).sobel(1, 1).depth) + assert_equal(:cv32f, CvMat.new(16, 16, :cv32f, 1).sobel(1, 1).depth) + + (DEPTH.keys - [:cv8u, :cv32f]).each { |depth| + assert_raise(RuntimeError) { + CvMat.new(3, 3, depth).sobel(1, 1) + } + } + + # Uncomment the following lines to view the images + # snap(['original', mat0], ['sobel(1,0)', mat1], ['sobel(0,1)', mat2], + # ['sobel(1,1)', mat3], ['sobel(1,1,3)', mat4], ['sobel(1,1,5)', mat5]) + end + + def test_laplace + mat0 = CvMat.load(FILENAME_LENA256x256, CV_LOAD_IMAGE_GRAYSCALE) + + mat1 = mat0.laplace.convert_scale_abs(:scale => 1, :shift => 0) + mat2 = mat0.laplace(3).convert_scale_abs(:scale => 1, :shift => 0) + mat3 = mat0.laplace(5).convert_scale_abs(:scale => 1, :shift => 0) + + assert_equal('824f8de75bfead5d83c4226f3948ce69', hash_img(mat1)) + assert_equal('824f8de75bfead5d83c4226f3948ce69', hash_img(mat2)) + assert_equal('23850bb8cfe9fd1b82cd73b7b4659369', hash_img(mat3)) + + assert_equal(:cv16s, CvMat.new(16, 16, :cv8u, 1).laplace.depth) + assert_equal(:cv32f, CvMat.new(16, 16, :cv32f, 1).laplace.depth) + + (DEPTH.keys - [:cv8u, :cv32f]).each { |depth| + assert_raise(RuntimeError) { + CvMat.new(3, 3, depth).laplace + } + } + + # Uncomment the following line to view the images + # snap(['original', mat0], ['laplace', mat1], ['laplace(3)', mat2], ['laplace(5)', mat3]) + end + + def test_canny + mat0 = CvMat.load(FILENAME_LENA256x256, CV_LOAD_IMAGE_GRAYSCALE) + mat1 = mat0.canny(50, 200) + mat2 = mat0.canny(50, 200, 3) + mat3 = mat0.canny(50, 200, 5) + + assert_equal('ec3e88035bb98b5c5f1a08c8e07ab0a8', hash_img(mat1)) + assert_equal('ec3e88035bb98b5c5f1a08c8e07ab0a8', hash_img(mat2)) + assert_equal('1983a6d325d11eea3261462103b0dae1', hash_img(mat3)) + + # Uncomment the following line to view the images + # snap(['canny(50,200)', mat1], ['canny(50,200,3)', mat2], ['canny(50,200,5)', mat3]) + end + + def test_pre_corner_detect + mat0 = CvMat.load(FILENAME_LENA256x256, CV_LOAD_IMAGE_GRAYSCALE) + mat1 = mat0.pre_corner_detect + mat2 = mat0.pre_corner_detect(3) + mat3 = mat0.pre_corner_detect(5) + + assert_equal('fe7c8a1d07a3dd0fb6a02d6a6de0fe9f', hash_img(mat1)) + assert_equal('fe7c8a1d07a3dd0fb6a02d6a6de0fe9f', hash_img(mat2)) + assert_equal('42e7443ffd389d15343d3c6bdc42f553', hash_img(mat3)) + + # Uncomment the following lines to show the images + # snap(['original', mat0], ['pre_coner_detect', mat1], + # ['pre_coner_detect(3)', mat2], ['pre_coner_detect(5)', mat3]) + end + + def test_corner_eigenvv + mat0 = CvMat.load(FILENAME_LENA256x256, CV_LOAD_IMAGE_GRAYSCALE) + mat1 = mat0.corner_eigenvv(3) + mat2 = mat0.corner_eigenvv(3, 3) + + flunk('FIXME: CvMat#corner_eigenvv is not tested yet.') + end + + def test_corner_min_eigen_val + mat0 = CvMat.load(FILENAME_LENA256x256, CV_LOAD_IMAGE_GRAYSCALE) + mat1 = mat0.corner_min_eigen_val(3) + mat2 = mat0.corner_min_eigen_val(3, 3) + + flunk('FIXME: CvMat#corner_min_eigen_val is not tested yet.') + end + + def test_corner_harris + mat0 = CvMat.load(FILENAME_LENA256x256, CV_LOAD_IMAGE_GRAYSCALE) + mat1 = mat0.corner_harris(3) + mat2 = mat0.corner_harris(3, 3) + mat3 = mat0.corner_harris(3, 3, 0.04) + mat4 = mat0.corner_harris(3, 7, 0.01) + + assert_equal('6ceb54b54cc98a72de7cb75649fb0a12', hash_img(mat1)) + assert_equal('6ceb54b54cc98a72de7cb75649fb0a12', hash_img(mat2)) + assert_equal('6ceb54b54cc98a72de7cb75649fb0a12', hash_img(mat3)) + assert_equal('4e703deb9a418bbf37e3283f4a7d4d32', hash_img(mat4)) + + # Uncomment the following lines to show the images + # snap(['original', mat0], ['corner_harris(3)', mat1], ['corner_harris(3,3)', mat2], + # ['corner_harris(3,3,0.04)', mat3], ['corner_harris(3,7,0.01)', mat4]) + end + + def test_find_corner_sub_pix + flunk('FIXME: CvMat#corner_min_eigen_val is not implemented yet.') + end + + def test_good_features_to_track + mat0 = CvMat.load(FILENAME_LENA32x32, CV_LOAD_IMAGE_GRAYSCALE) + mask = create_cvmat(mat0.rows, mat0.cols, :cv8u, 1) { |j, i, c| + if (i > 8 and i < 18) and (j > 8 and j < 18) + CvScalar.new(1) + else + CvScalar.new(0) + end + } + + corners1 = mat0.good_features_to_track(0.2, 5) + corners2 = mat0.good_features_to_track(0.2, 5, :mask => mask) + corners3 = mat0.good_features_to_track(0.2, 5, :block_size => 7) + corners4 = mat0.good_features_to_track(0.2, 5, :use_harris => true) + corners5 = mat0.good_features_to_track(0.2, 5, :k => 0.01) + corners6 = mat0.good_features_to_track(0.2, 5, :max => 1) + + expected1 = [[24, 7], [20, 23], [17, 11], [26, 29], [30, 24], + [19, 16], [28, 2], [13, 18], [14, 4]] + assert_equal(expected1.size, corners1.size) + expected1.each_with_index { |e, i| + assert_equal(e[0], corners1[i].x.to_i) + assert_equal(e[1], corners1[i].y.to_i) + } + expected2 = [[17, 11], [17, 16]] + assert_equal(expected2.size, corners2.size) + expected2.each_with_index { |e, i| + assert_equal(e[0], corners2[i].x.to_i) + assert_equal(e[1], corners2[i].y.to_i) + } + + expected3 = [[21, 7], [22, 23], [18, 12], [28, 4], [28, 26], + [17, 27], [13, 20], [10, 11], [14, 5]] + assert_equal(expected3.size, corners3.size) + expected3.each_with_index { |e, i| + assert_equal(e[0], corners3[i].x.to_i) + assert_equal(e[1], corners3[i].y.to_i) + } + + expected4 = [[24, 8], [20, 23], [16, 11], + [20, 16],[27, 28], [28, 2]] + assert_equal(expected4.size, corners4.size) + expected4.each_with_index { |e, i| + assert_equal(e[0], corners4[i].x.to_i) + assert_equal(e[1], corners4[i].y.to_i) + } + + expected5 = [[24, 7], [20, 23], [17, 11], [26, 29], [30, 24], + [19, 16], [28, 2], [13, 18], [14, 4]] + assert_equal(expected5.size, corners5.size) + expected5.each_with_index { |e, i| + assert_equal(e[0], corners5[i].x.to_i) + assert_equal(e[1], corners5[i].y.to_i) + } + + assert_equal(1, corners6.size) + assert_equal(24, corners6[0].x.to_i) + assert_equal(7, corners6[0].y.to_i) + + assert_raise(ArgumentError) { + mat0.good_features_to_track(0.2, 5, :max => 0) + } + end +end + +