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.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index ed0d6f76ac..4a8544b910 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. */ @@ -808,6 +809,13 @@ public: */ CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(), OutputArray straight_qrcode = noArray()); + + /** @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(int codeIdx = 0); }; class CV_EXPORTS_W_SIMPLE QRCodeDetectorAruco : public GraphicalCodeDetector { diff --git a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp index ed697c50c0..adc52379b9 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/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 new file mode 100644 index 0000000000..2f453a6a91 --- /dev/null +++ b/modules/objdetect/misc/java/gen_dict.json @@ -0,0 +1,68 @@ +{ + "ManualFuncs" : { + "QRCodeEncoder" : { + "QRCodeEncoder" : { + "j_code" : [ + "\n", + "/** 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);", + "}", + "\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" + ] + } + } + }, + "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, 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..82bb881fad --- /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 /* OBJDETECT_CONVERTERS_HPP */ diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index af567cbc04..225c8c6610 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -2,13 +2,19 @@ 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; +import java.nio.charset.Charset; public class QRCodeDetectorTest extends OpenCVTestCase { @@ -50,4 +56,26 @@ 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() { + 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, 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); + assertArrayEquals(inp, output); + + List < byte[] > outputs = new ArrayList< byte[] >(); + assertTrue(detector.detectAndDecodeBytesMulti(qrcode, outputs)); + assertEquals(detector.getEncoding(0), QRCodeEncoder.ECI_SHIFT_JIS); + assertArrayEquals(inp, outputs.get(0)); + } } diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index 95194e1c46..2a03b04d0a 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; +class NativeByteArray +{ +public: + inline NativeByteArray& operator=(const std::string& from) { + val = from; + return *this; + } + 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 0237900572..8da95ccd00 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 ''' =============================================================================== @@ -8,7 +9,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 +51,36 @@ 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_ascii(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, str)) + self.assertTrue("Müllheimstrasse" in decoded_data) + + def test_kanji(self): + 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_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_bytes) + self.assertEqual(detector.getEncoding(), cv.QRCodeEncoder_ECI_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 ac1e7fcadc..c20385d7b6 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(int codeIdx); }; QRCodeDetector::QRCodeDetector() { @@ -994,6 +997,13 @@ QRCodeDetector& QRCodeDetector::setEpsY(double epsY) { return *this; } +QRCodeEncoder::ECIEncodings QRCodeDetector::getEncoding(int codeIdx) { + auto& encodings = std::dynamic_pointer_cast(p)->encodings; + CV_Assert(codeIdx >= 0); + CV_Assert(codeIdx < static_cast(encodings.size())); + return encodings[codeIdx]; +} + bool ImplContour::detect(InputArray in, OutputArray points) const { Mat inarr; @@ -1035,6 +1045,8 @@ public: uint8_t total_num = 1; } structure_info; + QRCodeEncoder::ECIEncodings eci; + protected: double getNumModules(); Mat getHomography() { @@ -2802,7 +2814,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 @@ -2895,7 +2906,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: @@ -2966,6 +2977,7 @@ std::string ImplContour::decode(InputArray in, InputArray points, OutputArray st alignmentMarkers = {qrdec.alignment_coords}; updateQrCorners = qrdec.getOriginalPoints(); } + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } @@ -2999,6 +3011,7 @@ String ImplContour::decodeCurved(InputArray in, InputArray points, OutputArray s { qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1); } + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } @@ -4111,20 +4124,22 @@ bool ImplContour::decodeMulti( straight_qrcode.assign(tmp_straight_qrcodes); } - decoded_info.clear(); + decoded_info.resize(info.size()); + encodings.resize(info.size()); for (size_t i = 0; i < info.size(); i++) { auto& decoder = qrdec[i]; + 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; } @@ -4145,7 +4160,7 @@ bool ImplContour::decodeMulti( break; } } - decoded_info.push_back(decoded); + decoded_info[i] = decoded; } alignmentMarkers.resize(src_points.size()); 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); } } 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)) { 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(