This commit is contained in:
Dmitry Kurtaev 2025-06-05 14:13:18 +00:00 committed by GitHub
commit a3584a1e04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 250 additions and 9 deletions

View File

@ -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

View File

@ -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 {

View File

@ -73,6 +73,17 @@ public:
*/
CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector<std::string>& 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<NativeByteArray>& decoded_info,
OutputArrayOfArrays straight_code = noArray()) const;
CV_WRAP_AS(detectAndDecodeBytesMulti) bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector<NativeByteArray>& decoded_info, OutputArray points = noArray(),
OutputArrayOfArrays straight_code = noArray()) const;
#endif
struct Impl;
protected:
Ptr<Impl> p;

View File

@ -0,0 +1 @@
misc/java/src/cpp/objdetect_converters.hpp

View File

@ -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<cv::QRCodeEncoder>* me = (Ptr<cv::QRCodeEncoder>*) self; //TODO: check for NULL",
" const char* n_encoded_info = reinterpret_cast<char*>(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<jsize>(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, static_cast<jsize>(%(n)s.size()), reinterpret_cast<const jbyte*>(%(n)s.c_str()));",
"cast_from": "std::string"
},
"vector_NativeByteArray": {
"j_type": "List<byte[]>",
"jn_type": "List<byte[]>",
"jni_type": "jobject",
"jni_var": "std::vector< std::string > %(n)s",
"suffix": "Ljava_util_List",
"v_type": "vector_NativeByteArray"
}
}
}

View File

@ -0,0 +1,20 @@
#include "objdetect_converters.hpp"
#define LOG_TAG "org.opencv.objdetect"
void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector<std::string>& 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<std::string>::iterator it = vs.begin(); it != vs.end(); ++it)
{
jsize sz = static_cast<jsize>((*it).size());
jbyteArray element = env->NewByteArray(sz);
env->SetByteArrayRegion(element, 0, sz, reinterpret_cast<const jbyte*>((*it).c_str()));
env->CallBooleanMethod(list, m_add, element);
env->DeleteLocalRef(element);
}
}

View File

@ -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 <jni.h>
#include "opencv_java.hpp"
#include "opencv2/core.hpp"
void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector<std::string>& vs, jobject list);
#endif /* OBJDETECT_CONVERTERS_HPP */

View File

@ -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<String>(output), new HashSet<String>(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));
}
}

View File

@ -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<std::string> {};
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

View File

@ -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)

View File

@ -963,6 +963,7 @@ public:
double epsX, epsY;
mutable vector<vector<Point2f>> alignmentMarkers;
mutable vector<Point2f> updateQrCorners;
mutable vector<QRCodeEncoder::ECIEncodings> 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<ImplContour>(p)->encodings;
CV_Assert(codeIdx >= 0);
CV_Assert(codeIdx < static_cast<int>(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());

View File

@ -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);
}
}

View File

@ -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))
{

View File

@ -265,6 +265,7 @@ _PREDEFINED_TYPES = (
export_name="ExtractMetaCallback",
required_modules=("gapi",)
),
PrimitiveTypeNode("NativeByteArray", "bytes"),
)
PREDEFINED_TYPES = dict(