From e0ca191b05ac7bb1b26bd3b9b3acaab0171e812a Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Tue, 3 Oct 2023 11:17:00 +0300 Subject: [PATCH 01/13] Return bytes array if UTF-8 decoding failed --- .../objdetect/include/opencv2/objdetect.hpp | 7 +++++ .../misc/python/pyopencv_objdetect.hpp | 27 +++++++++++++++++++ modules/objdetect/src/qrcode.cpp | 14 +++++++++- modules/python/src2/cv2_convert.cpp | 6 +++-- modules/python/src2/gen2.py | 3 +++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index ed0d6f76ac..6c734ba789 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -808,6 +808,13 @@ public: */ CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(), OutputArray straight_qrcode = noArray()); + + /** @brief Returns a kid of encoding for the decoded info from the latest QR codes + @param codeIdx an index of the previously decoded QR code. + When @ref decode or @ref detectAndDecode is used, valid value is zero. + For @ref decodeMulti or @ref detectAndDecodeMulti use indices corresponding to the output order. + */ + CV_WRAP QRCodeEncoder::ECIEncodings getEncoding(size_t codeIdx = 0); }; class CV_EXPORTS_W_SIMPLE QRCodeDetectorAruco : public GraphicalCodeDetector { diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index 95194e1c46..9385fae838 100644 --- a/modules/objdetect/misc/python/pyopencv_objdetect.hpp +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -7,4 +7,31 @@ typedef QRCodeEncoder::Params QRCodeEncoder_Params; typedef HOGDescriptor::HistogramNormType HOGDescriptor_HistogramNormType; typedef HOGDescriptor::DescriptorStorageFormat HOGDescriptor_DescriptorStorageFormat; +static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecode(PyObject* self, PyObject* py_args, PyObject* kw); + +static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI(PyObject* self, PyObject* py_args, PyObject* kw) { + // Run original method + PyObject* retval = pyopencv_cv_GraphicalCodeDetector_detectAndDecode(self, py_args, kw); + + if (PyErr_Occurred()) { + PyObject *type = nullptr, *value = nullptr, *traceback = nullptr; + PyErr_Fetch(&type, &value, &traceback); + + PyObject* object = PyUnicodeDecodeError_GetObject(value); + if (object && PyBytes_Check(object)) { + // TODO: use getEncoding. For now return just bytes array. + PyTuple_SetItem(retval, 0, object); + // PyObject* decoded = PyUnicode_FromEncodedObject(object, "ISO-8859-1", NULL); + // PyTuple_SetItem(retval, 0, decoded); + } else { + PyErr_Restore(type, value, traceback); + } + } + return retval; +} + +// TODO: copy docstring somehow +#define PYOPENCV_EXTRA_METHODS_GraphicalCodeDetector \ + {"detectAndDecode", CV_PY_FN_WITH_KW_(pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI, 0), "detectAndDecode(img[, points[, straight_code]]) -> retval, points, straight_code\n. @brief Both detects and decodes graphical code\n. \n. @param img grayscale or color (BGR) image containing graphical code.\n. @param points optional output array of vertices of the found graphical code quadrangle, will be empty if not found.\n. @param straight_code The optional output image containing binarized code"}, + #endif diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index ac1e7fcadc..6e4bd833f2 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -963,6 +963,7 @@ public: double epsX, epsY; mutable vector> alignmentMarkers; mutable vector updateQrCorners; + mutable vector encodings; bool useAlignmentMarkers = true; bool detect(InputArray in, OutputArray points) const override; @@ -978,6 +979,8 @@ public: String decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode); std::string detectAndDecodeCurved(InputArray in, OutputArray points, OutputArray straight_qrcode); + + QRCodeEncoder::ECIEncodings getEncoding(size_t codeIdx); }; QRCodeDetector::QRCodeDetector() { @@ -994,6 +997,12 @@ QRCodeDetector& QRCodeDetector::setEpsY(double epsY) { return *this; } +QRCodeEncoder::ECIEncodings QRCodeDetector::getEncoding(size_t codeIdx) { + auto& encodings = std::dynamic_pointer_cast(p)->encodings; + CV_Assert(codeIdx < encodings.size()); + return encodings[codeIdx]; +} + bool ImplContour::detect(InputArray in, OutputArray points) const { Mat inarr; @@ -1035,6 +1044,8 @@ public: uint8_t total_num = 1; } structure_info; + QRCodeEncoder::ECIEncodings eci; + protected: double getNumModules(); Mat getHomography() { @@ -2802,7 +2813,6 @@ static std::string encodeUTF8_bytesarray(const uint8_t* str, const size_t size) bool QRDecode::decodingProcess() { - QRCodeEncoder::ECIEncodings eci; const uint8_t* payload; size_t payload_len; #ifdef HAVE_QUIRC @@ -2966,6 +2976,7 @@ std::string ImplContour::decode(InputArray in, InputArray points, OutputArray st alignmentMarkers = {qrdec.alignment_coords}; updateQrCorners = qrdec.getOriginalPoints(); } + encodings.resize(1, qrdec.encoding); return ok ? decoded_info : std::string(); } @@ -2999,6 +3010,7 @@ String ImplContour::decodeCurved(InputArray in, InputArray points, OutputArray s { qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1); } + encodings.resize(1, qrdec.encoding); return ok ? decoded_info : std::string(); } diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index 0626e42e53..6352c6067a 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -739,14 +739,16 @@ bool pyopencv_to(PyObject* obj, String &value, const ArgInfo& info) template<> PyObject* pyopencv_from(const String& value) { - return PyString_FromString(value.empty() ? "" : value.c_str()); + PyObject* ret = PyString_FromString(value.empty() ? "" : value.c_str()); + return ret ? ret : Py_None; } #if CV_VERSION_MAJOR == 3 template<> PyObject* pyopencv_from(const std::string& value) { - return PyString_FromString(value.empty() ? "" : value.c_str()); + PyObject* ret = PyString_FromString(value.empty() ? "" : value.c_str()); + return ret ? ret : Py_None; } #endif diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 7d9f75063c..4649b0ebe8 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -133,6 +133,9 @@ static PyGetSetDef pyopencv_${name}_getseters[] = static PyMethodDef pyopencv_${name}_methods[] = { +#ifdef PYOPENCV_EXTRA_METHODS_${name} + PYOPENCV_EXTRA_METHODS_${name} +#endif ${methods_inits} {NULL, NULL} }; From bc29dd74f30e3e6d1ad3468e9a8bb1956a44e6f0 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Tue, 3 Oct 2023 11:26:34 +0300 Subject: [PATCH 02/13] Add test --- .../misc/python/test/test_qrcode_detect.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/objdetect/misc/python/test/test_qrcode_detect.py b/modules/objdetect/misc/python/test/test_qrcode_detect.py index 0237900572..eb74499dd6 100644 --- a/modules/objdetect/misc/python/test/test_qrcode_detect.py +++ b/modules/objdetect/misc/python/test/test_qrcode_detect.py @@ -8,7 +8,7 @@ import os import numpy as np import cv2 as cv -from tests_common import NewOpenCVTests +from tests_common import NewOpenCVTests, unittest class qrcode_detector_test(NewOpenCVTests): @@ -50,3 +50,15 @@ class qrcode_detector_test(NewOpenCVTests): self.assertTrue("STEP BACK" in decoded_data) self.assertTrue("QUESTION" in decoded_data) self.assertEqual(points.shape, (6, 4, 2)) + + def test_decode_non_utf8(self): + import sys + if sys.version_info[0] < 3: + raise unittest.SkipTest('Python 2.x is not supported') + + img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/umlaut.png')) + self.assertFalse(img is None) + detector = cv.QRCodeDetector() + decoded_data, _, _ = detector.detectAndDecode(img) + self.assertTrue(isinstance(decoded_data, bytes)) + self.assertTrue(u"M\u00FCllheimstrasse" in decoded_data.decode('ISO-8859-1')) From dcdaff976da52e8231c4727a0d0d1066aab12916 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Wed, 18 Oct 2023 23:02:35 +0300 Subject: [PATCH 03/13] ECI enum --- .../objdetect/include/opencv2/objdetect.hpp | 3 ++- .../misc/python/pyopencv_objdetect.hpp | 25 +++++++++++++++---- modules/objdetect/src/qrcode.cpp | 6 ++--- modules/objdetect/test/test_qrcode_encode.cpp | 4 ++- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index 6c734ba789..b5ccfb1503 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -729,7 +729,8 @@ public: }; enum ECIEncodings { - ECI_UTF8 = 26 + ECI_SHIFT_JIS = 20, + ECI_UTF8 = 26, }; /** @brief QR code encoder parameters. */ diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index 9385fae838..60404f69f0 100644 --- a/modules/objdetect/misc/python/pyopencv_objdetect.hpp +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -7,22 +7,37 @@ typedef QRCodeEncoder::Params QRCodeEncoder_Params; typedef HOGDescriptor::HistogramNormType HOGDescriptor_HistogramNormType; typedef HOGDescriptor::DescriptorStorageFormat HOGDescriptor_DescriptorStorageFormat; +static const char* eciString(QRCodeEncoder::ECIEncodings eci) { + switch (eci) { + case QRCodeEncoder::ECIEncodings::ECI_UTF8: + return "utf-8"; + case QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS: + return "shift-jis"; + } +} + static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecode(PyObject* self, PyObject* py_args, PyObject* kw); static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI(PyObject* self, PyObject* py_args, PyObject* kw) { // Run original method PyObject* retval = pyopencv_cv_GraphicalCodeDetector_detectAndDecode(self, py_args, kw); + cv::GraphicalCodeDetector* obj = 0; + pyopencv_GraphicalCodeDetector_getp(self, obj); + // if (obj->getEncoding() != ECI_UTF8) { + + // } + if (PyErr_Occurred()) { PyObject *type = nullptr, *value = nullptr, *traceback = nullptr; PyErr_Fetch(&type, &value, &traceback); PyObject* object = PyUnicodeDecodeError_GetObject(value); if (object && PyBytes_Check(object)) { - // TODO: use getEncoding. For now return just bytes array. - PyTuple_SetItem(retval, 0, object); - // PyObject* decoded = PyUnicode_FromEncodedObject(object, "ISO-8859-1", NULL); - // PyTuple_SetItem(retval, 0, decoded); + // PyTuple_SetItem(retval, 0, object); + const char* encoding = eciString(QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS); + PyObject* decoded = PyUnicode_FromEncodedObject(object, encoding, NULL); + PyTuple_SetItem(retval, 0, decoded); } else { PyErr_Restore(type, value, traceback); } @@ -32,6 +47,6 @@ static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI(PyObject* // TODO: copy docstring somehow #define PYOPENCV_EXTRA_METHODS_GraphicalCodeDetector \ - {"detectAndDecode", CV_PY_FN_WITH_KW_(pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI, 0), "detectAndDecode(img[, points[, straight_code]]) -> retval, points, straight_code\n. @brief Both detects and decodes graphical code\n. \n. @param img grayscale or color (BGR) image containing graphical code.\n. @param points optional output array of vertices of the found graphical code quadrangle, will be empty if not found.\n. @param straight_code The optional output image containing binarized code"}, + {"detectAndDecode", CV_PY_FN_WITH_KW_(pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI, 0), ""}, #endif diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index 6e4bd833f2..12cb33ff79 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -2905,7 +2905,7 @@ bool QRDecode::decodingProcess() return true; case QRCodeEncoder::EncodeMode::MODE_KANJI: // FIXIT BUG: we must return UTF-8 compatible string - CV_LOG_WARNING(NULL, "QR: Kanji is not supported properly"); + eci = QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS; result_info.assign((const char*)payload, payload_len); return true; case QRCodeEncoder::EncodeMode::MODE_ECI: @@ -2976,7 +2976,7 @@ std::string ImplContour::decode(InputArray in, InputArray points, OutputArray st alignmentMarkers = {qrdec.alignment_coords}; updateQrCorners = qrdec.getOriginalPoints(); } - encodings.resize(1, qrdec.encoding); + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } @@ -3010,7 +3010,7 @@ String ImplContour::decodeCurved(InputArray in, InputArray points, OutputArray s { qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1); } - encodings.resize(1, qrdec.encoding); + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } diff --git a/modules/objdetect/test/test_qrcode_encode.cpp b/modules/objdetect/test/test_qrcode_encode.cpp index f6cf1c069f..f90af1d9f9 100644 --- a/modules/objdetect/test/test_qrcode_encode.cpp +++ b/modules/objdetect/test/test_qrcode_encode.cpp @@ -343,9 +343,11 @@ TEST(Objdetect_QRCode_Encode_Kanji, regression) } Mat straight_barcode; - std::string decoded_info = QRCodeDetector().decode(resized_src, corners, straight_barcode); + QRCodeDetector detector; + std::string decoded_info = detector.decode(resized_src, corners, straight_barcode); EXPECT_FALSE(decoded_info.empty()) << "The generated QRcode cannot be decoded."; EXPECT_EQ(input_info, decoded_info); + EXPECT_EQ(detector.getEncoding(), QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS); } } From efccf18e9771950350b2701c22398a4890d8dca2 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Fri, 30 May 2025 09:10:03 +0300 Subject: [PATCH 04/13] Introduce decodeBytes --- .../misc/python/pyopencv_objdetect.hpp | 28 +++++-------------- .../misc/python/test/test_qrcode_detect.py | 4 +-- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index 60404f69f0..e84658fcc7 100644 --- a/modules/objdetect/misc/python/pyopencv_objdetect.hpp +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -7,46 +7,32 @@ typedef QRCodeEncoder::Params QRCodeEncoder_Params; typedef HOGDescriptor::HistogramNormType HOGDescriptor_HistogramNormType; typedef HOGDescriptor::DescriptorStorageFormat HOGDescriptor_DescriptorStorageFormat; -static const char* eciString(QRCodeEncoder::ECIEncodings eci) { - switch (eci) { - case QRCodeEncoder::ECIEncodings::ECI_UTF8: - return "utf-8"; - case QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS: - return "shift-jis"; - } -} - static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecode(PyObject* self, PyObject* py_args, PyObject* kw); -static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI(PyObject* self, PyObject* py_args, PyObject* kw) { +static PyObject* detectAndDecodeBytes(PyObject* self, PyObject* py_args, PyObject* kw) { // Run original method PyObject* retval = pyopencv_cv_GraphicalCodeDetector_detectAndDecode(self, py_args, kw); - cv::GraphicalCodeDetector* obj = 0; - pyopencv_GraphicalCodeDetector_getp(self, obj); - // if (obj->getEncoding() != ECI_UTF8) { - - // } - if (PyErr_Occurred()) { PyObject *type = nullptr, *value = nullptr, *traceback = nullptr; PyErr_Fetch(&type, &value, &traceback); PyObject* object = PyUnicodeDecodeError_GetObject(value); if (object && PyBytes_Check(object)) { - // PyTuple_SetItem(retval, 0, object); - const char* encoding = eciString(QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS); - PyObject* decoded = PyUnicode_FromEncodedObject(object, encoding, NULL); - PyTuple_SetItem(retval, 0, decoded); + PyTuple_SetItem(retval, 0, object); } else { PyErr_Restore(type, value, traceback); } + } else { + PyObject* str = PyTuple_GetItem(retval, 0); + PyObject* bytes = PyUnicode_AsEncodedString(str, "utf-8", 0); + PyTuple_SetItem(retval, 0, bytes); } return retval; } // TODO: copy docstring somehow #define PYOPENCV_EXTRA_METHODS_GraphicalCodeDetector \ - {"detectAndDecode", CV_PY_FN_WITH_KW_(pyopencv_cv_GraphicalCodeDetector_detectAndDecodeECI, 0), ""}, + {"detectAndDecodeBytes", CV_PY_FN_WITH_KW_(detectAndDecodeBytes, 0), ""}, #endif diff --git a/modules/objdetect/misc/python/test/test_qrcode_detect.py b/modules/objdetect/misc/python/test/test_qrcode_detect.py index eb74499dd6..ea2f500c2a 100644 --- a/modules/objdetect/misc/python/test/test_qrcode_detect.py +++ b/modules/objdetect/misc/python/test/test_qrcode_detect.py @@ -59,6 +59,6 @@ class qrcode_detector_test(NewOpenCVTests): img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/umlaut.png')) self.assertFalse(img is None) detector = cv.QRCodeDetector() - decoded_data, _, _ = detector.detectAndDecode(img) + decoded_data, _, _ = detector.detectAndDecodeBytes(img) self.assertTrue(isinstance(decoded_data, bytes)) - self.assertTrue(u"M\u00FCllheimstrasse" in decoded_data.decode('ISO-8859-1')) + self.assertTrue(u"M\u00FCllheimstrasse" in decoded_data.decode('utf-8')) From 8801c6f75eaea9db4fd161112f75dfaa79253485 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Fri, 30 May 2025 17:32:06 +0300 Subject: [PATCH 05/13] Allow conversion to std::string from Python bytes. Add Kanji Python test --- .../misc/python/test/test_qrcode_detect.py | 24 +++++++++++++++---- modules/python/src2/pycompat.hpp | 9 +++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/modules/objdetect/misc/python/test/test_qrcode_detect.py b/modules/objdetect/misc/python/test/test_qrcode_detect.py index ea2f500c2a..e6f56e72fb 100644 --- a/modules/objdetect/misc/python/test/test_qrcode_detect.py +++ b/modules/objdetect/misc/python/test/test_qrcode_detect.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #!/usr/bin/env python ''' =============================================================================== @@ -51,7 +52,7 @@ class qrcode_detector_test(NewOpenCVTests): self.assertTrue("QUESTION" in decoded_data) self.assertEqual(points.shape, (6, 4, 2)) - def test_decode_non_utf8(self): + def test_decode_non_ascii(self): import sys if sys.version_info[0] < 3: raise unittest.SkipTest('Python 2.x is not supported') @@ -59,6 +60,21 @@ class qrcode_detector_test(NewOpenCVTests): img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/umlaut.png')) self.assertFalse(img is None) detector = cv.QRCodeDetector() - decoded_data, _, _ = detector.detectAndDecodeBytes(img) - self.assertTrue(isinstance(decoded_data, bytes)) - self.assertTrue(u"M\u00FCllheimstrasse" in decoded_data.decode('utf-8')) + decoded_data, _, _ = detector.detectAndDecode(img) + self.assertTrue(isinstance(decoded_data, str)) + self.assertTrue("Müllheimstrasse" in decoded_data) + + def test_kanji(self): + inp = b"\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x90\xa2\x8a\x45" + + params = cv.QRCodeEncoder_Params() + params.mode = cv.QRCodeEncoder_MODE_KANJI + encoder = cv.QRCodeEncoder_create(params) + qrcode = encoder.encode(inp) + qrcode = cv.resize(qrcode, (0, 0), fx=2, fy=2, interpolation=cv.INTER_NEAREST) + + detector = cv.QRCodeDetector() + data, _, _ = detector.detectAndDecodeBytes(qrcode) + self.assertEqual(data, inp) + self.assertEqual(detector.getEncoding(), cv.QRCodeEncoder_ECI_SHIFT_JIS) + self.assertEqual(data.decode("shift-jis"), "こんにちは世界") diff --git a/modules/python/src2/pycompat.hpp b/modules/python/src2/pycompat.hpp index 05a3909562..c936f5e66a 100644 --- a/modules/python/src2/pycompat.hpp +++ b/modules/python/src2/pycompat.hpp @@ -84,6 +84,15 @@ static inline bool getUnicodeString(PyObject * obj, std::string &str) } Py_XDECREF(bytes); } + else if (PyBytes_Check(obj)) + { + const char * raw = PyBytes_AsString(obj); + if (raw) + { + str = std::string(raw); + res = true; + } + } #if PY_MAJOR_VERSION < 3 else if (PyString_Check(obj)) { From cb8d6c59a783e4d8698a4298be89a2eefe1f43f4 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Mon, 2 Jun 2025 11:23:48 +0300 Subject: [PATCH 06/13] Java bindings for QR code with bytes encoding/decoding --- .../objdetect/include/opencv2/objdetect.hpp | 4 +- modules/objdetect/misc/java/gen_dict.json | 92 +++++++++++++++++++ .../misc/java/test/QRCodeDetectorTest.java | 22 +++++ modules/objdetect/src/qrcode.cpp | 5 +- 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 modules/objdetect/misc/java/gen_dict.json diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index b5ccfb1503..4a8544b910 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -810,12 +810,12 @@ public: CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(), OutputArray straight_qrcode = noArray()); - /** @brief Returns a kid of encoding for the decoded info from the latest QR codes + /** @brief Returns a kind of encoding for the decoded info from the latest @ref decode or @ref detectAndDecode call @param codeIdx an index of the previously decoded QR code. When @ref decode or @ref detectAndDecode is used, valid value is zero. For @ref decodeMulti or @ref detectAndDecodeMulti use indices corresponding to the output order. */ - CV_WRAP QRCodeEncoder::ECIEncodings getEncoding(size_t codeIdx = 0); + CV_WRAP QRCodeEncoder::ECIEncodings getEncoding(int codeIdx = 0); }; class CV_EXPORTS_W_SIMPLE QRCodeDetectorAruco : public GraphicalCodeDetector { diff --git a/modules/objdetect/misc/java/gen_dict.json b/modules/objdetect/misc/java/gen_dict.json new file mode 100644 index 0000000000..4e6139a9da --- /dev/null +++ b/modules/objdetect/misc/java/gen_dict.json @@ -0,0 +1,92 @@ +{ + "ManualFuncs" : { + "QRCodeEncoder" : { + "QRCodeEncoder" : { + "j_code" : [ + "\n", + "/**", + " * Constructor of streaming callback object with abstract 'read' and 'seek' methods that should be implemented in Java code.
", + " * NOTE: Implemented callbacks should be called from the creation thread to avoid JNI performance degradation", + "*/", + "public void encode(byte[] encoded_info, Mat qrcode) {", + " encode_1(nativeObj, encoded_info, qrcode.nativeObj);", + "}", + "\n" + ], + "jn_code": [ + "\n", + "private static native void encode_1(long nativeObj, byte[] encoded_info, long qrcode_nativeObj);", + "\n" + ], + "cpp_code": [ + "//", + "// void cv::QRCodeEncoder::encode(String encoded_info, Mat& qrcode)", + "//", + "\n", + "JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11 (JNIEnv*, jclass, jlong, jbyteArray, jlong);", + "\n", + "JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11", + "(JNIEnv* env, jclass , jlong self, jbyteArray encoded_info, jlong qrcode_nativeObj)", + "{", + "", + " static const char method_name[] = \"objdetect::encode_11()\";", + " try {", + " LOGD(\"%s\", method_name);", + " Ptr* me = (Ptr*) self; //TODO: check for NULL", + " const char* n_encoded_info = reinterpret_cast(env->GetByteArrayElements(encoded_info, NULL));", + " Mat& qrcode = *((Mat*)qrcode_nativeObj);", + " (*me)->encode( n_encoded_info, qrcode );", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + "}", + "\n" + ] + } + }, + "GraphicalCodeDetector" : { + "GraphicalCodeDetector" : { + "j_code" : [ + "\n", + "public byte[] detectAndDecodeBytes(Mat img) {", + " return detectAndDecodeBytes_0(nativeObj, img.nativeObj);", + "}", + "\n" + ], + "jn_code": [ + "\n", + "private static native byte[] detectAndDecodeBytes_0(long nativeObj, long img_nativeObj);", + "\n" + ], + "cpp_code": [ + "JNIEXPORT jbyteArray JNICALL Java_org_opencv_objdetect_GraphicalCodeDetector_detectAndDecodeBytes_10 (JNIEnv*, jclass, jlong, jlong);", + "\n", + "JNIEXPORT jbyteArray JNICALL Java_org_opencv_objdetect_GraphicalCodeDetector_detectAndDecodeBytes_10", + " (JNIEnv* env, jclass , jlong self, jlong img_nativeObj)", + "{", + " ", + " static const char method_name[] = \"objdetect::detectAndDecodeBytes_10()\";", + " try {", + " LOGD(\"%s\", method_name);", + " cv::GraphicalCodeDetector* me = (cv::GraphicalCodeDetector*) self; //TODO: check for NULL", + " Mat& img = *((Mat*)img_nativeObj);", + " std::string result = me->detectAndDecode( img );", + " jsize sz = result.size();", + " jbyteArray _retval_ = env->NewByteArray(static_cast(sz));", + " env->SetByteArrayRegion(_retval_, 0, sz, reinterpret_cast(&result[0]));", + " return _retval_;", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + " return 0;", + "}", + "\n" + ] + } + } + } +} diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index af567cbc04..f675f47e77 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -2,13 +2,18 @@ package org.opencv.test.objdetect; import java.util.List; import org.opencv.core.Mat; +import org.opencv.core.Size; import org.opencv.objdetect.QRCodeDetector; +import org.opencv.objdetect.QRCodeEncoder; +import org.opencv.objdetect.QRCodeEncoder_Params; import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; import org.opencv.test.OpenCVTestCase; import java.util.Arrays; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.io.UnsupportedEncodingException; public class QRCodeDetectorTest extends OpenCVTestCase { @@ -50,4 +55,21 @@ public class QRCodeDetectorTest extends OpenCVTestCase { List < String > expectedResults = Arrays.asList("SKIP", "EXTRA", "TWO STEPS FORWARD", "STEP BACK", "QUESTION", "STEP FORWARD"); assertEquals(new HashSet(output), new HashSet(expectedResults)); } + + public void testKanji() throws UnsupportedEncodingException { + String inp = new String("こんにちは世界"); + + QRCodeEncoder_Params params = new QRCodeEncoder_Params(); + params.set_mode(QRCodeEncoder.MODE_KANJI); + QRCodeEncoder encoder = QRCodeEncoder.create(params); + + Mat qrcode = new Mat(); + encoder.encode(inp.getBytes("Shift_JIS"), qrcode); + Imgproc.resize(qrcode, qrcode, new Size(0, 0), 2, 2, Imgproc.INTER_NEAREST); + + QRCodeDetector detector = new QRCodeDetector(); + byte[] output = detector.detectAndDecodeBytes(qrcode); + assertEquals(detector.getEncoding(), QRCodeEncoder.ECI_SHIFT_JIS); + assertEquals(inp, new String(output, "Shift_JIS")); + } } diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index 12cb33ff79..ac233f32e1 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -980,7 +980,7 @@ public: std::string detectAndDecodeCurved(InputArray in, OutputArray points, OutputArray straight_qrcode); - QRCodeEncoder::ECIEncodings getEncoding(size_t codeIdx); + QRCodeEncoder::ECIEncodings getEncoding(int codeIdx); }; QRCodeDetector::QRCodeDetector() { @@ -997,8 +997,9 @@ QRCodeDetector& QRCodeDetector::setEpsY(double epsY) { return *this; } -QRCodeEncoder::ECIEncodings QRCodeDetector::getEncoding(size_t codeIdx) { +QRCodeEncoder::ECIEncodings QRCodeDetector::getEncoding(int codeIdx) { auto& encodings = std::dynamic_pointer_cast(p)->encodings; + CV_Assert(codeIdx >= 0); CV_Assert(codeIdx < encodings.size()); return encodings[codeIdx]; } From f4445bbe4a2fd5ca486799e9b64eeb308404b272 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Mon, 2 Jun 2025 18:27:53 +0300 Subject: [PATCH 07/13] Handle python bindings by alias to bytes type --- .../objdetect/graphical_code_detector.hpp | 11 ++++++ .../misc/python/pyopencv_objdetect.hpp | 38 +++++++------------ modules/objdetect/src/qrcode.cpp | 2 +- modules/python/src2/cv2_convert.cpp | 6 +-- modules/python/src2/gen2.py | 3 -- .../predefined_types.py | 1 + 6 files changed, 28 insertions(+), 33 deletions(-) diff --git a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp index ed697c50c0..d30d42ce52 100644 --- a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp @@ -73,6 +73,17 @@ public: */ CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), OutputArrayOfArrays straight_code = noArray()) const; + +#ifdef OPENCV_BINDINGS_PARSER + CV_WRAP_AS(detectAndDecodeBytes) NativeByteArray detectAndDecode(InputArray img, OutputArray points = noArray(), + OutputArray straight_code = noArray()) const; + CV_WRAP_AS(decodeBytes) NativeByteArray decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const; + // CV_WRAP_AS(decodeBytesMulti) bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector& decoded_info, + // OutputArrayOfArrays straight_code = noArray()) const; + // CV_WRAP_AS(detectAndDecodeBytesMulti) bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), + // OutputArrayOfArrays straight_code = noArray()) const; +#endif + struct Impl; protected: Ptr p; diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index e84658fcc7..770c0a5e0e 100644 --- a/modules/objdetect/misc/python/pyopencv_objdetect.hpp +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -7,32 +7,20 @@ typedef QRCodeEncoder::Params QRCodeEncoder_Params; typedef HOGDescriptor::HistogramNormType HOGDescriptor_HistogramNormType; typedef HOGDescriptor::DescriptorStorageFormat HOGDescriptor_DescriptorStorageFormat; -static PyObject* pyopencv_cv_GraphicalCodeDetector_detectAndDecode(PyObject* self, PyObject* py_args, PyObject* kw); - -static PyObject* detectAndDecodeBytes(PyObject* self, PyObject* py_args, PyObject* kw) { - // Run original method - PyObject* retval = pyopencv_cv_GraphicalCodeDetector_detectAndDecode(self, py_args, kw); - - if (PyErr_Occurred()) { - PyObject *type = nullptr, *value = nullptr, *traceback = nullptr; - PyErr_Fetch(&type, &value, &traceback); - - PyObject* object = PyUnicodeDecodeError_GetObject(value); - if (object && PyBytes_Check(object)) { - PyTuple_SetItem(retval, 0, object); - } else { - PyErr_Restore(type, value, traceback); - } - } else { - PyObject* str = PyTuple_GetItem(retval, 0); - PyObject* bytes = PyUnicode_AsEncodedString(str, "utf-8", 0); - PyTuple_SetItem(retval, 0, bytes); +class NativeByteArray +{ +public: + inline NativeByteArray& operator=(const std::string& from) { + val = from; + return *this; } - return retval; + std::string val; +}; + +template<> +PyObject* pyopencv_from(const NativeByteArray& from) +{ + return PyBytes_FromStringAndSize(from.val.c_str(), from.val.size()); } -// TODO: copy docstring somehow -#define PYOPENCV_EXTRA_METHODS_GraphicalCodeDetector \ - {"detectAndDecodeBytes", CV_PY_FN_WITH_KW_(detectAndDecodeBytes, 0), ""}, - #endif diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index ac233f32e1..2601f4e15b 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -1000,7 +1000,7 @@ QRCodeDetector& QRCodeDetector::setEpsY(double epsY) { QRCodeEncoder::ECIEncodings QRCodeDetector::getEncoding(int codeIdx) { auto& encodings = std::dynamic_pointer_cast(p)->encodings; CV_Assert(codeIdx >= 0); - CV_Assert(codeIdx < encodings.size()); + CV_Assert(codeIdx < static_cast(encodings.size())); return encodings[codeIdx]; } diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index 6352c6067a..0626e42e53 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -739,16 +739,14 @@ bool pyopencv_to(PyObject* obj, String &value, const ArgInfo& info) template<> PyObject* pyopencv_from(const String& value) { - PyObject* ret = PyString_FromString(value.empty() ? "" : value.c_str()); - return ret ? ret : Py_None; + return PyString_FromString(value.empty() ? "" : value.c_str()); } #if CV_VERSION_MAJOR == 3 template<> PyObject* pyopencv_from(const std::string& value) { - PyObject* ret = PyString_FromString(value.empty() ? "" : value.c_str()); - return ret ? ret : Py_None; + return PyString_FromString(value.empty() ? "" : value.c_str()); } #endif diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 4649b0ebe8..7d9f75063c 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -133,9 +133,6 @@ static PyGetSetDef pyopencv_${name}_getseters[] = static PyMethodDef pyopencv_${name}_methods[] = { -#ifdef PYOPENCV_EXTRA_METHODS_${name} - PYOPENCV_EXTRA_METHODS_${name} -#endif ${methods_inits} {NULL, NULL} }; diff --git a/modules/python/src2/typing_stubs_generation/predefined_types.py b/modules/python/src2/typing_stubs_generation/predefined_types.py index 6879d1a18d..d7ed78b231 100644 --- a/modules/python/src2/typing_stubs_generation/predefined_types.py +++ b/modules/python/src2/typing_stubs_generation/predefined_types.py @@ -265,6 +265,7 @@ _PREDEFINED_TYPES = ( export_name="ExtractMetaCallback", required_modules=("gapi",) ), + PrimitiveTypeNode("NativeByteArray", "bytes"), ) PREDEFINED_TYPES = dict( From 30fc9ed9ba7855a65d92120cc5dab9c65b94613a Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Tue, 3 Jun 2025 11:37:31 +0300 Subject: [PATCH 08/13] Handle Java bindings --- modules/java/generator/gen_java.py | 3 + .../objdetect/graphical_code_detector.hpp | 4 -- modules/objdetect/misc/java/gen_dict.json | 58 +++++-------------- 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/modules/java/generator/gen_java.py b/modules/java/generator/gen_java.py index 0ffa5bd6ae..797863249d 100755 --- a/modules/java/generator/gen_java.py +++ b/modules/java/generator/gen_java.py @@ -1002,6 +1002,9 @@ class JavaWrapperGenerator(object): ret = "return (jlong) _retval_;" elif type_dict[fi.ctype]["jni_type"] == "jdoubleArray": ret = "return _da_retval_;" + elif "jni_var" in type_dict[ret_type]: + c_epilogue.append(type_dict[ret_type]["jni_var"] % {"n" : '_retval_'}) + ret = f"return {type_dict[ret_type]['jni_name'] % {'n' : '_retval_'}};" # hack: replacing func call with property set/get name = fi.name diff --git a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp index d30d42ce52..7d46c99443 100644 --- a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp @@ -78,10 +78,6 @@ public: CV_WRAP_AS(detectAndDecodeBytes) NativeByteArray detectAndDecode(InputArray img, OutputArray points = noArray(), OutputArray straight_code = noArray()) const; CV_WRAP_AS(decodeBytes) NativeByteArray decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const; - // CV_WRAP_AS(decodeBytesMulti) bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector& decoded_info, - // OutputArrayOfArrays straight_code = noArray()) const; - // CV_WRAP_AS(detectAndDecodeBytesMulti) bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), - // OutputArrayOfArrays straight_code = noArray()) const; #endif struct Impl; diff --git a/modules/objdetect/misc/java/gen_dict.json b/modules/objdetect/misc/java/gen_dict.json index 4e6139a9da..54e5d3af92 100644 --- a/modules/objdetect/misc/java/gen_dict.json +++ b/modules/objdetect/misc/java/gen_dict.json @@ -4,9 +4,9 @@ "QRCodeEncoder" : { "j_code" : [ "\n", - "/**", - " * Constructor of streaming callback object with abstract 'read' and 'seek' methods that should be implemented in Java code.
", - " * NOTE: Implemented callbacks should be called from the creation thread to avoid JNI performance degradation", + "/** Generates QR code from input string.", + "@param encoded_info Input bytes to encode.", + "@param qrcode Generated QR code.", "*/", "public void encode(byte[] encoded_info, Mat qrcode) {", " encode_1(nativeObj, encoded_info, qrcode.nativeObj);", @@ -45,48 +45,16 @@ "\n" ] } - }, - "GraphicalCodeDetector" : { - "GraphicalCodeDetector" : { - "j_code" : [ - "\n", - "public byte[] detectAndDecodeBytes(Mat img) {", - " return detectAndDecodeBytes_0(nativeObj, img.nativeObj);", - "}", - "\n" - ], - "jn_code": [ - "\n", - "private static native byte[] detectAndDecodeBytes_0(long nativeObj, long img_nativeObj);", - "\n" - ], - "cpp_code": [ - "JNIEXPORT jbyteArray JNICALL Java_org_opencv_objdetect_GraphicalCodeDetector_detectAndDecodeBytes_10 (JNIEnv*, jclass, jlong, jlong);", - "\n", - "JNIEXPORT jbyteArray JNICALL Java_org_opencv_objdetect_GraphicalCodeDetector_detectAndDecodeBytes_10", - " (JNIEnv* env, jclass , jlong self, jlong img_nativeObj)", - "{", - " ", - " static const char method_name[] = \"objdetect::detectAndDecodeBytes_10()\";", - " try {", - " LOGD(\"%s\", method_name);", - " cv::GraphicalCodeDetector* me = (cv::GraphicalCodeDetector*) self; //TODO: check for NULL", - " Mat& img = *((Mat*)img_nativeObj);", - " std::string result = me->detectAndDecode( img );", - " jsize sz = result.size();", - " jbyteArray _retval_ = env->NewByteArray(static_cast(sz));", - " env->SetByteArrayRegion(_retval_, 0, sz, reinterpret_cast(&result[0]));", - " return _retval_;", - " } catch(const std::exception &e) {", - " throwJavaException(env, &e, method_name);", - " } catch (...) {", - " throwJavaException(env, 0, method_name);", - " }", - " return 0;", - "}", - "\n" - ] - } + } + }, + "type_dict": { + "NativeByteArray": { + "j_type" : "byte[]", + "jn_type": "byte[]", + "jni_type": "jbyteArray", + "jni_name": "n_%(n)s", + "jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, %(n)s.size(), reinterpret_cast(&%(n)s[0]));", + "cast_from": "std::string" } } } From 39342dd06a5f68cc810fcf8db2896df43d60e257 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Tue, 3 Jun 2025 17:55:26 +0300 Subject: [PATCH 09/13] Escape string in Java --- modules/objdetect/misc/java/test/QRCodeDetectorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index f675f47e77..b6f2a37fd6 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -57,7 +57,7 @@ public class QRCodeDetectorTest extends OpenCVTestCase { } public void testKanji() throws UnsupportedEncodingException { - String inp = new String("こんにちは世界"); + String inp = new String("\u3053\u3093\u306B\u3061\u306F\u4E16\u754C"); QRCodeEncoder_Params params = new QRCodeEncoder_Params(); params.set_mode(QRCodeEncoder.MODE_KANJI); From 63ccf3859bdff1664eb304931e2aa7b19e2f01be Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 5 Jun 2025 08:40:38 +0300 Subject: [PATCH 10/13] decodeMulti in Python --- .../opencv2/objdetect/graphical_code_detector.hpp | 4 ++++ modules/objdetect/misc/java/gen_dict.json | 2 +- .../objdetect/misc/python/pyopencv_objdetect.hpp | 11 +++++++++++ .../misc/python/test/test_qrcode_detect.py | 14 ++++++++++---- modules/objdetect/src/qrcode.cpp | 2 ++ 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp index 7d46c99443..adc52379b9 100644 --- a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp @@ -78,6 +78,10 @@ public: CV_WRAP_AS(detectAndDecodeBytes) NativeByteArray detectAndDecode(InputArray img, OutputArray points = noArray(), OutputArray straight_code = noArray()) const; CV_WRAP_AS(decodeBytes) NativeByteArray decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const; + CV_WRAP_AS(decodeBytesMulti) bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector& decoded_info, + OutputArrayOfArrays straight_code = noArray()) const; + CV_WRAP_AS(detectAndDecodeBytesMulti) bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), + OutputArrayOfArrays straight_code = noArray()) const; #endif struct Impl; diff --git a/modules/objdetect/misc/java/gen_dict.json b/modules/objdetect/misc/java/gen_dict.json index 54e5d3af92..ec69584a86 100644 --- a/modules/objdetect/misc/java/gen_dict.json +++ b/modules/objdetect/misc/java/gen_dict.json @@ -53,7 +53,7 @@ "jn_type": "byte[]", "jni_type": "jbyteArray", "jni_name": "n_%(n)s", - "jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, %(n)s.size(), reinterpret_cast(&%(n)s[0]));", + "jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, static_cast(%(n)s.size()), reinterpret_cast(&%(n)s[0]));", "cast_from": "std::string" } } diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index 770c0a5e0e..2a03b04d0a 100644 --- a/modules/objdetect/misc/python/pyopencv_objdetect.hpp +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -17,10 +17,21 @@ public: std::string val; }; +class vector_NativeByteArray : public std::vector {}; + template<> PyObject* pyopencv_from(const NativeByteArray& from) { return PyBytes_FromStringAndSize(from.val.c_str(), from.val.size()); } +template<> +PyObject* pyopencv_from(const vector_NativeByteArray& results) +{ + PyObject* list = PyList_New(results.size()); + for(size_t i = 0; i < results.size(); ++i) + PyList_SetItem(list, i, PyBytes_FromStringAndSize(results[i].c_str(), results[i].size())); + return list; +} + #endif diff --git a/modules/objdetect/misc/python/test/test_qrcode_detect.py b/modules/objdetect/misc/python/test/test_qrcode_detect.py index e6f56e72fb..8da95ccd00 100644 --- a/modules/objdetect/misc/python/test/test_qrcode_detect.py +++ b/modules/objdetect/misc/python/test/test_qrcode_detect.py @@ -65,16 +65,22 @@ class qrcode_detector_test(NewOpenCVTests): self.assertTrue("Müllheimstrasse" in decoded_data) def test_kanji(self): - inp = b"\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x90\xa2\x8a\x45" + inp = "こんにちは世界" + inp_bytes = inp.encode("shift-jis") params = cv.QRCodeEncoder_Params() params.mode = cv.QRCodeEncoder_MODE_KANJI encoder = cv.QRCodeEncoder_create(params) - qrcode = encoder.encode(inp) + qrcode = encoder.encode(inp_bytes) qrcode = cv.resize(qrcode, (0, 0), fx=2, fy=2, interpolation=cv.INTER_NEAREST) detector = cv.QRCodeDetector() data, _, _ = detector.detectAndDecodeBytes(qrcode) - self.assertEqual(data, inp) + self.assertEqual(data, inp_bytes) self.assertEqual(detector.getEncoding(), cv.QRCodeEncoder_ECI_SHIFT_JIS) - self.assertEqual(data.decode("shift-jis"), "こんにちは世界") + self.assertEqual(data.decode("shift-jis"), inp) + + _, data, _, _ = detector.detectAndDecodeBytesMulti(qrcode) + self.assertEqual(data[0], inp_bytes) + self.assertEqual(detector.getEncoding(0), cv.QRCodeEncoder_ECI_SHIFT_JIS) + self.assertEqual(data[0].decode("shift-jis"), inp) diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index 2601f4e15b..f2e2700f82 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -4125,9 +4125,11 @@ bool ImplContour::decodeMulti( } decoded_info.clear(); + encodings.clear(); for (size_t i = 0; i < info.size(); i++) { auto& decoder = qrdec[i]; + encodings.push_back(decoder.eci); if (!decoder.isStructured()) { decoded_info.push_back(info[i]); From 901d5b6682135693a19e2de8e4ceaed53db51b4f Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 5 Jun 2025 09:04:59 +0300 Subject: [PATCH 11/13] decodeMulti in Java --- modules/objdetect/misc/java/filelist_common | 1 + modules/objdetect/misc/java/gen_dict.json | 10 +++++++++- .../java/src/cpp/objdetect_converters.cpp | 20 +++++++++++++++++++ .../java/src/cpp/objdetect_converters.hpp | 14 +++++++++++++ .../misc/java/test/QRCodeDetectorTest.java | 5 +++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 modules/objdetect/misc/java/filelist_common create mode 100644 modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp create mode 100644 modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp diff --git a/modules/objdetect/misc/java/filelist_common b/modules/objdetect/misc/java/filelist_common new file mode 100644 index 0000000000..56da1c5df7 --- /dev/null +++ b/modules/objdetect/misc/java/filelist_common @@ -0,0 +1 @@ +misc/java/src/cpp/objdetect_converters.hpp diff --git a/modules/objdetect/misc/java/gen_dict.json b/modules/objdetect/misc/java/gen_dict.json index ec69584a86..2f453a6a91 100644 --- a/modules/objdetect/misc/java/gen_dict.json +++ b/modules/objdetect/misc/java/gen_dict.json @@ -53,8 +53,16 @@ "jn_type": "byte[]", "jni_type": "jbyteArray", "jni_name": "n_%(n)s", - "jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, static_cast(%(n)s.size()), reinterpret_cast(&%(n)s[0]));", + "jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, static_cast(%(n)s.size()), reinterpret_cast(%(n)s.c_str()));", "cast_from": "std::string" + }, + "vector_NativeByteArray": { + "j_type": "List", + "jn_type": "List", + "jni_type": "jobject", + "jni_var": "std::vector< std::string > %(n)s", + "suffix": "Ljava_util_List", + "v_type": "vector_NativeByteArray" } } } diff --git a/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp b/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp new file mode 100644 index 0000000000..3f9f533769 --- /dev/null +++ b/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp @@ -0,0 +1,20 @@ +#include "objdetect_converters.hpp" + +#define LOG_TAG "org.opencv.objdetect" + +void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector& vs, jobject list) +{ + static jclass juArrayList = ARRAYLIST(env); + jmethodID m_clear = LIST_CLEAR(env, juArrayList); + jmethodID m_add = LIST_ADD(env, juArrayList); + + env->CallVoidMethod(list, m_clear); + for (std::vector::iterator it = vs.begin(); it != vs.end(); ++it) + { + jsize sz = static_cast((*it).size()); + jbyteArray element = env->NewByteArray(sz); + env->SetByteArrayRegion(element, 0, sz, reinterpret_cast((*it).c_str())); + env->CallBooleanMethod(list, m_add, element); + env->DeleteLocalRef(element); + } +} diff --git a/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp new file mode 100644 index 0000000000..f7420ba93f --- /dev/null +++ b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp @@ -0,0 +1,14 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +#ifndef OBJDETECT_CONVERTERS_HPP +#define OBJDETECT_CONVERTERS_HPP + +#include +#include "opencv_java.hpp" +#include "opencv2/core.hpp" + +void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector& vs, jobject list); + +#endif /* DNN_CONVERTERS_HPP */ diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index b6f2a37fd6..fffc46554e 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -71,5 +71,10 @@ public class QRCodeDetectorTest extends OpenCVTestCase { byte[] output = detector.detectAndDecodeBytes(qrcode); assertEquals(detector.getEncoding(), QRCodeEncoder.ECI_SHIFT_JIS); assertEquals(inp, new String(output, "Shift_JIS")); + + List < byte[] > outputs = new ArrayList< byte[] >(); + assertTrue(detector.detectAndDecodeBytesMulti(qrcode, outputs)); + assertEquals(detector.getEncoding(0), QRCodeEncoder.ECI_SHIFT_JIS); + assertEquals(inp, new String(outputs.get(0), "Shift_JIS")); } } From d4f94bfb1b6a4dcf5e31caecba8358eff2aef91b Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 5 Jun 2025 12:41:24 +0300 Subject: [PATCH 12/13] Try skip Java test if charset is not supported --- .../misc/java/src/cpp/objdetect_converters.hpp | 2 +- .../objdetect/misc/java/test/QRCodeDetectorTest.java | 4 ++++ modules/objdetect/src/qrcode.cpp | 12 ++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp index f7420ba93f..82bb881fad 100644 --- a/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp +++ b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp @@ -11,4 +11,4 @@ void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector& vs, jobject list); -#endif /* DNN_CONVERTERS_HPP */ +#endif /* OBJDETECT_CONVERTERS_HPP */ diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index fffc46554e..2a4ff81675 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; public class QRCodeDetectorTest extends OpenCVTestCase { @@ -57,6 +58,9 @@ public class QRCodeDetectorTest extends OpenCVTestCase { } public void testKanji() throws UnsupportedEncodingException { + if (!Charset.isSupported("Shift_JIS")) + throw new TestSkipException(); + String inp = new String("\u3053\u3093\u306B\u3061\u306F\u4E16\u754C"); QRCodeEncoder_Params params = new QRCodeEncoder_Params(); diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index f2e2700f82..c20385d7b6 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -4124,22 +4124,22 @@ bool ImplContour::decodeMulti( straight_qrcode.assign(tmp_straight_qrcodes); } - decoded_info.clear(); - encodings.clear(); + decoded_info.resize(info.size()); + encodings.resize(info.size()); for (size_t i = 0; i < info.size(); i++) { auto& decoder = qrdec[i]; - encodings.push_back(decoder.eci); + encodings[i] = decoder.eci; if (!decoder.isStructured()) { - decoded_info.push_back(info[i]); + decoded_info[i] = info[i]; continue; } // Store final message corresponding to 0-th code in a sequence. if (decoder.structure_info.sequence_num != 0) { - decoded_info.push_back(""); + decoded_info[i] = ""; continue; } @@ -4160,7 +4160,7 @@ bool ImplContour::decodeMulti( break; } } - decoded_info.push_back(decoded); + decoded_info[i] = decoded; } alignmentMarkers.resize(src_points.size()); From b91ffb8f1cffc4c6ea68c08924ea0867b38d0030 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 5 Jun 2025 17:13:06 +0300 Subject: [PATCH 13/13] Drop string encoding/decoding from Java test --- .../misc/java/test/QRCodeDetectorTest.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index 2a4ff81675..225c8c6610 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -57,28 +57,25 @@ public class QRCodeDetectorTest extends OpenCVTestCase { assertEquals(new HashSet(output), new HashSet(expectedResults)); } - public void testKanji() throws UnsupportedEncodingException { - if (!Charset.isSupported("Shift_JIS")) - throw new TestSkipException(); - - String inp = new String("\u3053\u3093\u306B\u3061\u306F\u4E16\u754C"); - + public void testKanji() { + byte[] inp = new byte[]{(byte)0x82, (byte)0xb1, (byte)0x82, (byte)0xf1, (byte)0x82, (byte)0xc9, (byte)0x82, + (byte)0xbf, (byte)0x82, (byte)0xcd, (byte)0x90, (byte)0xa2, (byte)0x8a, (byte)0x45}; QRCodeEncoder_Params params = new QRCodeEncoder_Params(); params.set_mode(QRCodeEncoder.MODE_KANJI); QRCodeEncoder encoder = QRCodeEncoder.create(params); Mat qrcode = new Mat(); - encoder.encode(inp.getBytes("Shift_JIS"), qrcode); + encoder.encode(inp, qrcode); Imgproc.resize(qrcode, qrcode, new Size(0, 0), 2, 2, Imgproc.INTER_NEAREST); QRCodeDetector detector = new QRCodeDetector(); byte[] output = detector.detectAndDecodeBytes(qrcode); assertEquals(detector.getEncoding(), QRCodeEncoder.ECI_SHIFT_JIS); - assertEquals(inp, new String(output, "Shift_JIS")); + assertArrayEquals(inp, output); List < byte[] > outputs = new ArrayList< byte[] >(); assertTrue(detector.detectAndDecodeBytesMulti(qrcode, outputs)); assertEquals(detector.getEncoding(0), QRCodeEncoder.ECI_SHIFT_JIS); - assertEquals(inp, new String(outputs.get(0), "Shift_JIS")); + assertArrayEquals(inp, outputs.get(0)); } }