Травление cv2.KeyPoint вызывает PicklingError

Я хочу искать серфики во всех изображениях в данном каталоге и сохранять их ключевые точки и дескрипторы для будущего использования. Я решил использовать рассол, как показано ниже:

#!/usr/bin/env python
import os
import pickle
import cv2

class Frame:
  def __init__(self, filename):
    surf = cv2.SURF(500, 4, 2, True)
    self.filename = filename
    self.keypoints, self.descriptors = surf.detect(cv2.imread(filename, cv2.CV_LOAD_IMAGE_GRAYSCALE), None, False)

if __name__ == '__main__':

  Fdb = open('db.dat', 'wb')
  base_path = "img/"
  frame_base = []

  for filename in os.listdir(base_path):
    frame_base.append(Frame(base_path+filename))
    print filename

  pickle.dump(frame_base,Fdb,-1)

  Fdb.close()

Когда я пытаюсь выполнить, я получаю следующую ошибку:

File "src/pickle_test.py", line 23, in <module>
    pickle.dump(frame_base,Fdb,-1)
...
pickle.PicklingError: Can't pickle <type 'cv2.KeyPoint'>: it not the same object as cv2.KeyPoint

Кто-нибудь знает, что это значит и как его исправить? Я использую Python 2.6 и Opencv 2.3.1

Большое спасибо

Ответы

Ответ 1

Проблема заключается в том, что вы не можете сбрасывать cv2.KeyPoint в файл pickle. У меня была такая же проблема, и мне удалось обойти ее, по существу, сериализовать и десериализовать ключевые точки перед тем, как сбросить их с помощью Pickle.

Итак, представляем каждую ключевую точку и ее дескриптор с кортежем:

temp = (point.pt, point.size, point.angle, point.response, point.octave, 
        point.class_id, desc)       

Добавьте все эти точки в список, который вы затем сбросите с помощью Pickle.

Затем, когда вы хотите снова получить данные, загрузите все данные с помощью Pickle:

temp_feature = cv2.KeyPoint(x=point[0][0],y=point[0][1],_size=point[1], _angle=point[2], 
                            _response=point[3], _octave=point[4], _class_id=point[5]) 
temp_descriptor = point[6]

Создайте cv2.KeyPoint из этих данных, используя приведенный выше код, и затем вы можете использовать эти точки для создания списка функций.

Я подозреваю, что есть более аккуратный способ сделать это, но это работает отлично (и быстро) для меня. Возможно, вам придется немного поиграть со своим форматом данных, так как мои функции хранятся в списках формата. Я попытался представить выше, используя мою идею на своей общей основе. Я надеюсь, что это может вам помочь.

Ответ 2

Частью проблемы является cv2.KeyPoint - это функция в Python, которая возвращает объект cv2.KeyPoint. Pickle запутывается, потому что буквально " <type 'cv2.KeyPoint'> [is] not the same object as cv2.KeyPoint ". То есть cv2.KeyPoint является функциональным объектом, тогда как тип был cv2.KeyPoint. Почему OpenCV такой, я могу только догадываться, если я не буду копать. У меня есть ощущение, что это как-то связано с тем, что он является оберткой вокруг библиотеки C/C++.

Python дает вам возможность исправить это самостоятельно. В этом посте я нашел вдохновение о методах травления на уроках.

Я на самом деле использую этот фрагмент кода, сильно измененный по сравнению с оригиналом в посте

import copyreg
import cv2

def _pickle_keypoints(point):
    return cv2.KeyPoint, (*point.pt, point.size, point.angle,
                          point.response, point.octave, point.class_id)

copyreg.pickle(cv2.KeyPoint().__class__, _pickle_keypoints)

Ключевые моменты заметки:

  • В Python 2 вам нужно использовать copy_reg вместо copyreg и point.pt[0], point.pt[1] вместо *point.pt.
  • cv2.KeyPoint какой-то причине вы не можете напрямую получить доступ к классу cv2.KeyPoint, поэтому вы создаете временный объект и используете его.
  • В copyreg будет использоваться проблемная функция cv2.KeyPoint как я указал в выводе _pickle_keypoints при снятии засечек, поэтому нам не нужно реализовывать подпрограмму расщепления.
  • И чтобы быть тошнотворно завершенным, cv2::KeyPoint::KeyPoint - перегруженная функция в C++, но в Python это не совсем то, что нужно. В то время как в C++ есть функция, которая берет точку для первого аргумента, в Python она будет пытаться вместо этого интерпретировать это как int. * Разворачивает точку в два аргумента, x и y чтобы соответствовать единственному конструктору аргумента int.

Я использовал отличное решение Каспера, пока не понял, что это возможно.

Ответ 3

Решение, аналогичное предоставленному Poik. Просто позвоните один раз, прежде чем засолить.

def patch_Keypoint_pickiling(self):
    # Create the bundling between class and arguements to save for Keypoint class
    # See : https://stackoverflow.com/info/50337569/pickle-exception-for-cv2-boost-when-using-multiprocessing/50394788#50394788
    def _pickle_keypoint(keypoint): #  : cv2.KeyPoint
        return cv2.KeyPoint, (
            keypoint.pt[0],
            keypoint.pt[1],
            keypoint.size,
            keypoint.angle,
            keypoint.response,
            keypoint.octave,
            keypoint.class_id,
        )
    # C++ Constructor, notice order of arguments : 
    # KeyPoint (float x, float y, float _size, float _angle=-1, float _response=0, int _octave=0, int _class_id=-1)

    # Apply the bundling to pickle
    copyreg.pickle(cv2.KeyPoint().__class__, _pickle_keypoint)

Больше, чем для кода, это для невероятно четкого объяснения, доступного там: fooobar.com/info/15684049/...

Обратите внимание, что если вы хотите распространить эту идею на другой "непикабельный" класс openCV, вам нужно всего лишь создать функцию, аналогичную "_pickle_keypoint". Убедитесь, что вы храните атрибуты в том же порядке, что и конструктор. Вы можете скопировать конструктор C++, даже в Python, как я сделал. Большинство конструкторов C++ и Python, похоже, не сильно отличаются.

У меня есть проблема с кортежем "pt". Тем не менее, конструктор C++ существует для координат, разделенных X и Y, и, следовательно, допускает это исправление/обходной путь.