Esempio n. 1
0
    def test_load_calib_imgs_paths():
        """
        Test load_calib_imgs path creation/checking.

        All `calib_imgs_paths` tests are basically the same
        """
        global p, o
        if not os_exists(testdir+'/raw'):
            raise RuntimeError('test \'raw\' directory could not be found')
        # Setup
        calib = CalibratePSEye()
        calib.init_chessboard(p, o)
        try:
            calib.load_calib_imgs(testdir+'/raw')

            # Make sure everything was created properly
            for p in ('/raw', '/corners'):
                if not isdir(calib.calibpath + p):
                    raise RuntimeError('path \'%s\' wasn\'t created')

            # Make sure raw images were copied correctly
            for fn in listdir(testdir + '/raw'):
                f1 = calib.calibpath + '/raw/' + fn
                f2 = testdir + '/raw/' + fn
                g1 = cv_imread(f1)
                i2 = cv_imread(f2)
                g2 = cvtColor(cvtColor(i2, COLOR_RGB2GRAY), COLOR_GRAY2RGB)
                if not array_equal(g1, g2):
                    raise RuntimeError('frame \'%s\' did not match' % fn)
                debug('\'%s\' matched' % fn)
        finally:
            calib.removepath()
Esempio n. 2
0
    def test_record_calib_imgs_member_data():
        """
        Test member data assignment inside of `record_calib_imgs`

        `record_calib_imgs` is called with a negative countdown to force
        immediate chessboard logging. The test calibration images provided
        should all have valid chessboards for the provided params.
        """
        # Setup
        h, w, _ = cv_imread(testdir+'/raw/f00001.jpg').shape
        nf = len([
            f for f in listdir(testdir+'/raw') if f[-4:].lower() == '.jpg'
        ])

        # Tests
        calib = CalibratePSEye()
        calib.init_chessboard(p, o)
        try:
            calib.record_calib_imgs(
                cam=testdir+'/raw/f%05d.jpg', nframes=nf, countdown=-1
            )
            if calib.w != w:
                raise RuntimeError('\'w\' wasn\'t set properly')
            if calib.h != h:
                raise RuntimeError('\'h\' wasn\'t set properly')
            if calib.img_arr.shape != (h,w,1,nf):
                raise RuntimeError('\'img_arr.shape\' wasn\'t set properly')
            if calib.img_arr.dtype != uint8:
                raise RuntimeError('\'img_arr.dtype\' wasn\'t set properly')
        finally:
            calib.removepath()
Esempio n. 3
0
 def get_next_frame(self):
     self.current_frame += 1
     frame_path = self.frame_list.get_next_path()
     if frame_path is None and (self.end_frame is None
                                or self.current_frame < self.end_frame):
         r = self.buffer
         self.buffer = cv_imread(frame_path)
         return r
     else:
         return None
Esempio n. 4
0
    def correct_and_save(self, imgpath):
        """
        Correct images and then saves them with the calibration timestamp.

        INPUTS
            imgpath -- str -- path to images to correct

        OUTPUTS
            undistorted_imgs -- uint8 N x H x W x BYTES

        NOTE
            Only corrects filenames ending in '.jpg'

        ALGORITHM
            Saves corrected images at same level as the 'imgpath' directory, but
            adds a timestamp which references the calibration dataset/params
            used. `undistorted_YYMMDD-HHmmss/` and `remapped_YYMMDD-HHmmss/`

        EXCEPTIONS
            Raises RuntimeError If calibration path isn't set. Likely results
                from failing to load/compute calibration matrices.
        """
        if type(imgpath) != str:
            raise TypeError('imgpath must be str')
        if self.calibpath is None:
            raise RuntimeError(
                'Be sure to set self.calibpath (did you compute/load calibrations?)'
            )

        # Find images to correct
        potential_imgs = listdir(imgpath)
        fn_imgs = [f for f in potential_imgs
                   if f[-4:].lower() == '.jpg']  # select jpegs
        img_list = [cv_imread(imgpath + '/' + fn)
                    for fn in fn_imgs]  # read all jpegs

        # Make Save Directories
        savedir = realpath(imgpath + '/..')
        timestamp = self.get_timestamp()
        copyfile(self.calibpath + '/camera_params.csv',
                 savedir + '/' + timestamp + '_camera_params.csv')
        ud_path = savedir + '/' + timestamp + '_undistorted'
        if not isdir(ud_path):
            if os_exists(ud_path):
                os_remove(ud_path)
            mkdir(ud_path)

        # Correct & Save Frames
        ud = self.correct(img_list)
        for i in range(len(img_list)):
            fnud = ud_path + ('/f%s' % str(i + 1).zfill(5)) + '.jpg'
            cv_imwrite(fnud, ud[i, ...], (IMWRITE_JPEG_QUALITY, 100))

        # Return in case we want to use them later
        return ud
Esempio n. 5
0
def read_image(img_name, grey=False, use_opencv=False, uint8=False):
    """
    Read an image file (.png) into a numpy array in which each entry is
    a row of pixels (i.e. ``len(img)`` is the image height in px. If
    grey is True (default is False), returns a grayscale image (dtype
    uint8 if RGBA, and dtype float32 if greyscale). use_opencv uses the
    `cv2.imread` function rather than `imageio.imread`, which always
    returns a dtype of uint8. uint8 will enforce dtype of uint8 (i.e.
    for greyscale from `imageio.imread`) if set to True, but defaults
    to False.
    """
    data_dir = Path('..') / 'img'
    if use_opencv:
        if grey:
            img = cv_imread(data_dir / img_name, 0)
        else:
            img = cv_imread(data_dir / img_name)
    else:
        img = imread(data_dir / img_name, as_gray=grey)
        if uint8 and img.dtype != 'uint8':
            img = np.uint8(img)
    return img
Esempio n. 6
0
    def inspect(self):
        """
        Run through calibration images, showing original and undistorted.
        Intended to be run after saving calibration images.
        """
        # access calibration images
        if self.calibpath is None:
            raise RuntimeError('calibpath is unset')
        imgpath = self.calibpath + '/corners2'
        fns = [
            imgpath + '/' + f for f in listdir(imgpath)
            if f[-4:].lower() == '.jpg'
        ]
        fns.sort()
        imgs = [cv_imread(f) for f in fns]
        shape = list(imgs[0].shape)
        shape.append(len(imgs))

        # undistort
        #   go through 1-by-1 b/c asarray(imgs) gets shaped wrong & reshape messes with things
        ud = zeros(shape, dtype=uint8)
        for i in range(len(imgs)):
            ud[..., i] = self.correct(imgs[i].squeeze())

        # display side-by-side
        for i in range(len(imgs)):
            img = imgs[i].copy()
            u = ud[..., i].copy()
            cv_putText(img,
                       fns[i][-10:], (5, 15),
                       1,
                       1, (0, 0, 255),
                       thickness=2)
            cv_putText(img,
                       'Press space for next image', (5, 30),
                       1,
                       1, (0, 0, 255),
                       thickness=2)
            cv_putText(u, 'Undistort', (5, 15), 1, 1, (0, 0, 255), thickness=2)
            frame = concatenate((img, u), axis=0)
            cv_imshow('frame', frame)
            press = waitKey(-1)
            if press == ord('q'):
                break
        destroyAllWindows()
Esempio n. 7
0
    def test_internal_correct_and_save():
        """
        Test internal correction saving method.
        """
        calib = CalibratePSEye()
        fn_c = calibsdir + '/camera_params.csv'
        # Asserts
        for t in (int, float, complex, list, tuple, range, dict, set,
                  frozenset, bool, bytes, bytearray, memoryview):
                try:
                    calib.correct_and_save(t)
                except TypeError:
                    pass
                else:
                    raise RuntimeError('Failed to catch %s imgpath' % t.__name__)
        calib.load_calibrations(fn_c)
        cp = calib.calibpath
        calib.calibpath = None
        try:
            calib.correct_and_save('file-that-does-not-exist')
        except RuntimeError:
            pass
        else:
            raise RuntimeError('Failed to catch _calib_path is None')

        # Saving
        calib.calibpath = cp
        imgpath = testdir + '/raw'
        storeddir = testdir + '/00000000-000000_undistorted'
        storedcp = testdir + '/00000000-000000_camera_params.csv'
        if os_exists(storeddir):
            rmtree(storeddir)
        if os_exists(storedcp):
            os_remove(storedcp)
        ud1 = calib.correct_and_save(imgpath)
        try:
            # Proper saving
            if not os_exists(storeddir) or not os_exists(storedcp):
                raise RuntimeError('Error creating corrected directories')
            imgcount1 = len([f for f in listdir(imgpath) if f[-4:].lower() == '.jpg'])
            imgcount2 = len([f for f in listdir(storeddir) if f[-4:].lower() == '.jpg'])
            if imgcount1 != imgcount2:
                raise RuntimeError('Not all images were saved')

            # Correct calibration
            #   Check pre-save equality
            imglist = [f for f in listdir(imgpath) if f[-4:].lower() == '.jpg']
            rawimg = [cv_imread(imgpath + '/' + f) for f in imglist]
            ud2 = calib.correct(rawimg)  # will know if `correct` works
            if not array_equal(ud1, ud2):
                raise RuntimeError('Failed pre-save equality check')

            #   Check post-save equality
            for i in range(len(imglist)):
                fnud = storeddir + ('/_f%s' % str(i+1).zfill(5)) + '.jpg'
                cv_imwrite(fnud, ud2[i,...], (IMWRITE_JPEG_QUALITY, 100))
            ud1list = [cv_imread(storeddir + '/' + f) for f in imglist]
            ud2list = [cv_imread(storeddir + '/_' + f) for f in imglist]

            ud1reload = asarray(ud1list, dtype='uint8')
            ud2reload = asarray(ud2list, dtype='uint8')
            if not array_equal(ud1reload, ud2reload):
                raise RuntimeError('Failed reload equality check')
        finally:
            os_remove(storedcp)
            rmtree(storeddir)
            try:
                if os_exists(storedcp):
                    raise RuntimeError('failed to deleted cameraParams csv')
                if os_exists(storeddir):
                    raise RuntimeError('failed to remove undistored img dir')
            except AssertionError:
                raise RuntimeError('Exception during test cleanup')
Esempio n. 8
0
    def test_internal_correct():
        """
        Test internal correction method.

        Discovered this song while debugging this test case on 2020-06-19
        https://open.spotify.com/track/5XgYWQKEqSqA5vXJmwZa6n?si=HvcZD32-T2KRspTPcb4uGQ
        """
        calib = CalibratePSEye()
        calib.load_calibrations(calibsdir + '/camera_params.csv')

        # test datatype check
        try:
            for t in ('uint16', 'uint32', 'uint64', 'int16', 'int32', 'int64',
                      'float32', 'float64', 'object'):
                frames = zeros((240, 320, 3), dtype=t)
                calib.correct(frames)
        except TypeError:
            pass
        else:
            raise RuntimeError('Failed to catch not-uint8 dtype')

        # test size check
        try:
            frames = zeros((240,320), dtype='uint8')
            calib.correct(frames)
        except TypeError:
            pass
        else:
            raise RuntimeError('Failed to catch frames too few dimensions')
        try:
            frames = zeros((240,320,1,1,1), dtype='uint8')
            calib.correct(frames)
        except TypeError:
            pass
        else:
            raise RuntimeError('Failed to catch frames too many dimensions')

        # setup test
        frames = []
        for f in listdir(testdir+'/raw'):
            if f[-4:].lower() == '.jpg':
                frames.append(cv_imread(testdir + '/raw/' + f))
        fshape = [len(frames)] + list(frames[0].shape)
        u1 = zeros(fshape, dtype='uint8')
        for i in range(len(frames)):
            f = frames[i].copy()
            u1[i,...] = undistort(f, calib.cameraMatrix, calib.distCoeffs, None)

        # test single frame
        u2 = calib.correct(frames[0])
        if not array_equal(u1[0,...], u2):
            raise RuntimeError('Single-frame undistort/remap is incorrect')

        # test several frames
        u2 = calib.correct(asarray(frames, dtype='uint8'))
        if not array_equal(u1, u2):
            raise RuntimeError('Multi-frame ndarray undistort/remap is incorrect')

        u2 = calib.correct(frames)
        if not array_equal(u1, u2):
            raise RuntimeError('Multi-frame list undistort/remap is incorrect')
Esempio n. 9
0
    def clean_calib_imgs(self, basepath=None, rawpath=None, cpath=None):
        """
        Provides interface to sanitize calibration images using a
        tutorial-like imshow() interface. Deletes frames, and removes elements
        from self.corners_arr, if they are not None.

        INPUTS
            (optional)
            basepath -- str -- Base path for calib frames; None
            rawpath  -- str -- Path to frames for calibration; None
            cpath    -- str -- Path to frames with chessboard; None

        NOTE
            The frames in each path must have the same name, e.g. 'f00001.jpg'

        USAGE
            SPECIFIED ONLY BASEPATH
                When all other paths the frame paths will
                be defined as:
                    rawpath = basepath + '/raw'
                    cpath = basepath + '/corners'

            PATHS TO ALL FRAMES
                If rawpath and cpath are all explicitly defined, they will be
                used as is. basepath will be ignored.
        """
        # Parse paths
        if basepath is not None:
            basepath = realpath(basepath)
            if basepath[basepath.rfind('/') + 1:] not in ('raw', 'corners',
                                                          'corners2'):
                logging.warning(
                    'Assuming \'%s\' is base dir for all unspecified frames' %
                    basepath)
                basepath = basepath
            else:
                basepath = dirname(basepath)
        if rawpath is None:
            rawpath = basepath + '/raw'
        if cpath is None:
            cpath = basepath + '/corners'

        # Select images to save
        fn_imgs = [f for f in listdir(cpath) if f[-4:].lower() == '.jpg']
        fn_imgs.sort()
        i = 0
        while i < len(fn_imgs):
            f = fn_imgs[i]
            img = cv_imread(cpath + '/' + f)
            if img is None:  # catch deleted image
                fn_imgs.pop(i)
                if self.corners_arr is not None:
                    self.corners_arr.pop(i)
                i += 1
                continue
            cv_putText(img, f, (5, 15), 1, 1, (0, 0, 255), thickness=2)
            cv_putText(img,
                       '\'r\' to remove', (5, 30),
                       1,
                       1, (0, 0, 255),
                       thickness=2)
            cv_putText(img,
                       'left bracket to go back', (5, 45),
                       1,
                       1, (0, 0, 255),
                       thickness=2)
            cv_imshow('image', img)

            # interface
            press = waitKey(-1)
            if press == ord('r'):
                fn = fn_imgs[i][fn_imgs[i].rfind('f'):]
                os_remove(rawpath + '/' + fn)
                os_remove(cpath + '/' + fn)
            elif press in (27, 81, 113):  # esc, q, Q
                break
            elif press == 91:  # left bracket
                i -= 2
            i += 1
        destroyAllWindows()
Esempio n. 10
0
    def load_calib_imgs(self, img_path, clean=False):
        """
        Load calibration JPGs from directory & get chessboard. Be sure to
        call init_chessboard() first.

        INPUTS
            img_path -- str -- path to calibration jpegs
            (optional)
            clean -- bool -- Whether or not to go through sanitization; False

        EXCEPTIONS
            raises RuntimeError: when chessboard hasn't been properly
                initialized by constructor or `init_chessboard`.

        ALGORITHM
            Finds all files ending in '.jpg' and loads them. Objp should have
            been handled in init_chessboard()
        """
        if type(img_path) != str:
            raise TypeError('img_path must be str')
        if not (type(clean) == bool or clean in (0, 1)):
            raise TypeError('clean must be bool')
        if self.img_arr is not None or self.calibpath is None:
            raise RuntimeError('Did you call init_chessboard() first?')

        # Find calibration images and process
        potential_files = listdir(img_path)
        fn_imgs = [
            img_path + '/' + f for f in potential_files
            if f[-4:].lower() == '.jpg'
        ]
        imageshape = cv_imread(fn_imgs[0]).shape
        self.h = imageshape[0]
        self.w = imageshape[1]

        # Save images in calib path
        rawpath = self.calibpath + '/raw'  # copy to current calibration dir
        if rawpath != img_path:
            if not isdir(rawpath):
                if os_exists(rawpath):
                    os_remove(rawpath)
                mkdir(rawpath)
            for f in fn_imgs:
                copyfile(f, rawpath + '/' + basename(f))
        # corners frames for debugging
        cpath = self.calibpath + '/corners'  # save drawn corners for debug
        mkdir(cpath)
        logging.info('saving raw frames to \'%s\'' % rawpath)
        logging.info('saving corners frames to \'%s\'' % cpath)

        # Load images
        self.img_arr = zeros((self.h, self.w, 1, len(fn_imgs)), uint8)
        for i in range(len(fn_imgs)):
            f = fn_imgs[i]
            if imageshape[-1] == 3:
                self.img_arr[..., i] = cvtColor(cv_imread(f),
                                                COLOR_RGB2GRAY)[..., newaxis]
            else:
                self.img_arr[..., i] = cv_imread(f)

        # Chessboard computations
        logging.debug('finding chessboards...')
        for i in range(self.img_arr.shape[-1]):
            gray = self.img_arr[..., i].copy()
            corners = self._find_chessboard(gray)
            if corners is None:
                logging.error('Failed to find chessboard at frame \'%s\'' %
                              str(i + 1).zfill(5))
                continue
            self.corners_arr.append(corners)
            self.objpoints.append(self.objp)  # 3d position (same for all?)

            # save chessboard images for debugging
            #   cvt to rgb for color chessboard
            fn_c = cpath + ('/f%s' % str(i + 1).zfill(5)) + '.jpg'
            gray_color = cvtColor(gray, COLOR_GRAY2RGB)
            img_corners = drawChessboardCorners(gray_color, self.boardsize,
                                                corners, 1)
            cv_imwrite(fn_c, img_corners, (IMWRITE_JPEG_QUALITY, 100))

        # Go through chessboards to make sure okay
        if clean:
            basepath = dirname(cpath)
            self.clean_calib_imgs(basepath=basepath)
        logging.debug('load_calib_imgs() done!')